后台搭建
// 在新后台项目文件中
npm init -y
快速创建pack.json 配置文件用于包的锁定
npm install koa
安装 最新的 koa2
app.js 启动入口
data/ 返回的json数据
middleware/ 独立出来存放所有中间件
koa_response_data.js 返回数据存放
koa_response_duration.js 请求消耗的时间
koa_response_header.js 设置响应头
utils/
file_utils.js 快速读取某一个目录之下文件的工具
//1. 开始
npm init -y
npm install koa
// 2. 在根目录/middleware/koa_response_header.js 中
// 设置响应头的中间件
module.exports = async (ctx, next) => {
const contentType = ‘application/json; charset=utf-8’
//告诉浏览器返回的是json数据 编码是utf-8
ctx.set(‘Content-Type’, contentType) // 内容类型
ctx.set(“Access-Control-Allow-Origin”, “*”)
// 为了允许跨域
ctx.set(“Access-Control-Allow-Methods”, “OPTIONS, GET, PUT, POST, DELETE”)
// 为了允许跨域
await next()
}
// 3. 在根目录/middleware/koa_response_duration.js 中
// 计算服务器消耗时长的中间件
module.exports = async (ctx, next) => {
// 记录开始时间
const start = Date.now()
// 让内层中间件得到执行
await next()
// 记录结束的时间
const end = Date.now()
// 设置响应头 X-Response-Time
const duration = end - start
// ctx.set 设置响应头
ctx.set(‘X-Response-Time’, duration + ‘ms’)
}
// 4. 在根目录/utils/file_utils.js 中
// 读取文件的工具方法
const fs = require(‘fs’)
module.exports.getFileJsonData = (filePath) => {
// 根据文件的路径, 读取文件的内容
return new Promise((resolve, reject) => {
// 为了将读取的文件返回出去 使用 Promise 包裹此异步函数 返回出去
// 异步 指的是第一个函数尚未结束 又调用了第二个函数 或第三第四个函数时第一步仍未结束
// 层层嵌套中的return是返回不到 第一个函数的
// 使用 Promise 可以做到这一点
fs.readFile(filePath, ‘utf-8’, (error, data) => {
// 文件路径 编码 成功/失败的回调
// filePath 此路径是自己拼成的绝对路径
if(error) {
// 读取文件失败
reject(error)
} else {
// 读取文件成功
resolve(data)
}
})
})
}
// 5. 在 根目录/middleware/koa_response_data.js 中
// 处理业务逻辑的中间件,读取某个json文件的数据
const path = require(‘path’)
const fileUtils = require(’…/utils/file_utils’)
module.exports = async (ctx, next) => {
// 根据url
const url = ctx.request.url // /api/seller …/data/seller.json
// 请求的 url
let filePath = url.replace(’/api’, ‘’) //制作接口 api/seller
// 将 /api 替换为 空字符串 赋值给 filePath
filePath = ‘…/data’ + filePath + ‘.json’ // …/data/seller.json
// 取得 /seller 在前面加上’…/data’ 后面加上 .json
// 就成了存放.json文件的当前路径
filePath = path.join(__dirname, filePath)
// 然后 将相对路径转为绝对路径 其绝对路径是调用函数获得的 不会项目挪地方就不能用
try {
const ret = await fileUtils.getFileJsonData(filePath)
// 读取绝对路径 的.json文件
// 由于读取使用了Promise 所以返回的是个 Promise参数
//使用es6 async 往前最近的函数 数值 = await简化它 即可得到数数值
ctx.response.body = ret // 将数据返回给浏览器
// 如果 try 出错 不会报错 会触发 catch
} catch (error) {
const errorMsg = {
message: ‘读取文件内容失败, 文件资源不存在’,
status: 404
}
ctx.response.body = JSON.stringify(errorMsg)
// 将errorMsg 对象 转化为 json格式的字符串
}
console.log(filePath)
await next()
}
// 6. 在 根目录/ app.js 中
// 服务器的入口文件
// 1.创建KOA的实例对象
const Koa = require(‘koa’)
const app = new Koa()
// 2.绑定中间件
// 绑定第一层中间件
const respDurationMiddleware = require(’./middleware/koa_response_duration’)
app.use(respDurationMiddleware)
// 绑定第二层中间件
const respHeaderMiddleware = require(’./middleware/koa_response_header’)
app.use(respHeaderMiddleware)
// 绑定第三层中间件
const respDataMiddleware = require(’./middleware/koa_response_data’)
app.use(respDataMiddleware)
// 3.绑定端口号 8888
app.listen(8888)
// 7. 在根目录/data
全部是.json 数据 json文件名 等于 请求路径加 api/文件名
seller.json = http://localhost:8888/api/seller
相对应
// 8.实际请求的跨域配置 无操作 理解
当前页面的地址 和 Ajax 获取数据的地址 不一致
浏览器地址栏的请求地址 和 要发送的请求地址 的 对比
要满足 同 协议 同域名 同端口
http://127.0.0.1:5500/cors_demo%20copy/index.html
和 http://localhost:8888/api/haha
端口不同 跨域了
解决方法
在中间件请求头中已经添加了
ctx.set(“Access-Control-Allow-Origin”, “*”)
// 为了允许跨域
ctx.set(“Access-Control-Allow-Methods”, “OPTIONS, GET, PUT, POST, DELETE”)
// 为了允许跨域
// 通用代码结构流程
//1. 在assets /css /global.less
html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.com-page {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-container {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-chart {
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
border-radius: 20px;
}
//2. 在 views/TrendPage.vue中
// 3. 在@/components/Trend.vue中
//4. 在main.js中引入
// 引入全局的样式文件
import ‘./assets/css/global.less’
/ 将 window.echarts 重新赋值到 Vue上
// 后面可很方便的通过 this.
e
c
h
a
r
t
s
获
取
V
u
e
.
p
r
o
t
o
t
y
p
e
.
echarts 获取 Vue.prototype.
echarts获取Vue.prototype.echarts = window.echarts
import axios from ‘axios’
// 请求基准路径的配置
axios.defaults.baseURL = ‘http://127.0.0.1:8888/api/’
// 将axios挂载到Vue的原型对象上
// 后面可很方便的通过 this.
h
t
t
p
获
取
V
u
e
.
p
r
o
t
o
t
y
p
e
.
http 获取 Vue.prototype.
http获取Vue.prototype.http = axios
- 在router/index.js中
import TrendPage from ‘@/views/TrendPage’
const routes = [
{
path: ‘/trendpage’,
component: TrendPage
}
]
// 继续后台搭建
// websocket 数据的推送
// 1. 安装websocket
npm i ws -S
// 2 . 在app.js中
const webSocketService = require(’./service/web_socket_service’)
// 开启服务端的监听 , 监听客户端的链接
// 当 某一个客户端链接成功之后 , 就会对这个客户端进行message事件的监听
webSocketService.listen()
// 3. 在根目录/service/web_socket_service.js中
const path = require(‘path’)
const fileUtils = require(’…/utils/file_utils’)
const WebSocket = require(‘ws’)
// 创建 WebSocket 服务端对象, 他所绑定的端口号是9998
const wss = new WebSocket.Server({
port:9998
})
// 服务端开起了监听
module.exports.listen = () => {
// 对客户端的链接事件进行监听
// client :代表的是客户端链接的socket对象
wss.on(‘connection’, client => {
console.log(‘有客户端链接成功了…’)
// 对客户端的链接对象进行message事件的监听
// msg : 由客户端发给服务端的数据
client.on(‘message’,async msg => {
console.log(‘客户端发送数据给服务端了:’+ msg)
let payload = JSON.parse(msg) // json格式的字符串
const action = payload.action
if(action === ‘getData’){
let filePath = ‘…/data/’ + payload.chartName + ‘.json’
// 定义要读取本地 的哪个 json
// payload.charName // trend seller map rank hot stock
filePath = path.join(__dirname, filePath)
// 组装路径
const ret = await fileUtils.getFileJsonData(filePath)
// 需要在服务端获取到数据的基础之上, 增加一个data的字段
// data 所对应的值 , 就是某个json文件的内容
payload.data = ret
client.send(JSON.stringify(payload))
} else {
// 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
// wss.clients // 所有客户端的链接 他是个数组可以循环遍历
wss.clients.forEach(client => {
client.send(msg)
})
}
// 由服务端往客户端发送数据
// client.send(‘hello socket from backend’)
})
})}
// 继续前台的搭建 前台的改造 取消axios 使用websocket的send
//1. 在main.js中
// 对服务端 进行websocket的链接
import SocketService from ‘@/utils/socket_service.js’
SocketService.Instance.connect() // get Instance 调用时无需小括号
// 其他的组件 this.
s
o
c
k
e
t
.
里
面
的
方
法
V
u
e
.
p
r
o
t
o
t
y
p
e
.
socket. 里面的方法 Vue.prototype.
socket.里面的方法Vue.prototype.socket = SocketService.Instance
//2.在@/utils/socket_service.js中
export default class SocketService {
// 被挂载到了原型对象
/*
不管谁通过调用导出的 SocketService()得到的实例对象
只能是同一个实例对象 这就是我们讲的
单例的设计模式
*/
static instance = null
static get Instance () {
if (!this.instance) {
this.instance = new SocketService()
// 这个实例对象里面有connect () { registerCallBack ( unRegisterCallBack (socketType) { // 发送数据的方法 send (data) { 就是 class SocketService {这个类里有的他都有
}
return this.instance
}
// 和服务端链接的socket对象
ws = null // 这并不是赋值而是 SocketService 内部的 ws
// 存储回调函数的
callBackMapping = {}
// 标识是否连接成功
connected = false
// 记录重试的次数
sendRetryCount = 0
// 重新连接尝试的次数
connectRetryCount = 0
// 定义链接服务器的方法 在main.js就已经进行调用了
connect () {
// 在main.js中调用此方法
// 连接服务器
if (!window.WebSocket) {
return console.log(‘您的浏览器不支持WebSocket’)
}
this.ws = new WebSocket(‘ws://localhost:9998’)
// 链接成功的事件
this.ws.onopen = () => {
console.log('链接服务端成功了')
this.connected = true
// 重置重新链接的次数
this.connectRetryCount = 0
}
// 链接服务端失败
this.ws.onclose = () => {
console.log('链接服务端失败')
this.connected = false
this.connectRetryCount++
setTimeout(() => {
this.connect()
}, 500 * this.connectRetryCount)
}
// 得到服务端发送过来的数据
this.ws.onmessage = msg => {
console.log('从服务端获取到了数据')
// 真正服务端发送过来的原始数据 是在msg中的data字段
// console.log(msg.data)
const recvData = JSON.parse(msg.data)
const socketType = recvData.socketType
// 获取自己发过去 服务器原封不动携带回来的 函数名
// 判断回调函数是否存在
if (this.callBackMapping[socketType]) {
const action = recvData.action // action:getData
if (action === 'getData') {
const realData = JSON.parse(recvData.data)
console.log(realData)
this.callBackMapping[socketType].call(this, realData)
} else if (action === 'fullScreen') {
} else if (action === 'themeChange') {
}
}
}
}
// 注册回调函数。注册过函数以后,当有事件发生的时候(鼠标光标移动,鼠标键按下),就会自动调用被注册的函数。
// 回调函数的注册 把 callBack 存起来了 socketType:hotUp
registerCallBack (socketType, callBack) {
this.callBackMapping[socketType] = callBack
} // 需挂载全局主动调用
// 取消某一个回调函数
unRegisterCallBack (socketType) {
this.callBackMapping[socketType] = null
} // 需挂载全局主动调用
// 发送数据的方法
send (data) {
// 判断此时此刻有没有连接成功
if (this.connected) {
this.sendRetryCount = 0
// 成功
this.ws.send(JSON.stringify(data)) // 发请求
} else {
this.sendRetryCount++
// 失败
setTimeout(() => {
// 延时
this.send(data) // 发请求
}, this.sendRetryCount * 500) // 500毫秒
}
} // 需挂载全局主动调用
}
// 3. 在每个地图的 .vue 页面中
created () {
// 在组件组件创建完成之后 进行回调函数的注册
this.$socket.registerCallBack('trendData', this.getData)
// 这个 trendData的getData会被socket存起来
},
destroyed () {
this.KaTeX parse error: Expected 'EOF', got '}' at position 42: …'trendData') }̲, mounted () { …socket.send({
// // 你不能往一个正在连接状态的websocket发送数据
// // 要等他连接成功 已在.js 改善了$socket.send 方法
// action: ‘getData’, // 这次的请求是为了? 获取数据
// socketType: ‘trendData’, // 获取什么数据
// charName: ‘trend’, // 哪一个图表的获取呢
// value: ‘’ // 主题 缩放
// })
// }, 3000)
// 请求里不能有注释
this.$socket.send({
action: 'getData',
socketType: 'trendData',
chartName: 'trend',
value: ''
})
// this.callBackMapping[socketType].call(this, realData)
// 服务器会返回数据给 on监听的 msg 把msg 给realData
},
methods:{
getData (ret) {
// socket 可以把一个函数转化为 使用 socket 发送请求返回的参数
// 这个 trendData的getData会被 socket 存进去
// await this.
h
t
t
p
.
g
e
t
(
)
/
/
对
a
l
l
D
a
t
a
进
行
赋
值
/
/
c
o
n
s
t
d
a
t
a
:
r
e
t
=
t
h
i
s
.
http.get() // 对 allData 进行赋值 // const { data: ret } = this.
http.get()//对allData进行赋值//constdata:ret=this.http(‘trend’)
this.allData = ret
console.log(ret, 999)
this.updateChart()
},
}
缩放与异端同步
<Rank ref="rank" />
<div class="resize">
<!-- icon-compress-alt -->
<!-- 4缩放图标 -->
<span
:class="[
'iconfont',
fullScreenStatus.rank ? 'icon-compress-alt' : 'icon-expand-alt'
]"
@click="changeSize('rank')"
></span>
</div>
// 全屏样式的定义
.fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
margin: 0 !important;
z-index: 100;
}
主题切换与异端同步
// 在store/index.js中
import Vue from ‘vue’
import Vuex from ‘vuex’
Vue.use(Vuex)
export default new Vuex.Store({
state: {
theme: ‘chalk’
},
mutations: {
changeTheme (state) {
if (state.theme === ‘chalk’) {
state.theme = ‘vintage’
} else {
state.theme = ‘chalk’
}
}
},
actions: {},
modules: {}
})
// 在ScreenPage.vue 组合页面中
handleChangeThme () {
// 修改VueX中的数据
this.$store.commit(‘changeTheme’)
}
// 在其它使用主题 .vue中
import { mapState } from ‘vuex’
this.chartInstance = this.
e
c
h
a
r
t
s
.
i
n
i
t
(
t
h
i
s
.
echarts.init(this.
echarts.init(this.refs.seller_ref, this.theme) // theme可切换vuex主题 'chalk’主题 在public/index.html中已被引用
computed: {
…mapState([‘theme’])
},
watch: {
theme () {
console.log(‘主题切换了’)
this.储存echarts的变量.dispose() // 销毁当前图表
this.initChart() // 重新以最新的主题名称初始化图表对象
this.screenAdapter() // 完成屏幕的适配
this.updateChart() // 更新图表的展示
}
},
// 主题修正
// 在 @/utils/theme_utils.js中
const theme = {
chalk: {
// 背景颜色
backgroundColor: ‘#161522’,
// 标题的文字颜色
titleColor: ‘#ffffff’,
// 左上角logo的图标路径
logoSrc: ‘logo_dark.png’,
// 切换主题按钮的图片路径
themeSrc: ‘qiehuan_dark.png’,
// 页面顶部的边框图片
headerBorderSrc: ‘header_border_dark.png’
},
vintage: {
// 背景颜色
backgroundColor: ‘#eeeeee’,
// 标题的文字颜色
titleColor: ‘#000000’,
// 左上角logo的图标路径
logoSrc: ‘logo_light2.png’,
// 切换主题按钮的图片路径
themeSrc: ‘qiehuan_light.png’,
// 页面顶部的边框图片
headerBorderSrc: ‘header_border_light.png’
}
}
// 调用getThemeValue 将反回传入主题对应的颜色对象
export function getThemeValue (themeName) {
return theme[themeName]
}
//在其他 因为主题变化看不见的 字体 图标 背景等中 的.vue中
import { getThemeValue } from ‘@/utils/theme_utils’
color: getThemeValue(this.theme).titleColor // 主题变化时的 左右箭头变色
由于大部分class 使用了计算属性所以 继续直接添加 这一行即可
comStyle () {
// 因为用的下拉框非echarts的自带标题
// 所以 做标题分辨率适配的时候 无法设置属性
// 因此 需要在此处 设计 动态字体大小样式的数值
return {
fontSize: this.titleFontSize + ‘px’,
color: getThemeValue(this.theme).titleColor // 主题变化时的 标题的反向变色
}
再来计算属性
contatnerStyle () {
return {
backgroundColor: getThemeValue(this.theme).backgroundColor,
color: getThemeValue(this.theme).titleColor
}