Commit 964bf964 authored by liubinyu's avatar liubinyu

12月7日,湖南第一次完整版

parents
/node_modules
/dist
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
{
"name": "hnsc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.6.4",
"echarts": "^4.9.0",
"element-ui": "^2.13.1",
"vue": "^2.6.11",
"vue-router": "^3.1.6",
"vuex": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-router": "~4.3.0",
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"compression-webpack-plugin": "^5.0.2",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"sass-resources-loader": "^2.0.3",
"vue-template-compiler": "^2.6.11"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>
<!-- 入口 -->
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {};
</script>
// 电厂信息接口
import request from '@/common/request.js'
export default {
/**
* @todo: 获取电厂列表
* @param: 无
*/
getPlantList: () => {
return request({
url: 'plant/info/list',
method: 'get',
})
},
/**
* @todo: 获取机组上所有测点
* @param: id 机组id
*/
getCharacterList: (id) => {
return request({
url: `plant/device/downBox/${id}`,
method: 'get',
})
},
/**
* @todo: 获取测点趋势数据
* @param: keys 测点kkscode列表,status 数据类型0原始1周均线,timeModelQuery 时间范围
*/
getTendency: (data) => {
return request({
url: 'plant/measure/tendency',
method: 'post',
data
})
}
}
// 用户和角色相关接口
import request from '@/common/request.js'
import {Message} from 'element-ui'
export default {
/**
* @todo: 登录
* @param: username
* @param: password
*/
login: (username, password) => {
return request({
url: 'user/login',
method: 'post',
data: {username,password},
noToken: true,
// 自定义拦截器,获取token
responseInterceptor: response => {
localStorage.setItem('electric_user_token', response.headers.authorization || '');
const res = response.data
// 后台返回的数据如果不是200,也抛出reject
if (res.code !== 200) {
Message({message: res.msg || 'Error', type: 'error',})
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res.data
}
}
})
},
/**
* @todo: 获取电厂的用户列表
* @param {Number} pageNo 页码
* @param {Number} pageSize 每页记录数
*/
getUserList: (params) => {
return request({
url: 'userInfo/page',
method: 'get',
params
})
},
/**
* @todo: 新增用户
* @param: {String} username 用户名
* @param: {String} password 密码
*/
addUser: (data) => {
return request({
url: 'userInfo/new',
method: 'post',
data
})
},
/**
* @todo: 修改用户
* @param: {Number} id 用户id
* @param: {String} username 用户名
* @param: {String} password 密码
*/
editUser: (data) => {
return request({
url: 'userInfo/edit',
method: 'post',
data
})
},
/**
* @todo: 删除用户
* @param: {Number} id 用户id
*/
delUser: (id) => {
return request({
url: `userInfo/del?id=${id}`,
method: 'post',
})
},
/**
* @todo: 获取电厂的角色列表
*/
getRoleList: () => {
return request({
url: `role/list`,
method: 'post',
})
},
/**
* @todo: 新增/修改角色
* @param: {Object} param 角色信息参数,其中id为角色id(有就是更新,没有则为新增)
*/
newOrUpdateRole: (param) => {
return request({
url: `role/newOrUpdate`,
method: 'post',
data: param
})
},
}
\ No newline at end of file
// 报警记录
import request from '@/common/request.js'
export default {
/**
* @todo: 获取报警列表
* @param: plantId 电厂id
* @param: deviceId 机组id
* @param: eventInfo 0未处理1已处理
* @param: pageNo 页码
* @param: pageSize 页尺寸
*/
getWarnList: (params) => {
return request({
url: 'alarm/record/page/list',
method: 'get',
params
})
},
/**
* @todo: 改变事件状态
* @param: id 事件id
* @param: eventInfo 0未处理1已处理
*/
changeState: (data) => {
return request({
url: '/alarm/record/eventInfo/status',
method: 'put',
data
})
},
}
This diff is collapsed.
This diff is collapsed.
// 常量
// const server_ip = '8.130.25.40'; // 测试服务器
// const server_ip = '127.0.0.1';
const server_ip = '192.168.192.128'; // 线上内网ip
// const server_ip = '192.168.43.122:8258';
export const CONFIG = {
SERVER_IP: server_ip,
SOCKET: server_ip + '/socket', // 线上
// SOCKET: server_ip, // 本地
API_URL: '/plant/',
TITLE: '湖南三厂机组实时监测系统', // 项目名称
CHARACTER_TAB: '#', // 测点名字里的标识,比如1F定子CA线电压,中的"F",因为会根据这个标识来分割测点名字,可以为空,也可以为多个字符
}
// 路由映射,key为路由名name(存库),value为显示名
export const RouteMap = {
FactoryMonitoring: '全厂监测',
CrewMonitoring: '机组监测',
TrendAnalysis: '趋势分析',
AlarmRecord: '报警记录',
UserManage: '用户管理',
RoleManage: '角色管理'
}
// 按钮映射
export const ButtonMap = {
UserDelete: '用户删除',
}
// 首页图表样式
export const ChartStyle = {
loadingColor: 'rgba(0, 0, 0, 0.5)', // 加载动画颜色
color: ['#509FFF', '#E5394C', '#0011A5', '#FF57BF', '#43C6AC', '#AA076B', '#0ED2F7', '#9733EE'], // 数据颜色
colorGradients: ['#6AF6FE', '#F0724D', '#00BAE6', '#FF8B7C', '#F8FFAE', '#E991C3', '#92FE9D', '#C3A0FF'], // 数据渐变颜色
// 标题样式
title: {
color: '#93FCFF',
fontSize: 14,
},
// 图例样式
legend: {
color: '#ffffff',
fontSize: 12,
},
// 轴样式
axis: {
color: '#5caac3',
fontSize: 10,
lineHeight: 14,
},
// 数据文字样式
label: {
color: '#ffffff',
fontSize: 12,
},
// 辅助线样式
split: {
color: '#93FCFF',
opacity: 0.2,
}
}
// 机组状态
export const CrewState = {
// 报警
warn: {
key: 'warn',
name: '报警',
color: '#F3B242'
},
// 危险
danger: {
key: 'danger',
name: '危险',
color: '#E53B4C'
},
// 停机
shutdown: {
key: 'shutdown',
name: '停机',
color: '#55B8F0'
},
// 正常
normal: {
key: 'normal',
name: '正常',
color: '#59E594'
}
}
// 报警数据,key为字段前缀,不要更改
export const WarningConfig = {
// 高高报警(一级)
protect: {
name: '一级报警',
color: '#FF636B',
color2: '#823237',
state: 5,
warnLevel: 1, // 报警等级,会依次按照这个字段统计报警数量,用于阈值更新里的测试
},
// 高报警(二级)
alert: {
name: '二级报警',
color: '#FFBA49',
color2: '#825e24',
state: 4,
warnLevel: 2,
},
// 预报警(三级)
earlywarning: {
name: '三级报警',
color: '#ffff00',
color2: '#828200',
state: 3,
warnLevel: 3,
},
// // 上趋势报警
// trendalert: {
// name: '趋势报警',
// color: '#0000ff',
// color2: '#000082',
// state: 6,
// warnLevel: 4,
// },
// // 下趋势报警
// trendalert2: {
// name: '趋势报警',
// color: '#00aaff',
// color2: '#005782',
// state: 7,
// warnLevel: 5,
// },
// 正常
normal: {
name: '正常',
color: '#00de6b',
color2: '#00823c',
state: 1
},
// 无效
invalid: {
name: '无效',
color: '#a2a2a2',
color2: '#484848',
state: 2
},
}
// 时间线的按钮组
export const AllDateLineBtn = [{
name: '原始分析',
value: 0
}, {
name: '本周',
value: 1
// }, {
// name: '本月趋势',
// value: 2
// }, {
// name: '本年趋势',
// value: 3
}];
// 获取日期时间选择快捷时间,用于方便管理
export function GetPickerOptions() {
return {
shortcuts: [{
text: '最近一小时',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000);
picker.$emit('pick', [start, end]);
}
},
{
text: '最近三小时',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 3);
picker.$emit('pick', [start, end]);
}
},
{
text: '最近一天',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24);
picker.$emit('pick', [start, end]);
}
},
{
text: '最近三天',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
picker.$emit('pick', [start, end]);
}
},
]
}
}
// 日期选择的默认时间
export const DefaultDateSelect = () => [Date.now() - 3600 * 1000, Date.now()]; // 最近1小时
// 全厂监测测点配置
export const FactoryMConfig = {
power: ['有功功率'], // 功率
// 框框
list: [
["上导X向摆度"],
["上导Y向摆度"],
["下导X向摆度"],
["下导Y向摆度"],
["法兰X向摆度"],
["法兰Y向摆度"],
["水导X向摆度"],
["水导Y向摆度"],
["上机架X向振动"],
["上机架Y向振动"],
["上机架Z向振动"],
["下机架X向振动"],
["下机架Y向振动"],
["下机架Z向振动"],
["顶盖X向振动"],
["顶盖Y向振动"],
["顶盖Z向振动X"],
["顶盖Z向振动Y"],
["顶盖Z向振动"]
]
}
// 机组监测测点配置
export const CrewMConfig = {
// 顶部
top: [
['转速'],
['有功功率'],
],
// 框框左上
listLeftTop: [
["上导X向摆度"],
["上导Y向摆度"],
["下导X向摆度"],
["下导Y向摆度"],
["法兰X向摆度"],
["法兰Y向摆度"],
["水导X向摆度"],
["水导Y向摆度"],
["上机架X向振动"],
["上机架Y向振动"],
["上机架Z向振动"],
],
// 框框右上
listRightTop: [
["大轴轴向位移A"],
["大轴轴向位移B"],
["大轴轴向位移C"],
["下机架X向振动"],
["下机架Y向振动"],
["下机架Z向振动"],
["瓦架X向振动"],
["瓦架Z向振动"],
],
// 框框左下
listLeftBottom: [
["定子机座X向振动"],
["定子机座Y向振动"],
["定子基座X向振动"],
["定子基座Y向振动"],
["定子铁芯X向振动"],
["定子铁芯Y向振动"],
["蜗壳进口压力脉动"],
["蜗壳差压"],
["导叶后压力脉动"]
],
// 框框右下
listRightBottom: [
["顶盖碰磨Z向振动"],
["顶盖碰磨X向振动"],
["顶盖X向振动"],
["顶盖Y向振动"],
["顶盖Z向振动X"],
["顶盖Z向振动Y"],
["顶盖Z向振动"],
["顶盖下压力脉动"],
["尾水管X向振动"],
["尾水管进口压力脉动"],
["尾水管出口压力脉动"],
["尾水管出口压力"],
],
// 图表左下
leftBottom: [
["有功功率"],
["无功功率"],
["接力器行程"],
["励磁电压"],
["励磁电流"],
["转速"],
["发电机出口开关"],
["励磁开关"],
["上游水位"],
["下游水位"],
["水头"],
["导叶开度"]
],
// 图表右上
rightTop: [
["上机架X向振动"],
["上机架Y向振动"],
["上机架Z向振动"],
["定子机座X向振动"],
["定子机座Y向振动"],
["定子基座X向振动"],
["定子基座Y向振动"],
["定子铁芯X向振动"],
["定子铁芯Y向振动"]
],
// 图表右中
rightCenter: [
["顶盖碰磨Z向振动"],
["顶盖碰磨X向振动"],
["顶盖X向振动"],
["顶盖Y向振动"],
["顶盖Z向振动X"],
["顶盖Z向振动Y"],
["顶盖Z向振动"],
["尾水管X向振动"],
["顶盖下压力脉动"],
["尾水管进口压力脉动"],
["尾水管出口压力脉动"],
["尾水管出口压力"],
["蜗壳进口压力脉动"],
["蜗壳差压"],
["导叶后压力脉动"]
],
// 图表右下
rightBottom: [
["上导X向摆度"],
["上导Y向摆度"],
["上导瓦温"],
["下导X向摆度"],
["下导Y向摆度"],
["推力瓦温"],
["水导X向摆度"],
["水导Y向摆度"],
["水导瓦温"]
],
}
import axios from 'axios'
import {
CONFIG
} from './const.js'
import {
userLogout
} from './util.js'
import {
Message
} from 'element-ui'
import router from '../router'
// 封装接口请求,如果不是正常返回,均会用reject处理,并且会默认抛出message提示,如果需要自定义,可以在不同接口配置响应拦截器
const service = axios.create({
baseURL: CONFIG.API_URL,
})
service.interceptors.request.use(
config => {
// 添加token
const token = localStorage.getItem('electric_user_token');
if (!config.noToken && token) config.headers['Authorization'] = token;
return config
},
error => {
console.log('request error:', error)
return Promise.reject(error)
}
)
service.interceptors.response.use(
response => {
// 如果设置了拦截器,执行,否则执行默认拦截器
if (response.config.responseInterceptor) {
return response.config.responseInterceptor(response);
} else {
const res = response.data
// 后台返回的数据如果不是200,也抛出reject
if (res.code !== 200) {
Message({
message: res.msg || 'Error',
type: 'error',
})
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res.data
}
}
},
error => {
console.log('response error:' + error)
if (error && error.response) {
// 处理响应状态
switch (error.response.status) {
case 401:
userLogout();
break
default:
Message({
message: error.message,
type: 'error',
})
break
}
}
return Promise.reject(error)
}
)
export default service
This diff is collapsed.
// 图表基础,负责建立,销毁图表,监听尺寸变化,监听图表事件
import echarts from 'echarts';
import {
debounce
} from '@/common/util.js'
export default {
props: {
// 图表dom的id,注意同一个页面的图表使用不同的id区分,最好全局都是唯一的
id: {
type: String,
required: true
},
// 数据体,只关心数据
chartData: {
type: Object | Array,
required: true
},
},
data() {
return {
chart: null, // 图表对象
resizeHandler: null, // 监听窗口变化的监听器
initMaxCount: 10, // 图表在初始化时会获取dom元素,如果没有获取到(可能是延迟加载),则会用settimeout延时初始化,这个数字为最多尝试初始化次数
};
},
watch: {
// 数据改变时改变图表
chartData: {
deep: true,
handler(val) {
this.setOptions(val);
}
}
},
created() {
this.beforeInit?.(echarts);
},
mounted() {
this.$nextTick(() => {
this.initChart();
this.initListener()
});
},
activated() {
// 激活时,如果没有图表,则重新生成,因为有可能在mounted阶段还未生成图表时,就切换了页面,导致未捕获到元素,无法生成图表
if (!this.chart) {
this.$nextTick(() => {
this.initChart();
});
}
this.initListener()
},
deactivated() {
this.destroyListener()
},
beforeDestroy() {
this.destroyListener();
this.destroyChart();
},
methods: {
// 建立图表对象
initChart() {
let dom = document.getElementById(this.id);
// if (dom) console.log(this.id, 'echart-width', dom.offsetWidth, 'echart-height', dom.offsetHeight);
// else console.log(this.id, 'dom不存在');
// 如果没有dom,则延时建立
if (!dom || !dom.offsetWidth) {
this.initMaxCount--;
if (this.initMaxCount > 0) setTimeout(() => this.initChart(), 100);
return;
}
this.chart = echarts.init(dom); // 初始化图表
// 绑定监听事件,如果有的话
if (this.eventListener) {
for (let item of this.eventListener) {
this.chart.on(item.event, item.handler);
}
}
this.setOptions(this.chartData);
},
// 释放图表对象
destroyChart() {
if (!this.chart) return;
this.chart.dispose();
this.chart = null;
},
// 监听窗口尺寸变化
initListener() {
if (this.resizeHandler) return;
// 防抖处理
this.resizeHandler = debounce(() => {
this.resize()
}, 100);
window.addEventListener('resize', this.resizeHandler);
},
// 移除监听器
destroyListener() {
if (!this.resizeHandler) return;
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
},
// echart的resize
resize() {
this.chart && this.chart._dom.offsetWidth && this.chart.resize();
},
// 清屏
clear() {
this.chart && this.chart.clear();
}
}
}
<!-- 柱状 - 圆弧 -->
<template>
<div :id="id" class="chart" />
</template>
<script>
import ChartBase from '@/components/Base/ChartsBase.js';
import {
ChartStyle
} from '@/common/const.js';
import echarts from 'echarts';
export default {
mixins: [ChartBase],
methods: {
setOptions(chartData) {
if (!this.chart || !chartData) return;
let option = {
tooltip: {
formatter: params => {
if (!params.value) return '';
return params.value;
}
},
title: [],
xAxis: [],
yAxis: [],
series: [],
grid: [],
};
// 找到最大值,作为单轴时y轴的最大值
let yMax;
for (let i = 0; i < chartData.length; i++) {
for (let j = 0; j < chartData[i].dataList.length; j++) {
for (let k = 0; k < chartData[i].dataList[j].value.length; k++) {
if(!yMax || yMax < chartData[i].dataList[j].value[k]) yMax = chartData[i].dataList[j].value[k];
}
}
}
for (let i = 0, len = chartData.length; i < len; i++) {
option.title.push({
text: '{a| } ' + chartData[i].title,
textStyle: {
color: ChartStyle.title.color,
fontSize: ChartStyle.title.fontSize,
// width: '100%',
fontWeight: 200,
rich: {
a: {
width: 5,
height: 5,
backgroundColor: ChartStyle.title.color,
borderRadius: 50,
shadowColor: 'rgba(147,252,255,0.30)',
shadowBlur: 5,
}
}
},
left: 100 / len / 2 + (100 / len) * i - 1 + '%',
top: '3',
textAlign: 'center'
});
option.xAxis.push({
data: chartData[i].xAxis,
axisLine: {
lineStyle: {
color: ChartStyle.axis.color,
}
},
axisTick: {
show: false
},
axisLabel: {
color: ChartStyle.axis.color,
fontSize: ChartStyle.axis.fontSize
},
gridIndex: i
});
option.yAxis.push({
max: yMax * 1.2, // 单轴
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
},
splitLine: {
show: false
},
gridIndex: i
});
option.grid.push({
top: '20%',
left: (100 / len) * i + 15 + '%',
right: (100 / len) * (len - i - 1) + 15 + '%',
bottom: '5%',
containLabel: true
});
for (let j = 0; j < chartData[i].dataList.length; j++) {
option.series.push({
type: 'bar',
name: chartData[i].dataList[j].name,
data: chartData[i].dataList[j].value,
// label: {
// show: true,
// position: 'top',
// color: ChartStyle.label.color,
// fontSize: ChartStyle.label.fontSize ,
// },
itemStyle: {
barBorderRadius: [50, 50, 50, 50], //(顺时针左上,右上,右下,左下)
// 4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: ChartStyle.colorGradients[i] },
{ offset: 1, color: ChartStyle.color[i] },
])
},
barMaxWidth: 12,
xAxisIndex: i,
yAxisIndex: i
});
}
}
this.chart.setOption(option);
},
}
};
</script>
<style lang="scss" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>
<!-- 柱状 - 圆柱 -->
<template>
<div :id="id" class="chart" />
</template>
<script>
import ChartBase from '@/components/Base/ChartsBase.js';
import {
ChartStyle
} from '@/common/const.js';
import echarts from 'echarts';
export default {
mixins: [ChartBase],
methods: {
setOptions(chartData) {
if (!this.chart || !chartData) return;
let option = {
tooltip: {
formatter: params => {
if (!params.value) return '';
return params.value;
}
},
title: [],
xAxis: [],
yAxis: [],
series: [],
grid: [],
};
// 找到最大值,作为单轴时y轴的最大值
let yMax;
for (let i = 0; i < chartData.length; i++) {
for (let j = 0; j < chartData[i].dataList.length; j++) {
for (let k = 0; k < chartData[i].dataList[j].value.length; k++) {
if(!yMax || yMax < chartData[i].dataList[j].value[k]) yMax = chartData[i].dataList[j].value[k];
}
}
}
for (let i = 0, len = chartData.length; i < len; i++) {
option.title.push({
text: '{a| } ' + chartData[i].title,
textStyle: {
color: ChartStyle.title.color,
fontSize: ChartStyle.title.fontSize,
// width: '100%',
fontWeight: 200,
rich: {
a: {
width: 5,
height: 5,
backgroundColor: ChartStyle.title.color,
borderRadius: 50,
shadowColor: 'rgba(147,252,255,0.30)',
shadowBlur: 5,
}
}
},
left: 100 / len / 2 + (100 / len) * i - 1 + '%',
top: '3',
textAlign: 'center'
});
option.xAxis.push({
data: chartData[i].xAxis,
axisLine: {
lineStyle: {
color: ChartStyle.axis.color,
}
},
axisTick: {
show: false
},
axisLabel: {
color: ChartStyle.axis.color,
fontSize: ChartStyle.axis.fontSize
},
gridIndex: i
});
option.yAxis.push({
max: yMax * 1.2, // 单轴
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
},
splitLine: {
show: false
},
gridIndex: i
});
option.grid.push({
top: '20%',
left: (100 / len) * i + 15 + '%',
right: (100 / len) * (len - i - 1) + 15 + '%',
bottom: '5%',
containLabel: true
});
for (let j = 0; j < chartData[i].dataList.length; j++) {
option.series.push({
type: 'bar',
data: chartData[i].dataList[j].value,
showBackground: true,
backgroundStyle: {
color: 'rgba(0, 0, 0, 0.1)'
},
itemStyle: {
color: 'transparent'
},
barMaxWidth: 25,
xAxisIndex: i,
yAxisIndex: i
});
option.series.push({
type: 'pictorialBar',
data: chartData[i].dataList[j].value,
symbolSize: [25, 10],
symbolOffset: [0, -10],
symbolPosition: 'start',
itemStyle: {
normal: {
color: ChartStyle.color[i],
}
},
xAxisIndex: i,
yAxisIndex: i
});
option.series.push({
type: 'pictorialBar',
name: chartData[i].dataList[j].name,
data: chartData[i].dataList[j].value,
symbol: 'rect',
symbolClip: true,
symbolSize: [25, '100%'],
symbolOffset: [0, -15],
symbolPosition: 'start',
itemStyle: {
// 4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: ChartStyle.colorGradients[i] },
{ offset: 1, color: ChartStyle.color[i] },
])
},
xAxisIndex: i,
yAxisIndex: i
});
option.series.push({
type: 'pictorialBar',
data: chartData[i].dataList[j].value,
symbolSize: [25, 10],
symbolOffset: [0, -5],
symbolPosition: 'end',
itemStyle: {
normal: {
color: ChartStyle.colorGradients[i],
shadowColor: ChartStyle.color[i],
shadowBlur: 1
}
},
// label: {
// show: true,
// position: 'top',
// color: ChartStyle.label.color,
// fontSize: ChartStyle.label.fontSize ,
// },
xAxisIndex: i,
yAxisIndex: i
})
}
}
this.chart.setOption(option);
},
}
};
</script>
<style lang="scss" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>
This diff is collapsed.
This diff is collapsed.
<!-- 图表的按钮 -->
<template>
<div class="btns global">
<!-- 图表类型按钮,注意按钮的值需要和modelinfo组件里的对应 -->
<div class="btn-line">
<el-button class="btn-style" :class="param.chartBtn == 1 ? 'btn-active' : 'btn'" type="primary" @click="changeChart(1)">
趋势分析
</el-button>
<el-button class="btn-style" :class="param.sameAxis ? 'btn-active' : 'btn'" type="primary" @click="changeAxis">
共用坐标
</el-button>
</div>
<!-- 时间线按钮 -->
<div class="btn-line" v-if="param.chartBtn == 1">
<el-button v-for="btn of AllDateLineBtn" :key="btn.value" class="btn-style" :class="param.dateLineBtn == btn.value ? 'btn-active' : 'btn'"
type="primary" @click="changeDateLine(btn.value)">
{{ btn.name }}
</el-button>
<el-date-picker class="date-picker" v-model="param.dateSelect" type="datetimerange" unlink-panels
prefix-icon="none" :picker-options="pickerOptions" :clearable="false" :editable="false"
value-format="timestamp" start-placeholder="开始时间" end-placeholder="结束时间" @change="changeDate" />
</div>
</div>
</template>
<script>
import {
GetPickerOptions,
DefaultDateSelect,
AllDateLineBtn
} from '@/common/const.js';
export default {
props: {
// 选择的列表
checkList: {
type: Array,
default () {
return [];
}
},
},
data() {
return {
// 时间线的按钮组
AllDateLineBtn: AllDateLineBtn,
// 选择的按钮,即图表参数
param: {
chartBtn: 1, // 选择的图表按钮
dateLineBtn: 0, // 选择的时间趋势按钮
dateSelect: DefaultDateSelect(), // 选择的日期
sameAxis: true, // 是否共坐标
},
// 快捷日期选择
pickerOptions: GetPickerOptions(),
};
},
watch: {
checkList: {
immediate: true,
handler(val) {
if (!val || !val.length) return;
this.changeChart(1); // 模拟按钮点击
}
}
},
methods: {
// 切换图表类型
changeChart(val) {
this.param.chartBtn = val;
this.$emit('changeBtn', this.param);
},
// 切换时间线
changeDateLine(val) {
this.param.dateLineBtn = val;
let today = new Date();
let minusDay = today.getDay() != 0 ? today.getDay() - 1 : 6;
this.param.dateSelect = val ? [today.setHours(0, 0, 0, 0) - (minusDay * 1000 * 3600 * 24), Date.now()] : DefaultDateSelect();
this.$emit('changeBtn', this.param);
},
// 切换日期
changeDate(val) {
if (!val) this.param.dateSelect = DefaultDateSelect(); // 如果没有选择日期,则采用默认日期
this.$emit('changeBtn', this.param);
},
// 切换共用坐标
changeAxis() {
this.param.sameAxis = !this.param.sameAxis;
this.$emit('changeAxis', this.param);
},
// 重置按钮状态
reset() {
this.param = {
chartBtn: 1,
dateLineBtn: 0,
dateSelect: DefaultDateSelect(),
sameAxis: true,
};
},
}
};
</script>
<style lang="scss" scoped>
.btns {
display: flex;
flex-direction: column;
.btn-line {
display: flex;
.btn-style {
min-width: 6rem;
height: 2.5rem;
margin: 0 1rem 1rem 0;
font-size: 0.875rem;
}
.btn {
@extend %btn-default;
border-radius: 50px;
}
.btn-active {
@extend %btn-active;
border-radius: 50px;
}
}
.date-picker {
width: 30rem;
height: 2.5rem !important;
margin: 0 1rem 1rem 0;
padding: 0;
border-radius: 50px;
display: flex;
justify-content: center;
/deep/ .el-range-input {
font-size: 0.875rem;
background-color: transparent;
color: #fff;
}
/deep/ .el-input__icon {
width: 0;
}
/deep/ .el-range-separator{
@extend %flex-center;
color: #fff;
}
}
}
</style>
<!-- 普通折线图 -->
<template>
<div :id="id" class="charts-line" />
</template>
<script>
import ChartBase from '@/components/Base/ChartsBase.js';
import {
ChartStyle
} from '@/common/const.js';
import {
toFixedNum
} from '@/common/util.js';
import echarts from 'echarts';
export default {
mixins: [ChartBase],
methods: {
setOptions(chartData) {
if (!this.chart || !chartData) return;
let option;
// 共轴
if (chartData.sameAxis) {
option = {
tooltip: {
trigger: 'axis',
formatter: params => {
let newParams = [...params];
let tooltip = (chartData.xName || '') + params[0].axisValue + '<br/>';
newParams.sort((a, b) => a.seriesIndex - b.seriesIndex);
newParams.forEach(p => {
let item = chartData.dataList.find(i => i.name == p.seriesName);
const cont = p.seriesName + ': ' + toFixedNum(p.value,
4) + (item.unit || '') + '<br/>';
tooltip += cont;
});
return tooltip;
},
textStyle: {
align: 'left'
}
},
axisPointer: {
lineStyle: {
type: 'dashed'
}
},
dataZoom: {
type: 'inside'
},
legend: {
bottom: '5%',
textStyle: {
color: ChartStyle.legend.color,
fontSize: ChartStyle.legend.fontSize,
},
itemGap: 40,
icon: 'pin',
},
color: ChartStyle.color,
xAxis: {
data: chartData.xAxis ?? chartData.xaxis,
axisLine: {
lineStyle: {
color: ChartStyle.title.color,
}
},
axisTick: {
show: false
},
axisLabel: {
fontSize: ChartStyle.axis.fontSize,
formatter: val => {
let arr = val.split(' ');
return arr[0] + (arr[1] ? '\n' + arr[1] : '');
}
},
},
yAxis: {
nameTextStyle: {
color: ChartStyle.title.color
},
scale: true,
splitNumber: 3,
axisLine: {
show: false,
},
axisTick: {
show: false
},
axisLabel: {
color: ChartStyle.title.color,
fontSize: ChartStyle.axis.fontSize
},
splitLine: {
lineStyle: {
type: 'dashed',
color: ChartStyle.split.color,
opacity: ChartStyle.split.opacity,
}
}
},
series: [],
grid: {
top: '5%',
left: '2%',
right: '2%',
bottom: '15%',
containLabel: true
}
};
for (let i = 0, len = chartData.dataList.length; i < len; i++) {
let item = chartData.dataList[i];
option.series.push({
type: 'line',
name: item.name,
data: item.value,
showSymbol: false,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1,
color: ChartStyle.color[i]
},
{
offset: 0,
color: ChartStyle.colorGradients[i]
},
])
}
});
}
// 不共轴
} else {
option = {
tooltip: {
trigger: 'axis',
formatter: params => {
let newParams = [...params];
let tooltip = (chartData.xName || '') + params[0].axisValue + '<br/>';
newParams.sort((a, b) => a.seriesIndex - b.seriesIndex);
newParams.forEach(p => {
let item = chartData.dataList.find(i => i.name == p.seriesName);
const cont = p.seriesName + ': ' + toFixedNum(p.value,
4) + (item.unit || '') + '<br/>';
tooltip += cont;
});
return tooltip;
},
textStyle: {
align: 'left'
}
},
axisPointer: {
lineStyle: {
type: 'dashed'
},
link: {
xAxisIndex: []
}
},
dataZoom: {
type: 'inside',
xAxisIndex: []
},
legend: [],
color: ChartStyle.color,
xAxis: [],
yAxis: [],
series: [],
grid: [],
};
for (let i = 0, len = chartData.dataList.length; i < len; i++) {
let item = chartData.dataList[i];
option.axisPointer.link.xAxisIndex.push(i);
option.dataZoom.xAxisIndex.push(i);
option.legend.push({
data: [item.name],
top: (100 / len) * i + 10 + '%',
right: '10',
textStyle: {
color: ChartStyle.legend.color,
fontSize: ChartStyle.legend.fontSize,
},
itemGap: 40,
icon: 'pin',
formatter: function (name) {
let len = name.length
if(len < 15){
for (let i = 0; i < (15 - len); i++) {
name += ' ';
}
}
return name;
}
});
option.xAxis.push({
data: chartData.xAxis ?? chartData.xaxis,
axisLine: {
lineStyle: {
color: ChartStyle.title.color,
}
},
axisTick: {
show: false
},
axisLabel: {
fontSize: ChartStyle.axis.fontSize,
formatter: val => {
let arr = val.split(' ');
return arr[0] + (arr[1] ? '\n' + arr[1] : '');
}
},
gridIndex: i
});
option.yAxis.push({
name: item.unit || '',
nameTextStyle: {
color: ChartStyle.title.color
},
scale: true,
splitNumber: 1,
axisLine: {
show: false,
},
axisTick: {
show: false
},
axisLabel: {
color: ChartStyle.title.color,
fontSize: ChartStyle.axis.fontSize
},
splitLine: {
lineStyle: {
type: 'dashed',
color: ChartStyle.split.color,
opacity: ChartStyle.split.opacity,
}
},
gridIndex: i
});
option.series.push({
type: 'line',
name: item.name,
data: item.value,
showSymbol: false,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 1,
color: ChartStyle.color[i]
},
{
offset: 0,
color: ChartStyle.colorGradients[i]
},
])
},
xAxisIndex: i,
yAxisIndex: i,
});
option.grid.push({
top: (100 / len) * i + 7 + '%',
left: '2%',
right: '230',
bottom: (100 / len) * (len - i - 1) + 2 + '%',
containLabel: true
});
}
}
this.chart.setOption(option);
}
}
};
</script>
<style lang="scss" scoped>
.charts-line {
width: 100%;
height: 100%;
}
</style>
<!-- 折线图 -->
<template>
<div :id="id" class="chart" />
</template>
<script>
import ChartBase from '@/components/Base/ChartsBase.js';
import {
ChartStyle
} from '@/common/const.js';
import {
toFixedNum
} from '@/common/util.js';
export default {
mixins: [ChartBase],
methods: {
setOptions(chartData) {
if (!this.chart || !chartData) return;
let option = {
tooltip: {
trigger: 'axis',
formatter: params => {
let newParams = [...params];
let tooltip = params[0].axisValue + '<br/>';
newParams.forEach(p => {
const cont = p.marker + ' ' + p.seriesName + ': ' + toFixedNum(p.value,
2) + '<br/>';
tooltip += cont;
});
return tooltip;
},
textStyle: {
align: 'left'
}
},
dataZoom: {
type: 'inside'
},
color: ChartStyle.color,
xAxis: {
data: chartData.xAxis,
axisLine: {
lineStyle: {
color: ChartStyle.axis.color,
}
},
axisTick: {
show: false
},
axisLabel: {
fontSize: ChartStyle.axis.fontSize,
interval: parseInt(chartData.xAxis.length / 5),
showMinLabel: false,
}
},
yAxis: {
name: chartData.unit ?? '',
nameTextStyle: {
color: ChartStyle.axis.color
},
scale: true,
splitNumber: 3,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: ChartStyle.axis.color,
fontSize: ChartStyle.axis.fontSize
},
splitLine: {
lineStyle: {
type: 'dashed',
color: ChartStyle.split.color,
opacity: ChartStyle.split.opacity,
}
}
},
series: [],
grid: {
top: '15%',
left: '2%',
right: '2%',
bottom: '12%',
containLabel: true
}
};
// if(chartData.dataList.length > 1){
option.legend = {
bottom: 0,
textStyle: {
color: ChartStyle.legend.color,
fontSize: ChartStyle.legend.fontSize
},
icon: 'diamond',
itemHeight: 10,
itemGap: 40,
}
// }
for (let i of chartData.dataList) {
option.series.push({
type: 'line',
step: 'end',
name: i.name,
data: i.value
});
}
this.chart.setOption(option);
}
}
};
</script>
<style lang="scss" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>
<!-- 地图 -->
<template>
<div :id="id" class="chart" />
</template>
<script>
import ChartBase from '@/components/Base/ChartsBase.js';
export default {
mixins: [ChartBase],
computed: {
// 事件监听列表
eventListener() {
return [{
event: 'click',
handler: this.onclick
}, {
event: 'mouseover',
handler: this.onmouseover
}, {
event: 'mouseout',
handler: this.onmouseout
}];
}
},
methods: {
// 注册地图数据
beforeInit(echarts) {
echarts.registerMap('hunan', require('@/assets/json/hunan.json'));
},
async setOptions(chartData) {
if (!this.chart || !chartData) return;
let option;
option = {
geo: [{
map: 'hunan',
silent: true,
aspectScale: 0.9,
itemStyle: {
normal: {
areaColor: '#1b3370',
borderColor: '#93FCFF',
}
},
}],
series: [{
type: 'effectScatter',
coordinateSystem: 'geo',
data: chartData,
rippleEffect: {
scale: 4,
brushType: 'stroke'
},
label: {
show: true,
position: [0, -45],
color: '#fff',
backgroundColor: 'rgba(0,0,0,0.5)',
borderColor: '#93FCFF',
borderWidth: 1,
padding: 10,
formatter: (val) => {
return val.name;
},
},
hoverAnimation: true,
itemStyle: {
color: (params) => {
return params.data.active ? '#ff0000' : '#0055ff'
},
shadowBlur: 15,
shadowColor: '#333'
},
}]
};
this.chart.setOption(option);
},
onclick(e) {
this.$emit('clickFactory', e.dataIndex);
},
onmouseover(e) {
this.$emit('selectFactory', e.dataIndex);
},
onmouseout(e) {
this.$emit('selectFactory');
}
}
};
</script>
<style lang="scss" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>
<!-- 图表左侧的机组状态统计 -->
<template>
<div class="state-count">
<div class="item" :style="{'background-image': 'linear-gradient(to right, rgba(0,0,0,0), ' + item.color + ', rgba(0,0,0,0))'}" v-for="(item, key) in crewState">
{{item.name}}: {{copyCount[key]}}
</div>
</div>
</template>
<script>
import {CrewState} from '@/common/const.js';
export default {
props: {
count: {
type: Object,
default(){
return {
warn: 0,
danger: 0,
shutdown: 0,
normal: 0
}
}
}
},
watch: {
count(val){
if(!val) return;
else this.copyCount = val;
}
},
data() {
return {
crewState: CrewState, // 机组状态
// 复制count,防止count为空
copyCount: {
warn: 0,
danger: 0,
shutdown: 0,
normal: 0
},
}
}
}
</script>
<style lang="scss" scoped>
.state-count{
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
.item{
width: 100%;
height: 17%;
@extend %flex-center;
font-size: 0.875rem;
}
}
</style>
<!-- 数字翻牌器 -->
<template>
<div class="number-flipper">
<div class="up" v-if="title">
<div class="i" />{{title}}
</div>
<div class="down">
<num :number="relNumber" />
<span class="unit">{{unit}}</span>
</div>
</div>
</template>
<script>
import Num from './num.vue'
export default {
components: {
Num,
},
props: {
title: {
type: String,
default: '',
},
unit: {
type: String,
default: '',
},
number: {
type: Number | String,
default: 0,
},
// 小数位数
point: {
type:Number,
default: 0,
}
},
data() {
return {
relNumber: 0,
}
},
watch: {
number: {
immediate: true,
handler(val) {
this.relNumber = parseFloat(val).toFixed(this.point < 0 ? 0 : this.point);
}
}
},
}
</script>
<style lang="scss" scoped>
.number-flipper {
.up{
color: $color-cyan;
@extend %flex-center;
margin-bottom: 0.5rem;
.i {
width: 0.2rem;
height: 0.2rem;
border-radius: 50%;
background: $color-cyan;
box-shadow: 0 0 0.12rem 0.15rem rgba($color: $color-cyan, $alpha: 0.3);
margin-right: 0.5rem;
}
}
.down{
display: flex;
justify-content: center;
align-items: flex-end;
.unit{
font-size: 0.875rem;
color: $color-cyan;
}
}
}
</style>
<!-- 数字翻牌器单个数字 -->
<template>
<div id="dataNums" class="number">
<div :class="item.num != '.' ? 'n-bg' : 'p-bg'" v-for="(item,index) in list" :key="index">
<div v-if="item.num != '.'" class="nums" :style="{transition:'all 1s ease-in-out 0s',top:'-'+item.top+'px'}">
<span v-for="(item2,index2) in numList" :key="index2">{{item2}}</span>
</div>
<div v-else class="point"></div>
</div>
</div>
</template>
<script>
export default {
props: {
number: {
type: Number | String,
default: 0
},
},
data() {
return {
height: 0,
list: [],
numList: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
},
watch: {
number: {
immediate: true,
handler(val) {
this.$nextTick(()=>{
this.scroll();
})
}
}
},
created() {
this.init();
},
methods: {
init() {
this.list = this.number.toString().split('').map(i => {
return {
num: i == '.' ? i : 0,
top: 0,
}
});
},
getHeight(){
this.height = parseFloat(getComputedStyle(document.getElementById("dataNums")).height);
},
scroll() {
if(!this.height) this.getHeight();
this.list = this.number.toString().split('').map(i => {
return {
num: i,
top: parseFloat(i * this.height),
}
});
}
}
}
</script>
<style lang="scss" scoped>
$bg-height: 2.8125rem;
.number {
text-align: center;
height: $bg-height;
display: flex;
.n-bg{
position: relative;
width: 2.15625rem;
height: $bg-height;
overflow: hidden;
background-image: url(../../assets/img/num-bg.png);
@extend %background-center;
margin-right: 0.3rem;
}
.p-bg{
position: relative;
width: 0.2rem;
height: $bg-height;
margin-right: 0.5rem;
}
.nums {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
span {
width: 100%;
height: 100%;
line-height: $bg-height;
float: left;
font-size: 2.25rem;
font-weight: bold;
color: $color-cyan;
}
}
.point{
position: absolute;
bottom: 0.3rem;
width: 0.3rem;
height: 0.3rem;
background: $color-cyan;
}
}
</style>
<!-- 滚动窗口 -->
<template>
<div class="scroll-window">
<img v-if="direction" class="top top-right" src="../../assets/img/win-right-top.png" />
<img v-else class="top top-left" src="../../assets/img/win-left-top.png" />
<div class="main">
<slot />
</div>
<img v-if="direction" class="bottom bottom-left" src="../../assets/img/win-right-bottom.png" />
<img v-else class="bottom bottom-right" src="../../assets/img/win-left-bottom.png" />
</div>
</template>
<script>
export default {
// 方向,0为左边,1为右边
props: ['direction'],
}
</script>
<style lang="scss" scoped>
.scroll-window{
width: 100%;
height: 100%;
position: relative;
.top{
width: 8.5rem;
height: auto;
position: absolute;
top: -0.25rem;
}
.top-left {
left: 0;
}
.top-right {
right: 0;
}
.bottom{
width: 5.75rem;
height: auto;
position: absolute;
bottom: -0.75rem;
}
.bottom-left {
left: -0.625rem;
}
.bottom-right {
right: -0.625rem;
}
.main{
height: 100%;
background-color: rgba(10,23,55,0.80);
border: 1px solid rgba(9,77,148,0.50);
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.10);
padding: 0.5rem 0;
box-sizing: border-box;
}
}
</style>
This diff is collapsed.
<!-- 日期和用户 -->
<template>
<div class="user-info">
<div class="block">
<i class="el-icon-date"></i>
<div>{{ time }}</div>
</div>
<div class="block">
<i class="el-icon-user"></i>
<el-dropdown trigger="click" @command="handler">
<span class="dropdown-txt"> {{ $x_user ? $x_user.username : '未登录' }} </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="quit">注销</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import {
dateFormat
} from '@/common/util.js'
export default {
data() {
return {
userTimer: null, // 存放定时器
time: ''
};
},
mounted() {
this.$nextTick(() => {
this.initTimer();
});
},
beforeDestroy() {
window.clearTimeout(this.userTimer);
this.userTimer = null;
},
methods: {
// 初始化定时器
initTimer() {
this.getTime();
this.userTimer = setInterval(this.getTime, 1000);
},
// 获取当前时间
getTime() {
this.time = dateFormat(new Date(), 'YYYY/MM/DD HH:mm:ss z');
},
// 点击用户名的下拉菜单命令
handler(command) {
switch (command) {
case 'quit':
this.logout();
break;
}
},
// 注销,这里没清除用户,是考虑还可以返回,不重新登录
logout() {
this.$router.push('/login');
}
}
};
</script>
<style lang="scss" scoped>
.user-info {
font-size: 1rem;
color: $color-cyan;
width: 100%;
height: 1.875rem;
display: flex;
justify-content: flex-start;
align-items: center;
.block {
height: 100%;
display: flex;
align-items: center;
margin-right: 1.875rem;
i {
margin-right: 0.625rem;
}
.dropdown-txt {
color: $color-cyan;
}
}
}
</style>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { mapState } from 'vuex'
import './style/index.scss'
// import './style/icons.css'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
// 将state混入computed
Vue.mixin({
computed: {
...mapState(Object.keys(store.state))
}
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store/index.js'
import Index from '@/views/Index'
import {
makeMap
} from '@/common/util.js';
Vue.use(VueRouter)
let IndexRoutePath = '/FactoryMonitoring'; // 首页即('/')跳转的页面路径,默认为机组监测,如果用户的角色没有这个页面的权限,会改为用户权限里的第一个页面
// 普通路由,这里的路由为初始路由,不能有角色控制,后面的为角色路由,角色字段为role,表示该数组内的角色都可以访问此路由,如果没有角色,则都可以访问
const normalRoutes = [{
// 默认页
path: '/',
}, {
// 登录页
path: '/login',
name: 'Login',
component: () => import('@/views/Login')
}, {
// 电厂选择页
path: '/factory',
name: 'Factory',
component: () => import('@/views/Factory')
}, {
// 404
path: '/404',
name: '404',
component: () => import('@/views/404')
}]
// 角色路由
const roleRoutes = [{
// 全厂监测
path: '/FactoryMonitoring',
name: 'FactoryMonitoring',
component: Index,
redirect: '/FactoryMonitoring/index',
children: [{
path: 'index',
component: () => import('@/views/FactoryMonitoring'),
}]
}, {
// 机组监测
path: '/CrewMonitoring',
name: 'CrewMonitoring',
component: Index,
redirect: '/CrewMonitoring/index',
children: [{
path: 'index',
component: () => import('@/views/CrewMonitoring'),
}]
}, {
// 趋势分析
path: '/TrendAnalysis',
name: 'TrendAnalysis',
component: Index,
redirect: '/TrendAnalysis/index',
children: [{
path: 'index',
component: () => import('@/views/TrendAnalysis'),
}]
}, {
// 报警记录
path: '/AlarmRecord',
name: 'AlarmRecord',
component: Index,
redirect: '/AlarmRecord/index',
children: [{
path: 'index',
component: () => import('@/views/AlarmRecord'),
}]
}, {
// 用户管理
path: '/UserManage',
name: 'UserManage',
component: Index,
redirect: '/UserManage/index',
children: [{
path: 'index',
component: () => import('@/views/UserManage'),
}]
}, {
// 角色管理
path: '/RoleManage',
name: 'RoleManage',
component: Index,
redirect: '/RoleManage/index',
children: [{
path: 'index',
component: () => import('@/views/RoleManage'),
}]
}]
// 匹配其他所有路由,必须放在最后添加到路由里,所以单独建了一个
const anyRoutes = [{
path: '*',
redirect: '/404'
}]
// 过滤掉角色相关路由
function filterRoute(routes, checkPermission) {
let newRoute = [];
if (checkPermission) {
routes.forEach(i => {
let tmp = { ...i
};
if (!tmp.name || checkPermission(tmp.name)) newRoute.push(tmp);
if (tmp.children) tmp.children = filterRoute(tmp.children, checkPermission); // 过滤子路由
})
}
return newRoute
}
// 初始化路由方法,返回新路由对象,只包含基础路由,不包含角色,角色路由应该在登录以后调用initRoleRoute方法动态添加
const createRouter = () => new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes: normalRoutes,
})
const router = createRouter();
// 重置角色路由
function initRoleRoute() {
let newRoleRoutes = roleRoutes;
// 过滤角色路由
if (store.state.$x_user?.checkPermission) {
newRoleRoutes = filterRoute(roleRoutes, store.state.$x_user.checkPermission);
// 如果该角色没有机组监测页(CrewMonitoring),则把该角色的第一个权限页改为默认页
if (newRoleRoutes.length && !newRoleRoutes.find(i => i.path == IndexRoutePath)) IndexRoutePath = newRoleRoutes[0].path;
}
normalRoutes[0].redirect = IndexRoutePath;
const newRouter = createRouter(); // 新建路由
router.matcher = newRouter.matcher; // 置换router
router.addRoutes([...newRoleRoutes, ...anyRoutes]);
console.log('路由初始化完成', newRoleRoutes);
}
// 初始化用户、电厂、机组列表,从本地读取,如果都存在并未过期,返回undefined,否则返回1或2
function initLocal() {
// 获取用户信息
let user = JSON.parse(localStorage.getItem('electric_user'));
let token = localStorage.getItem('electric_user_token');
if (!user || !token || user.endTime < Date.now()) return 1; // 如果没有用户和token,或者过期,说明未登录,则返回1,意味着需要跳转到登录页
user.data.token = token; // 将token放入user里,方便全局取
user.data.checkPermission = makeMap(user.data.permission); // 添加一个查询权限的参数
store.commit('user', user.data);
// 获取电厂信息
let factoryList = JSON.parse(localStorage.getItem('electric_factoryList'));
let factory = JSON.parse(localStorage.getItem('electric_factory'));
if (!factory || !factoryList || factory.endTime < Date.now()) return 2; // 如果没有电厂和设备列表,或者过期,说明未选择电厂,则返回2,意味着需要跳转到电厂选择页
store.commit('factory', factory.data);
store.commit('factoryList', factoryList.data);
initRoleRoute();
}
// 如果没有用户则跳转到登录
router.beforeEach((to, from, next) => {
// 如果是已登录,放行
if (to.path === '/login' || (store.state.$x_user && store.state.$x_factory)) {
next();
} else if (to.path === '/factory' && store.state.$x_user) {
next();
// 如果未登录,检查本地用户,并且添加角色路由后放行
} else {
const res = initLocal();
if (res == 1) {
next('/login');
} else if (res == 2) {
next('/factory');
} else {
next({ ...to
})
}
}
})
export default router
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
$x_user: null, // 用户信息
$x_factoryList: null, // 电厂列表
$x_factory: null, // 当前选择的电厂信息
$x_crew: null, // 当前选择的机组信息
$x_allData: [], // 所有测点数据
},
mutations: {
user(state, data) {
state.$x_user = data;
},
factoryList(state, data) {
state.$x_factoryList = data;
},
factory(state, data) {
state.$x_factory = data;
},
crew(state, data) {
state.$x_crew = data;
},
allData(state, data) {
state.$x_allData = data;
},
},
actions: {},
modules: {}
})
// 全局样式,最底部修改了el的默认样式,但必须在父元素加上global才行
@media (max-width: 1900px) {
html {
font-size: 12px;
}
}
@media (max-width: 1400px) {
html {
font-size: 10px;
}
}
#app {
width: 100%;
height: 100%;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
}
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
color: #FFF;
letter-spacing: 0.125rem;
background-color: #061540;
}
$color-cyan: #93FCFF;
$color-blue: #0984FF;
$color-deep-blue: #1C529D;
$txt-color: #eeeeee;
// 背景图片居中
%background-center {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
// flex居中
%flex-center {
display: flex;
justify-content: center;
align-items: center;
}
// 文字渐变
%txt-gradients {
text-shadow: 0 0.125rem 0.5rem rgba(78, 97, 214, 0.50);
// background: linear-gradient(to bottom, #C0D0FF, #ffffff, #A8D4FE);
// -webkit-background-clip: text;
// background-clip: text;
// color: transparent;
color: #C0D0FF;
}
// position居中
%pos-center{
position: absolute;
left: 0;
right: 0;
margin: auto;
}
// 背景框的位置
%bg-position {
@extend %pos-center;
top: 3.125rem;
width: 98.2%;
height: 93.5%;
background-image: url(../assets/img/border-inside.png);
background-size: 100% 100%;
}
// 按钮样式
%btn-default {
color: #fff;
background-color: transparent;
border-color: $color-cyan;
font-size: 1rem;
padding: 0.75rem 1rem;
letter-spacing: 0.125rem;
@extend %flex-center;
}
%btn-hover {
background-color: $color-blue;
}
%btn-active {
@extend %btn-default;
background-color: $color-deep-blue;
}
// 默认输入框样式
.global ::v-deep.el-input__inner {
border: 1px solid $color-cyan;
background-color: transparent;
color: #fff;
}
// 默认图表样式
.global ::v-deep.el-table {
color: $txt-color;
background-color: transparent;
}
.global ::v-deep.el-table thead {
color: $txt-color;
}
.global ::v-deep.el-table th {
font-weight: 200;
background-color: $color-deep-blue;
border-bottom: none;
border-right: 1px solid #000;
font-size: 1rem;
padding: 0;
height: 3.5rem;
}
.global ::v-deep.el-table th:nth-last-child(2),
.global ::v-deep.el-table th:nth-last-child(1) {
border-right: none;
}
.global ::v-deep.el-table tr {
background-color: transparent;
}
.global ::v-deep.el-table td {
font-size: 0.875rem;
padding: 0;
height: 3.5rem;
border-bottom-color: rgba($color: #fff, $alpha: 0.2);
}
.global ::v-deep.el-table--border::after, .el-table--group::after, .el-table::before{
background-color: transparent;
}
.global ::v-deep.el-table tbody tr:hover > td {
background-color: $color-deep-blue;
}
// 默认分页样式
.global ::v-deep.btn-prev,
.global ::v-deep.btn-next,
.global ::v-deep.el-pager li,
.global ::v-deep.el-pager li.btn-quicknext,
.global ::v-deep.el-pager li.btn-quickprev {
color: #fff;
background-color: transparent;
}
.global ::v-deep.el-pager li.active{
color: $color-blue;
}
.global ::v-deep.el-pager li:hover{
color: $color-blue;
}
// 默认弹框样式
.global ::v-deep.el-dialog {
background-color: rgba(10, 23, 55, 1);
border: 1px solid rgba(9, 77, 148, 0.5);
}
.global ::v-deep.el-dialog__title {
color: #fff;
}
\ No newline at end of file
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/img/404.png" alt="404" />
<img class="pic-404__child left" src="@/assets/img/404_cloud.png" alt="404" />
<img class="pic-404__child mid" src="@/assets/img/404_cloud.png" alt="404" />
<img class="pic-404__child right" src="@/assets/img/404_cloud.png" alt="404" />
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">Administrators said that you can not enter this page...</div>
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
<a href="" class="bullshit__return-home">Back to home</a>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.wscn-http404-container {
transform: translate(-50%, -50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
module.exports = {
publicPath: './',
assetsDir: 'static',
// productionSourceMap: false,
// 代理
devServer: {
proxy: {
'^/plant/': {
// target: 'http://8.130.25.40',
target: 'http://192.168.43.122:8258',
ws: true,
changeOrigin: true,
pathRewrite: {
'/plant/' : '/'
}
}
}
},
// gzip压缩
configureWebpack: config => {
const CompressionWebpackPlugin = require('compression-webpack-plugin');
config.plugins.push(
new CompressionWebpackPlugin()
)
},
chainWebpack: config => {
const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach(item => {
item
.use('sass-resources-loader')
.loader('sass-resources-loader')
.options({
resources: './src/style/index.scss',
})
.end()
})
config
.plugin('html')
.tap(args => {
args[0].title = 'hn-monitor'
return args
})
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment