代码提交

This commit is contained in:
name
2025-09-25 13:32:47 +08:00
parent 63022ffecd
commit 66b650750a
132 changed files with 35432 additions and 0 deletions

3
.dev.production Normal file
View File

@@ -0,0 +1,3 @@
NODE_ENV = 'production'
VITE_APP_BASE_API = http://120.24.64.5:8088/mall-admin
VITE_APP_BASE_URL = /index/

2
.env.development Normal file
View File

@@ -0,0 +1,2 @@
NODE_ENV = 'development'
VITE_APP_BASE_API = http://120.24.64.5:8088/mall-admin

28
.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
offline
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.zip*
*.ntvs*
*.njsproj
*.sln
*.sw?
vite.config*.ts
pnpm-lock.yaml

BIN
@jiaminghi.rar Normal file

Binary file not shown.

36
README.en.md Normal file
View File

@@ -0,0 +1,36 @@
# vue3-vite-ts-echarts5-axios 大屏可视化基础模板
#### Description
基于vue3-vite-ts-echarts5-axios+element-plus-mockjs -atav做的大屏可视化基础模板可用于大屏可视化也可用于其他项目 已封装axios
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

9
auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
}

69
components.d.ts vendored Normal file
View File

@@ -0,0 +1,69 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
DatePicker: typeof import('./src/components/datePicker/index.vue')['default']
EchartMap3D: typeof import('./src/components/echartMap3D.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
MyEchart: typeof import('./src/components/echarts/MyEchart.vue')['default']
MyEchartMap: typeof import('./src/components/MyEchartMap.vue')['default']
PointTree: typeof import('./src/components/tree/pointTree.vue')['default']
Rmsboxi: typeof import('./src/components/BX/rmsboxi.vue')['default']
Rmsboxi1: typeof import('./src/components/BX/rmsboxi1.vue')['default']
Rmsboxi2: typeof import('./src/components/BX/rmsboxi2.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Shushiboxi: typeof import('./src/components/BX/shushiboxi.vue')['default']
Shushiboxi1: typeof import('./src/components/BX/shushiboxi1.vue')['default']
Shushiboxi2: typeof import('./src/components/BX/shushiboxi2.vue')['default']
SystemTree: typeof import('./src/components/tree/systemTree.vue')['default']
WaveForm: typeof import('./src/components/BX/waveForm.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>电压暂降监测平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

2301
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "vite-ts-demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@jiaminghi/data-view": "^2.10.0",
"@kjgl77/datav-vue3": "^1.4.2",
"axios": "^1.3.4",
"echarts": "^5.4.3",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.9.11",
"html2canvas": "^1.4.1",
"jquery": "^3.7.1",
"js-table2excel": "^1.1.2",
"lodash": "^4.17.21",
"mockjs": "^1.1.0",
"splitpanes": "^4.0.4",
"vue": "^3.2.47",
"vue-baidu-map-3x": "^1.0.40",
"vue-baidu-map-offline": "^1.0.7",
"vue-router": "^4.1.6",
"vuex": "^4.0.2",
"xe-utils": "^3.7.5",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/node": "^18.15.3",
"@vitejs/plugin-vue": "^4.1.0",
"less": "^4.1.3",
"sass": "^1.59.3",
"typescript": "^4.9.3",
"unplugin-auto-import": "^0.15.1",
"unplugin-vue-components": "^0.24.1",
"vite": "^4.2.0",
"vite-plugin-mock": "^2.9.6",
"vue-tsc": "^1.2.0"
}
}

45
src/App.vue Normal file
View File

@@ -0,0 +1,45 @@
<template>
<div class="app">
<router-view />
</div>
</template>
<script setup lang="ts">
import * as echarts from "echarts";
import { provide, onBeforeMount } from "vue";
provide("echarts", echarts);
import { useStore } from "vuex";
const store = useStore();
// 子页面https://child.com
window.addEventListener("message", (event) => {
console.log("🚀 ~ 传参了:");
// 2. 获取数据
const data = event.data;
if (data.username) {
// 调用token
window.sessionStorage.setItem("userInfo", JSON.stringify(data));
store.dispatch("loginAction", data);
}
});
// setTimeout(() => {
store.dispatch("loginAction", {
username: "cdf",
password: "@#001njcnpqs",
});
// }, 100);
onBeforeMount(() => {
window.sessionStorage.setItem("token", "");
console.log("销毁页面了");
});
</script>
<style lang="less" scoped>
@import "@/assets/scss/element.scss";
.app {
width: 100vw;
height: 100vh;
background-color: #000;
overflow: hidden;
}
</style>

13
src/api/login/login.ts Normal file
View File

@@ -0,0 +1,13 @@
// 导入axios实例
import service from "@/utils/request";
// 登录获取token
export function login(data: object) {
return service({
url: "/cn_authenticate",
method: "post",
data,
});
}

339
src/api/manage_wx/index.ts Normal file
View File

@@ -0,0 +1,339 @@
// 导入axios实例
import http from "@/utils/request_wx";
// 项目管理弹框列表
export function projectList(data: object) {
return http.request({
url: "/cs-harmonic-boot/csconfiguration/queryPage",
method: "post",
data,
});
}
// 新增
export function add(data) {
return http.request({
url: "/cs-harmonic-boot/csconfiguration/add",
method: "post",
data,
});
}
// 修改 删除
export function edit(data) {
return http.request({
url: "/cs-harmonic-boot/csconfiguration/audit",
method: "post",
data,
});
}
// 激活
export function active(data) {
return http.request({
url: "/cs-harmonic-boot/csconfiguration/active",
method: "get",
params: data,
});
}
// 激活 改变画布状态
export function getActive(data) {
return http.request({
url: "/cs-harmonic-boot/csconfiguration/getActive",
method: "get",
params: data,
});
}
// 右侧监测点规模统计
export function ledgerScale(data) {
return http.request({
url: "/scale/ledgerScale",
method: "post",
data: data,
});
}
// 暂降溯源统计
export function eventSource(data) {
return http.request({
url: "/scale/eventSource",
method: "post",
data: data,
});
}
// 暂降聚合个数
export function eventAggregation(data) {
return http.request({
url: "/scale/eventAggregation",
method: "post",
data: data,
});
}
// 闪烁点统计
export function hasEventList(data) {
return http.request({
url: "/scale/hasEventList",
method: "post",
data: data,
});
}
// 实时信息
export function getEventList(data) {
return http.request({
url: "/scale/eventList",
method: "post",
data: data,
});
}
// 聚合
export function clickImage(data) {
return http.request({
url: "/scale/clickImage",
method: "get",
params: data,
});
}
// 单个点击图元,查看详情
export function processEvents(data) {
return http.request({
url: "/process/processEvents",
method: "post",
data: data,
});
}
// 监测点规模统计 站点弹框详情
export function stationPage(data) {
return http.request({
url: "/scale/stationPage",
method: "post",
data: data,
});
}
// 监测点规模统计 终端弹框详情
export function devPage(data) {
return http.request({
url: "/scale/devPage",
method: "post",
data: data,
});
}
// 监测点规模统计 检测点弹框详情
export function linePage(data) {
return http.request({
url: "/scale/linePage",
method: "post",
data: data,
});
}
// 获取区域中断终端
export function getTerminalTreeForFive(data: any) {
return http.request({
url: "/terminalTree/getTerminalTreeForFive",
method: "post",
data: data,
});
}
// 台账变更推送
export function ledgerChangePush() {
return http.request({
url: "/device/ledgerChangePush",
method: "post",
});
}
// 台账变更推送
export function getPushResult(data: any) {
return http.request({
url: "/device/getPushResult",
method: "post",
params: data,
});
}
//负荷数据 用采数据列表
export function userDataList(data: any) {
return http.request({
url: "/responsibility/userDataList",
method: "post",
data,
});
}
// 完整性详情
export function userDataIntegrityList(data: any) {
return http.request({
url: "/responsibility/userDataIntegrityList",
method: "post",
data,
});
}
//删除用采数据
export function deleteUserDataByIds(data: any) {
return http.request({
url: "/responsibility/deleteUserDataByIds",
method: "post",
data,
});
}
// 上传文件
export function uploadUserData(data: any) {
return http.request({
url: "/responsibility/uploadUserData",
method: "post",
data,
});
}
//执行
export function getHistoryHarmData(data: any) {
return http.request({
url: "/harmonic/getHistoryHarmData",
method: "post",
data,
});
}
//生成谐波责任指标
export function getResponsibilityData(data: any) {
return http.request({
url: "/responsibility/getResponsibilityData",
method: "post",
data,
});
}
//生成动态谐波责任数据
export function getDynamicData(data: any) {
return http.request({
url: "/responsibility/getDynamicData",
method: "post",
data,
});
}
// 谐波溯源表格
export function responsibilityList(data: any) {
return http.request({
url: "/responsibility/responsibilityList",
method: "post",
data,
});
}
// 谐波溯源详情
export function displayHistoryData(data: any) {
return http.request({
url: "/responsibility/displayHistoryData",
method: "get",
params: data,
});
}
// 谐波溯源删除
export function deleteByIds(data: any) {
return http.request({
url: "/responsibility/deleteByIds",
method: "post",
data,
});
}
// 谐波放大表格
export function getInfoList(data: any) {
return http.request({
url: "/harmonicUp/getInfoList",
method: "post",
data: data,
});
}
// 谐波放大测点
export function tableInfo(data: any) {
return http.request({
url: "/harmonicUp/tableInfo",
method: "post",
data: data,
});
}
// 谐波放大测点详情
export function getDetail(data: any) {
return http.request({
url: "/harmonicUp/getDetail",
method: "post",
data: data,
});
}
// 稳态指标
export function realTimeData(data) {
return http.request({
url: "/data/realTimeData",
method: "post",
params: data,
});
}
// 趋势图
export function getHistoryResult(data) {
return http.request({
url: "/harmonic/getHistoryResult",
method: "post",
data,
});
}
// 谐波溯源表格点击事件
export function harmOneImage(data) {
return http.request({
url: "/scale/harmOneImage",
method: "get",
params: data,
});
}
// 谐波暂降详情点击
export function eventListByLineId(data) {
return http.request({
url: "/scale/eventListByLineId",
method: "post",
data: data,
});
}
// 部门列表
export function loginDeptTree(data) {
return http.request({
url: "/dept/loginDeptTree",
method: "get",
params: data,
});
}
// 事件类型
export function getDicDataByTypeCode(data) {
return http.request({
url: "/dicData/getDicDataByTypeCode",
method: "get",
params: data,
});
}
// 谐波放大闪烁点
export function hasUpEventList(data) {
return http.request({
url: "/scale/hasUpEventList",
method: "post",
data: data,
});
}

272
src/api/statistics/index.ts Normal file
View File

@@ -0,0 +1,272 @@
// 导入axios实例
import service from "@/utils/request";
export function initLedger(data: object) {
return service({
url: "/largescreen/initLedger",
method: "post",
data,
});
}
// 台账规模统计
export function ledgercount(data: object) {
return service({
url: "/largescreen/ledgercount",
method: "post",
data,
});
}
// 告警统计分析
export function alarmAnalysis(data: object) {
return service({
url: "/largescreen/alarmAnalysis",
method: "post",
data,
});
}
// 查询告警统计分析
export function alarmAnalysisDetail(data: object) {
return service({
url: "/largescreen/alarmAnalysisDetail",
method: "post",
data,
});
}
// 暂降事件趋势/远程通知趋势
export function eventTrend(data: object) {
return service({
url: "/largescreen/eventTrend",
method: "post",
data,
});
}
// 获取历史暂降事件趋势
export function noDealEventList(data: object) {
return service({
url: "/largescreen/noDealEventList",
method: "post",
data,
});
}
// 处理暂降事件
export function lookEvent(data: object) {
return service({
url: "/largescreen/lookEvent",
method: "post",
data,
});
}
// 暂降事件列表
export function eventList(data: object) {
return service({
url: "/largescreen/eventList",
method: "post",
data,
});
}
// 远程通知列表
export function msgSendList(data: object) {
return service({
url: "/largescreen/msgSendList",
method: "post",
data,
});
}
// 暂降事件列表详情按钮
export function eventMsgDetail(params: object) {
return service({
url: "/largescreen/eventMsgDetail",
method: "get",
params,
});
}
// 短信处理
export function msgHandle(data: object) {
return service({
url: "/largescreen/msgHandle",
method: "post",
data,
});
}
// 地图统计数量
export function mapCount(data: object) {
return service({
url: "/largescreen/mapCount",
method: "post",
data,
});
}
// 已发送短信列表
export function hasSendMsgPage(data: object) {
return service({
url: "/largescreen/hasSendMsgPage",
method: "post",
data,
});
}
// 暂降平台配置
export function eventConfig(data: object) {
return service({
url: "/config/eventConfig",
method: "post",
data,
});
}
// 查询配置
export function queryConfig() {
return service({
url: "/config/queryConfig",
method: "get",
});
}
// 模拟短信发送
export function simulationSend(data: any) {
return service({
url: "/accept/simulationSend",
method: "post",
data,
});
}
// 趋势图
export function getTransientAnalyseWave(data: object) {
return service({
url: "/accept/getTransientAnalyseWave",
method: "post",
data,
});
}
// 电能质量监测终端运行状态
export function devFlagCount(data: object) {
return service({
url: "/largescreen/devFlagCount",
method: "post",
data,
});
}
// 电能质量监测终端运行状态详情
export function devicePage(data: object) {
return service({
url: "/largescreen/devicePage",
method: "post",
data,
});
}
// 各区域终端运行状态
export function regionDevCount(data: object) {
return service({
url: "/largescreen/regionDevCount",
method: "post",
data,
});
}
export function rightEventOpenForDetail(data: object) {
return service({
url: "/right/rightEventOpenForDetail",
method: "post",
data,
});
}
// 供电区域下拉
export function gdSelect() {
return service({
url: "/right/gdSelect",
method: "get",
});
}
// 供电区域下拉
export function bdSelect() {
return service({
url: "/right/bdSelect",
method: "get",
});
}
// 地图查询
export function substationCount(data: object) {
return service({
url: "/largescreen/substationCount",
method: "post",
data,
});
}
// 暂降事件列表
export function eventPage(data: object) {
return service({
url: "/largescreen/eventPage",
method: "post",
data,
});
}
// 电压暂降告警统计
export function rightImportUser(data: object) {
return service({
url: "/right/rightImportUser",
method: "post",
data,
});
}
// 电压暂降告警统计
export function rightEvent(data: object) {
return service({
url: "/right/rightEvent",
method: "post",
data,
});
}
// 电压暂降告警统计弹框
export function rightEventOpen(data: object) {
return service({
url: "/right/rightEventOpen",
method: "post",
data,
});
}
export function rightEventOpenClone(data: object) {
return service({
url: "/right/rightEventOpenClone",
method: "post",
data,
});
}
// 重要敏感用户暂降告警统计
export function userEventList(data: object) {
return service({
url: "/largescreen/userEventList",
method: "post",
data,
});
}
export function rightImportOpenDetail(data: object) {
return service({
url: "/right/rightImportOpenDetail",
method: "post",
data,
});
}
// 对象类型
export function getDicTree(data: object) {
return service({
url: "/dicTree/getDicTree",
method: "get",
params: data,
});
}
// 终端弹框
export function rightEventDevOpen(data: object) {
return service({
url: "/right/rightEventDevOpen",
method: "post",
data,
});
}

BIN
src/assets/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

1
src/assets/gaoJ.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752714800249" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2803" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M959.800889 318.762667a30.364444 30.364444 0 0 1-11.377778 41.543111l-98.133333 56.632889-30.222222-52.864 94.435555-56.632889a47.872 47.872 0 0 1 22.641778-3.783111 34.688 34.688 0 0 1 22.656 15.104z m-898.844445 0c4.48-6.968889 11.107556-12.273778 18.887112-15.104a24.974222 24.974222 0 0 1 22.656 3.783111l94.435555 56.632889-30.222222 52.864-94.421333-56.632889a30.364444 30.364444 0 0 1-11.377778-41.543111h0.042666zM249.799111 129.962667a47.900444 47.900444 0 0 1 22.656-3.783111 37.688889 37.688889 0 0 1 18.887111 15.104l56.661334 94.407111-52.878223 30.208-56.647111-94.407111a30.364444 30.364444 0 0 1 11.377778-41.528889h-0.056889zM513.848889 56.888889a37.105778 37.105778 0 0 1 36.636444 31.530667v109.511111h-70.570666v-109.511111C476.131556 73.315556 494.961778 56.888889 513.848889 56.888889z m253.383111 73.073778a30.364444 30.364444 0 0 1 11.377778 41.528889l-56.647111 94.407111-52.878223-30.208 56.647112-94.407111a66.304 66.304 0 0 1 18.887111-15.104c7.552 0 18.887111 0 22.656 3.783111h-0.042667zM533.020444 409.329778L378.168889 665.614222h124.629333l-30.222222 181.902222 162.247111-252.387555h-132.024889l30.222222-185.799111z m-271.928888 483.100444h-64.782223V594.360889c1.521778-172.103111 141.952-310.556444 314.055111-309.632A308.48 308.48 0 0 1 819.868444 594.346667v298.069333H261.091556z m-169.955556 0H933.404444a37.12 37.12 0 0 1 33.991112 37.603556 33.877333 33.877333 0 0 1-33.991112 33.991111H91.164444a33.848889 33.848889 0 0 1-33.991111-33.991111 37.12 37.12 0 0 1 33.991111-37.603556" fill="#323233" p-id="2804"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

539
src/assets/icon/demo.css Normal file
View File

@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4977098" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe60c;</span>
<div class="name">告警</div>
<div class="code-name">&amp;#xe60c;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1752716651599') format('woff2'),
url('iconfont.woff?t=1752716651599') format('woff'),
url('iconfont.ttf?t=1752716651599') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-gaojing"></span>
<div class="name">
告警
</div>
<div class="code-name">.icon-gaojing
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gaojing"></use>
</svg>
<div class="name">告警</div>
<div class="code-name">#icon-gaojing</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
@font-face {
font-family: "iconfont"; /* Project id 4977098 */
src: url('iconfont.woff2?t=1752716651599') format('woff2'),
url('iconfont.woff?t=1752716651599') format('woff'),
url('iconfont.ttf?t=1752716651599') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-gaojing:before {
content: "\e60c";
}

View File

@@ -0,0 +1 @@
window._iconfont_svg_string_4977098='<svg><symbol id="icon-gaojing" viewBox="0 0 1024 1024"><path d="M959.800889 318.762667a30.364444 30.364444 0 0 1-11.377778 41.543111l-98.133333 56.632889-30.222222-52.864 94.435555-56.632889a47.872 47.872 0 0 1 22.641778-3.783111 34.688 34.688 0 0 1 22.656 15.104z m-898.844445 0c4.48-6.968889 11.107556-12.273778 18.887112-15.104a24.974222 24.974222 0 0 1 22.656 3.783111l94.435555 56.632889-30.222222 52.864-94.421333-56.632889a30.364444 30.364444 0 0 1-11.377778-41.543111h0.042666zM249.799111 129.962667a47.900444 47.900444 0 0 1 22.656-3.783111 37.688889 37.688889 0 0 1 18.887111 15.104l56.661334 94.407111-52.878223 30.208-56.647111-94.407111a30.364444 30.364444 0 0 1 11.377778-41.528889h-0.056889zM513.848889 56.888889a37.105778 37.105778 0 0 1 36.636444 31.530667v109.511111h-70.570666v-109.511111C476.131556 73.315556 494.961778 56.888889 513.848889 56.888889z m253.383111 73.073778a30.364444 30.364444 0 0 1 11.377778 41.528889l-56.647111 94.407111-52.878223-30.208 56.647112-94.407111a66.304 66.304 0 0 1 18.887111-15.104c7.552 0 18.887111 0 22.656 3.783111h-0.042667zM533.020444 409.329778L378.168889 665.614222h124.629333l-30.222222 181.902222 162.247111-252.387555h-132.024889l30.222222-185.799111z m-271.928888 483.100444h-64.782223V594.360889c1.521778-172.103111 141.952-310.556444 314.055111-309.632A308.48 308.48 0 0 1 819.868444 594.346667v298.069333H261.091556z m-169.955556 0H933.404444a37.12 37.12 0 0 1 33.991112 37.603556 33.877333 33.877333 0 0 1-33.991112 33.991111H91.164444a33.848889 33.848889 0 0 1-33.991111-33.991111 37.12 37.12 0 0 1 33.991111-37.603556" fill="#323233" ></path></symbol></svg>',(n=>{var t=(e=(e=document.getElementsByTagName("script"))[e.length-1]).getAttribute("data-injectcss"),e=e.getAttribute("data-disable-injectsvg");if(!e){var o,i,a,c,d,l=function(t,e){e.parentNode.insertBefore(t,e)};if(t&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}o=function(){var t,e=document.createElement("div");e.innerHTML=n._iconfont_svg_string_4977098,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?l(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(a=o,c=n.document,d=!1,r(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,s())})}function s(){d||(d=!0,a())}function r(){try{c.documentElement.doScroll("left")}catch(t){return void setTimeout(r,50)}s()}})(window);

View File

@@ -0,0 +1,16 @@
{
"id": "4977098",
"name": "北京暂降平台",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "36284864",
"name": "告警",
"font_class": "gaojing",
"unicode": "e60c",
"unicode_decimal": 58892
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/assets/img/bdz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

BIN
src/assets/img/dw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/assets/img/point.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/assets/img/text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

BIN
src/assets/img/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
src/assets/jcd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/assets/mp3/9578.mp3 Normal file

Binary file not shown.

BIN
src/assets/pageBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -0,0 +1,198 @@
#index {
color: #d3d6dd;
width: 1920px;
height: 1080px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: left top;
.bg {
width: 100%;
height: 100%;
padding: 16px 16px 0 16px;
background-image: url("../../assets/pageBg.png");
background-size: cover;
background-position: center center;
}
.headerBox {
width: 100%;
height: 124px;
background-image: url("../../assets/download.png");
background-size: cover;
background-position: center center;
}
.secondLine {
position: absolute;
right: 20px;
top: 30px;
}
.host-body {
//头部样式
.dv-dec-10,
.dv-dec-10-s {
width: 33.3%;
height: 5px;
}
.dv-dec-10-s {
transform: rotateY(180deg);
}
.dv-dec-8 {
width: 200px;
height: 50px;
}
.title {
position: relative;
width: 500px;
text-align: center;
background-size: cover;
background-repeat: no-repeat;
.title-text {
width: 100%;
font-size: 35px;
position: absolute;
line-height: 35px;
top: 10px;
left: 50%;
font-weight: 700;
transform: translate(-50%);
}
.dv-dec-6 {
position: absolute;
bottom: -30px;
left: 50%;
width: 250px;
height: 8px;
transform: translate(-50%);
}
}
// 第二行
.react-r-s,
.react-l-s {
background-color: #0f1325;
}
// 平行四边形
.react-right {
&.react-l-s {
text-align: right;
width: 150px;
}
font-size: 18px;
width: 200px;
line-height: 30px;
text-align: center;
transform: skewX(-45deg);
.react-after {
position: absolute;
right: -25px;
top: 0;
height: 24px;
width: 50px;
background-color: #0f1325;
transform: skewX(45deg);
}
.text {
display: inline-block;
transform: skewX(45deg);
}
}
// .react-left {
// &.react-l-s {
// width: 500px;
// text-align: left;
// }
// font-size: 18px;
// width: 100px;
// height: 50px;
// line-height: 50px;
// text-align: center;
// transform: skewX(45deg);
// background-color: #0f1325;
// .react-before {
// position: absolute;
// left: -25px;
// top: 0;
// height: 50px;
// width: 50px;
// background-color: #0f1325;
// transform: skewX(-45deg);
// }
// .text {
// display: inline-block;
// transform: skewX(-45deg);
// }
// }
.body-box {
margin-top: 10px;
display: flex;
flex-direction: column;
//下方区域的布局
.content-box {
display: grid;
gap: 5px;
grid-template-columns: 3fr 1fr;
// grid-template-columns: 1fr 2fr 1fr;
}
//下方区域的布局
.content-left {
display: grid;
gap: 5px;
grid-template-rows: 1fr, 2fr;
// grid-template-columns: 1fr 2fr 1fr;
}
// 底部数据
.bototm-box {
margin-top: 5px;
display: grid;
gap: 5px;
grid-template-columns: 1fr 2fr 1fr;
}
}
}
.dv-border-box-13 {
padding: 20px 10px 0 10px;
}
.writing {
text-decoration: underline;
color: #ffcc00;
cursor: pointer;
margin-right: 20px;
white-space: nowrap;
}
.titleBox {
display: flex;
justify-content: space-between;
width: 100%;
line-height: 32px;
font-size: 18px;
padding-left: 20px;
margin: 0 auto;
// background-image: url("@/assets/img/title.png");
// background-size: cover;
// background-position: center center;
background: linear-gradient(
90deg,
#cccccc80 0%,
#1d83ce70 1%,
#1d83ce50 35%,
#1d83ce30 70%,
#1d83ce10 100% /* 活力橙 */
);
}
}

View File

@@ -0,0 +1,97 @@
// 颜色
$colors: (
"primary": #db9e3f,
"info-1": #4394e4,
"info": #4b67af,
"white": #ffffff,
"light": #f9f9f9,
"grey-1": #999999,
"grey": #666666,
"dark-1": #5f5f5f,
"dark": #222222,
"black-1": #171823,
"black": #000000,
);
// 字体大小
$base-font-size: 16px;
$font-sizes: (
xxs: 0.1,
//8px
xs: 0.125,
//10px
sm: 0.2875,
//12px
md: 0.1625,
//13px
lg: 0.175,
//14px
xl: 0.2,
//16px
xxl: 0.225,
//18px
xxxl: 0.25 //20px,,,,
);
// 宽高
.w-100 {
width: 100%;
}
.h-100 {
height: 100%;
}
//flex
.d-flex {
display: flex;
}
.flex-column {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-nowrap {
flex-wrap: nowrap;
}
$flex-jc: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
evenly: space-evenly,
);
$flex-ai: (
start: flex-start,
end: flex-end,
center: center,
stretch: stretch,
);
.flex-1 {
flex: 1;
}
//.mt-1 => margin top
//spacing
$spacing-types: (
m: margin,
p: padding,
);
$spacing-directions: (
t: top,
r: right,
b: bottom,
l: left,
);
$spacing-base-size: 16px;
$spacing-sizes: (
0: 0,
1: 0.25,
2: 0.5,
3: 1,
4: 1.5,
5: 3,
);

View File

@@ -0,0 +1,101 @@
:deep(.el-dialog__body) {
color: #fff !important;
padding: 10px;
}
:deep(.el-form-item__label) {
color: #fff !important;
}
:deep(.el-dialog__header) {
padding: 10px;
background-color: #21232b;
border-bottom: 1px solid #fff;
}
:deep(.el-dialog__footer) {
padding: 0 10px 10px !important;
}
:deep(.el-descriptions__label.el-descriptions__cell) {
background: #0a73ff40 !important;
color: #fff !important;
font-weight: bold;
}
:deep(.el-descriptions) {
--el-descriptions-table-border: 1px solid #0a73ff;
.el-descriptions__content.el-descriptions__cell.is-bordered-content {
color: #fff;
}
.el-descriptions__body {
background-color: #00000050;
}
}
:deep(.tableBox) {
padding: 10px;
.el-table {
--el-table-border-color: #0a73ff;
--el-table-row-hover-bg-color: #0a73ff20;
--el-table-header-bg-color: #0a73ff40;
--el-table-bg-color: #ffffff00;
text-align: center;
// th {
// background-color: #0a73ff;
// color: #fff;
// }
tr {
background-color: #00000050 !important;
color: #fff;
}
}
}
:deep(
.el-table--striped
.el-table__body
tr.el-table__row--striped
td.el-table__cell
) {
background: #5aa1ff29;
}
.titles {
display: flex;
align-items: center;
text-align: center;
margin-left: 20px;
div {
background-color: #0a73ff70;
color: #ccc;
cursor: pointer;
&:nth-child(1) {
margin-right: 10px;
}
}
.titleClick {
background-color: #0a73ff;
color: #fff;
}
}
:deep(.el-dialog) {
--el-dialog-bg-color: #343849c7 !important;
padding: 0px;
--el-dialog-margin-top: 8vh;
.el-dialog__title,
.el-dialog__close {
color: #fff;
}
}
:deep(.el-drawer) {
--el-drawer-bg-color: #343849c7;
color: #fff;
.el-drawer__header {
// background-color: #21232b;
background-color: #9f1700;
}
}
:deep(.el-pagination) {
.el-pagination__total,
.el-pagination__goto,
.el-pagination__classifier {
color: #fff;
}
// --el-text-color-regular: #fff;
}

348
src/assets/scss/index.css Normal file
View File

@@ -0,0 +1,348 @@
@charset "UTF-8";
#index {
color: #d3d6dd;
width: 1920px;
height: 1080px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: left top;
--el-text-color-primary: #9b9b9b;
}
#index .bg {
width: 100%;
height: 100%;
padding: 16px 16px 0 16px;
background-image: url("../../assets/pageBg.png");
background-size: cover;
background-position: center center;
}
#index .headerBox {
width: 100%;
height: 124px;
background-image: url("../../assets/download.png");
background-size: cover;
background-position: center center;
}
#index .secondLine {
position: absolute;
right: 20px;
top: 30px;
}
#index .host-body .dv-dec-10,
#index .host-body .dv-dec-10-s {
width: 33.3%;
height: 5px;
}
#index .host-body .dv-dec-10-s {
transform: rotateY(180deg);
}
#index .host-body .dv-dec-8 {
width: 200px;
height: 50px;
}
#index .host-body .title {
position: relative;
width: 500px;
text-align: center;
background-size: cover;
background-repeat: no-repeat;
}
#index .host-body .title .title-text {
width: 100%;
font-size: 35px;
position: absolute;
line-height: 35px;
top: 10px;
left: 50%;
font-weight: 700;
transform: translate(-50%);
}
#index .host-body .title .dv-dec-6 {
position: absolute;
bottom: -30px;
left: 50%;
width: 250px;
height: 8px;
transform: translate(-50%);
}
#index .host-body .react-r-s,
#index .host-body .react-l-s {
background-color: #0f1325;
}
#index .host-body .react-right {
font-size: 15px;
width: 200px;
line-height: 30px;
text-align: center;
transform: skewX(-45deg);
}
#index .host-body .react-right.react-l-s {
text-align: right;
width: 150px;
}
#index .host-body .react-right .react-after {
position: absolute;
right: -25px;
top: 0;
height: 24px;
width: 50px;
background-color: #0f1325;
transform: skewX(45deg);
}
#index .host-body .react-right .text {
display: inline-block;
transform: skewX(45deg);
}
#index .host-body .body-box {
margin-top: 10px;
display: flex;
flex-direction: column;
}
#index .host-body .body-box .content-box {
display: grid;
gap: 5px;
grid-template-columns: 1fr 2fr 1fr;
}
#index .host-body .body-box .bototm-box {
margin-top: 5px;
display: grid;
gap: 5px;
grid-template-columns: 1fr 2fr 1fr;
}
#index .dv-border-box-13 {
padding: 20px 10px 0 10px;
}
#index .writing {
text-decoration: underline;
color: #daa520;
cursor: pointer;
margin-right: 20px;
white-space: nowrap;
}
#index .titleBox {
display: flex;
justify-content: space-between;
width: 100%;
line-height: 32px;
font-size: 18px;
padding-left: 20px;
margin: 0 auto;
background-image: url("@/assets/img/title.png");
background-size: cover;
background-position: center center;
}
.el-form-item {
margin-bottom: 10px;
}
/* 呼吸闪烁的容器样式 红色*/
.bg-red {
/* 设置初始的边框样式 */
/* 添加呼吸动画 */
animation: breathing_red 2s ease-in-out infinite;
}
/* 定义呼吸动画 */
@keyframes breathing_red {
0% {
/* 开始时边框颜色较淡 */
border-color: rgba(255, 0, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 0, 0, 0.2);
}
50% {
/* 中间时边框颜色最深 */
border-color: red;
box-shadow: inset 0 0 100px rgba(255, 0, 0, 0.8);
}
100% {
/* 结束时边框颜色回到较淡状态 */
border-color: rgba(255, 0, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 0, 0, 0.2);
}
}
/* 呼吸闪烁的容器样式 */
.bg-yellow {
/* 设置初始的边框样式 */
/* 添加呼吸动画 */
animation: breathing_yellow 2s ease-in-out infinite;
}
/* 定义呼吸动画 */
@keyframes breathing_yellow {
0% {
/* 开始时边框颜色较淡 */
border-color: rgba(255, 219, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 219, 0, 0.2);
}
50% {
/* 中间时边框颜色最深 */
border-color: #ffdb00;
box-shadow: inset 0 0 100px rgba(255, 219, 0, 0.8);
}
100% {
/* 结束时边框颜色回到较淡状态 */
border-color: rgba(255, 219, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 219, 0, 0.2);
}
}
.scroll-box {
width: 1820px;
height: 40px;
padding: 10px 15px;
overflow: hidden;
position: relative;
left: 50px;
}
.scroll-content {
position: absolute;
white-space: nowrap;
animation: scroll 0s linear infinite;
/* 根据内容长度调整动画时间 */
padding-left: 100%;
/* 初始位置偏移 */
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
/* 闪烁动画定义 */
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
/* 半透明效果实现闪烁 */
}
}
.animate-flash-red {
animation: flash 1s infinite;
/* 1秒周期无限循环 */
color: #ff2501;
cursor: pointer;
}
.animate-flash-yellow {
animation: flash 1s infinite;
/* 1秒周期无限循环 */
color: #bb7b00;
cursor: pointer;
}
/* 悬停暂停效果 */
.scroll-box:hover .scroll-content {
animation-play-state: paused;
}
:deep(.el-divider--horizontal) {
margin: 10px 0;
}
@keyframes step-scale {
0% {
transform: scale(1.2);
}
50% {
transform: scale(1.2);
}
/* 突变时放大 */
100% {
transform: scale(1.2);
}
/* 稳定后保持放大 */
}
.step-scale {
animation: step-scale 1s infinite;
/* forwards保持最终状态 */
text-align: center;
color: #ff0000;
/* 警告色 */
}
div {
/* 滚动条轨道 */
/* 滚动条滑块hover状态 */
}
div::-webkit-scrollbar {
width: 6px;
/* 滚动条宽度 */
}
div::-webkit-scrollbar-thumb {
border-radius: 10px;
/* 滑块圆角 */
background: rgba(204, 204, 204, 0.8);
/* 设置滚动条轨道背景色 */
cursor: pointer;
transition: var(--el-transition-duration) background-color;
}
div::-webkit-scrollbar-thumb:hover {
background: rgba(204, 204, 204, 0.5);
/* 设置滚动条轨道背景色 */
}
div .el-divider--horizontal {
border-top: 1px var(--el-color-primary) var(--el-border-style);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}

1
src/assets/scss/index.min.css vendored Normal file
View File

@@ -0,0 +1 @@
#index{color:#d3d6dd;width:1920px;height:1080px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);transform-origin:left top;--el-text-color-primary:#9b9b9b}#index .bg{width:100%;height:100%;padding:16px 16px 0 16px;background-image:url("../../assets/pageBg.png");background-size:cover;background-position:center center}#index .headerBox{width:100%;height:124px;background-image:url("../../assets/download.png");background-size:cover;background-position:center center}#index .secondLine{position:absolute;right:20px;top:30px}#index .host-body .dv-dec-10,#index .host-body .dv-dec-10-s{width:33.3%;height:5px}#index .host-body .dv-dec-10-s{transform:rotateY(180deg)}#index .host-body .dv-dec-8{width:200px;height:50px}#index .host-body .title{position:relative;width:500px;text-align:center;background-size:cover;background-repeat:no-repeat}#index .host-body .title .title-text{width:100%;font-size:35px;position:absolute;line-height:35px;top:10px;left:50%;font-weight:700;transform:translate(-50%)}#index .host-body .title .dv-dec-6{position:absolute;bottom:-30px;left:50%;width:250px;height:8px;transform:translate(-50%)}#index .host-body .react-r-s,#index .host-body .react-l-s{background-color:#0f1325}#index .host-body .react-right{font-size:15px;width:200px;line-height:30px;text-align:center;transform:skewX(-45deg)}#index .host-body .react-right.react-l-s{text-align:right;width:150px}#index .host-body .react-right .react-after{position:absolute;right:-25px;top:0;height:24px;width:50px;background-color:#0f1325;transform:skewX(45deg)}#index .host-body .react-right .text{display:inline-block;transform:skewX(45deg)}#index .host-body .body-box{margin-top:10px;display:flex;flex-direction:column}#index .host-body .body-box .content-box{display:grid;gap:5px;grid-template-columns:1fr 2fr 1fr}#index .host-body .body-box .bototm-box{margin-top:5px;display:grid;gap:5px;grid-template-columns:1fr 2fr 1fr}#index .dv-border-box-13{padding:20px 10px 0 10px}#index .writing{text-decoration:underline;color:#daa520;cursor:pointer;margin-right:20px;white-space:nowrap}#index .titleBox{display:flex;justify-content:space-between;width:100%;line-height:32px;font-size:18px;padding-left:20px;margin:0 auto;background-image:url("@/assets/img/title.png");background-size:cover;background-position:center center}.el-form-item{margin-bottom:10px}.bg-red{animation:breathing_red 2s ease-in-out infinite}@keyframes breathing_red{0%{border-color:rgba(255,0,0,0.3);box-shadow:inset 0 0 50px rgba(255,0,0,0.2)}50%{border-color:red;box-shadow:inset 0 0 100px rgba(255,0,0,0.8)}100%{border-color:rgba(255,0,0,0.3);box-shadow:inset 0 0 50px rgba(255,0,0,0.2)}}.bg-yellow{animation:breathing_yellow 2s ease-in-out infinite}@keyframes breathing_yellow{0%{border-color:rgba(255,219,0,0.3);box-shadow:inset 0 0 50px rgba(255,219,0,0.2)}50%{border-color:#ffdb00;box-shadow:inset 0 0 100px rgba(255,219,0,0.8)}100%{border-color:rgba(255,219,0,0.3);box-shadow:inset 0 0 50px rgba(255,219,0,0.2)}}.scroll-box{width:1820px;height:40px;padding:10px 15px;overflow:hidden;position:relative;left:50px}.scroll-content{position:absolute;white-space:nowrap;animation:scroll 0s linear infinite;padding-left:100%}@keyframes scroll{0%{transform:translateX(0)}100%{transform:translateX(-100%)}}@keyframes flash{0%,100%{opacity:1}50%{opacity:0.3}}.animate-flash-red{animation:flash 1s infinite;color:#ff2501;cursor:pointer}.animate-flash-yellow{animation:flash 1s infinite;color:#bb7b00;cursor:pointer}.scroll-box:hover .scroll-content{animation-play-state:paused}:deep(.el-divider--horizontal){margin:10px 0}@keyframes step-scale{0%{transform:scale(1.2)}50%{transform:scale(1.2)}100%{transform:scale(1.2)}}.step-scale{animation:step-scale 1s infinite;text-align:center;color:#ff0000}div::-webkit-scrollbar{width:6px}div::-webkit-scrollbar-thumb{border-radius:10px;background:rgba(204,204,204,0.8);cursor:pointer;transition:var(--el-transition-duration) background-color}div::-webkit-scrollbar-thumb:hover{background:rgba(204,204,204,0.5)}div .el-divider--horizontal{border-top:1px var(--el-color-primary) var(--el-border-style)}:deep(.el-input-group__append,.el-input-group__prepend){background-color:#ffffff00}:deep(.frontBox){background-color:var(--el-color-primary) !important;color:#fff !important}::v-deep(.el-tabs__item){color:#fff}::v-deep(.el-tabs__item.is-active){color:var(--el-color-primary)}::v-deep(.el-tabs__nav-wrap::after){color:var(--el-color-primary)}::v-deep(.el-tabs__active-bar){color:var(--el-color-primary)}

368
src/assets/scss/index.scss Normal file
View File

@@ -0,0 +1,368 @@
#index {
color: #d3d6dd;
width: 1920px;
height: 1080px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transform-origin: left top;
--el-text-color-primary: #9b9b9b;
.bg {
width: 100%;
height: 100%;
padding: 16px 16px 0 16px;
background-image: url("../../assets/pageBg.png");
background-size: cover;
background-position: center center;
}
.headerBox {
width: 100%;
height: 124px;
background-image: url("../../assets/download.png");
background-size: cover;
background-position: center center;
}
.secondLine {
position: absolute;
right: 20px;
top: 30px;
}
.host-body {
//头部样式
.dv-dec-10,
.dv-dec-10-s {
width: 33.3%;
height: 5px;
}
.dv-dec-10-s {
transform: rotateY(180deg);
}
.dv-dec-8 {
width: 200px;
height: 50px;
}
.title {
position: relative;
width: 500px;
text-align: center;
background-size: cover;
background-repeat: no-repeat;
.title-text {
width: 100%;
font-size: 35px;
position: absolute;
line-height: 35px;
top: 10px;
left: 50%;
font-weight: 700;
transform: translate(-50%);
}
.dv-dec-6 {
position: absolute;
bottom: -30px;
left: 50%;
width: 250px;
height: 8px;
transform: translate(-50%);
}
}
// 第二行
.react-r-s,
.react-l-s {
background-color: #0f1325;
}
// 平行四边形
.react-right {
&.react-l-s {
text-align: right;
width: 150px;
}
font-size: 15px;
width: 200px;
line-height: 30px;
text-align: center;
transform: skewX(-45deg);
.react-after {
position: absolute;
right: -25px;
top: 0;
height: 24px;
width: 50px;
background-color: #0f1325;
transform: skewX(45deg);
}
.text {
display: inline-block;
transform: skewX(45deg);
}
}
// .react-left {
// &.react-l-s {
// width: 500px;
// text-align: left;
// }
// font-size: 18px;
// width: 100px;
// height: 50px;
// line-height: 50px;
// text-align: center;
// transform: skewX(45deg);
// background-color: #0f1325;
// .react-before {
// position: absolute;
// left: -25px;
// top: 0;
// height: 50px;
// width: 50px;
// background-color: #0f1325;
// transform: skewX(-45deg);
// }
// .text {
// display: inline-block;
// transform: skewX(-45deg);
// }
// }
.body-box {
margin-top: 10px;
display: flex;
flex-direction: column;
//下方区域的布局
.content-box {
display: grid;
gap: 5px;
grid-template-columns: 1fr 2fr 1fr;
}
// 底部数据
.bototm-box {
margin-top: 5px;
display: grid;
gap: 5px;
grid-template-columns: 1fr 2fr 1fr;
}
}
}
.dv-border-box-13 {
padding: 20px 10px 0 10px;
}
.writing {
text-decoration: underline;
color: #daa520;
cursor: pointer;
margin-right: 20px;
white-space: nowrap;
}
.titleBox {
display: flex;
justify-content: space-between;
width: 100%;
// height: 30px;
line-height: 32px;
font-size: 18px;
padding-left: 20px;
margin: 0 auto;
background-image: url("@/assets/img/title.png");
background-size: cover;
background-position: center center;
}
}
.el-form-item {
margin-bottom: 10px;
}
/* 呼吸闪烁的容器样式 红色*/
.bg-red {
/* 设置初始的边框样式 */
// border: 1px solid rgba(255, 0, 0, 0.3);
/* 添加呼吸动画 */
animation: breathing_red 2s ease-in-out infinite;
}
/* 定义呼吸动画 */
@keyframes breathing_red {
0% {
/* 开始时边框颜色较淡 */
border-color: rgba(255, 0, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 0, 0, 0.2);
}
50% {
/* 中间时边框颜色最深 */
border-color: rgba(255, 0, 0, 1);
box-shadow: inset 0 0 100px rgba(255, 0, 0, 0.8);
}
100% {
/* 结束时边框颜色回到较淡状态 */
border-color: rgba(255, 0, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 0, 0, 0.2);
}
}
/* 呼吸闪烁的容器样式 */
.bg-yellow {
/* 设置初始的边框样式 */
// border: 1px solid rgba(255, 219, 0, 0.3);
/* 添加呼吸动画 */
animation: breathing_yellow 2s ease-in-out infinite;
}
/* 定义呼吸动画 */
@keyframes breathing_yellow {
0% {
/* 开始时边框颜色较淡 */
border-color: rgba(255, 219, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 219, 0, 0.2);
}
50% {
/* 中间时边框颜色最深 */
border-color: rgba(255, 219, 0, 1);
box-shadow: inset 0 0 100px rgba(255, 219, 0, 0.8);
}
100% {
/* 结束时边框颜色回到较淡状态 */
border-color: rgba(255, 219, 0, 0.3);
box-shadow: inset 0 0 50px rgba(255, 219, 0, 0.2);
}
}
.scroll-box {
width: 1820px;
height: 40px;
padding: 10px 15px;
overflow: hidden;
position: relative;
left: 50px;
}
.scroll-content {
position: absolute;
white-space: nowrap;
animation: scroll 0s linear infinite;
/* 根据内容长度调整动画时间 */
padding-left: 100%;
/* 初始位置偏移 */
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
/* 闪烁动画定义 */
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3; /* 半透明效果实现闪烁 */
}
}
.animate-flash-red {
animation: flash 1s infinite; /* 1秒周期无限循环 */
color: #ff2501;
cursor: pointer;
}
.animate-flash-yellow {
animation: flash 1s infinite; /* 1秒周期无限循环 */
color: #bb7b00;
cursor: pointer;
}
/* 悬停暂停效果 */
.scroll-box:hover .scroll-content {
animation-play-state: paused;
}
:deep(.el-divider--horizontal) {
margin: 10px 0;
}
@keyframes step-scale {
0% {
transform: scale(1.2);
}
50% {
transform: scale(1.2);
} /* 突变时放大 */
100% {
transform: scale(1.2);
} /* 稳定后保持放大 */
}
.step-scale {
animation: step-scale 1s infinite; /* forwards保持最终状态 */
text-align: center;
color: #ff0000; /* 警告色 */
}
div {
&::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
/* 滚动条轨道 */
// &::-webkit-scrollbar-track {
// background: #ccc; /* 轨道背景色 */
// border-radius: 10px; /* 轨道圆角 */
// }
// /* 滚动条滑块 */
&::-webkit-scrollbar-thumb {
// background: #1d83ce; /* 滑块颜色 */
border-radius: 10px; /* 滑块圆角 */
background: rgba(204, 204, 204, 0.8); /* 设置滚动条轨道背景色 */
cursor: pointer;
transition: var(--el-transition-duration) background-color;
}
/* 滚动条滑块hover状态 */
&::-webkit-scrollbar-thumb:hover {
// background: #1d83ce; /* hover时的滑块颜色 */
background: rgba(204, 204, 204, 0.5); /* 设置滚动条轨道背景色 */
}
.el-divider--horizontal {
border-top: 1px var(--el-color-primary) var(--el-border-style);
}
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}

253
src/assets/scss/style.scss Normal file
View File

@@ -0,0 +1,253 @@
@import "./variables";
// 全局样式
* {
margin: 0;
padding: 0;
list-style-type: none;
box-sizing: border-box;
outline: none;
--el-color-primary: #0a73ff;
--el-color-success: #2e8b57;
--el-color-warning: #daa520;
--el-color-danger: #a52a2a;
--el-scrollbar-bg-color: #ccc;
--el-scrollbar-opacity: 0.7;
// --el-fill-color-blank: #ffffff00;
}
html {
margin: 0;
padding: 0;
}
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.2em;
background-color: #f1f1f1;
margin: 0;
padding: 0;
overflow: hidden;
/* 滚动条整体 */
::-webkit-scrollbar {
width: 8px; /* 垂直滚动条宽度 */
height: 8px; /* 水平滚动条高度 */
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: var(--el-color-primary); /* 滑块颜色 */
border-radius: 4px; /* 滑块圆角 */
transition: background 0.3s; /* 滑块 hover 过渡效果 */
}
}
a {
color: #343440;
text-decoration: none;
box-sizing: border-box;
}
.clearfix {
&::after {
content: "";
display: table;
height: 0;
line-height: 0;
visibility: hidden;
clear: both;
}
}
// 图标
.iconfont {
font-size: 20px !important;
color: #5cd9e8;
}
//浮动
.float-r {
float: right;
}
//浮动
.float-l {
float: left;
}
// 字体加粗
.fw-b {
font-weight: bold;
}
//文章一行显示,多余省略号显示
.title-item {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bg-color-black {
background-color: rgba(19, 25, 47, 0.6);
}
.bg-color-blue {
background-color: #1a5cd7;
}
.colorBlack {
color: #272727 !important;
&:hover {
color: #272727 !important;
}
}
.colorGrass {
color: #33cea0;
&:hover {
color: #33cea0 !important;
}
}
.colorRed {
color: #ff5722;
&:hover {
color: #ff5722 !important;
}
}
.colorText {
color: #d3d6dd !important;
&:hover {
color: #d3d6dd !important;
}
}
.colorBlue {
color: #257dff !important;
&:hover {
color: #257dff !important;
}
}
//颜色
@each $colorkey, $color in $colors {
.text-#{$colorkey} {
color: $color;
}
.bg-#{$colorkey} {
background-color: $color;
}
}
//对齐
@each $var in (left, center, right) {
.text-#{$var} {
text-align: $var !important;
}
}
//flex
@each $key, $value in $flex-jc {
.jc-#{$key} {
justify-content: $value;
}
}
@each $key, $value in $flex-ai {
.ai-#{$key} {
align-items: $value;
}
}
//字体
@each $fontkey, $fontvalue in $font-sizes {
.fs-#{$fontkey} {
font-size: $fontvalue * $base-font-size;
}
}
//.mt-1 => margin top
//spacing
@each $typekey, $type in $spacing-types {
//.m-1
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}-#{$sizekey} {
#{$type}: $size * $spacing-base-size;
}
}
//.mx-1
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}x-#{$sizekey} {
#{$type}-left: $size * $spacing-base-size;
#{$type}-right: $size * $spacing-base-size;
}
.#{$typekey}y-#{$sizekey} {
#{$type}-top: $size * $spacing-base-size;
#{$type}-bottom: $size * $spacing-base-size;
}
}
//.mt-1
@each $directionkey, $direction in $spacing-directions {
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}#{$directionkey}-#{$sizekey} {
#{$type}-#{$direction}: $size * $spacing-base-size;
}
}
}
.#{$typekey} {
#{$type}: 0;
}
@for $i from 0 through 100 {
.md#{$i} {
margin: #{$i}px !important;
}
.mt#{$i} {
margin-top: #{$i}px !important;
}
.mr#{$i} {
margin-right: #{$i}px !important;
}
.mb#{$i} {
margin-bottom: #{$i}px !important;
}
.ml#{$i} {
margin-left: #{$i}px !important;
}
.pd#{$i} {
padding: #{$i}px !important;
}
.pt#{$i} {
padding-top: #{$i}px !important;
}
.pr#{$i} {
padding-right: #{$i}px !important;
}
.pb#{$i} {
padding-bottom: #{$i}px !important;
}
.pl#{$i} {
padding-left: #{$i}px !important;
}
}
}

BIN
src/assets/titleBG.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1024 KiB

BIN
src/assets/txycyzj.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/assets/vue.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,311 @@
// 辅助函数
const getMax = (temp, tempA, tempB, tempC) => {
temp = temp > tempA ? temp : tempA;
temp = temp > tempB ? temp : tempB;
if (tempC !== undefined) {
temp = temp > tempC ? temp : tempC;
}
return temp;
};
const getMaxTwo = (temp, tempA, tempB) => {
temp = temp > tempA ? temp : tempA;
temp = temp > tempB ? temp : tempB;
return temp;
};
const getMin = (temp, tempA, tempB, tempC) => {
temp = temp < tempA ? temp : tempA;
temp = temp < tempB ? temp : tempB;
if (tempC !== undefined) {
temp = temp < tempC ? temp : tempC;
}
return temp;
};
const getMinOpen = (temp, tempA, tempB) => {
temp = temp < tempA ? temp : tempA;
temp = temp < tempB ? temp : tempB;
return temp;
};
// 数据处理函数
const fliteWaveData = (wp, step, iphasicValue, isOpen) => {
const rmsData = wp.listRmsData;
const pt = Number(wp.pt) / 1000;
const ct = Number(wp.ct);
const titleList = wp.waveTitle;
let xishu = pt;
let aTitle = "",
bTitle = "",
cTitle = "",
unit = "电压";
let rmsvFirstX = 0,
rmsvFirstY = 0,
rmsvSecondX = 0,
rmsvSecondY = 0,
firstZhou = "a",
secondeZhou = "a";
let ifmax = 0,
ifmin = 0,
ismax = 0,
ismin = 0,
rfmax = 0,
rfmin = 0,
rsmax = 0,
rsmin = 0;
const shunshiFA = [];
const shunshiFB = [];
const shunshiFC = [];
const shunshiSA = [];
const shunshiSB = [];
const shunshiSC = [];
const rmsFA = [];
const rmsFB = [];
const rmsFC = [];
const rmsSA = [];
const rmsSB = [];
const rmsSC = [];
if (titleList[iphasicValue * step + 1]?.substring(0, 1) !== "U") {
xishu = ct;
unit = "电流";
}
for (let i = 1; i <= iphasicValue; i++) {
switch (i) {
case 1:
aTitle = titleList[iphasicValue * step + i]?.substring(1) || "";
break;
case 2:
bTitle = titleList[iphasicValue * step + i]?.substring(1) || "";
break;
case 3:
cTitle = titleList[iphasicValue * step + i]?.substring(1) || "";
break;
}
}
if (rmsData[0] && rmsData[0][iphasicValue * step + 1] !== undefined) {
rfmax = rmsData[0][iphasicValue * step + 1] * xishu;
rfmin = rmsData[0][iphasicValue * step + 1] * xishu;
rmsvFirstY = rmsData[0][iphasicValue * step + 1] * xishu;
rmsvFirstX = rmsData[0][0];
rsmax = rmsData[0][iphasicValue * step + 1];
rsmin = rmsData[0][iphasicValue * step + 1];
rmsvSecondY = rmsData[0][iphasicValue * step + 1];
rmsvSecondX = rmsData[0][0];
}
for (let rms = 0; rms < rmsData.length; rms++) {
if (!rmsData[rms] || rmsData[rms][iphasicValue * step + 1] === undefined) {
break;
}
switch (iphasicValue) {
case 1:
const rmsFirstA = rmsData[rms][iphasicValue * step + 1] * xishu;
rmsFA.push([rmsData[rms][0], rmsFirstA]);
rfmax = rfmax > rmsFirstA ? rfmax : rmsFirstA;
rfmin = rfmin < rmsFirstA ? rfmin : rmsFirstA;
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin;
firstZhou = "a";
rmsvFirstX = rmsData[rms][0];
}
const rmsSecondA = rmsData[rms][iphasicValue * step + 1];
rmsSA.push([rmsData[rms][0], rmsSecondA]);
rsmax = rsmax > rmsSecondA ? rsmax : rmsSecondA;
rsmin = rsmin < rmsSecondA ? rsmin : rmsSecondA;
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin;
secondeZhou = "a";
rmsvSecondX = rmsData[rms][0];
}
break;
case 2:
const rmsFirstA2 = rmsData[rms][iphasicValue * step + 1] * xishu;
const rmsFirstB2 = rmsData[rms][iphasicValue * step + 2] * xishu;
rmsFA.push([rmsData[rms][0], rmsFirstA2]);
rmsFB.push([rmsData[rms][0], rmsFirstB2]);
rfmax = getMaxTwo(rfmax, rmsFirstA2, rmsFirstB2);
rfmin = getMinOpen(rfmin, rmsFirstA2, rmsFirstB2);
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin;
if (rfmin === rmsFirstA2) {
firstZhou = "a";
} else if (rfmin === rmsFirstB2) {
firstZhou = "b";
}
rmsvFirstX = rmsData[rms][0];
}
const rmsSecondA2 = rmsData[rms][iphasicValue * step + 1];
const rmsSecondB2 = rmsData[rms][iphasicValue * step + 2];
rmsSA.push([rmsData[rms][0], rmsSecondA2]);
rmsSB.push([rmsData[rms][0], rmsSecondB2]);
rsmax = getMaxTwo(rsmax, rmsSecondA2, rmsSecondB2);
rsmin = getMinOpen(rsmin, rmsSecondA2, rmsSecondB2);
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin;
if (rsmin === rmsSecondA2) {
secondeZhou = "a";
} else if (rsmin === rmsSecondB2) {
secondeZhou = "b";
}
rmsvSecondX = rmsData[rms][0];
}
break;
case 3:
const rmsFirstA3 = rmsData[rms][iphasicValue * step + 1] * xishu;
const rmsFirstB3 = rmsData[rms][iphasicValue * step + 2] * xishu;
const rmsFirstC3 = rmsData[rms][iphasicValue * step + 3] * xishu;
rmsFA.push([rmsData[rms][0], rmsFirstA3]);
rmsFB.push([rmsData[rms][0], rmsFirstB3]);
rmsFC.push([rmsData[rms][0], rmsFirstC3]);
rfmax = getMax(rfmax, rmsFirstA3, rmsFirstB3, rmsFirstC3);
rfmin = isOpen
? getMinOpen(rfmin, rmsFirstA3, rmsFirstC3)
: getMin(rfmin, rmsFirstA3, rmsFirstB3, rmsFirstC3);
if (rfmin < rmsvFirstY) {
rmsvFirstY = rfmin;
if (rfmin === rmsFirstA3) {
firstZhou = "a";
} else if (rfmin === rmsFirstB3) {
firstZhou = "b";
} else {
firstZhou = "c";
}
rmsvFirstX = rmsData[rms][0];
}
const rmsSecondA3 = rmsData[rms][iphasicValue * step + 1];
const rmsSecondB3 = rmsData[rms][iphasicValue * step + 2];
const rmsSecondC3 = rmsData[rms][iphasicValue * step + 3];
rmsSA.push([rmsData[rms][0], rmsSecondA3]);
rmsSB.push([rmsData[rms][0], rmsSecondB3]);
rmsSC.push([rmsData[rms][0], rmsSecondC3]);
rsmax = getMax(rsmax, rmsSecondA3, rmsSecondB3, rmsSecondC3);
rsmin = isOpen
? getMinOpen(rsmin, rmsSecondA3, rmsSecondC3)
: getMin(rsmin, rmsSecondA3, rmsSecondB3, rmsSecondC3);
if (rsmin < rmsvSecondY) {
rmsvSecondY = rsmin;
if (rsmin === rmsSecondA3) {
secondeZhou = "a";
} else if (rsmin === rmsSecondB3) {
secondeZhou = "b";
} else {
secondeZhou = "c";
}
rmsvSecondX = rmsData[rms][0];
}
break;
}
}
const instantF = { max: ifmax, min: ifmin };
const instantS = { max: ismax, min: ismin };
const RMSF = { max: rfmax, min: rfmin };
const RMSS = { max: rsmax, min: rsmin };
const RMSFMinDetail = { rmsvFirstX, rmsvFirstY, firstZhou };
const RMSSMinDetail = { rmsvSecondX, rmsvSecondY, secondeZhou };
const shunshiF = { shunshiFA, shunshiFB, shunshiFC };
const shunshiS = { shunshiSA, shunshiSB, shunshiSC };
const RMSFWave = { rmsFA, rmsFB, rmsFC };
const RMSSWave = { rmsSA, rmsSB, rmsSC };
const title = { aTitle, bTitle, cTitle, unit };
return {
instantF,
instantS,
RMSF,
RMSS,
RMSFMinDetail,
RMSSMinDetail,
shunshiF,
shunshiS,
RMSFWave,
RMSSWave,
title,
unit,
};
};
// 监听消息
self.onmessage = function (e) {
const { wp, isOpen, value, boxoList } = JSON.parse(e.data);
try {
const iphasicValue = wp.iphasic || 1;
const picCounts = (wp.waveTitle.length - 1) / iphasicValue;
const waveDatas = [];
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(wp, i, iphasicValue, isOpen,boxoList);
waveDatas.push(data);
}
// 处理标题
let titles = "";
if (boxoList.systemType == "pms") {
titles =
"变电站名称:" +
boxoList.powerStationName +
" 监测点名称:" +
boxoList.measurementPointName +
" 发生时刻:" +
boxoList.startTime +
" 残余电压:" +
(boxoList.featureAmplitude * 100).toFixed(2) +
"% 持续时间:" +
boxoList.duration +
"s";
} else if (boxoList.systemType == "ZL") {
titles =
" 监测点名称:" +
boxoList.equipmentName +
" 发生时刻:" +
boxoList.startTime +
" 残余电压:" +
boxoList.evtParamVVaDepth +
" 持续时间:" +
boxoList.evtParamTm +
"s";
} else {
titles =
"变电站名称:" +
boxoList.subName +
" 监测点名称:" +
boxoList.lineName +
" 发生时刻:" +
boxoList.startTime +
" 残余电压:" +
(boxoList.featureAmplitude * 100).toFixed(2) +
"% 持续时间:" +
boxoList.duration +
"s";
}
// 发送处理结果回主线程
self.postMessage({
titles: titles,
success: true,
waveDatas,
time: wp.time,
type: wp.waveType,
severity: wp.yzd,
iphasic: iphasicValue,
});
} catch (error) {
self.postMessage({
success: false,
error: error.message,
});
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
// waveData.worker.js
self.addEventListener('message', function(e) {
const { wp, value, iphasic, isOpen, boxoList } = JSON.parse(e.data);
// 处理波形数据的函数
const fliteWaveData = (wp, step) => {
// 将原有的fliteWaveData函数实现复制到这里
const shunData = wp.listWaveData;
const pt = Number(wp.pt) / 1000;
const ct = Number(wp.ct);
const titleList = wp.waveTitle;
let xishu = pt;
let aTitle = '', bTitle = '', cTitle = '', unit = '电压';
let ifmax = 0, ifmin = 0, ismax = 0, ismin = 0;
const shunshiFA = [];
const shunshiFB = [];
const shunshiFC = [];
const shunshiSA = [];
const shunshiSB = [];
const shunshiSC = [];
if (shunData.length > 0) {
if (titleList[iphasic * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct;
unit = '电流';
}
for (let i = 1; i <= iphasic; i++) {
switch (i) {
case 1:
aTitle = titleList[iphasic * step + i]?.substring(1) || '';
break;
case 2:
bTitle = titleList[iphasic * step + i]?.substring(1) || '';
break;
case 3:
cTitle = titleList[iphasic * step + i]?.substring(1) || '';
break;
}
}
if (shunData[0][iphasic * step + 1] !== undefined) {
ifmax = shunData[0][iphasic * step + 1] * xishu;
ifmin = shunData[0][iphasic * step + 1] * xishu;
ismax = shunData[0][iphasic * step + 1];
ismin = shunData[0][iphasic * step + 1];
}
for (let shun = 0; shun < shunData.length; shun++) {
if (shunData[shun][iphasic * step + 1] === undefined) {
break;
}
switch (iphasic) {
case 1:
const shunFirstA = shunData[shun][iphasic * step + 1] * xishu;
shunshiFA.push([shunData[shun][0], shunFirstA]);
ifmax = Math.max(ifmax, shunFirstA);
ifmin = Math.min(ifmin, shunFirstA);
const shunSecondA = shunData[shun][iphasic * step + 1];
shunshiSA.push([shunData[shun][0], shunSecondA]);
ismax = Math.max(ismax, shunSecondA);
ismin = Math.min(ismin, shunSecondA);
break;
case 2:
const shunFirstA2 = shunData[shun][iphasic * step + 1] * xishu;
const shunFirstB2 = shunData[shun][iphasic * step + 2] * xishu;
shunshiFA.push([shunData[shun][0], shunFirstA2]);
shunshiFB.push([shunData[shun][0], shunFirstB2]);
ifmax = Math.max(ifmax, shunFirstA2, shunFirstB2);
ifmin = Math.min(ifmin, shunFirstA2, shunFirstB2);
const shunSecondA2 = shunData[shun][iphasic * step + 1];
const shunSecondB2 = shunData[shun][iphasic * step + 2];
shunshiSA.push([shunData[shun][0], shunSecondA2]);
shunshiSB.push([shunData[shun][0], shunSecondB2]);
ismax = Math.max(ismax, shunSecondA2, shunSecondB2);
ismin = Math.min(ismin, shunSecondA2, shunSecondB2);
break;
case 3:
const shunFirstA3 = shunData[shun][iphasic * step + 1] * xishu;
const shunFirstB3 = shunData[shun][iphasic * step + 2] * xishu;
const shunFirstC3 = shunData[shun][iphasic * step + 3] * xishu;
shunshiFA.push([shunData[shun][0], shunFirstA3]);
shunshiFB.push([shunData[shun][0], shunFirstB3]);
shunshiFC.push([shunData[shun][0], shunFirstC3]);
ifmax = Math.max(ifmax, shunFirstA3, shunFirstB3, shunFirstC3);
ifmin = isOpen ? Math.min(ifmin, shunFirstA3, shunFirstC3) : Math.min(ifmin, shunFirstA3, shunFirstB3, shunFirstC3);
const shunSecondA3 = shunData[shun][iphasic * step + 1];
const shunSecondB3 = shunData[shun][iphasic * step + 2];
const shunSecondC3 = shunData[shun][iphasic * step + 3];
shunshiSA.push([shunData[shun][0], shunSecondA3]);
shunshiSB.push([shunData[shun][0], shunSecondB3]);
shunshiSC.push([shunData[shun][0], shunSecondC3]);
ismax = Math.max(ismax, shunSecondA3, shunSecondB3, shunSecondC3);
ismin = isOpen ? Math.min(ismin, shunSecondA3, shunSecondC3) : Math.min(ismin, shunSecondA3, shunSecondB3, shunSecondC3);
break;
}
}
}
const instantF = { max: ifmax, min: ifmin };
const instantS = { max: ismax, min: ismin };
const shunshiF = { shunshiFA, shunshiFB, shunshiFC };
const shunshiS = { shunshiSA, shunshiSB, shunshiSC };
const title = { aTitle, bTitle, cTitle, unit };
return { instantF, instantS, shunshiF, shunshiS, title, unit };
};
// 处理标题
let titles = '';
if (boxoList.systemType == 'pms') {
titles = '变电站名称:' +
boxoList.powerStationName +
' 监测点名称:' +
boxoList.measurementPointName +
' 发生时刻:' +
boxoList.startTime +
' 残余电压:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.duration +
's';
} else if (boxoList.systemType == 'ZL') {
titles = ' 监测点名称:' +
boxoList.equipmentName +
' 发生时刻:' +
boxoList.startTime +
' 残余电压:' +
boxoList.evtParamVVaDepth +
' 持续时间:' +
boxoList.evtParamTm +
's';
} else {
titles = '变电站名称:' +
boxoList.subName +
' 监测点名称:' +
boxoList.lineName +
' 发生时刻:' +
boxoList.startTime +
' 残余电压:' +
(boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
boxoList.duration +
's';
}
const iphasicValue = wp.iphasic || 1;
const picCounts = (wp.waveTitle.length - 1) / iphasicValue;
const waveDatas = [];
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(wp, i);
waveDatas.push(data);
}
const time = wp.time;
const type = wp.waveType;
let severity = wp.yzd;
if (severity < 0) {
severity = '/';
type = '/';
}
// 将处理结果发送回主线程
self.postMessage({
waveDatas,
time,
type,
severity,
titles,
iphasic: iphasicValue
});
});

View File

@@ -0,0 +1,863 @@
<template>
<div v-loading="loading" :element-loading-background="'rgba(122, 122, 122, 0.8)'" class="boxbx"
style="position: relative;height: 100%;">
<div id="boxsj" style="background: #343849c7">
<div id="shushi" :style="`height:${vh};overflow: hidden;`">
<div class="bx" id="wave"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import html2canvas from 'html2canvas'
import $ from 'jquery'
import * as echarts from 'echarts'
import url from '@/assets/img/point.png'
// 创建Worker
let waveDataWorker: Worker | null = null;
interface WaveData {
instantF: { max: number; min: number }
instantS: { max: number; min: number }
shunshiF: {
shunshiFA: number[][]
shunshiFB: number[][]
shunshiFC: number[][]
}
shunshiS: {
shunshiSA: number[][]
shunshiSB: number[][]
shunshiSC: number[][]
}
title: {
aTitle: string
bTitle: string
cTitle: string
unit: string
}
unit: string
}
interface Props {
value?: number
flag?: string | number | boolean
parentHeight?: string | number | boolean
DColor?: boolean
boxoList?: any
wp?: any
}
const props = withDefaults(defineProps<Props>(), {
value: 2,
flag: 3,
parentHeight: 0,
DColor: false,
boxoList: () => ({}),
wp: () => ({})
})
const loading = ref(true)
const tabvalue = ref(props.value)
const valA: any = ref(0)
const valB: any = ref(0)
const isOpen = ref(false)
const time = ref('')
const type = ref('')
const severity: any = ref('')
const iphasic: any = ref('')
const eventValue = ref('')
const persistTime = ref('')
const lineName = ref('')
const subName = ref('')
const waveDatas = ref<WaveData[]>([])
const ptpass = ref('')
const waveHeight = ref<number>()
const rmsHeight = ref<number>()
const color = ref('#006565')
const charts = ref({})
const arrpoints = ref([])
const titles = ref('')
const zoom = ref(1)
const myChartess = ref<echarts.ECharts | null>(null)
const myChartess1 = ref<echarts.ECharts | null>(null)
const myChartess2 = ref<echarts.ECharts | null>(null)
const myChartess3 = ref<echarts.ECharts | null>(null)
const myChartess4 = ref<echarts.ECharts | null>(null)
const myChartess5 = ref<echarts.ECharts | null>(null)
const vh = computed(() => {
if (props.flag == 1) {
return '690px'
} else if (props.parentHeight != 0) {
return '310px'
} else {
return '350px'
}
})
const vw = computed(() => '100%')
watch(() => props.value, (newVal) => {
if (newVal == 2) {
initWaves()
} else {
$('#wave1').remove()
initWaves()
}
})
onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom');
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1);
window.addEventListener('resize', handleResize)
nextTick(() => {
setTimeout(() => {
query()
}, 500)
})
})
onBeforeUnmount(() => {
console.log('组件卸载');
if (waveDataWorker) {
waveDataWorker.terminate();
waveDataWorker = null;
}
backbxlb();
window.removeEventListener('resize', handleResize);
})
const handleResize = () => {
const zoomValue = document.body.style.getPropertyValue('zoom');
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1);
}
const download = () => {
const boxsj = document.getElementById('boxsj')
if (boxsj) {
html2canvas(boxsj, {
scale: 2,
}).then(function (canvas) {
const creatIMg = document.createElement('a')
creatIMg.download = '瞬间波形.png'
creatIMg.href = canvas.toDataURL()
creatIMg.click()
creatIMg.remove()
})
}
}
const query = () => {
loading.value = true
initWaves()
}
const waveData = (instantF: any, instantS: any, shunshiF: any, shunshiS: any, title: any, unit: any): WaveData => {
return {
instantF,
instantS,
shunshiF,
shunshiS,
title,
unit
}
}
// 在组件中修改initWaves函数
const initWaves = () => {
if (props.wp) {
loading.value = true;
iphasic.value = props.wp.iphasic || 1
// 使用Web Worker处理数据
if (!waveDataWorker) {
waveDataWorker = new Worker(new URL('./shuWorker.js', import.meta.url));
waveDataWorker.onmessage = function (e) {
const data = e.data;
titles.value = data.titles;
iphasic.value = data.iphasic;
time.value = data.time;
type.value = data.type;
severity.value = data.severity;
initWave(data.waveDatas, data.time, data.type, data.severity, isOpen.value);
loading.value = false;
};
waveDataWorker.onerror = function (error) {
console.error('Worker error:', error);
loading.value = false;
// 备用方案:在主线程处理数据
// processDataInMainThread();
};
}
// 发送数据到Worker
waveDataWorker.postMessage(JSON.stringify({
wp: props.wp,
value: props.value,
iphasic: iphasic.value,
isOpen: isOpen.value,
boxoList: props.boxoList
}));
} else {
initWave(null, null, null, null, null);
}
}
const initWave = (waveDatas: WaveData[] | null, time: string | null, type: string | null, severity: string | null, isOpen: boolean | null) => {
$('div.bx1').remove()
let picHeight = vh.value
const show = !isOpen
let isvisible = false
let cu: number[][] = []
let titleText = ''
let unit = ''
let max: any = 0, min: any = 0
let a: string | null = null, b: string | null = null, c: string | null = null
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = []
const colors: string[] = []
if (!waveDatas) {
titleText = '该事件暂无波形图'
} else if (waveDatas.length > 0) {
titleText = titles.value
if (Number(eventValue.value) <= 90) {
isvisible = true
}
switch (iphasic.value) {
case 1:
a = waveDatas[0].title.aTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
}
colors.push('#DAA520', '#fff', '#fff')
break
case 2:
a = waveDatas[0].title.aTitle
b = waveDatas[0].title.bTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
bdata = waveDatas[0].shunshiF.shunshiFB
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
bdata = waveDatas[0].shunshiS.shunshiSB
}
colors.push('#DAA520', '#2E8B57', '#fff')
break
case 3:
a = waveDatas[0].title.aTitle
b = waveDatas[0].title.bTitle
c = waveDatas[0].title.cTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
bdata = waveDatas[0].shunshiF.shunshiFB
cdata = waveDatas[0].shunshiF.shunshiFC
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
bdata = waveDatas[0].shunshiS.shunshiSB
cdata = waveDatas[0].shunshiS.shunshiSC
}
colors.push('#DAA520', '#2E8B57', '#A52a2a')
break
}
if (waveDatas[0].title.unit === '电压') {
unit = props.value === 1 ? 'kV' : 'V'
} else {
unit = 'A'
}
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const waveId = 'wave' + step
const newDivShunshi = $(`<div style="height:${vh.value};overflow: hidden;">
<div class='bx1' id='${waveId}'></div>
</div>`)
newDivShunshi.insertAfter($('#shushi'))
$(`#${waveId}`).css('height', picHeight).css('width', vw.value)
}
} else {
titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 残余电压:${(Number(eventValue.value) * 1).toFixed(0)}% 持续时间:${persistTime.value}s`
}
const wave = document.getElementById('wave')
if (!wave) return
const myChartes = echarts.init(wave)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] }
setTimeout(() => {
wave.style.width = '100%'
wave.style.height = vh.value
}, 0)
const option = {
tooltip: {
top: '10px',
trigger: 'axis',
borderColor: 'grey',
style: {
color: '#fff',
fontSize: '15px',
padding: 10
},
formatter: function (params: any) {
let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') {
tips += params[i].marker + params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>'
}
}
return tips
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0
},
title: {
left: 'center',
text: titleText,
textStyle: {
fontSize: '0.8rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
}
},
legend: {
right: 50,
top: 25,
verticalAlign: 'top',
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0]
}
},
toolbox: {
right: 20,
top: 15,
feature: {
myCustomDownload: {
title: '',
icon: 'path://M892.342857 463.238095l-73.142857-68.266666-258.438095 258.438095V29.257143h-97.52381v624.152381L204.8 394.971429 131.657143 463.238095l380.342857 380.342857zM107.27619 897.219048h804.571429v97.523809H107.27619z',
onclick: () => download()
}
}
},
xAxis: {
type: 'value',
name: '时间\n(ms)',
boundaryGap: false,
min: props.wp?.listWaveData[0][0] || 0,
max: props.wp?.listWaveData[props.wp?.listWaveData.length - 1][0] + 1 || 1,
title: {
text: 'ms',
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
enabled: true,
align: 'high'
},
splitLine: { show: false },
nameTextStyle: { fontSize: '0.6rem' },
axisTick: { alignWithLabel: true },
axisLine: {
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
if (valA.value != (value - 0).toFixed(0)) {
valA.value = Number((value - 0).toFixed(0))
return (value - 0).toFixed(0)
}
return ''
}
}
},
yAxis: {
type: 'value',
name: unit,
title: {
align: 'high',
offset: 0,
text: unit,
rotation: 0,
y: -10
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: max.toFixed(2) * 1.1,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
opposite: false,
nameTextStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
axisLine: {
show: true,
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
return (value - 0).toFixed(2)
}
},
splitLine: {
lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread],
type: 'dashed',
opacity: 0.5
}
}
},
grid: {
left: '1%',
right: '2.8%',
bottom: '40px',
top: '70px',
containLabel: true
},
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100
}
],
series: [
{
name: a,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#DAA520' },
data: adata
},
{
name: b,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#2E8B57' },
data: bdata
},
{
name: c,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#A52a2a' },
data: cdata
},
{
name: '暂降触发点',
type: 'scatter',
symbol: 'image://' + url,
itemStyle: { width: 18, height: 18 },
data: cu
}
]
}
myChartes.setOption(option)
myChartess.value = myChartes
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
if (waveDatas && waveDatas.length > 1) {
const waveDatasTemp = waveDatas.slice(1).reverse()
for (let step = 0; step < waveDatasTemp.length; step++) {
drawPics(waveDatasTemp[step], picHeight, step, show, myChartes, titleText)
}
}
}
const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show: boolean, myChartes1: echarts.ECharts, title: string) => {
step = step + 1
const waveId = 'wave' + step
let a: string | null = null, b: string | null = null, c: string | null = null
let max: any = 0, min: any = 0, unit = ''
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = []
const colors: string[] = []
switch (iphasic.value) {
case 1:
a = waveDataTemp.title.aTitle
colors.push('#DAA520', '#fff', '#fff')
break
case 2:
a = waveDataTemp.title.aTitle
b = waveDataTemp.title.bTitle
colors.push('#DAA520', '#2E8B57', '#fff')
break
case 3:
a = waveDataTemp.title.aTitle
b = waveDataTemp.title.bTitle
c = waveDataTemp.title.cTitle
colors.push('#DAA520', '#2E8B57', '#A52a2a')
break
}
if (props.value === 1) {
max = waveDataTemp.instantF.max
min = waveDataTemp.instantF.min
adata = waveDataTemp.shunshiF.shunshiFA
bdata = waveDataTemp.shunshiF.shunshiFB
cdata = waveDataTemp.shunshiF.shunshiFC
} else {
max = waveDataTemp.instantS.max
min = waveDataTemp.instantS.min
adata = waveDataTemp.shunshiS.shunshiSA
bdata = waveDataTemp.shunshiS.shunshiSB
cdata = waveDataTemp.shunshiS.shunshiSC
}
if (waveDataTemp.title.unit === '电压') {
unit = props.value === 1 ? 'kV' : 'V'
} else {
unit = 'A'
}
let titlename = ''
if (props.boxoList.systemType == 'ZL') {
const str = waveId.split('e')
const str1 = Number(str[1])
props.wp.channelNames.forEach((element: string, i: number) => {
if (i == 4 || i == 7 || i == 10) {
if (str1 == 1 && i == 4) {
const s = element.split('A')
const s1 = s[0] == 'LI' ? '电网侧-电流' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
if (str1 == 2 && i == 7) {
const s = element.split('A')
const s1 = s[0] == 'SU' ? '负载侧-电压' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
if (str1 == 3 && i == 10) {
const s = element.split('A')
const s1 = s[0] == 'SI' ? '负载侧-电流' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
}
})
}
const waveIds = document.getElementById(waveId)
if (!waveIds) return
const myChartes = echarts.init(waveIds)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] }
const option = {
tooltip: {
trigger: 'axis',
borderColor: 'grey',
formatter: function (params: any) {
let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') {
tips += params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>'
}
}
return tips
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0
},
title: {
left: 'center',
text: titlename || title,
textStyle: {
fontSize: '0.8rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
}
},
legend: {
right: 50,
top: 25,
verticalAlign: 'top',
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0]
}
},
xAxis: {
type: 'value',
name: '时间\n(ms)',
boundaryGap: false,
min: props.wp.listWaveData[0][0],
max: props.wp.listWaveData[props.wp.listWaveData.length - 1][0] + 1,
title: {
text: 'ms',
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
enabled: true,
align: 'high'
},
splitLine: { show: false },
axisTick: { alignWithLabel: true },
axisLine: {
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
nameTextStyle: { fontSize: '0.6rem' },
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
if (valB.value != (value - 0).toFixed(0)) {
valB.value = Number((value - 0).toFixed(0))
return (value - 0).toFixed(0)
}
return ''
}
}
},
yAxis: {
type: 'value',
name: unit,
title: {
align: 'high',
offset: 0,
text: unit,
rotation: 0,
y: -10
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: max.toFixed(2) * 1.1,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
opposite: false,
nameTextStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
axisLine: {
show: true,
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
return (value - 0).toFixed(2)
}
},
splitLine: {
lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread],
type: 'dashed',
opacity: 0.5
}
}
},
grid: {
left: '1%',
right: '2.8%',
bottom: '40px',
top: '70px',
containLabel: true
},
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100
}
],
series: [
{
name: a,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#DAA520' },
data: adata
},
{
name: b,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#2E8B57' },
data: bdata
},
{
name: c,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#A52a2a' },
data: cdata
}
]
}
myChartes.setOption(option)
switch (step) {
case 1: myChartess1.value = myChartes; break
case 2: myChartess2.value = myChartes; break
case 3: myChartess3.value = myChartes; break
case 4: myChartess4.value = myChartes; break
case 5: myChartess5.value = myChartes; break
}
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
echarts.connect([myChartes1, myChartes])
}
const backbxlb = () => {
waveDatas.value = []
const charts = [myChartess.value, myChartess1.value, myChartess2.value, myChartess3.value, myChartess4.value, myChartess5.value]
charts.forEach(chart => {
if (chart) {
chart.dispose()
}
})
myChartess.value = null
myChartess1.value = null
myChartess2.value = null
myChartess3.value = null
myChartess4.value = null
myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[])
charts
.filter(Boolean)
.forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose();
}
});
}
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
temp = temp > tempC ? temp : tempC
return temp
}
const getMaxTwo = (temp: number, tempA: number, tempB: number): number => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
return temp
}
const getMin = (temp: number, tempA: number, tempB: number, tempC: number): number => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
temp = temp < tempC ? temp : tempC
return temp
}
const getMinOpen = (temp: number, tempA: number, tempB: number): number => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
return temp
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,984 @@
<template>
<div v-loading="loading" :element-loading-background="'rgba(122, 122, 122, 0.8)'" class="boxbx"
style="position: relative;height: 100%;">
<div id="boxsj" style="background: #343849c7">
<div id="shushi" :style="`height:${vh};overflow: hidden;`">
<div class="bx" id="wave"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import html2canvas from 'html2canvas'
import $ from 'jquery'
import * as echarts from 'echarts'
import url from '@/assets/img/point.png'
interface WaveData {
instantF: { max: number; min: number }
instantS: { max: number; min: number }
shunshiF: {
shunshiFA: number[][]
shunshiFB: number[][]
shunshiFC: number[][]
}
shunshiS: {
shunshiSA: number[][]
shunshiSB: number[][]
shunshiSC: number[][]
}
title: {
aTitle: string
bTitle: string
cTitle: string
unit: string
}
unit: string
}
interface Props {
value?: number
flag?: string | number | boolean
parentHeight?: string | number | boolean
DColor?: boolean
boxoList?: any
wp?: any
}
const props = withDefaults(defineProps<Props>(), {
value: 2,
flag: 3,
parentHeight: 0,
DColor: false,
boxoList: () => ({}),
wp: () => ({})
})
const loading = ref(true)
const tabvalue = ref(props.value)
const valA: any = ref(0)
const valB: any = ref(0)
const isOpen = ref(false)
const time = ref('')
const type = ref('')
const severity: any = ref('')
const iphasic: any = ref('')
const eventValue = ref('')
const persistTime = ref('')
const lineName = ref('')
const subName = ref('')
const waveDatas = ref<WaveData[]>([])
const ptpass = ref('')
const waveHeight = ref<number>()
const rmsHeight = ref<number>()
const color = ref('#006565')
const charts = ref({})
const arrpoints = ref([])
const titles = ref('')
const zoom = ref(1)
const myChartess = ref<echarts.ECharts | null>(null)
const myChartess1 = ref<echarts.ECharts | null>(null)
const myChartess2 = ref<echarts.ECharts | null>(null)
const myChartess3 = ref<echarts.ECharts | null>(null)
const myChartess4 = ref<echarts.ECharts | null>(null)
const myChartess5 = ref<echarts.ECharts | null>(null)
const vh = computed(() => {
if (props.flag == 1) {
return '680px'
} else if (props.parentHeight != 0) {
return '300px'
} else {
return '340px'
}
})
const vw = computed(() => '100%')
watch(() => props.value, (newVal) => {
if (newVal == 2) {
initWaves()
} else {
$('#wave1').remove()
initWaves()
}
})
onMounted(() => {
const zoomValue = document.body.style.getPropertyValue('zoom');
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1);
window.addEventListener('resize', handleResize)
nextTick(() => {
setTimeout(() => {
query()
}, 500)
})
})
onBeforeUnmount(() => {
console.log('组件卸载');
backbxlb()
window.removeEventListener('resize', handleResize)
})
const handleResize = () => {
const zoomValue = document.body.style.getPropertyValue('zoom');
zoom.value = 1 / (zoomValue ? parseFloat(zoomValue) : 1);
}
const download = () => {
const boxsj = document.getElementById('boxsj')
if (boxsj) {
html2canvas(boxsj, {
scale: 2,
}).then(function (canvas) {
const creatIMg = document.createElement('a')
creatIMg.download = '瞬间波形.png'
creatIMg.href = canvas.toDataURL()
creatIMg.click()
creatIMg.remove()
})
}
}
const query = () => {
loading.value = true
initWaves()
}
const waveData = (instantF: any, instantS: any, shunshiF: any, shunshiS: any, title: any, unit: any): WaveData => {
return {
instantF,
instantS,
shunshiF,
shunshiS,
title,
unit
}
}
const initWaves = () => {
if (props.wp) {
if (props.boxoList.systemType == 'pms') {
titles.value =
'变电站名称:' +
props.boxoList.powerStationName +
' 监测点名称:' +
props.boxoList.measurementPointName +
' 发生时刻:' +
props.boxoList.startTime +
' 残余电压:' +
(props.boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
props.boxoList.duration +
's'
} else if (props.boxoList.systemType == 'ZL') {
titles.value =
' 监测点名称:' +
props.boxoList.equipmentName +
' 发生时刻:' +
props.boxoList.startTime +
' 残余电压:' +
props.boxoList.evtParamVVaDepth +
' 持续时间:' +
props.boxoList.evtParamTm +
's'
} else {
titles.value =
'变电站名称:' +
props.boxoList.subName +
' 监测点名称:' +
props.boxoList.lineName +
' 发生时刻:' +
props.boxoList.startTime +
' 残余电压:' +
(props.boxoList.featureAmplitude * 100).toFixed(2) +
'% 持续时间:' +
props.boxoList.duration +
's'
}
iphasic.value = props.wp.iphasic || 1
const picCounts = (props.wp.waveTitle.length - 1) / iphasic.value
waveDatas.value = []
for (let i = 0; i < picCounts; i++) {
const data = fliteWaveData(props.wp, i)
waveDatas.value.push(data)
}
time.value = props.wp.time
type.value = props.wp.waveType
severity.value = props.wp.yzd
if (severity.value < 0) {
severity.value = '/'
type.value = '/'
}
initWave(waveDatas.value, time.value, type.value, severity.value, isOpen.value)
} else {
initWave(null, null, null, null, null)
}
}
const fliteWaveData = (wp: any, step: number): WaveData => {
const shunData = wp.listWaveData
const pt = Number(wp.pt) / 1000
const ct = Number(wp.ct)
const titleList = wp.waveTitle
let xishu = pt
let aTitle = '', bTitle = '', cTitle = '', unit = '电压'
let ifmax = 0, ifmin = 0, ismax = 0, ismin = 0
const shunshiFA: number[][] = []
const shunshiFB: number[][] = []
const shunshiFC: number[][] = []
const shunshiSA: number[][] = []
const shunshiSB: number[][] = []
const shunshiSC: number[][] = []
if (shunData.length > 0) {
if (titleList[iphasic.value * step + 1]?.substring(0, 1) !== 'U') {
xishu = ct
unit = '电流'
}
for (let i = 1; i <= iphasic.value; i++) {
switch (i) {
case 1:
aTitle = titleList[iphasic.value * step + i]?.substring(1) || ''
break
case 2:
bTitle = titleList[iphasic.value * step + i]?.substring(1) || ''
break
case 3:
cTitle = titleList[iphasic.value * step + i]?.substring(1) || ''
break
}
}
if (shunData[0][iphasic.value * step + 1] !== undefined) {
ifmax = shunData[0][iphasic.value * step + 1] * xishu
ifmin = shunData[0][iphasic.value * step + 1] * xishu
ismax = shunData[0][iphasic.value * step + 1]
ismin = shunData[0][iphasic.value * step + 1]
}
for (let shun = 0; shun < shunData.length; shun++) {
if (shunData[shun][iphasic.value * step + 1] === undefined) {
break
}
switch (iphasic.value) {
case 1:
const shunFirstA = shunData[shun][iphasic.value * step + 1] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA])
ifmax = ifmax > shunFirstA ? ifmax : shunFirstA
ifmin = ifmin < shunFirstA ? ifmin : shunFirstA
const shunSecondA = shunData[shun][iphasic.value * step + 1]
shunshiSA.push([shunData[shun][0], shunSecondA])
ismax = ismax > shunSecondA ? ismax : shunSecondA
ismin = ismin < shunSecondA ? ismin : shunSecondA
break
case 2:
const shunFirstA2 = shunData[shun][iphasic.value * step + 1] * xishu
const shunFirstB2 = shunData[shun][iphasic.value * step + 2] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA2])
shunshiFB.push([shunData[shun][0], shunFirstB2])
ifmax = getMaxTwo(ifmax, shunFirstA2, shunFirstB2)
ifmin = getMinOpen(ifmin, shunFirstA2, shunFirstB2)
const shunSecondA2 = shunData[shun][iphasic.value * step + 1]
const shunSecondB2 = shunData[shun][iphasic.value * step + 2]
shunshiSA.push([shunData[shun][0], shunSecondA2])
shunshiSB.push([shunData[shun][0], shunSecondB2])
ismax = getMaxTwo(ismax, shunSecondA2, shunSecondB2)
ismin = getMinOpen(ismin, shunSecondA2, shunSecondB2)
break
case 3:
const shunFirstA3 = shunData[shun][iphasic.value * step + 1] * xishu
const shunFirstB3 = shunData[shun][iphasic.value * step + 2] * xishu
const shunFirstC3 = shunData[shun][iphasic.value * step + 3] * xishu
shunshiFA.push([shunData[shun][0], shunFirstA3])
shunshiFB.push([shunData[shun][0], shunFirstB3])
shunshiFC.push([shunData[shun][0], shunFirstC3])
ifmax = getMax(ifmax, shunFirstA3, shunFirstB3, shunFirstC3)
ifmin = isOpen.value ? getMinOpen(ifmin, shunFirstA3, shunFirstC3) : getMin(ifmin, shunFirstA3, shunFirstB3, shunFirstC3)
const shunSecondA3 = shunData[shun][iphasic.value * step + 1]
const shunSecondB3 = shunData[shun][iphasic.value * step + 2]
const shunSecondC3 = shunData[shun][iphasic.value * step + 3]
shunshiSA.push([shunData[shun][0], shunSecondA3])
shunshiSB.push([shunData[shun][0], shunSecondB3])
shunshiSC.push([shunData[shun][0], shunSecondC3])
ismax = getMax(ismax, shunSecondA3, shunSecondB3, shunSecondC3)
ismin = isOpen.value ? getMinOpen(ismin, shunSecondA3, shunSecondC3) : getMin(ismin, shunSecondA3, shunSecondB3, shunSecondC3)
break
}
}
}
const instantF = { max: ifmax, min: ifmin }
const instantS = { max: ismax, min: ismin }
const shunshiF = { shunshiFA, shunshiFB, shunshiFC }
const shunshiS = { shunshiSA, shunshiSB, shunshiSC }
const title = { aTitle, bTitle, cTitle, unit }
return waveData(instantF, instantS, shunshiF, shunshiS, title, unit)
}
const initWave = (waveDatas: WaveData[] | null, time: string | null, type: string | null, severity: string | null, isOpen: boolean | null) => {
$('div.bx1').remove()
let picHeight = vh.value
const show = !isOpen
let isvisible = false
let cu: number[][] = []
let titleText = ''
let unit = ''
let max: any = 0, min: any = 0
let a: string | null = null, b: string | null = null, c: string | null = null
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = []
const colors: string[] = []
if (!waveDatas) {
titleText = '该事件暂无波形图'
} else if (waveDatas.length > 0) {
titleText = titles.value
if (Number(eventValue.value) <= 90) {
isvisible = true
}
switch (iphasic.value) {
case 1:
a = waveDatas[0].title.aTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
}
colors.push('#DAA520', '#fff', '#fff')
break
case 2:
a = waveDatas[0].title.aTitle
b = waveDatas[0].title.bTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
bdata = waveDatas[0].shunshiF.shunshiFB
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
bdata = waveDatas[0].shunshiS.shunshiSB
}
colors.push('#DAA520', '#2E8B57', '#fff')
break
case 3:
a = waveDatas[0].title.aTitle
b = waveDatas[0].title.bTitle
c = waveDatas[0].title.cTitle
if (props.value === 1) {
cu = [[0, waveDatas[0].instantF.min]]
max = waveDatas[0].instantF.max
min = waveDatas[0].instantF.min
adata = waveDatas[0].shunshiF.shunshiFA
bdata = waveDatas[0].shunshiF.shunshiFB
cdata = waveDatas[0].shunshiF.shunshiFC
} else {
cu = [[0, waveDatas[0].instantS.min]]
max = waveDatas[0].instantS.max
min = waveDatas[0].instantS.min
adata = waveDatas[0].shunshiS.shunshiSA
bdata = waveDatas[0].shunshiS.shunshiSB
cdata = waveDatas[0].shunshiS.shunshiSC
}
colors.push('#DAA520', '#2E8B57', '#A52a2a')
break
}
if (waveDatas[0].title.unit === '电压') {
unit = props.value === 1 ? 'kV' : 'V'
} else {
unit = 'A'
}
for (let step = waveDatas.length - 1; step > 0 && step < waveDatas.length; step--) {
const waveId = 'wave' + step
const newDivShunshi = $(`<div style="height:${vh.value};overflow: hidden;">
<div class='bx1' id='${waveId}'></div>
</div>`)
newDivShunshi.insertAfter($('#shushi'))
$(`#${waveId}`).css('height', picHeight).css('width', vw.value)
}
} else {
titleText = `变电站名称:${subName.value} 监测点名称:${lineName.value} 发生时刻:${time} 残余电压:${(Number(eventValue.value) * 1).toFixed(0)}% 持续时间:${persistTime.value}s`
}
const wave = document.getElementById('wave')
if (!wave) return
const myChartes = echarts.init(wave)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] }
setTimeout(() => {
wave.style.width = '100%'
wave.style.height = vh.value
}, 0)
const option = {
tooltip: {
top: '10px',
trigger: 'axis',
borderColor: 'grey',
backgroundColor: '#fff',
style: {
color: '#fff',
fontSize: '15px',
padding: 10
},
formatter: function (params: any) {
let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') {
tips += params[i].marker + params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>'
}
}
return tips
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
borderWidth: 0
},
title: {
left: 'center',
text: titleText,
textStyle: {
fontSize: '0.8rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
}
},
legend: {
right: 50,
top: 25,
verticalAlign: 'top',
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0]
}
},
toolbox: {
right: 20,
top: 15,
feature: {
myCustomDownload: {
title: '',
icon: 'path://M892.342857 463.238095l-73.142857-68.266666-258.438095 258.438095V29.257143h-97.52381v624.152381L204.8 394.971429 131.657143 463.238095l380.342857 380.342857zM107.27619 897.219048h804.571429v97.523809H107.27619z',
onclick: () => download()
}
}
},
xAxis: {
type: 'value',
name: '时间\n(ms)',
boundaryGap: false,
min: props.wp?.listWaveData[0][0] || 0,
max: props.wp?.listWaveData[props.wp?.listWaveData.length - 1][0] + 1 || 1,
title: {
text: 'ms',
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
enabled: true,
align: 'high'
},
splitLine: { show: false },
nameTextStyle: { fontSize: '0.6rem' },
axisTick: { alignWithLabel: true },
axisLine: {
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
if (valA.value != (value - 0).toFixed(0)) {
valA.value = Number((value - 0).toFixed(0))
return (value - 0).toFixed(0)
}
return ''
}
}
},
yAxis: {
type: 'value',
name: unit,
title: {
align: 'high',
offset: 0,
text: unit,
rotation: 0,
y: -10
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: max.toFixed(2) * 1.1,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
opposite: false,
nameTextStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
axisLine: {
show: true,
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
return (value - 0).toFixed(2)
}
},
splitLine: {
lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread],
type: 'dashed',
opacity: 0.5
}
}
},
grid: {
left: '1%',
right: '2.8%',
bottom: '40px',
top: '70px',
containLabel: true
},
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100
}
],
series: [
{
name: a,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#DAA520' },
data: adata
},
{
name: b,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#2E8B57' },
data: bdata
},
{
name: c,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#A52a2a' },
data: cdata
},
{
name: '暂降触发点',
type: 'scatter',
symbol: 'image://' + url,
itemStyle: { width: 18, height: 18 },
data: cu
}
]
}
myChartes.setOption(option)
myChartess.value = myChartes
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
if (waveDatas && waveDatas.length > 1) {
const waveDatasTemp = waveDatas.slice(1).reverse()
for (let step = 0; step < waveDatasTemp.length; step++) {
drawPics(waveDatasTemp[step], picHeight, step, show, myChartes, titleText)
}
}
}
const drawPics = (waveDataTemp: WaveData, picHeight: string, step: number, show: boolean, myChartes1: echarts.ECharts, title: string) => {
step = step + 1
const waveId = 'wave' + step
let a: string | null = null, b: string | null = null, c: string | null = null
let max: any = 0, min: any = 0, unit = ''
let adata: number[][] = [], bdata: number[][] = [], cdata: number[][] = []
const colors: string[] = []
switch (iphasic.value) {
case 1:
a = waveDataTemp.title.aTitle
colors.push('#DAA520', '#fff', '#fff')
break
case 2:
a = waveDataTemp.title.aTitle
b = waveDataTemp.title.bTitle
colors.push('#DAA520', '#2E8B57', '#fff')
break
case 3:
a = waveDataTemp.title.aTitle
b = waveDataTemp.title.bTitle
c = waveDataTemp.title.cTitle
colors.push('#DAA520', '#2E8B57', '#A52a2a')
break
}
if (props.value === 1) {
max = waveDataTemp.instantF.max
min = waveDataTemp.instantF.min
adata = waveDataTemp.shunshiF.shunshiFA
bdata = waveDataTemp.shunshiF.shunshiFB
cdata = waveDataTemp.shunshiF.shunshiFC
} else {
max = waveDataTemp.instantS.max
min = waveDataTemp.instantS.min
adata = waveDataTemp.shunshiS.shunshiSA
bdata = waveDataTemp.shunshiS.shunshiSB
cdata = waveDataTemp.shunshiS.shunshiSC
}
if (waveDataTemp.title.unit === '电压') {
unit = props.value === 1 ? 'kV' : 'V'
} else {
unit = 'A'
}
let titlename = ''
if (props.boxoList.systemType == 'ZL') {
const str = waveId.split('e')
const str1 = Number(str[1])
props.wp.channelNames.forEach((element: string, i: number) => {
if (i == 4 || i == 7 || i == 10) {
if (str1 == 1 && i == 4) {
const s = element.split('A')
const s1 = s[0] == 'LI' ? '电网侧-电流' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
if (str1 == 2 && i == 7) {
const s = element.split('A')
const s1 = s[0] == 'SU' ? '负载侧-电压' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
if (str1 == 3 && i == 10) {
const s = element.split('A')
const s1 = s[0] == 'SI' ? '负载侧-电流' : s[0] + '侧' + s[1]
titlename = s1 + ' ' + title
}
}
})
}
const waveIds = document.getElementById(waveId)
if (!waveIds) return
const myChartes = echarts.init(waveIds)
const echartsColor = { WordColor: "#fff", thread: "#fff", FigureColor: ["#07CCCA ", "#00BFF5", "#FFBF00", "#77DA63", "#D5FF6B", "#Ff6600", "#FF9100", "#5B6E96", "#66FFCC", "#B3B3B3", "#FF00FF", "#CC00FF", "#FF9999"] }
const option = {
tooltip: {
trigger: 'axis',
borderColor: 'grey',
formatter: function (params: any) {
let tips = '时刻:' + params[0].data[0] + '</br/>'
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != '暂降触发点') {
tips += params[i].seriesName + ':' + (params[i].value[1] - 0).toFixed(2) + '<br/>'
}
}
return tips
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0
},
title: {
left: 'center',
text: titlename || title,
textStyle: {
fontSize: '0.8rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
}
},
legend: {
right: 50,
top: 25,
verticalAlign: 'top',
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
rich: { a: { verticalAlign: 'middle' } },
padding: [0, 0, 0, 0]
}
},
xAxis: {
type: 'value',
name: '时间\n(ms)',
boundaryGap: false,
min: props.wp.listWaveData[0][0],
max: props.wp.listWaveData[props.wp.listWaveData.length - 1][0] + 1,
title: {
text: 'ms',
textStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
enabled: true,
align: 'high'
},
splitLine: { show: false },
axisTick: { alignWithLabel: true },
axisLine: {
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
nameTextStyle: { fontSize: '0.6rem' },
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
if (valB.value != (value - 0).toFixed(0)) {
valB.value = Number((value - 0).toFixed(0))
return (value - 0).toFixed(0)
}
return ''
}
}
},
yAxis: {
type: 'value',
name: unit,
title: {
align: 'high',
offset: 0,
text: unit,
rotation: 0,
y: -10
},
boundaryGap: [0, '100%'],
showLastLabel: true,
max: max.toFixed(2) * 1.1,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
opposite: false,
nameTextStyle: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor
},
axisLine: {
show: true,
lineStyle: {
color: props.DColor ? '#fff' : echartsColor.thread
},
onZero: false
},
axisLabel: {
fontSize: '0.6rem',
color: props.DColor ? '#fff' : echartsColor.WordColor,
formatter: function (value: number) {
return (value - 0).toFixed(2)
}
},
splitLine: {
lineStyle: {
color: [props.DColor ? '#fff' : echartsColor.thread],
type: 'dashed',
opacity: 0.5
}
}
},
grid: {
left: '1%',
right: '2.8%',
bottom: '40px',
top: '70px',
containLabel: true
},
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100
}
],
series: [
{
name: a,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#DAA520' },
data: adata
},
{
name: b,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#2E8B57' },
data: bdata
},
{
name: c,
type: 'line',
large: true,
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#A52a2a' },
data: cdata
}
]
}
myChartes.setOption(option)
switch (step) {
case 1: myChartess1.value = myChartes; break
case 2: myChartess2.value = myChartes; break
case 3: myChartess3.value = myChartes; break
case 4: myChartess4.value = myChartes; break
case 5: myChartess5.value = myChartes; break
}
setTimeout(() => {
myChartes.resize()
loading.value = false
}, 400)
echarts.connect([myChartes1, myChartes])
}
const backbxlb = () => {
waveDatas.value = []
const charts = [myChartess.value, myChartess1.value, myChartess2.value, myChartess3.value, myChartess4.value, myChartess5.value]
charts.forEach(chart => {
if (chart) {
chart.dispose()
}
})
myChartess.value = null
myChartess1.value = null
myChartess2.value = null
myChartess3.value = null
myChartess4.value = null
myChartess5.value = null
// echarts.disconnect(charts.filter(Boolean) as echarts.ECharts[])
charts
.filter(Boolean)
.forEach(chart => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose();
}
});
}
const getMax = (temp: number, tempA: number, tempB: number, tempC: number): number => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
temp = temp > tempC ? temp : tempC
return temp
}
const getMaxTwo = (temp: number, tempA: number, tempB: number): number => {
temp = temp > tempA ? temp : tempA
temp = temp > tempB ? temp : tempB
return temp
}
const getMin = (temp: number, tempA: number, tempB: number, tempC: number): number => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
temp = temp < tempC ? temp : tempC
return temp
}
const getMinOpen = (temp: number, tempA: number, tempB: number): number => {
temp = temp < tempA ? temp : tempA
temp = temp < tempB ? temp : tempB
return temp
}
</script>

View File

@@ -0,0 +1,189 @@
<template>
<div v-if="view2" v-loading="loading" element-loading-background="#343849c7">
<el-row v-if="view4">
<el-col :span="12">
<span style="font-size: 14px; line-height: 30px">值类型选择</span>
<el-select
style="width: 150px"
@change="changeView"
size="small"
v-model="value"
placeholder="请选择值类型"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
<div
v-if="view4"
element-loading-background="rgba(122, 122, 122, 0.8)"
style="height: 750px"
>
<el-tabs
class="default-main"
v-model="bxactiveName"
@tab-click="bxhandleClick"
>
<el-tab-pane
label="瞬时波形"
name="ssbx"
class="boxbx pb10"
:style="'height:' + bxecharts + ';overflow-y: auto;'"
>
<shushiboxi
ref="shushiboxiRef"
v-if="bxactiveName == 'ssbx' && showBoxi"
:value="value"
:parentHeight="parentHeight"
:boxoList="boxoList"
:wp="wp"
></shushiboxi>
</el-tab-pane>
<el-tab-pane
label="RMS波形"
class="boxbx pb10"
name="rmsbx"
:style="'height:' + bxecharts + ';overflow-y: auto;'"
>
<rmsboxi
ref="rmsboxiRef"
v-if="bxactiveName == 'rmsbx' && showBoxi"
:value="value"
:parentHeight="parentHeight"
:boxoList="boxoList"
:wp="wp"
></rmsboxi>
</el-tab-pane>
</el-tabs>
</div>
<el-empty v-else description="暂无数据" style="height: 700px" />
</div>
</template>
<script setup lang="ts">
import shushiboxi from "./shushiboxi.vue";
import rmsboxi from "./rmsboxi.vue";
import { ref, reactive } from "vue";
import { getTransientAnalyseWave } from "@/api/statistics/index";
const emit = defineEmits(["backbxlb"]);
interface Props {
// boxoList: any
// wp: any,
senior?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
senior: false,
});
const parentHeight = ref(0);
const loading = ref(true);
const bxactiveName = ref("ssbx");
const rmsboxiRef = ref();
const value = ref(1);
const options = ref([
{
value: 1,
label: "一次值",
},
{
value: 2,
label: "二次值",
},
]);
const shushiboxiRef = ref();
const bxecharts = ref("700px");
const view2 = ref(true);
const boxoList: any = ref({});
const wp: any = ref(null);
const showBoxi = ref(true);
const view3 = ref(false);
const view4 = ref(false);
const GJList = ref([]);
const open = async (row: any) => {
console.log("🚀 ~ open ~ row:", row);
loading.value = true;
await getTransientAnalyseWave({
// id: "151984a0-4a2e-4c46-aece-493e1a2e24c1",
id: row.eventdetail_index,
systemType: 0,
type: 0,
})
.then((res) => {
row.loading = false;
if (res != undefined) {
boxoList.value = {
subName: row.bdname,
lineName: row.pointname,
startTime: row.timeid,
featureAmplitude: row.eventvalue,
duration: row.persisttime,
};
wp.value = res.data;
loading.value = false;
view4.value = true;
}
})
.catch(() => {
loading.value = false;
});
};
const bxhandleClick = (tab: any) => {
// if (shushiboxiRef.value) shushiboxiRef.value.backbxlb();
// if (rmsboxiRef.value) rmsboxiRef.value.backbxlb();
loading.value = true;
if (tab.name == "ssbx") {
bxactiveName.value = "ssbx";
} else if (tab.name == "rmsbx") {
bxactiveName.value = "rmsbx";
}
setTimeout(() => {
loading.value = false;
}, 0);
// console.log(tab, event);
};
const backbxlb = () => {
boxoList.value = null;
wp.value = null;
// if (shushiboxiRef.value) shushiboxiRef.value.backbxlb();
// if (rmsboxiRef.value) rmsboxiRef.value.backbxlb();
emit("backbxlb");
};
const setHeight = (h: any, vh: any) => {
if (h != false) {
parentHeight.value = h;
}
setTimeout(() => {
bxecharts.value = "700px";
}, 100);
};
// 高级分析
const changeView = () => {
// if (shushiboxiRef.value) shushiboxiRef.value.backbxlb();
// if (rmsboxiRef.value) rmsboxiRef.value.backbxlb();
showBoxi.value = false;
setTimeout(() => {
showBoxi.value = true;
}, 0);
};
const gaoBack = () => {
view2.value = true;
view3.value = false;
};
defineExpose({ open, setHeight });
</script>
<style lang="scss" scoped>
:deep(.el-tabs__item) {
// color: #c2c2c2;
--el-text-color-primary: #d3d4d6;
}
</style>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@@ -0,0 +1,300 @@
<!-- 地图组件 -->
<template>
<div style="position: relative">
<div class="bars_w" ref="chartMap" id="chartMap"></div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref, watch, onMounted, defineEmits } from "vue";
import * as echarts from "echarts";
import "echarts-gl";
import "echarts-liquidfill";
import "echarts/lib/component/dataZoom";
const props = defineProps(["options"]);
const myCharts = ref();
const showCircle = ref(false);
const fetchConfig = async (name: string) => {
const res = await import(`./mapJson/${name}.json`);
return res.default;
// GetEchar(res.default)
};
// fetchConfig()
const emit = defineEmits(["getRegionByRegion", "eliminate", "clickMap"]);
onMounted(() => {});
const GetEchar = async (name: string) => {
let chartDom = document.getElementById("chartMap");
myCharts.value?.dispose();
myCharts.value = echarts.init(chartDom);
echarts.registerMap(name, await fetchConfig(name)); //注册可用的地图
let option = {
title: {
left: "center",
top: "3%",
...props.options.title,
},
tooltip: {
trigger: "item",
axisPointer: {
type: "shadow",
label: {
color: "#fff",
fontSize: 16,
},
},
textStyle: {
color: "#fff",
fontStyle: "normal",
opacity: 0.35,
fontSize: 14,
},
backgroundColor: "rgba(0,0,0,0.55)",
...(props.options.tooltip || null),
},
legend: {
orient: "vertical",
left: 25,
bottom: 40,
itemWidth: 16,
itemHeight: 16,
...(props.options.legend || null),
},
color: [
...(props.options.color || ""),
"#07CCCA ",
"#00BFF5",
"#FFBF00",
"#77DA63",
"#D5FF6B",
"#Ff6600",
"#FF9100",
"#5B6E96",
"#66FFCC",
"#B3B3B3",
],
geo: {
map: name,
zoom: 1.3,
// top: 10,
// bottom: 0,
roam: true,
rotation: 90,
label: {
normal: {
show: true,
fontSize: "12",
color: "#fff",
},
},
itemStyle: {
normal: {
color: "#0a73ff80", //地图背景色
borderColor: "#fff",
borderWidth: 1,
areaColor: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: "#0a73ff80", // 0% 处的颜色
},
{
offset: 1,
color: "#0a73ff80", // 100% 处的颜色
},
],
globalCoord: false, // 缺省为 false
},
shadowColor: "#0a73ff80",
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10,
},
emphasis: {
areaColor: "#0a73ff70",
shadowOffsetX: 0,
shadowOffsetY: 0,
borderWidth: 0,
color: "#fff",
},
},
},
...props.options.options,
};
if (props.options.visualMap) {
option.visualMap = props.options.visualMap;
}
if (props.options.geo3D) {
option.geo = null;
option.geo3D = {
map: name,
...(props.options.geo3D || null),
};
}
myCharts.value.setOption(option);
window.addEventListener("resize", resizeHandler);
const flag1 = ref(true);
// 点击事件
myCharts.value.off("click");
myCharts.value.on("click", (e: any) => {
setIcon('')
if (props.options.geo3D) {
// emit('clickMap', e)
if (flag1.value) {
flag1.value = false;
setTimeout(() => {
emit("clickMap", e);
flag1.value = true;
}, 100);
}
} else {
// if (name == dictData.state.area?.[0].name && e.componentIndex == 0) {
if (name == "中国" && e.componentIndex == 0) {
// console.log('🚀 ~ file: MyEchartMap.vue:156 ~ myCharts.value.on ~ MapReturn(e.name):', MapReturn(e.name))
}
}
});
myCharts.value.on("datarangeselected", function (params: any) {
// 手动调用restore方法恢复地图的默认颜色映射
myCharts.value.dispatchAction({
type: "restore",
});
});
};
// 记录上次操作的点信息
let lastModifiedPoint = null; // 格式: {name, oldColor, oldSize}
/**
* 修改地图上指定名称点的样式并显示提示框
* @param {string} targetName - 要修改的点的name属性值
* @param {string} newColor - 新的颜色值(如"#ff0000"
* @param {number} newSize - 新的图标大小如20
*/
function setIcon(targetName) {
// 获取当前图表实例替换为你的地图容器ID
const chartInstance = echarts.getInstanceByDom(
document.getElementById("chartMap")
);
if (!chartInstance) return;
// 获取当前配置项
const option = chartInstance.getOption();
if (!option || !option.series ) return;
// 找到对应的series你的场景中是第一个series
const targetSeries = option.series[0];
let pointIndex = -1;
let currentPoint = null;
// 1. 先恢复上次修改的点的样式
if (lastModifiedPoint) {
targetSeries.data.forEach((item) => {
if (item.name === lastModifiedPoint.name) {
// 恢复原始颜色和大小
item.itemStyle.color = lastModifiedPoint.oldColor;
item.symbolSize = lastModifiedPoint.oldSize;
}
});
}
// 2. 查找当前目标点并记录原始样式
targetSeries.data.forEach((item, index) => {
if (item.name === targetName) {
pointIndex = index;
currentPoint = item;
// 记录当前点的原始样式(用于下次恢复)
lastModifiedPoint = {
name: item.name,
oldColor: "#00FF00", // 默认为原始绿色
oldSize: 15, // 默认为原始大小15
};
// 修改为新样式
item.itemStyle = { ...item.itemStyle, color: "#ff0000" };
item.symbolSize = 25;
}
});
// 3. 更新图表并显示提示框
if (pointIndex !== -1 && currentPoint) {
chartInstance.setOption(option);
// 关闭之前的提示框(避免重叠)
chartInstance.dispatchAction({ type: "hideTip" });
// 显示当前点的提示框
setTimeout(() => {
chartInstance.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: pointIndex,
});
}, 50);
} else {
// 如果未找到目标点,清空上次记录
// lastModifiedPoint = null;
// console.warn(`未找到名称为${targetName}的点`);
// 如果未找到目标点,恢复所有点的样式并清空记录
targetSeries.data.forEach((item) => {
// 恢复默认样式
item.itemStyle = { ...item.itemStyle, color: "#00FF00" };
item.symbolSize = 15;
});
// 清空上次修改记录
lastModifiedPoint = null;
// 隐藏提示框
chartInstance.dispatchAction({ type: "hideTip" });
// 更新图表
chartInstance.setOption(option);
console.warn(`未找到名称为${targetName}的点,已恢复所有点原始样式`);
}
}
const resizeHandler = () => {
myCharts.value?.resize();
myCharts.value?.setOption(myCharts.value.getOption()); // 强制更新配置
};
onBeforeUnmount(() => {
window.removeEventListener("resize", resizeHandler);
myCharts.value?.dispose();
});
defineExpose({ GetEchar, setIcon });
watch(
() => props.options,
(newVal, oldVal) => {
// GetEchar('中国')
}
);
</script>
<style lang="scss" scoped>
.bars_w {
width: 100%;
height: 100%;
}
.iconfont {
cursor: pointer;
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
font-size: 20px;
color: var(--el-color-primary) !important;
}
</style>

View File

@@ -0,0 +1,671 @@
<template>
<div style="width: 540px">
<el-select
v-model="interval"
style="min-width: 70px; width: 70px; margin-right: 10px"
size="small"
@change="timeChange"
>
<el-option
v-for="item in timeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-date-picker
v-model="timeValue"
size="small"
type="daterange"
:disabled="disabledPicker"
:disabled-date="isFutureDate"
style="width: 190px; margin-right: 10px"
unlink-panels
:clearable="false"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
:shortcuts="shortcuts"
/>
<el-button
:disabled="backDisabled"
type="primary"
size="small"
:icon="DArrowLeft"
@click="preClick"
></el-button>
<el-button type="primary" :icon="VideoPause" @click="nowTime" size="small"
>当前</el-button
>
<el-button
:disabled="preDisabled"
type="primary"
size="small"
:icon="DArrowRight"
@click="next"
></el-button>
</div>
</template>
<script lang="ts" setup>
import { DArrowLeft, VideoPause, DArrowRight } from "@element-plus/icons-vue";
import { ref, onMounted, nextTick, watch } from "vue";
import { useStore } from "vuex";
const store = useStore();
const emit = defineEmits(["timeChangeInfo"]);
interface Props {
nextFlag?: boolean;
theCurrentTime?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
nextFlag: false,
theCurrentTime: true,
});
const interval = ref(3);
const timeFlag = ref(1);
const count = ref(0);
const disabledPicker = ref(true);
const timeValue = ref([]);
const backDisabled = ref(false);
const preDisabled = ref(true);
const timeOptions: any = ref([
{ label: "年份", value: 1 },
{ label: "季度", value: 2 },
{ label: "月份", value: 3 },
{ label: "周", value: 4 },
{ label: "自定义", value: 5 },
]);
const shortcuts = [
{
text: "最近一周",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
},
},
{
text: "最近一个月",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
},
},
{
text: "最近3个月",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
},
},
];
const isFutureDate = (time: any) => {
return time && time > Date.now();
};
onMounted(() => {
timeChange(3);
});
// 选择时间范围
const timeChange = (e: number) => {
backDisabled.value = false;
preDisabled.value = props.nextFlag ? false : true;
count.value = 0;
if (e == 1) {
disabledPicker.value = true;
timeValue.value = [setTime(1), setTime()];
} else if (e == 2) {
disabledPicker.value = true;
timeValue.value = [setTime(2), setTime()];
} else if (e == 3) {
disabledPicker.value = true;
timeValue.value = [setTime(3), setTime()];
} else if (e == 4) {
let year = parseInt(setTime().substring(0, 4));
let month = parseInt(setTime().substring(5, 7));
let date = parseInt(setTime().substring(8, 10));
var start = new Date(year, month - 1, date);
var dayOfWeek = start.getDay() == 0 ? 7 : start.getDay() - 1; // 如果为周日则置为7天
disabledPicker.value = true;
timeValue.value = [setTime(0, dayOfWeek), setTime(0, -6 + dayOfWeek)];
} else if (e == 5) {
disabledPicker.value = false;
backDisabled.value = true;
preDisabled.value = props.nextFlag ? false : true;
timeValue.value = [setTime(), setTime()];
}
if (e == 1 || e == 2) {
timeFlag.value = 0;
} else {
timeFlag.value = 1;
}
};
// 当前
const nowTime = () => {
console.log(interval.value, "000000000");
timeChange(interval.value);
};
// 上一个
const preClick = () => {
preDisabled.value = false;
let startTime = timeValue.value[0];
let endTime = timeValue.value[1];
let year = parseInt(startTime.substring(0, 4));
let month = parseInt(startTime.substring(5, 7));
let date = parseInt(startTime.substring(8, 10));
//按月
if (interval.value == 3) {
// 换年份
if (month == 1) {
year = year - 1;
startTime = year + "-12-01";
endTime = year + "-12-31";
} else if (month <= 10) {
month = month - 1;
startTime = year + "-0" + month + "-01";
let day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
} else {
month = month - 1;
startTime = year + "-" + month + "-01";
let day = getDays(year, month);
endTime = year + "-" + month + "-" + day;
}
//按周
} else if (interval.value == 4) {
//根据开始时间推
let start = new Date(year, month - 1, date);
start.setDate(start.getDate() - 7);
startTime = formatTime(start);
var end = new Date(start);
end.setDate(start.getDate() + 6);
endTime = formatTime(end);
//按季度
} else if (interval.value == 2) {
// 换年份
if (month == 1) {
year = year - 1;
startTime = year + "-10-01";
endTime = year + "-12-31";
} else {
// 还是本年
month = month - 3;
startTime = year + "-0" + month + "-01";
month = month + 2;
var day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
}
//自定义
} else if (interval.value == 1) {
year = year - 1;
startTime = year + "-01-01";
endTime = year + "-12-31";
}
timeValue.value = [startTime, endTime];
// 判断向后键的状态
// var temp = NowgetEndTime()
// timeStatus(temp, endTime)
};
//下一个
const next = () => {
//向后
let startTime = timeValue.value[0];
let endTime = timeValue.value[1];
let year = parseInt(startTime.substring(0, 4));
let month = parseInt(startTime.substring(5, 7));
let date = parseInt(startTime.substring(8, 10));
var now = new Date();
// 获取当前年份
var presentY = now.getFullYear();
// 获取当前月份
var presentM = now.getMonth() + 1;
// 获取当前日期
var presentD = now.getDate();
if (interval.value == 3) {
if (month == 12) {
year = year + 1;
// 年份进位后,大于当前的年份,是不科学的
if (presentY < year && !props.nextFlag) {
startTime = presentY + "-12-01";
if (presentD < 10) {
endTime = presentY + "-12" + "-0" + presentD;
} else {
endTime = presentY + "-12" + "-" + presentD;
}
// 年份进位后,等于当前的年份
} else if (presentY == year) {
startTime = year + "-01-01";
if (presentM > 1) {
endTime = year + "-01-31";
} else {
if (presentD < 10) {
endTime = year + "-01" + "-0" + presentD;
} else {
endTime = year + "-01" + "-" + presentD;
}
}
// 年份进位后,依旧小于当前的年份
} else {
startTime = year + "-01-01";
endTime = year + "-01-31";
}
} else {
month = month + 1;
// 年份等于当前年份
if (presentY == year) {
// 月份超过当前月份,是不科学的
if (month >= presentM && !props.nextFlag) {
if (presentM < 10) {
startTime = year + "-0" + presentM + "-01";
if (presentD < 10) {
endTime = year + "-0" + presentM + "-0" + presentD;
} else {
endTime = year + "-0" + presentM + "-" + presentD;
}
} else {
startTime = year + "-" + presentM + "-01";
if (presentD < 10) {
endTime = year + "-" + presentM + "-0" + presentD;
} else {
endTime = year + "-" + presentM + "-" + presentD;
}
}
} else {
if (month < 10) {
startTime = year + "-0" + month + "-01";
var day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
} else {
startTime = year + "-" + month + "-01";
var day = getDays(year, month);
endTime = year + "-" + month + "-" + day;
}
}
// 年份小于当前的年份
} else {
if (month < 10) {
startTime = year + "-0" + month + "-01";
var day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
} else {
startTime = year + "-" + month + "-01";
var day = getDays(year, month);
endTime = year + "-" + month + "-" + day;
}
}
}
} else if (interval.value == 2) {
// 前进需要年份进位
if (month == 10) {
year = year + 1;
// 年份进位后大于当前年份是不科学的
if (year > presentY && !props.nextFlag) {
startTime = presentY + "-10-01";
if (presentD < 10) {
endTime = year + "-" + presentM + "-0" + presentD;
} else {
endTime = year + "-" + presentM + "-" + presentD;
}
} else if (year == presentY) {
startTime = year + "-01-01";
// 当前月份大约3月份
if (presentM > 3) {
endTime = year + "-03-31";
} else {
// 当前月份也在第一季度里
if (presentD < 10) {
endTime = year + "-0" + presentM + "-0" + presentD;
} else {
endTime = year + "-0" + presentM + "-" + presentD;
}
}
} else {
startTime = year + "-01-01";
endTime = year + "-03-31";
}
} else {
month = month + 3;
console.log("🚀 ~ next ~ presentM:", presentM, month);
// 季度进位后,超过当前月份是不科学的
if (year == presentY && !props.nextFlag) {
if (month >= presentM) {
// 当季度进位后大于当前月,以当前月的时间显示季度
if (presentM > 0 && presentM < 4) {
// 第一季度
startTime = year + "-01-01";
if (presentD < 10) {
endTime = year + "-0" + presentM + "-0" + presentD;
} else {
endTime = year + "-0" + presentM + "-" + presentD;
}
} else if (presentM > 3 && presentM < 7) {
console.log(123123);
// 第二季度
startTime = year + "-04-01";
if (presentD < 10) {
endTime = year + "-0" + presentM + "-0" + presentD;
} else {
endTime = year + "-0" + presentM + "-" + presentD;
}
} else if (presentM > 6 && presentM < 10) {
// 第三季度
startTime = year + "-07-01";
if (presentD < 10) {
endTime = year + "-0" + presentM + "-0" + presentD;
} else {
endTime = year + "-0" + presentM + "-" + presentD;
}
} else {
// 第四季度
startTime = year + "-10-01";
if (presentD < 10) {
endTime = year + "-" + presentM + "-0" + presentD;
} else {
endTime = year + "-" + presentM + "-" + presentD;
}
}
} else {
if (month == 10) {
startTime = year + "-" + month + "-01";
} else {
startTime = year + "-0" + month + "-01";
}
month = month + 2;
if (month >= presentM) {
endTime = NowgetEndTime();
} else {
var day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
}
}
} else {
if (month == 10) {
startTime = year + "-" + month + "-01";
month = month + 2;
var day = getDays(year, month);
endTime = year + "-" + month + "-" + day;
} else {
startTime = year + "-0" + month + "-01";
month = month + 2;
var day = getDays(year, month);
endTime = year + "-0" + month + "-" + day;
}
}
}
console.log(startTime, endTime);
} else if (interval.value == 5) {
} else if (interval.value == 4) {
//根据开始时间推
var start = new Date(year, month - 1, date);
start.setDate(start.getDate() + 7);
startTime = formatTime(start);
var end = new Date(start);
end.setDate(start.getDate() + 6);
endTime = formatTime(end);
} else {
year = year + 1;
// 年份进位后大于当前年份,是不科学的
if (year >= presentY && !props.nextFlag) {
startTime = presentY + "-01-01";
if (presentM < 10) {
if (presentD < 10) {
endTime = presentY + "-0" + presentM + "-0" + presentD;
} else {
endTime = presentY + "-0" + presentM + "-" + presentD;
}
} else {
if (presentD < 10) {
endTime = presentY + "-" + presentM + "-0" + presentD;
} else {
endTime = presentY + "-" + presentM + "-" + presentD;
}
}
} else {
startTime = year + "-01-01";
endTime = year + "-12-31";
}
}
if (!props.nextFlag) {
if (
new Date(endTime + " 00:00:00").getTime() >=
new Date(
(window as any).XEUtils.toDateString(new Date(), "yyyy-MM-dd ") +
" 00:00:00"
).getTime()
) {
preDisabled.value = props.nextFlag ? false : true;
}
}
timeValue.value = [startTime, endTime];
};
const setTime = (flag = 0, e = 0) => {
let dd = (window as any).XEUtils.toDateString(
new Date().getTime() - e * 3600 * 1000 * 24,
"dd"
);
let data = "";
if ((dd < 4 || dd == 0) && interval.value != 4 && !props.theCurrentTime) {
data = (window as any).XEUtils.toDateString(
new Date().getTime() - (e + dd) * 3600 * 1000 * 24,
"yyyy-MM-dd"
);
} else {
data = (window as any).XEUtils.toDateString(
new Date().getTime() - e * 3600 * 1000 * 24,
"yyyy-MM-dd"
);
}
if (flag == 1) {
data = data.slice(0, 5) + "01-01";
} else if (flag == 2) {
let quarter = parseInt(data.slice(5, 7));
if (0 < quarter && quarter <= 3) {
data = data.slice(0, 5) + "01-01";
} else if (3 < quarter && quarter <= 6) {
data = data.slice(0, 5) + "04-01";
} else if (6 < quarter && quarter <= 9) {
data = data.slice(0, 5) + "07-01";
} else {
data = data.slice(0, 5) + "10-01";
}
}
if (flag == 3) {
data = data.slice(0, 8) + "01";
}
return data;
};
// 获取月份的天数
const getDays = (year: any, month: any) => {
let max = new Date(year, month, 0).getDate();
return max;
};
// 时间格式化
const formatTime = (time: any) => {
return (
time.getFullYear() +
"-" +
(time.getMonth() + 1 < 10 ? "0" : "") +
(time.getMonth() + 1) +
"-" +
(time.getDate() < 10 ? "0" : "") +
time.getDate()
);
};
const NowgetEndTime = () => {
let now = new Date();
let sep = "-";
let year = now.getFullYear();
let month: any = now.getMonth() + 1;
if (month < 10) {
month = "0" + month;
}
let date: any = now.getDate();
if (date < 10) {
date = "0" + date;
}
// 拼接当前的日期
let endTime = year + sep + month + sep + date;
return endTime;
};
const setTimeOptions = (list: any) => {
timeOptions.value = list;
};
const setTheDate = (value: any) => {
interval.value = value;
timeChange(value);
};
// 获取时间范围的同比
function getYearOnYear(startDate: string, endDate: string): [string, string] {
const startYearAgo = new Date(startDate);
startYearAgo.setFullYear(startYearAgo.getFullYear() - 1);
const endYearAgo = new Date(endDate);
endYearAgo.setFullYear(endYearAgo.getFullYear() - 1);
return [formatDate(startYearAgo), formatDate(endYearAgo)];
}
// 获取时间范围的环比
function getMonthOnMonth(startDate: string, endDate: string): [string, string] {
const start = new Date(startDate);
const end = new Date(endDate);
const diffTime = end.getTime() - start.getTime() + 60 * 60 * 24 * 1000; // 计算时间差
const startMonthAgo = new Date(start);
startMonthAgo.setTime(startMonthAgo.getTime() - diffTime); // 将开始时间向前推移相同的时间差
const endMonthAgo = new Date(start);
endMonthAgo.setDate(start.getDate() - 1); // 结束时间是开始时间的前一天
return [formatDate(startMonthAgo), formatDate(endMonthAgo)];
}
// 格式化日期为 YYYY-MM-DD
function formatDate(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
watch(timeValue, async (newVal) => {
await store.dispatch("setTimeType", { type: interval, value: newVal });
await emit("timeChangeInfo");
});
defineExpose({
timeValue,
interval,
timeFlag,
setTimeOptions,
setTheDate,
getYearOnYear,
getMonthOnMonth,
timeChange,
});
</script>
<style scoped>
.demo-date-picker {
display: flex;
width: 100%;
padding: 0;
flex-wrap: wrap;
}
.demo-date-picker .block {
padding: 30px 0;
text-align: center;
border-right: solid 1px var(--el-border-color);
flex: 1;
}
.demo-date-picker .block:last-child {
border-right: none;
}
.demo-date-picker .demonstration {
display: block;
color: var(--el-text-color-secondary);
font-size: 14px;
margin-bottom: 20px;
}
:deep(.el-select__wrapper) {
background-color: #ffffff00;
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
}
:deep(.el-select__placeholder) {
color: var(--el-color-primary);
}
:deep(.el-range-editor.is-disabled) {
background-color: #ffffff00;
input {
background-color: #ffffff00;
color: var(--el-color-primary);
}
}
:deep(.el-range-editor.is-disabled input) {
background-color: #ffffff00;
color: var(--el-color-primary);
}
:deep(.el-.el-date-editor .el-range-input) {
--el-input-border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
:deep(.el-input__wrapper) {
background-color: #ffffff00;
color: var(--el-color-primary);
}
:deep(.el-range-editor.is-disabled .el-range-separator) {
color: var(--el-color-primary);
}
:deep(.el-date-editor .el-range-separator) {
color: var(--el-color-primary);
}
:deep(.el-date-editor .el-range__icon) {
color: var(--el-color-primary);
}
:deep(.el-date-editor .el-range-input) {
color: var(--el-color-primary);
}
:deep(.el-date-editor.el-input__wrapper) {
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
}
:deep(.el-select__caret) {
color: var(--el-color-primary);
}
:deep(.el-button--primary) {
--el-button-bg-color: #0a73ff00;
color: var(--el-color-primary);
}
:deep(.el-button.is-disabled) {
--el-button-bg-color: #0a73ff00;
background-color: rgb(160 206 255 / 17%);
}
</style>

View File

@@ -0,0 +1,278 @@
<!-- 地图组件 -->
<template>
<div style="position: relative">
<div class="bars_w" ref="chartMap" id="chartMap"></div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref, watch, onMounted, defineEmits } from "vue";
import * as echarts from "echarts";
import "echarts-gl";
// import "echarts/lib/component/dataZoom";
const props = defineProps(["options"]);
const myCharts = ref();
const fetchConfig = async (name: string) => {
const res = await import(`./mapJson/${name}.json`);
return res.default;
// GetEchar(res.default)
};
// fetchConfig()
const areaName = ref("");
const emit = defineEmits(["clickMap"]);
onMounted(() => {});
const GetEchar = async (name: string) => {
let chartDom = document.getElementById("chartMap");
myCharts.value?.dispose();
myCharts.value = echarts.init(chartDom);
echarts.registerMap(name, await fetchConfig(name)); //注册可用的地图
let option = {
title: {
left: "center",
top: "3%",
...props.options.title,
},
tooltip: {
trigger: "item",
axisPointer: {
type: "shadow",
label: {
color: "#fff",
fontSize: 16,
},
},
textStyle: {
color: "#fff",
fontStyle: "normal",
opacity: 0.35,
fontSize: 14,
},
backgroundColor: "rgba(0,0,0,0.55)",
...(props.options.tooltip || null),
},
color: [
...(props.options.color || ""),
"#07CCCA ",
"#00BFF5",
"#FFBF00",
"#77DA63",
"#D5FF6B",
"#Ff6600",
"#FF9100",
"#5B6E96",
"#66FFCC",
"#B3B3B3",
],
geo3D: {
show: false,
map: "北京",
viewControl: {
autoRotate: false, // 关闭自动旋转
center: [0, -10, 0], //位置
beta: 45, //x轴旋转
alpha: 50, //Y轴旋转
},
light: {
main: {
color: "#ffffff",
intensity: 1,
shadow: false,
},
},
itemStyle: {
color: "#4D96FA",
borderWidth: 1,
borderColor: "#fff",
opcity: 1,
},
shading: "realistic", //简化着色模式('color' 比 'realistic' 性能更好)
label: {
show: true,
position: "bottom",
distance: 0, // 紧贴地面
color: "#fff",
},
emphasis: {
label: {
formatter: function (params: any) {
areaName.value = params.name;
return params.name;
},
show: true,
textStyle: {
color: "orange",
fontSize: 18,
backgroundColor: "rgba(0,23,11,0)",
},
},
},
postEffect: {
enable: false, // 关闭后期特效(抗锯齿、 Bloom 等)
},
// 减少顶点计算
polygonOffset: {
factor: 1,
units: 1,
},
// 启用 GPU 加速渲染
renderOption: {
useGPU: true, // 启用 GPU 渲染
maxCache: 1000, // 缓存顶点数据
vertexLimit: 50000, // 限制顶点数量
},
// 优化材质计算
material: {
color: "#4D96FA",
opacity: 0.8,
roughness: 0.2, // 粗糙度(数值越低性能越好)
metalness: 0, // 金属度
},
groundPlane: true,
// ...(props.options.geo3Ds || null),
// data: originalDatas.datas,
// 将geo3d放在最底层
},
...props.options.options,
};
if (props.options.visualMap) {
option.visualMap = props.options.visualMap;
}
// if (props.options.geo3D) {
// option.geo = null;
// option.geo3D = {
// map: name,
// ...(props.options.geo3D || null),
// };
// }
myCharts.value.setOption(option);
// window.addEventListener("resize", resizeHandler);
const inquireTimer: any = ref(null);
// 点击事件
myCharts.value.off("click");
let selectedPoint = null;
myCharts.value.on("click", (params: any) => {
console.log("系列索引:", params.seriesIndex); // 应为 1监测点系列
console.log("数据索引:", params.dataIndex); // 应为 0、1、2...对应点1、点2...
// console.log("🚀 ~ myCharts.value.getZr ~ e:", e);
// // 清除上一次的定时器
// if (inquireTimer.value) {
// clearTimeout(inquireTimer.value);
// }
// inquireTimer.value = setTimeout(async () => {
// emit("clickMap", e.name);
// inquireTimer.value = null;
// }, 300);
});
// myCharts.value.getZr().on("click", (e: any) => {
// console.log("🚀 ~ myCharts.value.getZr ~ e:", e);
// // 清除上一次的定时器
// if (inquireTimer.value) {
// clearTimeout(inquireTimer.value);
// }
// inquireTimer.value = setTimeout(async () => {
// emit("clickMap", areaName.value);
// inquireTimer.value = null;
// }, 300);
// });
};
/**
* 外部触发:通过 name 修改指定图标的颜色并显示 tooltip
* @param {string} targetName - 目标点的 name如 "点1"
* @param {string} newColor - 新颜色(默认红色 "#ff0000"
*/
function setIcon(targetName, newColor = "#ff0000") {
console.log("🚀 ~ setIcon ~ targetName:", targetName);
// 获取当前图表配置(基于您的 echartMapList.value.options
const currentOptions = props.options.options;
// 找到 "监测点" 系列的索引(固定为 series 中的第1项因您的配置中 map3D 是第0项
const monitorSeriesIndex = currentOptions.series.findIndex(
(series) => series.name === "监测点"
);
if (monitorSeriesIndex === -1) {
console.warn("未找到 '监测点' 系列");
return;
}
// 获取监测点数据列表
const monitorData = currentOptions.series[monitorSeriesIndex].data;
// 找到 name 匹配的点的索引
const targetDataIndex = monitorData.findIndex(
(item) => item.name === targetName
);
if (targetDataIndex === -1) {
console.warn(`未找到名称为 ${targetName} 的点`);
return;
}
// 1. 重置所有点的颜色(恢复默认蓝色,避免多个点同时变色)
monitorData.forEach((item, index) => {
item.itemStyle.color = "#0000ff";
item.symbolSize = 17;
});
// 2. 修改目标点的颜色
monitorData[targetDataIndex].itemStyle.color = newColor;
monitorData[targetDataIndex].symbolSize = 25;
// 3. 更新图表配置,使颜色生效
myCharts.value.setOption(currentOptions);
myCharts.value.dispatchAction({
type: "showTip",
seriesIndex: monitorSeriesIndex,
dataIndex: targetDataIndex,
// 可选:强制指定基于鼠标的位置(如果百分比位置无效)
position: ["50%", "50%"],
});
console.log(`已通过外部触发修改 ${targetName} 的颜色为 ${newColor}`);
}
const resizeHandler = () => {
myCharts.value?.resize();
myCharts.value?.setOption(myCharts.value.getOption()); // 强制更新配置
};
onBeforeUnmount(() => {
// window.removeEventListener("resize", resizeHandler);
myCharts.value?.dispose();
});
defineExpose({ GetEchar, setIcon });
watch(
() => props.options,
(newVal, oldVal) => {
// GetEchar('中国')
}
);
</script>
<style lang="scss" scoped>
.bars_w {
width: 100%;
height: 100%;
}
.iconfont {
cursor: pointer;
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
font-size: 20px;
color: var(--el-color-primary) !important;
}
</style>

37
src/components/echarts.ts Normal file
View File

@@ -0,0 +1,37 @@
// 引入 echarts 核心模块。
import * as echarts from "echarts/core";
/** 引入柱状图and折线图图表图表后缀都为 Chart */
import { BarChart, LineChart } from "echarts/charts";
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
} from "echarts/components";
// 标签自动布局,全局过渡动画等
import { LabelLayout, UniversalTransition } from "echarts/features";
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的
import { CanvasRenderer } from "echarts/renderers";
// 注册组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
LineChart,
]);
// 导出
export default echarts;

View File

@@ -0,0 +1,324 @@
<template>
<div class="chart">
<div ref="chartRef" class="my-chart" />
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, defineExpose, watch, nextTick } from 'vue'
// import echarts from './echarts'
import * as echarts from 'echarts' // 全引入
import 'echarts-gl'
import 'echarts-liquidfill'
import 'echarts/lib/component/dataZoom'
import { color, gradeColor3 } from './color'
// import { useConfig } from '@/stores/config'
// const config = useConfig()
const emit = defineEmits(['triggerPoint', 'group'])
// color[0] = config.layout.elementUiPrimary[0]
color[0] = '#0a73ff'
const chartRef = ref<HTMLDivElement>()
const props = defineProps(['options', 'isInterVal', 'pieInterVal'])
let chart: echarts.ECharts | any = null
const resizeHandler = () => {
// 不在视野中的时候不进行resize
if (!chartRef.value) return
if (chartRef.value.offsetHeight == 0) return
chart.getZr().painter.getViewportRoot().style.display = 'none'
requestAnimationFrame(() => {
chart.resize()
chart.getZr().painter.getViewportRoot().style.display = ''
})
}
const initChart = () => {
if (!props.isInterVal && !props.pieInterVal) {
chart?.dispose()
}
// chart?.dispose()
chart = echarts.getInstanceByDom(chartRef.value as HTMLDivElement) || echarts.init(chartRef.value as HTMLDivElement)
const options = {
title: {
left: 'center',
textStyle: {
color: '#fff',
fontSize: 18,
},
...(props.options?.title || null)
},
tooltip: {
trigger: 'axis',
// axisPointer: {
// type: 'shadow',
// label: {
// color: '#fff',
// fontSize: 16
// }
// },
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
confine: true,
formatter: function (params: any) {
let tips = `<strong>${params[0]?.name}</strong></br>` // 标题加粗
params?.forEach((item: any) => {
const value =
item.value === 3.14159 || item.value === 0.14159
? '暂无数据'
: Math.round(item.value * 100) / 100 // 处理特殊值
tips += `<div style=" display: flex;justify-content: space-between;">
<span>${item.marker}
${item.seriesName}:
</span> ${value}
</div>` // 统一格式
})
return tips
},
...(props.options?.tooltip || null)
},
toolbox: {
right: 20,
top: 15,
feature: {
saveAsImage: {
title: '', // 按钮标题
icon: 'path://M892.342857 463.238095l-73.142857-68.266666-258.438095 258.438095V29.257143h-97.52381v624.152381L204.8 394.971429 131.657143 463.238095l380.342857 380.342857zM107.27619 897.219048h804.571429v97.523809H107.27619z' // 自定义图标路径
},
...(props.options?.toolbox?.featureProps || null)
},
emphasis: {
iconStyle: {
// borderColor: config.layout.elementUiPrimary[0], // 鼠标悬停时的边框颜色
// color: config.layout.elementUiPrimary[0] // 鼠标悬停时的图标颜色
borderColor: '#002B6A', // 鼠标悬停时的边框颜色
color: '#002B6A' // 鼠标悬停时的图标颜色
}
},
// },
...(props.options?.toolbox || null)
},
legend: {
right: 50,
top: 25,
itemGap: 10,
itemStyle: {},
// textStyle: {
fontSize: 12,
padding: [2, 0, 0, 0], //[上、右、下、左]
// },
itemWidth: 15,
itemHeight: 10,
...(props.options?.legend || null)
},
grid: {
top: '50px',
left: '30px',
right: '70px',
bottom: props.options?.options?.dataZoom === null ? '10px' : '40px',
containLabel: true,
...(props.options?.grid || null)
},
xAxis: props.options?.xAxis ? handlerXAxis() : null,
yAxis: props.options?.yAxis ? handlerYAxis() : null,
dataZoom: props.options?.dataZoom || [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100
}
],
color: props.options?.color || color,
series: props.options?.series,
...props.options?.options
}
// console.log(options.series,"获取x轴");
handlerBar(options)
// 处理柱状图
chart.setOption(options, true)
// chart.group = 'group'
emit('group', chart, chartRef.value)
// 添加点击事件
chart.on('click', function (params) {
if (params.seriesName == '暂态触发点') {
emit('triggerPoint', params.data)
}
})
setTimeout(() => {
chart.resize()
}, 0)
}
const handlerBar = (options: any) => {
if (Array.isArray(options.series)) {
options.series.forEach((item: any) => {
if (item.type === 'bar') {
item.barMinHeight = 10
item.barMaxWidth = 20
item.itemStyle = Object.assign(
{
color: (params: any) => {
if (params.value == 0 || params.value == 3.14159 || params.value == 0.14159) {
return '#ccc'
} else {
return props.options?.color
? props.options?.color[params.seriesIndex]
: color[params.seriesIndex]
}
}
},
item.itemStyle
)
}
})
}
}
const handlerYAxis = () => {
let temp = {
type: 'value',
nameGap: 15,
nameTextStyle: {
color: '#fff'
},
splitNumber: 5,
minInterval: 1,
axisLine: {
show: true,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
color: '#fff',
fontSize: 14,
formatter: function (value) {
return parseFloat(value.toFixed(1)) // 格式化显示为一位小数
}
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
color: ['#ccc'],
type: 'dashed',
opacity: 0.5
}
}
}
// props.options?.xAxis 是数组还是对象
if (Array.isArray(props.options?.yAxis)) {
return props.options?.yAxis.map((item: any) => {
return {
...temp,
...item
}
})
} else {
return {
...temp,
...props.options?.yAxis
}
}
}
const handlerXAxis = () => {
let temp = {
type: 'category',
axisTick: { show: false },
nameTextStyle: {
color: '#fff'
},
axisLine: {
// lineStyle: {
color: '#fff'
// }
},
axisLabel: {
// textStyle: {
fontFamily: 'dinproRegular',
color: '#fff',
fontSize: '12'
// }
}
// boundaryGap: false,
}
// props.options?.xAxis 是数组还是对象
if (Array.isArray(props.options?.xAxis)) {
return props.options?.xAxis.map((item: any) => {
return {
...temp,
...item
}
})
} else {
return {
...temp,
...props.options?.xAxis
}
}
}
let throttle: ReturnType<typeof setTimeout>
// 动态计算table高度
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (throttle) {
clearTimeout(throttle)
}
throttle = setTimeout(() => {
resizeHandler()
}, 100)
}
})
const setOptions = (options: any) => {
chart.setOption(options)
}
const getChart = () => {
return chart
}
onMounted(() => {
initChart()
resizeObserver.observe(chartRef.value!)
})
defineExpose({ initChart, setOptions, getChart })
onBeforeUnmount(() => {
resizeObserver.unobserve(chartRef.value!)
chart?.dispose()
})
watch(
() => props.options,
(newVal, oldVal) => {
initChart()
}
)
</script>
<style lang="scss" scoped>
.chart {
width: 100%;
height: 100%;
position: relative;
.el-button {
position: absolute;
right: 0px;
top: -60px;
}
.my-chart {
height: 100%;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,14 @@
export let color = [
'#07CCCA',
'#00BFF5',
'#FFBF00',
'#77DA63',
'#D5FF6B',
'#Ff6600',
'#FF9100',
'#5B6E96',
'#66FFCC',
'#B3B'
]
export const gradeColor3 = ['#339966', '#FFCC33', '#A52a2a']
export const gradeColor5 = ['#00CC00', '#99CC99', '#FF9900', '#996600', '#A52a2a']

View File

@@ -0,0 +1,84 @@
import * as echarts from 'echarts/core'
//引入需要的图表,需要什么就加什么
import {
BarChart,
LineChart,
PieChart,
ScatterChart,
EffectScatterChart,
RadarChart,
GaugeChart
} from 'echarts/charts'
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
ToolboxComponent,
// 数据集组件
DatasetComponent,
// 内置数据转换器组件 (filter, sort)
TransformComponent
} from 'echarts/components'
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features'
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers'
import type {
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
LineSeriesOption
} from 'echarts/charts'
import type {
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
TooltipComponentOption,
GridComponentOption,
LegendComponentOption,
ToolboxComponentOption,
DatasetComponentOption
} from 'echarts/components'
import type { ComposeOption } from 'echarts/core'
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = ComposeOption<
| BarSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| LegendComponentOption
| ToolboxComponentOption
>
// 注册必须的组件,上面引入的都需要在此注册
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
LegendComponent,
ToolboxComponent,
TransformComponent,
BarChart,
LineChart,
PieChart,
ScatterChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
EffectScatterChart,
RadarChart,
GaugeChart
])
// 导出
export default echarts

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,272 @@
<template>
<div style="height: 755px">
<div class="textInput">
<el-input v-model="filterText" placeholder="请输入内容" clearable maxlength="10" show-word-limit @input="change"
size="small">
<template #prefix>
<el-link :icon="Search"></el-link>
</template>
</el-input>
</div>
<!-- :check-strictly="true"
:check-on-click-node="true"
:expand-on-click-node="false" 点击三角标的时候展开点击文字不展开-->
<el-tree class="treeSet" ref="treeRef" :props="defaultProps" highlight-current
:filter-node-method="filterNode" node-key="id" :data="dataTree" v-bind="$attrs"
:default-expanded-keys="expandedKeys">
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-link v-if="data.level == -1" style="color: #0a73ff" :icon="HomeFilled"></el-link>
<el-link v-if="data.level == 0" style="color: #0a73ff" :icon="CollectionTag"></el-link>
<el-link v-if="data.level == 2" style="color: #0a73ff" :icon="Flag"></el-link>
<el-link v-if="data.level == 3" style="color: #0a73ff" :icon="Guide"></el-link>
<el-link v-if="data.level == 7" style="color: #0a73ff" :icon="OfficeBuilding"></el-link>
<el-link v-if="data.level == 6"
:style="{ color: data.comFlag == 0 ? 'red' : data.comFlag == 1 ? '#0a73ff' : 'gray' }" :icon="Location">
</el-link>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, nextTick } from "vue";
import { ElTree } from "element-plus";
import {
Search,
HomeFilled,
CollectionTag,
Flag,
OfficeBuilding,
Location,
Guide,
} from "@element-plus/icons-vue";
import { getTerminalTreeForFive } from "@/api/manage_wx";
import { useStore } from "vuex";
const store = useStore();
const filterText = ref("");
const defaultProps = {
label: "name",
value: "id",
};
const treeRef = ref();
const dataTree = ref([]);
const expandedKeys = ref([]);
const emit = defineEmits(["init"]);
const currentNodeKey = ref<string | number>("");
const specialCharsPattern =
/[`~!@$%^&*\-+=<>?:"{}|,.\/;'\\[\]·~@¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、~]/g;
const change = (val) => {
if (specialCharsPattern.test(val)) {
ElMessage.warning("禁止输入特殊字符!");
filterText.value = val.replace(
/[`~!@$%^&*\-+=<>?:"{}|,.\/;'\\[\]·~@¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、~]/g,
""
);
// console.log("🚀 ~ change ~ filterText.value:", filterText.value);
treeRef.value!.filter(filterText.value);
} else {
treeRef.value!.filter(filterText.value);
}
};
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true;
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node);
}
};
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true;
}
const level = node.level;
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false;
}
// 先取当前节点的父节点
let parentData = node.parent;
// 遍历当前节点的父节点
let index = 0;
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true;
}
// 否则的话再往上一层做匹配
parentData = parentData.parent;
index++;
}
// 没匹配到返回false
return false;
};
const classificationData = ref([]);
const formData = ref({
deptIndex: store.state.deptId,
});
const loadData = () => {
let form = JSON.parse(JSON.stringify(formData.value));
getTerminalTreeForFive(form).then((res) => {
console.log(res);
res.data = [
{
name: "电网拓扑",
level: -1,
id: 0,
children: res.data,
},
];
// expandedKeys.value = [res.data[0].id];
// 查找第一层级的最后一个子节点
const firstLevelChildren = res.data[0].children;
if (firstLevelChildren && firstLevelChildren.length > 0) {
let flag = true;
// 设置节点别名
res.data.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
item4.children.forEach((item5: any) => {
if (item5.level == 7) {
item5.children.forEach((item6: any) => {
item6.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}>${item6.name}`;
});
} else {
if (flag) {
expandedKeys.value = [item5.id];
treeRef.value.setCurrentKey(item5.id);
emit("init", item5);
flag = false
}
item5.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}`;
}
});
});
});
});
});
// const lastChild = firstLevelChildren[firstLevelChildren.length - 1];
// // 设置当前节点key
// currentNodeKey.value = lastChild.children[0].children[0].children[0].children[0].id;
// // 发送初始化事件
// emit("init", lastChild);
// // 设置默认选中节点
// nextTick(() => {
// if (treeRef.value) {
// treeRef.value.setCurrentKey(currentNodeKey.value);
// }
// });
}
dataTree.value = res.data;
});
};
loadData();
</script>
<style scoped lang="scss">
.cn-tree {
flex-shrink: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px;
height: 100%;
width: 100%;
:deep(.el-tree) {
border: 1px solid var(--el-border-color);
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
.custom-tree-node {
display: flex;
align-items: center;
}
.textInput {
display: flex;
align-items: center;
// height: 30px;
padding: 5px;
background-color: rgba(44, 46, 60);
}
.treeSet {
flex: 1;
overflow: scroll;
height: 97%;
background-color: rgba(44, 46, 60);
color: #fff;
}
// 悬浮时的样式
:deep(.el-tree-node__content:hover) {
background-color: #0a73ff70 !important;
color: #fff !important;
.custom-tree-node {
color: #fff !important;
}
.el-link {
color: #fff !important;
}
// 添加更具体的选择器来覆盖默认样式
span {
color: #fff !important;
}
}
:deep(.el-tree-node:focus > .el-tree-node__content) {
background-color: #0a73ff70 !important;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: #0a73ff70 !important;
color: #fff !important;
.custom-tree-node {
color: #fff !important;
}
.el-link {
color: #fff !important;
}
}
</style>

View File

@@ -0,0 +1,372 @@
<template>
<div>
<div class="textInput">
<el-input v-model="filterText" placeholder="请输入内容" clearable maxlength="10" show-word-limit @input="change"
size="small">
<template #prefix>
<el-link :icon="Search"></el-link>
</template>
</el-input>
</div>
<!-- :check-strictly="true"
:check-on-click-node="true"
:expand-on-click-node="false" 点击三角标的时候展开点击文字不展开-->
<el-tree class="treeSet" ref="treeRef" show-checkbox :props="defaultProps" highlight-current
:filter-node-method="filterNode" node-key="id" :data="dataTree" v-bind="$attrs"
:default-expanded-keys="expandedKeys">
<template #default="{ node, data }">
<el-radio-group v-model="totalId" v-if="data.level == 6" @click.stop="changeRadio(data)">
<el-radio :value="data.id"></el-radio>
</el-radio-group>
<div class="custom-tree-node">
<div>
<el-link v-if="data.level == -1" style="color: #0a73ff" :icon="HomeFilled"></el-link>
<el-link v-if="data.level == 0" style="color: #0a73ff" :icon="CollectionTag"></el-link>
<el-link v-if="data.level == 2" style="color: #0a73ff" :icon="Flag"></el-link>
<el-link v-if="data.level == 3" style="color: #0a73ff" :icon="Guide"></el-link>
<el-link v-if="data.level == 7" style="color: #0a73ff" :icon="OfficeBuilding"></el-link>
<el-link v-if="data.level == 6"
:style="{ color: data.comFlag == 0 ? 'red' : data.comFlag == 1 ? '#0a73ff' : 'gray' }"
:icon="Location">
</el-link>
<span style="margin-left: 4px">{{ node.label }}</span>
</div>
</div>
</template>
</el-tree>
</div>
<div class="legend-group">
<!-- 第一个图例正方形 + 谐波 -->
<div class="legend-item ml30">
<div class="icon-square"></div>
<span>用采用户</span>
</div>
<!-- 第二个图例圆形 + 背景 -->
<div class="legend-item">
<div class="icon-circle"></div>
<span>背景谐波用户</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, computed, nextTick } from "vue";
import { ElTree } from "element-plus";
import {
Search,
HomeFilled,
CollectionTag,
Flag,
OfficeBuilding,
Location,
Guide,
} from "@element-plus/icons-vue";
import { getTerminalTreeForFive } from "@/api/manage_wx";
import { useStore } from "vuex";
const totalId = ref('');
const store = useStore();
const filterText = ref("");
const defaultProps = {
label: "name",
value: "id",
disabled: 'disabled',
};
const treeRef = ref();
const dataTree = ref([]);
const expandedKeys = ref([]);
const emit = defineEmits(["init"]);
const currentNodeKey = ref<string | number>("");
const specialCharsPattern =
/[`~!@$%^&*\-+=<>?:"{}|,.\/;'\\[\]·~@¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、~]/g;
const change = (val) => {
if (specialCharsPattern.test(val)) {
ElMessage.warning("禁止输入特殊字符!");
filterText.value = val.replace(
/[`~!@$%^&*\-+=<>?:"{}|,.\/;'\\[\]·~@¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、~]/g,
""
);
treeRef.value!.filter(filterText.value);
} else {
treeRef.value!.filter(filterText.value);
}
};
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true;
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node);
}
};
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true;
}
const level = node.level;
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false;
}
// 先取当前节点的父节点
let parentData = node.parent;
// 遍历当前节点的父节点
let index = 0;
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true;
}
// 否则的话再往上一层做匹配
parentData = parentData.parent;
index++;
}
// 没匹配到返回false
return false;
};
const classificationData = ref([]);
const formData = ref({
deptIndex: store.state.deptId,
});
const nodeTreeCopy: any = ref({})
// 点击单选
const changeRadio = (data) => {
// 恢复原来禁用状态
if (nodeTreeCopy.value.disabled) {
nodeTreeCopy.value.disabled = false
}
setTimeout(() => {
data.disabled = true;
nodeTreeCopy.value = data
}, 0)
// 取消选中
const currentChecked = treeRef.value.getCheckedKeys(true);
const newChecked = currentChecked.filter(id => id !== data.id);
console.log("🚀 ~ changeRadio ~ newChecked:", newChecked)
treeRef.value.setCheckedKeys(newChecked);
};
const loadData = () => {
let form = JSON.parse(JSON.stringify(formData.value));
getTerminalTreeForFive(form).then((res) => {
console.log(res);
res.data = [
{
name: "电网拓扑",
level: -1,
id: 0,
children: res.data,
},
];
// expandedKeys.value = [res.data[0].id];
// 查找第一层级的最后一个子节点
const firstLevelChildren = res.data[0].children;
if (firstLevelChildren && firstLevelChildren.length > 0) {
let flag = true;
// 设置节点别名
res.data.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
item4.children.forEach((item5: any) => {
if (item5.level == 7) {
item5.children.forEach((item6: any) => {
item6.disabled = false
item6.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}>${item6.name}`;
});
} else {
if (flag) {
expandedKeys.value = [item5.id];
treeRef.value.setCurrentKey(item5.id);
emit("init", item5);
flag = false
}
item5.disabled = false
item5.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}`;
}
});
});
});
});
});
}
dataTree.value = res.data;
});
};
loadData();
</script>
<style scoped lang="scss">
.cn-tree {
flex-shrink: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 10px;
height: 100%;
width: 100%;
:deep(.el-tree) {
border: 1px solid var(--el-border-color);
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
.custom-tree-node {
display: flex;
align-items: center;
}
.textInput {
display: flex;
align-items: center;
// height: 30px;
padding: 5px;
background-color: rgba(44, 46, 60);
}
.treeSet {
height: 690px;
overflow-y: auto;
background-color: rgba(44, 46, 60);
color: #fff;
}
/* 图例容器:横向排列、居中对齐 */
.legend-group {
background-color: rgba(44, 46, 60);
padding: 5px;
color: #fff;
display: flex;
/* 两个图例之间的间距 */
align-items: center;
/* 单个图例:固定宽度 150px、横向布局 */
.legend-item {
width: 120px;
display: flex;
align-items: center;
gap: 8px;
// margin-left: 40px;
/* 图标与文字间距 */
font-size: 14px;
}
/* 正方形图标样式 */
.icon-square {
width: 12px;
height: 12px;
background-color: #fff;
/* 蓝色示例色,可自行修改 */
}
/* 圆形图标样式 */
.icon-circle {
width: 12px;
height: 12px;
border-radius: 50%;
/* 圆形关键属性 */
background-color: #fff;
/* 绿色示例色,可自行修改 */
}
}
// 悬浮时的样式
:deep(.el-tree-node__content:hover) {
background-color: #0a73ff70 !important;
color: #fff !important;
.custom-tree-node {
color: #fff !important;
}
.el-link {
color: #fff !important;
}
// 添加更具体的选择器来覆盖默认样式
span {
color: #fff !important;
}
}
:deep(.el-tree-node:focus > .el-tree-node__content) {
background-color: #0a73ff70 !important;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: #0a73ff70 !important;
color: #fff !important;
.custom-tree-node {
color: #fff !important;
}
.el-link {
color: #fff !important;
}
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
:deep(.el-radio__input.is-checked .el-radio__inner) {
background: #DAA520;
border-color: #DAA520;
}
:deep(.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner) {
background-color: #63636380;
border-color: #63636380;
}
:deep(.el-checkbox__input.is-disabled .el-checkbox__inner) {
background-color: #63636380;
border-color: #63636380;
}
</style>

5
src/constant/index.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
// 各类标题与图标
export type ModuleInfo = {
name: string
icon: string
}[]

47
src/constant/index.ts Normal file
View File

@@ -0,0 +1,47 @@
import { ModuleInfo } from "./index.d";
// 运维单位id
export const deptId = window.sessionStorage.getItem("deptId") || "10002";
export const timeType = 3; //类型(1年 2季度 3月份 4周 5日)")
// 星期
export const WEEK = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
// 主题名称与副标题名称
export const title = "电压暂降监测平台";
export const subtitle = ["数据分析1", "数据分析2", "vue-big-screen"];
// 组件颜色
export const color = [
["#0a73ff60", "#0a73ff"],
["#0a73ff", "#003a87"],
["#0a73ff", "#0a73ff"],
["#0a73ff60", "#0a73ff60"],
];
export const moduleInfo: ModuleInfo = [
// 中间的几个模块
{
name: "任务通过率",
icon: "icon-chart-bar",
},
{
name: "地图数据",
icon: "icon-tongji4",
},
{
name: "产品销售渠道分析",
icon: "icon-align-left",
},
{
name: "任务完成排行榜",
icon: "icon-zhibiao2",
},
// 底部两个模块
{
name: "数据统计图",
icon: "icon-vector",
},
{
name: "工单修复以及满意度统计图",
icon: "icon-fenxi7",
},
];

47
src/constant/index_wx.ts Normal file
View File

@@ -0,0 +1,47 @@
import { ModuleInfo } from "./index.d";
// 运维单位id
export const deptId = window.sessionStorage.getItem("deptId") || "10002";
export const timeType = 3; //类型(1年 2季度 3月份 4周 5日)")
// 星期
export const WEEK = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
// 主题名称与副标题名称
export const title = "无锡电能质量演示平台";
export const subtitle = ["数据分析1", "数据分析2", "vue-big-screen"];
// 组件颜色
export const color = [
["#0a73ff60", "#0a73ff"],
["#0a73ff", "#003a87"],
["#0a73ff", "#0a73ff"],
["#0a73ff60", "#0a73ff60"],
];
export const moduleInfo: ModuleInfo = [
// 中间的几个模块
{
name: "任务通过率",
icon: "icon-chart-bar",
},
{
name: "地图数据",
icon: "icon-tongji4",
},
{
name: "产品销售渠道分析",
icon: "icon-align-left",
},
{
name: "任务完成排行榜",
icon: "icon-zhibiao2",
},
// 底部两个模块
{
name: "数据统计图",
icon: "icon-vector",
},
{
name: "工单修复以及满意度统计图",
icon: "icon-fenxi7",
},
];

45
src/main.ts Normal file
View File

@@ -0,0 +1,45 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import store from "./store/index";
//import dataV from '@jiaminghi/data-view';//引入dataV可能如果启动报错看这个https://blog.csdn.net/qq_54753561/article/details/125583526
import echarts from "./components/echarts"; //echarts 根据官网封装的
import DataVVue3 from "@kjgl77/datav-vue3"; //https://datav-vue3.netlify.app/Guide/index.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95
import * as XEUtils from "xe-utils";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import zhCn from "element-plus/es/locale/lang/zh-cn";
// 引入全局css
import "./assets/scss/style.scss";
// 引入图表(所有图标见 icon 目录下的 demo_index.html
import "./assets/icon/iconfont.css";
import BaiduMap from "vue-baidu-map-3x";
import BaiduMapOffline from "vue-baidu-map-offline";
(window as any).XEUtils = XEUtils;
const app = createApp(App);
app.use(BaiduMapOffline, {
offline: true,
offlineConfig: {
imgext: ".png",
customstyle: "",
tiles_dir: "",
tiles_hybrid: "",
tiles_self: "",
tiles_v_dir: "",
tiles_satellite_dir: "",
tiles_road_dir: "",
tiles_v_road_dir: "",
home: "./plugin/offline/",
},
});
app.use(BaiduMap, {
// ak: 'Yp57V71dkOPiXjiN8VdcFRsVELzlVNKK',
ak: "RpQi6WNFZ9tseKzhdwOQsXwFsoVntnsN",
v: "3.0",
});
app.config.globalProperties.$echarts = echarts;
app.use(DataVVue3);
app.use(ElementPlus, {
locale: zhCn,
});
app.use(store).use(router).mount("#app");

19
src/mock/login.json Normal file
View File

@@ -0,0 +1,19 @@
[{
"id": "1",
"imgUrl": "/images/banner1.jpg"
},
{
"id": "2",
"imgUrl": "/images/banner2.jpg"
},
{
"id": "3",
"imgUrl": "/images/banner3.jpg"
},
{
"id": "4",
"imgUrl": "/images/banner4.jpg"
}
]

17
src/mock/login.ts Normal file
View File

@@ -0,0 +1,17 @@
import { MockMethod } from "vite-plugin-mock";
import login from "./login.json"
//login是变量可以随便命名
export default [
{
url: "/mock-login", // 注意这里只能是string格式
method: "post",
response: () => {
return {
code: 0,
message: 'ok',
menusList:login,
}
},
},
] as MockMethod[]

27
src/router/index.ts Normal file
View File

@@ -0,0 +1,27 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Index",
component: () => import("@/views/SagTraceResult_WX/index.vue"), // 懒加载组件
},
{
path: "/VoltageSag", //电压暂降监测平台_北京
name: "VoltageSag",
component: () => import("@/views/VoltageSag_BJ/index.vue"), // 懒加载组件
},
{
path: "/SagTraceResult", //暂降溯源演示平台_无锡
name: "SagTraceResult",
component: () => import("@/views/SagTraceResult_WX/index.vue"), // 懒加载组件
},
];
const base_url = import.meta.env.BASE_URL; //获取vite.config.js配置的base,默认是根目录/
const router = createRouter({
history: createWebHashHistory(base_url),
routes,
});
export default router;

15
src/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const componentOptions: ComponentOptions
export default componentOptions
}
// /* eslint-disable */
// declare module '*.vue' {
// import type { DefineComponent } from 'vue'
// const component: DefineComponent<{}, {}, any>
// export default component
// }
declare module '@jiaminghi/data-view'

119
src/store/index.ts Normal file
View File

@@ -0,0 +1,119 @@
import { createStore, Store, useStore } from "vuex";
import { login } from "@/api/login/login";
import { initLedger, queryConfig } from "@/api/statistics/index";
// const store = useStore();
// 定义状态接口
interface State {
count: number;
token: string;
deptId: string;
timeType: number;
timeValue: string[];
seriousNotice: number;
normalNotic: number;
voiceType: number;
screenNotic: number;
eventTypeList: string[];
eventValue: number;
eventDuration: number;
realData: []; //实时数据
}
// 初始状态
const state: State = {
count: 0,
token: "",
deptId: "10001",
timeType: 3, //类型(1年 2季度 3月份 4周 5日)")
timeValue: [], //类型(1年 2季度 3月份 4周 5日)")
seriousNotice: 0, //严重通知(0关闭 1开启)
normalNotic: 1, //普通通知(0关闭 1开启)
voiceType: 1, //语音类型(1人声音 2音频)
screenNotic: 1, //屏幕通知(0关闭 1开启)
eventTypeList: [], //触发类型(1告警 2事件)
eventValue: 0.7,
eventDuration: 5,
realData: [], //实时数据
};
// 定义Mutation类型
enum Mutations {
INCREMENT = "INCREMENT",
SET_TOKEN = "SET_TOKEN",
SET_TIME = "SET-TIME",
SET_CONFIG = "SET-CONFIG-TIME",
}
export default createStore({
state,
mutations: {
[Mutations.INCREMENT](state: State) {
state.count++;
},
[Mutations.SET_TOKEN](state: State, data: any) {
window.sessionStorage.setItem("token", data.token);
window.sessionStorage.setItem("deptId", data.deptId);
state.token = data.token;
state.deptId = data.deptId;
},
[Mutations.SET_TIME](state: State, data: any) {
state.timeType = data.type;
state.timeValue = data.value;
},
[Mutations.SET_CONFIG](state: State, data: any) {
state.seriousNotice = data.seriousNotice;
state.normalNotic = data.normalNotic;
state.voiceType = data.voiceType;
state.screenNotic = data.screenNotic;
state.eventTypeList = data.eventTypeList;
state.eventValue = data.eventValue;
state.eventDuration = data.eventDuration;
},
SET_REAL_DATA(state, data) {
state.realData = data;
},
},
actions: {
async loginAction({ commit }, data: any) {
try {
// const response = await login({
// username: "screen",
// password: "@#001njcnpqs",
// });
const response = await login(data);
commit(Mutations.SET_TOKEN, response.data);
// await initLedger({ deptId: response.data.deptId, type: 0 });
return;
return response.data;
} catch (error) {
console.error("登录失败:", error);
throw error;
}
},
increment({ commit }) {
commit(Mutations.INCREMENT);
},
setTimeType({ commit }, data: any) {
commit(Mutations.SET_TIME, data);
},
setConfig({ commit }, data: any) {
queryConfig().then((res) => {
commit(Mutations.SET_CONFIG, res.data);
});
},
updateRealData({ commit }, data) {
commit("SET_REAL_DATA", data);
},
},
getters: {
double(state: State) {
return 2 * state.count;
},
isAuthenticated(state: State) {
return !!state.token;
},
},
});

54
src/utils/color.ts Normal file
View File

@@ -0,0 +1,54 @@
/**
* 根据数值排名自动分配颜色
* @param {Array} data - 包含 name 和 value 的对象数组
* @returns {Array} - 添加了 color 属性的新数组
*/
export const assignColorsByRank = (data: any) => {
// 复制原数组避免修改
const sortedData = [...data].sort((a, b) => b.value - a.value);
const totalCount = sortedData.length;
// 计算各区间边界(向上取整确保覆盖所有数据)
const top20Percent = Math.ceil(totalCount * 0.2);
const middle60Percent = Math.ceil(totalCount * 0.8);
return sortedData.map((item, index) => {
// 处理 value 为 0 的特殊情况
if (item.value === 0) {
return {
...item,
itemStyle: {
color: "#4D96FA",
opacity: 0.5,
},
}; // 蓝色
}
// 根据排名分配颜色
if (index < top20Percent) {
return {
...item,
itemStyle: {
color: "#FF0000",
opacity: 0.5,
},
}; // 红色 - 前20%
} else if (index < middle60Percent) {
return {
...item,
itemStyle: {
color: "#FF9800",
opacity: 0.5,
},
}; // 橙色 - 中间60%
} else {
return {
...item,
itemStyle: {
color: "#4CAF50",
opacity: 0.5,
},
}; // 绿色 - 后20%
}
});
};

86
src/utils/common.ts Normal file
View File

@@ -0,0 +1,86 @@
import type { App } from 'vue'
/**
* 获取资源完整地址
* @param relativeUrl 资源相对地址
* @param domain 指定域名
*/
export const fullUrl = (relativeUrl: string, domain = '') => {
return domain + '/api/system-boot/image/toStream?bgImage=' + relativeUrl
}
/**
* 是否是外部链接
* @param path
*/
export function isExternal(path: string): boolean {
return /^(https?|ftp|mailto|tel):/.test(path)
}
/**
* 字符串补位
*/
const padStart = (str: string, maxLength: number, fillString = ' ') => {
if (str.length >= maxLength) return str
const fillLength = maxLength - str.length
let times = Math.ceil(fillLength / fillString.length)
while ((times >>= 1)) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str
}
/**
* 格式化时间戳
* @param dateTime 时间戳
* @param fmt 格式化方式默认yyyy-mm-dd hh:MM:ss
*/
export const timeFormat = (dateTime: string | number | null = null, fmt = 'yyyy-mm-dd hh:MM:ss') => {
if (dateTime == 'none') return '-'
if (!dateTime) dateTime = Number(new Date())
if (dateTime.toString().length === 10) {
dateTime = +dateTime * 1000
}
const date = new Date(dateTime)
let ret
const opt: any = {
'y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'h+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
's+': date.getSeconds().toString() // 秒
}
for (const k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt)
if (ret) {
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : padStart(opt[k], ret[1].length, '0'))
}
}
return fmt
}
/**
* el-form 密码正则校验 密码需要包含特殊字符字母数字长度为8-16
*/
export const validatePwd = (rule: any, value: string, callback: any) => {
if (value === '') {
callback(new Error('请输入密码'))
} else {
const reg = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@#$%^&+=!])(?=.*[a-zA-Z0-9])[A-Za-z\d@#$%^&+=!]{8,16}$/
if (!reg.test(value)) {
callback(new Error('密码需要包含特殊字符字母数字长度为8-16'))
} else {
callback()
}
}
}

159
src/utils/echartMethod.ts Normal file
View File

@@ -0,0 +1,159 @@
const dataProcessing = (arr: any[]) => {
return arr
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
}
const calculateValue = (o: number, value: number, num: number, isMin: boolean) => {
if (value === 0) {
return 0
} else if (value > 0 && Math.abs(value) < 1 && isMin == true) {
return 0
} else if (value > -1 && value < 0 && isMin == false) {
return 0
}
let base
if (Math.abs(o) >= 100) {
base = 100
} else if (Math.abs(o) >= 10) {
base = 10
} else if (Math.abs(o) >= 1) {
base = 1
} else {
base = 0.1
}
let calculatedValue
if (isMin) {
if (value < 0) {
calculatedValue = value + num * value
} else {
calculatedValue = value - num * value
}
} else {
if (value < 0) {
calculatedValue = value - num * value
} else {
calculatedValue = value + num * value
}
}
if (base === 0.1) {
return parseFloat(calculatedValue.toFixed(1))
} else if (isMin) {
return Math.floor(calculatedValue / base) * base
} else {
return Math.ceil(calculatedValue / base) * base
}
}
// 处理y轴最大最小值
export const yMethod = (arr: any) => {
let num = 0.1
let numList = dataProcessing(arr)
let maxValue = 0
let minValue = 0
let max = 0
let min = 0
maxValue = Math.max(...numList)
minValue = Math.min(...numList)
const o = maxValue - minValue
if (Math.abs(o) >= 300) {
num = 0.02
}
min = calculateValue(o, minValue, num, true)
max = calculateValue(o, maxValue, num, false)
// if (-100 >= minValue) {
// min = Math.floor((minValue + num * minValue) / 100) * 100
// } else if (-10 >= minValue && minValue > -100) {
// min = Math.floor((minValue + num * minValue) / 10) * 10
// } else if (-1 >= minValue && minValue > -10) {
// min = Math.floor(minValue + num * minValue)
// } else if (0 > minValue && minValue > -1) {
// min = parseFloat((minValue + num * minValue).toFixed(1))
// } else if (minValue == 0) {
// min = 0
// } else if (0 < minValue && minValue < 1) {
// min = parseFloat((minValue - num * minValue).toFixed(1))
// } else if (1 <= minValue && minValue < 10) {
// min = Math.floor(minValue - num * minValue)
// } else if (10 <= minValue && minValue < 100) {
// min = Math.floor((minValue - num * minValue) / 10) * 10
// } else if (100 <= minValue) {
// min = Math.floor((minValue - num * minValue) / 100) * 100
// }
// if (-100 >= maxValue) {
// max = Math.ceil((maxValue - num * maxValue) / 100) * 100
// } else if (-10 >= maxValue && maxValue > -100) {
// max = Math.ceil((maxValue - num * maxValue) / 10) * 10
// } else if (-1 >= maxValue && maxValue > -10) {
// max = Math.ceil(maxValue - num * maxValue)
// } else if (0 > maxValue && maxValue > -1) {
// max = parseFloat((maxValue - num * maxValue).toFixed(1))
// } else if (maxValue == 0) {
// max = 0
// } else if (0 < maxValue && maxValue < 1) {
// max = parseFloat((maxValue + num * maxValue).toFixed(1))
// } else if (1 <= maxValue && maxValue < 10) {
// max = Math.ceil(maxValue + num * maxValue)
// } else if (10 <= maxValue && maxValue < 100) {
// max = Math.ceil((maxValue + num * maxValue) / 10) * 10
// } else if (100 <= maxValue) {
// max = Math.ceil((maxValue + num * maxValue) / 100) * 100
// }
// if (maxValue > 1000 || minValue < -1000) {
// max = Math.ceil(maxValue / 100) * 100
// if (minValue == 0) {
// min = 0
// } else {
// min = Math.floor(minValue / 100) * 100
// }
// } else if (maxValue < 60 && minValue > 40) {
// max = 60
// min = 40
// } else if (maxValue == minValue && maxValue < 10 && minValue > 0) {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// } else if (maxValue == minValue && maxValue != 0 && minValue != 0) {
// max = Math.ceil(maxValue / 10 + 1) * 10
// min = Math.floor(minValue / 10 - 1) * 10
// } else {
// max = Math.ceil(maxValue / 10) * 10
// min = Math.floor(minValue / 10) * 10
// }
// if (maxValue > 0 && maxValue < 1) {
// max = 1
// } else if (max == 0 && minValue > -1 && minValue < 0) {
// min = -1
// }
return [min, max]
}
/**
* title['A相','B相',]
* data[[1,2],[3,4]]
*/
// 导出csv文件
const convertToCSV = (title: any, data: any) => {
console.log('🚀 ~ convertToCSV ~ data:', data)
let csv = ''
// 添加列头
csv += ',' + title.join(',') + '\n'
// 遍历数据并添加到CSV字符串中
data?.map(item => {
csv += item.join(',') + '\n'
})
return csv
}
export const exportCSV = (title: object, data: any, filename: string) => {
const csv = convertToCSV(title, data)
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
// 释放URL对象
URL.revokeObjectURL(link.href)
}

View File

@@ -0,0 +1,21 @@
// 封装请求错误代码提示
export const errorCodeType = function (code: string): string {
let msg: string = ""
switch (code) {
case "401":
msg = '认证失败,无法访问系统资源'
break;
case "403":
msg = '当前操作没有权限'
break;
case "404":
msg = '访问资源不存在'
break;
case "default":
msg = '系统未知错误,请反馈给管理员'
break;
default:
return '未知错误,请联系管理员'
}
return msg
}

115
src/utils/index.ts Normal file
View File

@@ -0,0 +1,115 @@
import { ElSkeletonItem } from "element-plus";
/**
* @param {date} time 需要转换的时间
* @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
* @returns {String}
*/
export const formatTime = (time: string | Date, fmt: string): string => {
if (!time) return "";
const date = new Date(time);
const o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
for (const k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
// @ts-ignore: Unreachable code error
RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;
};
export const speak = (content: any, onComplete: Function = () => {}) => {
// 检查浏览器是否支持语音合成
if (!window.speechSynthesis) {
console.warn("当前浏览器不支持语音合成功能");
return;
}
const utterance = new SpeechSynthesisUtterance(content);
utterance.lang = "zh-CN";
utterance.rate = 1;
utterance.rate = 1.5;
// 设置结束回调(通过参数传递)
utterance.onend = () => {
onComplete();
};
// 播放语音
window.speechSynthesis.speak(utterance);
};
// 关闭语音播报
export const stopSpeak = () => {
(document.getElementById("audioId") as HTMLAudioElement)?.pause();
window.speechSynthesis.cancel();
};
/**
* 获取时间范围
* @param {number} option - 1表示本周3表示本月
* @returns {{start: Date, end: Date}} - 包含开始时间和结束时间的对象
*/
export const getDateRange = (option: number) => {
console.log("🚀 ~ getDateRange ~ option:", option);
const now = new Date();
if (option === 1) {
//年
const firstDay = new Date(now.getFullYear(), 0, 1);
const lastDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
return formatDate(firstDay) + " 至 " + formatDate(lastDay);
}
ElSkeletonItem;
if (option === 3) {
// 获取本月日期范围
// 本月第一天
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
// 本月最后一天
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
return formatDate(firstDay) + " 至 " + formatDate(lastDay);
} else if (option === 4) {
// 获取本周日期范围
// 计算周一的日期
const monday = new Date(now);
monday.setDate(now.getDate() - now.getDay() + 1);
// 计算周日的日期
const sunday = new Date(now);
sunday.setDate(now.getDate() - now.getDay() + 7);
return formatDate(monday) + " 至 " + formatDate(sunday);
} else if (option === 6) {
// 获取本月日期范围
// 本月第一天
return formatDate(now);
}
};
/**
* 格式化日期为YYYY-MM-DD格式
* @param {Date} date - 要格式化的日期对象
* @returns {string} - 格式化后的日期字符串
*/
function formatDate(date: Date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}

11
src/utils/page-params.ts Normal file
View File

@@ -0,0 +1,11 @@
// 全局统一分页参数类型声明
declare interface PageParams {
pageNum: number,
pageSize: number,
type?: Model, // 可选参数
readonly sort?: string // 只读可选参数
}
interface Model {
type?: string
}
export default PageParams;

172
src/utils/request.ts Normal file
View File

@@ -0,0 +1,172 @@
import axios from "axios";
import { errorCodeType } from "./error-code-type";
import { ElMessage } from "element-plus";
import store from "@/store/index";
const service = axios.create({
baseURL: "/api",
timeout: 60 * 1000,
headers: { "Content-Type": "application/json;charset=utf-8" },
});
// 认证状态管理
let isAuthenticated = false;
let isAuthenticating = false;
const requestQueue: Array<() => void> = [];
// 执行队列中的请求
const executeQueue = () => {
while (requestQueue.length > 0) {
const nextRequest = requestQueue.shift();
if (nextRequest) nextRequest();
}
};
// 请求拦截器
service.interceptors.request.use(
(config) => {
const isAuthRequest = config.url === "/cn_authenticate";
if (!isAuthRequest) {
if (isAuthenticating) {
return new Promise((resolve) => {
requestQueue.push(() => {
resolve(service(config));
});
});
}
const token = window.sessionStorage.getItem("token");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
if (!isAuthenticated) {
return new Promise((resolve) => {
requestQueue.push(() => {
resolve(service(config));
});
});
}
} else {
if (isAuthenticating) {
return Promise.reject({ message: "认证中", isHandled: true });
}
isAuthenticating = true;
config.headers["Authorization"] = "";
}
// get请求参数处理保持原逻辑
// if (config.method === "get" && config.params) {
// // 这里用 toLowerCase() 确保兼容
// let url = config.url + "?";
// for (const propName of Object.keys(config.params)) {
// const value = config.params[propName];
// const part = encodeURIComponent(propName) + "=";
// if (value !== null && typeof value !== "undefined") {
// if (typeof value === "object") {
// for (const key of Object.keys(value)) {
// const params = propName + "[" + key + "]";
// const subPart = encodeURIComponent(params) + "=";
// url += subPart + encodeURIComponent(value[key]) + "&";
// }
// } else {
// url += part + encodeURIComponent(value) + "&";
// }
// }
// }
// url = url.slice(0, -1);
// config.params = {};
// config.url = url;
// }
return config;
},
(error) => {
console.log(error);
return Promise.reject(error);
}
);
// 响应拦截器(保持原逻辑,略作兼容处理)
let refreshQueue: any = [];
let isRefreshing = false;
service.interceptors.response.use(
(res) => {
if (res.config.url === "/cn_authenticate") {
isAuthenticating = false;
if (res.data.code === "A0000") {
isAuthenticated = true;
setTimeout(executeQueue, 1000);
} else {
isAuthenticated = false;
setTimeout(executeQueue, 1000);
}
}
const code = res.data["code"] || "A0000";
const msg =
errorCodeType(code) || res.data["msg"] || errorCodeType("default");
if (code === "A0000") {
return Promise.resolve(res.data);
} else if (code === "A0025") {
isAuthenticated = false;
const originalRequest = res.config;
if (!isRefreshing) {
isRefreshing = true;
return store
.dispatch(
"loginAction",
JSON.parse(window.localStorage.getItem("adminInfo") || "{}")
)
.then(() => {
isAuthenticated = true;
refreshQueue.forEach((callback: any) => callback());
refreshQueue = [];
return service(originalRequest);
})
.catch((err) => {
console.error("刷新失败:", err);
refreshQueue.forEach((callback: any) => callback());
refreshQueue = [];
return Promise.reject(err);
})
.finally(() => {
isRefreshing = false;
});
} else {
return new Promise((resolve) => {
refreshQueue.push(() => resolve(service(originalRequest)));
});
}
} else {
return Promise.reject(res.data);
}
},
(error) => {
if (error.isHandled) return Promise.reject(error);
if (error.config?.url === "/cn_authenticate") {
isAuthenticating = false;
isAuthenticated = false;
setTimeout(executeQueue, 1000);
}
console.log("err" + error);
let { message } = error;
if (message === "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = `系统接口${message.substr(message.length - 3)}异常`;
}
return Promise.reject(error);
}
);
export default service;

63
src/utils/request_wx.ts Normal file
View File

@@ -0,0 +1,63 @@
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
class HttpRequest {
private readonly baseUrl: string
constructor() {
this.baseUrl = '/api'
}
getInsideConfig() {
const config = {
baseURL: this.baseUrl, // 所有的请求地址前缀部分(没有后端请求不用写)
timeout: 80000 // 请求超时时间(毫秒)
}
return config
}
// 请求拦截
interceptors(instance: AxiosInstance, url: string | number | undefined) {
instance.interceptors.request.use(
config => {
// 添加全局的loading..
// config.headers['Authorization'] =
// 'bearer ' + JSON.parse(window.localStorage.getItem('adminInfo') || '{}').access_token; // 请求头带上token token要在登录的时候保存在localStorage中
// console.log(
// "🚀 ~ requestHandler ~ config.headers['Authorization']:",
// JSON.parse(window.localStorage.getItem('adminInfo') || '{}'),
// config.headers
// );
config.headers['Authorization'] =
'bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySW5kZXgiOiJmYTM3YjkzY2M5MGQ0YzE3ODRjYThmNmRlYmRkZWUxYSIsInVzZXJfbmFtZSI6InJvb3QiLCJzY29wZSI6WyJhbGwiXSwibmlja25hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJ1c2VyVHlwZSI6MCwiZGVwdEluZGV4IjoiNTY5OWU1OTE2YTE4YTYzODFlMWFjOTJkYTViZDI2MjgiLCJleHAiOjE4MjE4MTc2MTksImF1dGhvcml0aWVzIjpbInJvb3QiXSwianRpIjoiMmJiM2Q5ZTYtNmY3Yy00Yjg1LThiM2EtZDI2ODdmMTUzMDg5IiwiY2xpZW50X2lkIjoibmpjbnRlc3QiLCJoZWFkU2N1bHB0dXJlIjoicmVzb3VyY2VEYXRhLzMxNzRDRUFFOUQ0MjRGMjJCQjkxQTU4OURENjdCMDUxLmpwZyJ9.WjeYl1lvvJdDE1FUGIhS99rE5qKaBXOypWxmxK0svWweGqEbu1XCLjKm_YkiTwjZJ_oIcn5JOO9rvHFkkea76BUsYo5wlzuBBiy7sKqM1fFzOFQq6hdFevNTJAbYH9FiBxYxI-e9DZ5mvLGE6umOjUfn_FAsku2w6Uj5DtvpOKBWYzLEPTEifOqNI9he4zJAmVZniUUMf26SDoEdfu0TyrIS1j_qKaEb-cqR1XDhivdthEBK5m9vxJyXFZ5kofNxwQQkit_oiqJRkCZIt9TWAjCh-frzMHCvA30hkAr-VCD2JfCmmEr3hW_lmwfINaPtFVbHCdCKqdrl6VmF1HObaQ'
// 请求头携带token
return config
},
(error: any) => {
return Promise.reject(error)
}
)
//响应拦截
instance.interceptors.response.use(
res => {
//返回数据
const { data } = res
// console.log('返回数据处理', res)
return data
},
(error: any) => {
console.log('error==>', error)
return Promise.reject(error)
}
)
}
request(options: AxiosRequestConfig) {
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options)
this.interceptors(instance, options.url)
return instance(options)
}
}
const http = new HttpRequest()
export default http

71
src/utils/useDraw.ts Normal file
View File

@@ -0,0 +1,71 @@
import { ref } from "vue";
export default function useDraw() {
// 指向最外层容器
const appRef = ref();
// 定时函数
const timer:any = ref(0);
// 默认缩放值
const scale = {
width: "1",
height: "1",
};
// 设计稿尺寸px
const baseWidth = 1920;
const baseHeight = 1080;
// 需保持的比例
const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5));
const calcRate = () => {
// 当前宽高比
const currentRate = parseFloat(
(window.innerWidth / window.innerHeight).toFixed(5)
);
if (appRef.value) {
if (currentRate > baseProportion) {
// 表示更宽
scale.width = (
(window.innerHeight * baseProportion) /
baseWidth
).toFixed(5);
scale.height = (window.innerHeight / baseHeight).toFixed(5);
appRef.value.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`;
} else {
// 表示更高
scale.height = (
window.innerWidth /
baseProportion /
baseHeight
).toFixed(5);
scale.width = (window.innerWidth / baseWidth).toFixed(5);
appRef.value.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`;
}
window.sessionStorage.setItem("scaleWidth", scale.width);
window.sessionStorage.setItem("scaleheight", scale.height);
}
};
const resize = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
calcRate();
}, 200);
};
// 改变窗口大小重新绘制
const windowDraw = () => {
window.addEventListener("resize", resize);
};
// 改变窗口大小重新绘制
const unWindowDraw = () => {
window.removeEventListener("resize", resize);
};
return {
appRef,
calcRate,
windowDraw,
unWindowDraw,
};
}

View File

@@ -0,0 +1,210 @@
import { ElMessage, EVENT_CODE } from "element-plus";
// 定义消息类型,用于类型检查
type MessageType = {
[key: string]: any;
};
export default class SocketService {
// 单例模式实例
private static instance: SocketService | null = null;
// 和服务端连接的socket对象
private ws: WebSocket | null = null;
// 存储回调函数
private callBackMapping: {
[key: string]: ((message: MessageType) => void) | null;
} = {};
// 标识是否连接成功
private connected: boolean = false;
// 记录重试的次数
private sendRetryCount: number = 0;
// 重新连接尝试的次数
private connectRetryCount: number = 0;
// Web Worker 实例
private work: Worker | null = null;
// 临时的 Blob URL
private workerBlobUrl: string | null = null;
// 上次活动时间戳
private lastActivityTime: number = 0;
// 最后一次收到心跳回复时间
private lastResponseHeartTime: number = Date.now();
// 重新连接延迟,单位毫秒
private reconnectDelay: number = 5000;
// 单例模式获取实例
public static get Instance(): SocketService {
if (!this.instance) {
this.instance = new SocketService();
}
return this.instance;
}
// 定义连接服务器的方法
public async connect(id: any) {
if (!window.WebSocket) {
console.log("您的浏览器不支持WebSocket");
return;
}
// const response = await fetch("/vue/");
const response = await fetch("");
const mqttUrl = response.headers.get("X-Mqtt-Url");
console.log("🚀 ~ SocketService ~ connect ~ mqttUrl:", mqttUrl);
setTimeout(() => {
//"ws://10.156.193.182:18093/ws/screen" 北京
//"ws://192.168.1.130:19001/ws/askRealTimeData/" 无锡
const url = (mqttUrl || "ws://192.168.1.127:19001/ws/") + id;
// const url = (mqttUrl || "ws://192.168.1.63:18093/ws/screen") + id;
console.log("🚀 ~ SocketService ~ connect ~ url:", url);
this.ws = new WebSocket(url);
this.ws.onopen = () => this.handleOpen();
this.ws.onclose = () => this.handleClose();
this.ws.onerror = () => this.handleError();
this.ws.onmessage = (event) => this.handleMessage(event);
}, 0);
}
// 处理连接成功事件
private handleOpen(): void {
ElMessage.success("webSocket连接服务端成功了");
console.log("连接服务端成功了");
this.connected = true;
this.connectRetryCount = 0;
this.updateLastActivityTime();
this.startHeartbeat();
}
// 处理连接关闭事件
private handleClose(): void {
console.log("连接webSocket服务端关闭");
this.connected = false;
this.connectRetryCount++;
this.clearHeartbeat();
// 可根据需要添加重连逻辑
// setTimeout(() => this.connect(), 500 * this.connectRetryCount);
}
// 处理连接错误事件
private handleError(): void {
ElMessage.error("webSocket连接异常");
}
// 处理服务端发送过来的数据
private handleMessage(event: MessageEvent): void {
// console.log('🚀 ~ SocketService ~ handleMessage ~ event.data:', event.data)
if (event.data == "连接成功") {
this.sendHeartbeat();
} else if (event.data.length > 10) {
let message: MessageType;
try {
// console.log(
// 'Received message:', event.data)
message = JSON.parse(event.data);
this.callBackMapping["message"]!(message);
} catch (e) {
// console.error("消息解析失败", event.data, e);
return;
}
// console.log("🚀 ~ SocketService ~ handleMessage ~ message:", message)
// this.callBackMapping["message"]!(message);
} else {
// ElMessage.error(event.data);
}
}
// 启动心跳检测
private startHeartbeat(): void {
this.lastResponseHeartTime = Date.now();
const url = window.URL.createObjectURL(
new Blob([
"(function(e){setInterval(function(){this.postMessage(null)},30000)})()",
])
);
this.workerBlobUrl = url;
this.work = new Worker(url);
this.work.onmessage = (e) => this.handleWorkerMessage(e);
}
// 处理 Web Worker 消息
private handleWorkerMessage(e: MessageEvent): void {
// if (this.lastActivityTime - this.lastResponseHeartTime > 30000) {
// // 说明已经三轮心跳没收到回复了,关闭检测,提示用户。
// // ElMessage.error('业务主体模块发生未知异常,请尝试重新启动!')
// this.clearHeartbeat()
// return
// }
// console.log(123);
this.sendHeartbeat();
}
// 发送心跳消息
private sendHeartbeat(): void {
// console.log(new Date() + '进入心跳消息发送。。。。。。。。。。。。。')
if (this.ws) {
this.ws.send("alive");
this.updateLastActivityTime();
}
}
// 更新活动时间
private updateLastActivityTime(): void {
this.lastActivityTime = Date.now();
}
// 清除心跳检测
private clearHeartbeat(): void {
if (this.work) {
this.work.terminate();
this.work = null;
}
if (this.workerBlobUrl) {
window.URL.revokeObjectURL(this.workerBlobUrl);
this.workerBlobUrl = null;
}
}
// 回调函数的注册
public registerCallBack(
socketType: string,
callBack: (message: MessageType) => void
): void {
this.callBackMapping[socketType] = callBack;
}
// 取消某一个回调函数
public unRegisterCallBack(socketType: string): void {
this.callBackMapping[socketType] = null;
}
// 发送数据的方法
public send(data: any): void {
if (this.connected) {
this.sendRetryCount = 0;
try {
if (this.ws) {
this.ws.send(JSON.stringify(data));
}
} catch (e) {
if (this.ws) {
this.ws.send(data);
}
}
} else {
this.sendRetryCount++;
setTimeout(() => this.send(data), this.sendRetryCount * 500);
}
}
// 断开方法
public closeWs(): void {
if (this.connected && this.ws) {
this.ws.close();
}
console.log("执行WS关闭命令..");
}
}

View File

@@ -0,0 +1,89 @@
<!-- 谐波放大表格详情 -->
<template>
<el-dialog
v-model="visible"
:close-on-click-modal="false"
title="详情"
draggable
width="1000px"
@close="handleCloseDialog"
style="height: 600px"
>
<MyEChart :options="[]" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
const visible = ref(false);
const options = ref<any>(null);
const open = () => {
visible.value = true;
}
const handleCloseDialog = () => {
visible.value = false;
options.value = null;
};
// const showCharts = (row: any, valueType: number, name: string) => {
// getHistoryLineData({
// lineId: row.lineId,
// number: row.number,
// phaseType: row.phaseType,
// searchTime: row.timeId,
// targetCode: row.targetCode,
// valueType,
// }).then((res) => {
// options.value = {
// title: {
// text:
// row.subName +
// " " +
// row.lineName +
// " " +
// row.targetName +
// " " +
// row.phaseType +
// "相" +
// name,
// },
// legend: {
// show: false,
// },
// xAxis: {
// type: "category",
// name: "时间",
// data: res.data[0]?.value.map((item: any[]) => item[0]),
// },
// yAxis: {
// name: "%",
// type: "value",
// },
// series: [
// {
// name: name,
// data: res.data[0]?.value.map((item: any[]) => item[1]),
// type: "line",
// },
// ],
// options: {
// grid: {
// top: "50px",
// left: "40px",
// right: "60px",
// bottom: "10px",
// containLabel: true,
// },
// dataZoom: null,
// },
// };
// });
// };
defineExpose({
open,
});
</script>

View File

@@ -0,0 +1,68 @@
<template>
<el-dialog
v-model="visible"
title="责任度计算"
draggable
width="1700px"
style="height: 875px"
@close="handleClose"
:close-on-click-modal="false"
>
<div class="currentPosition">
当前位置{{ alias || "" }}
</div>
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="系统" name="1">
<system @setTitle="setTitle" v-if="activeName == '1'" />
</el-tab-pane>
<el-tab-pane label="配网" name="2">
<distributionNetwork @setTitle="setTitle" v-if="activeName == '2'" />
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import distributionNetwork from "./distributionNetwork.vue";
import system from "./system.vue";
const visible = ref(false);
const alias = ref("");
const activeName = ref("1");
const openDialog = () => {
visible.value = true;
};
onMounted(() => {});
const setTitle = (title: string) => {
alias.value = title;
};
const emit = defineEmits(["showCalculation", "close-dialog"]); // 打开弹窗
const handleClose = () => {
visible.value = false;
// 通知父组件显示收集界面
emit("showCalculation", false);
emit("close-dialog"); // 关闭弹窗
};
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.currentPosition {
position: absolute;
top: 60px;
right: 10px;
font-size: 14px;
font-weight: 600;
z-index: 10;
}
</style>

View File

@@ -0,0 +1,337 @@
<template>
<el-dialog
v-model="visible"
:close-on-click-modal="false"
title="用采数据管理"
draggable
>
<el-form label-width="auto" :model="form" inline class="formBox">
<el-form-item label="关键字">
<el-input
v-model="form.searchValue"
placeholder="关键字查询"
clearable
style="width: 180px"
size="small"
/>
</el-form-item>
<div class="mt5">
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
>
<el-button type="primary" :icon="Plus" size="small" @click="uploadFile"
>新增</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</el-form>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
v-loading="loading1"
element-loading-background="#343849c7"
border
>
<el-table-column prop="name" align="center" label="表名" />
<el-table-column
prop="startTime"
align="center"
label="起始时间"
width="150"
/>
<el-table-column
prop="endTime"
align="center"
label="截止时间"
width="150"
/>
<el-table-column prop="updateTime" align="center" label="更新时间" />
<el-table-column fixed="right" align="center" label="操作" width="140">
<template #default="{ row }">
<el-button
link
type="primary"
size="small"
v-if="row.integrity == 1"
@click="handleClick(row)"
>完整性详情</el-button
>
<el-button
link
type="danger"
size="small"
@click.stop="handleDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 详情 -->
<completenessDetails ref="completenessDetailsRef" @close="close" />
<el-dialog
v-model="dialogVisible"
draggable
title="上传数据"
width="500"
:close-on-click-modal="false"
:before-close="handleClose"
>
<el-upload
ref="upload"
action=""
v-model:file-list="fileList"
accept=".xlsx,.xls"
:auto-upload="false"
:on-change="choose"
:limit="2"
>
<el-button type="primary" :icon="Upload" size="small">上传文件</el-button>
</el-upload>
<template #footer>
<el-button @click="handleClose" size="small">取消</el-button>
<el-button
type="primary"
@click="submitupload"
:loading="loading"
size="small"
>确认</el-button
>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import completenessDetails from "./completenessDetails.vue";
import type { UploadInstance } from "element-plus";
import { Search, Plus, Upload, Download } from "@element-plus/icons-vue";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
import {
userDataList,
uploadUserData,
deleteUserDataByIds,
} from "@/api/manage_wx";
const store = useStore();
const visible = ref(false);
const openDialog = () => {
visible.value = true;
};
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
const completenessDetailsRef = ref();
const dialogVisible = ref(false);
const upload = ref<UploadInstance>();
const fileList = ref([]);
const loading = ref(false);
const loading1 = ref(false);
// 关闭上传弹框
const handleClose = () => {
fileList.value = [];
dialogVisible.value = false;
visible.value = true;
getTableData();
};
// 详情关闭弹框
const close = () => {
visible.value = true;
};
// 新增
const uploadFile = () => {
dialogVisible.value = true;
visible.value = false;
};
// 上传
const choose = (e: any) => {
upload.value!.clearFiles();
setTimeout(() => {
if (e.name.includes(".xls")) {
fileList.value = [e];
} else {
ElMessage.warning("请上传Excel文件");
}
}, 0);
};
// 上传
const submitupload = async () => {
if (fileList.value.length == 0) {
ElMessage.warning("请上传文件!");
return;
}
ElMessage.info("上传中,请稍等...");
const formData = new FormData();
formData.append("file", fileList.value[0].raw);
loading.value = true;
uploadUserData(formData)
.then((res) => {
ElMessage.success("上传成功");
loading.value = false;
handleClose();
// tableStore.index();
})
.catch((err) => {
loading.value = false;
});
};
// provide("tableStore", tableStore);
// tableStore.table.params.searchValue = "";
const onTableSizeChange = (size: number) => {
form.pageSize = size;
// 重新加载数据
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
// 重新加载数据
getTableData();
};
const getTableData = async () => {
loading1.value = true;
// form.deptId = store.state.deptId;
const res: any = await userDataList(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading1.value = false;
};
// 完整性详情
const handleClick = (row) => {
visible.value = false;
completenessDetailsRef.value.open(row);
};
// 删除
const handleDelete = (row) => {
ElMessageBox.confirm("确定删除?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
deleteUserDataByIds([row.id]).then(() => {
ElMessage.success("删除成功");
getTableData();
});
})
.catch(() => {
ElMessage({
type: "info",
message: "删除取消",
});
});
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
[
"表名",
"起始时间",
"截止时间",
"更新时间",
],
];
let list = [];
await userDataList({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.name,
item.startTime,
item.endTime,
item.updateTime,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "用采数据管理");
// 写出文件
XLSX.writeFile(workbook, "用采数据管理" + ".xlsx");
});
};
onMounted(() => {
getTableData();
});
defineExpose({
openDialog,
});
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
:deep(.el-upload-list__item-name) {
color: #fff;
}
:deep(.el-upload-list__item:hover) {
.el-upload-list__item-name {
color: #909399;
}
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="完整性不足详情"
:before-close="handleClose"
width="1000px"
>
<div class="formBox">
<el-form label-width="70px" :model="form" inline>
<el-form-item label="关键字">
<el-input
style="width: 150px"
v-model="form.searchValue"
placeholder="关键字查询"
clearable
size="small"
/>
</el-form-item>
</el-form>
<div>
<el-button
type="primary"
:icon="Search"
size="small"
@click="getTableData"
>查询</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
</div>
<div class="tableBox">
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="400px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
v-loading="loading"
element-loading-background="#343849c7"
border
>
<el-table-column prop="userNo" align="center" label="户号" />
<el-table-column prop="userName" align="center" label="用户名" />
<el-table-column
prop="lineNo"
align="center"
label="测量点局号"
width="180"
/>
<el-table-column
prop="updateTime"
align="center"
label="日期"
width="180"
/>
<el-table-column
prop="integrity"
align="center"
label="完整性(%)"
width="130"
>
<template #default="{ row }">
{{ Math.floor(row.integrity * 10000) / 100 }}
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted } from "vue";
import { Search, Download } from "@element-plus/icons-vue";
import { userDataIntegrityList } from "@/api/manage_wx";
import { useStore } from "vuex";
import * as XLSX from "xlsx";
const emits = defineEmits(["close"]);
const store = useStore();
const machineVisible = ref(false);
const form = reactive({
searchValue: "",
pageNum: 1,
pageSize: 20,
userDataId: "",
});
const loading = ref(false);
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
//form表单校验规则
const open = (row) => {
machineVisible.value = true;
form.searchValue = "";
form.pageNum = 1;
form.pageSize = 20;
form.userDataId = row.id;
getTableData();
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
getTableData();
};
const getTableData = async () => {
loading.value = true;
const res: any = await userDataIntegrityList(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
const handleClose = () => {
machineVisible.value = false;
emits("close");
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
["户号", "用户名", "测量点局号", "日期", "完整性(%)"],
];
let list = [];
await userDataIntegrityList({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.userNo,
item.userName,
item.lineNo,
item.updateTime,
Math.floor(item.integrity * 10000) / 100,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "完整性不足详情");
// 写出文件
XLSX.writeFile(workbook, "完整性不足详情" + ".xlsx");
});
};
onMounted(() => {});
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="暂降事件"
width="1200px"
>
<div class="tableBox">
<div style="display: flex;justify-content: flex-end;">
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
style="margin-top: 13px;"
>
<!-- <el-table-column type="index" align="center" label="序号" width="70" /> -->
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="startTime"
align="center"
label="发生时间"
show-overflow-tooltip
width="160"
/>
<el-table-column prop="stationName" align="center" label="变电站" />
<el-table-column prop="lineName" align="center" label="监测点" />
<el-table-column
prop="objName"
label="用户"
align="center"
show-overflow-tooltip
>
<template v-slot="scope">
<span>{{ scope.row.objName ? scope.row.objName : "/" }}</span>
</template>
</el-table-column>
<el-table-column
prop="eventType"
align="center"
label="触发类型"
width="80"
/>
<el-table-column
prop="featureAmplitude"
align="center"
label="残余电压(%)"
width="100"
>
<template v-slot="scope">
<span>{{ (scope.row.featureAmplitude * 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column
prop="duration"
align="center"
label="持续时间(S)"
width="100"
/>
<el-table-column fixed="right" label="操作" width="80" align="center">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click.stop="trendCharts(scope.row)"
>波形</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 波形图 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="trendVisible"
v-if="trendVisible"
title="波形"
width="70%"
@close="handleCloseTrend"
>
<waveForm ref="waveFormRef" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject } from "vue";
import { eventListByLineId } from "@/api/manage_wx";
import waveForm from "@/components/BX/waveForm.vue";
import * as XLSX from "xlsx";
import { Download } from "@element-plus/icons-vue";
const machineVisible = ref(false);
const form = reactive({
lineId: "",
pageNum: 1,
pageSize: 20,
searchBeginTime: "",
searchEndTime: "",
});
const total = ref(0); // 假设总条数为100
const loading = ref(false);
const tableData = ref([]);
import { useStore } from "vuex";
const store = useStore();
const trendVisible = ref(false);
const waveFormRef = ref();
//点击波形图
const trendCharts = (row: any) => {
row.eventdetail_index = row.eventId;
machineVisible.value = false;
trendVisible.value = true;
setTimeout(() => {
waveFormRef.value?.open({
...row,
bdname: row.stationName,
pointname: row.lineName,
timeid: row.startTime,
eventvalue: row.featureAmplitude,
persisttime: row.duration,
});
}, 500);
};
// 关闭波形图
const handleCloseTrend = () => {
trendVisible.value = false;
machineVisible.value = true;
};
const open = async (id: any) => {
form.pageNum = 1;
form.pageSize = 20;
form.lineId = id;
machineVisible.value = true;
await init();
};
const init = async () => {
loading.value = true;
eventListByLineId({
lineId: form.lineId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
}).then((res) => {
tableData.value = res.data.records;
total.value = res.data.total;
loading.value = false;
});
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
init();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
init();
};
// 暂降溯源导出
const exportTable = async () => {
let columnExpor: any = [
[
"发生时间",
"变电站",
"监测点",
"用户",
"触发类型",
"残余电压(%)",
"持续时间(S)",
],
];
let list = [];
await eventListByLineId({
lineId: form.lineId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.startTime,
item.stationName,
item.lineName,
item.objName,
item.eventType,
(item.featureAmplitude * 100).toFixed(2),
item.duration,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "暂降事件");
// 写出文件
XLSX.writeFile(workbook, "暂降事件" + ".xlsx");
});
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,558 @@
<template>
<!--暂降 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
title="暂降事件"
width="1000px"
>
<div class="tableBox">
<el-form
:inline="true"
style="display: flex; justify-content: space-between"
>
<el-form-item label="关键字筛选">
<el-input
clearable
style="width: 150px"
v-model="searchValue"
size="small"
placeholder="电站、测点、用户信息"
></el-input>
<el-popover placement="bottom" :width="550" trigger="click">
<template #reference>
<el-button
size="small"
:icon="DArrowRight"
type="primary"
style="margin-left: 10px"
>更多</el-button
>
</template>
<el-form label-width="auto">
<!-- <el-form-item label="关键字筛选">
<el-input
clearable
style="width: 150px"
v-model="searchValue"
size="small"
placeholder="电站、测点、用户信息"
></el-input>
</el-form-item> -->
<el-form-item label="运维单位">
<el-tree-select
v-model="deptsIndex"
:data="deptsList"
:render-after-expand="false"
clearable
size="small"
style="width: 150px"
:props="{
value: 'id',
label: 'name',
children: 'children',
}"
/>
<!-- <el-select
v-model="deptsIndex"
placeholder="请选择运维单位"
clearable
size="small"
:teleported="false"
style="width: 150px"
>
<el-option
v-for="item in deptsList"
:label="item.name"
:value="item.id"
:key="item.id"
></el-option>
</el-select> -->
</el-form-item>
<el-form-item label="触发类型">
<el-select
clearable
size="small"
:teleported="false"
v-model="eventForm.eventType"
placeholder="请选择触发类型"
style="width: 150px"
>
<el-option
v-for="item in eventTypeList"
:label="item.name"
:value="item.id"
:key="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="残余电压百分比">
<el-input-number
v-model="eventForm.eventValueMin"
:min="0"
style="width: 150px"
size="small"
:max="100"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventValueMin = 1) : null)
"
><template #suffix>
<span>%</span>
</template></el-input-number
>
<span> < 残余电压 < </span>
<el-input-number
v-model="eventForm.eventValueMax"
:min="0"
style="width: 150px"
size="small"
:max="100"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventValueMax = 1) : null)
"
><template #suffix>
<span>%</span>
</template></el-input-number
>
</el-form-item>
<el-form-item label="暂降持续事时间">
<el-input-number
v-model="eventForm.eventDurationMin"
:min="0"
style="width: 150px"
size="small"
:max="1000000"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventDurationMin = 1) : null)
"
><template #suffix>
<span>ms</span>
</template></el-input-number
>
<span> < 持续时间 < </span>
<el-input-number
v-model="eventForm.eventDurationMax"
:min="0"
style="width: 150px"
size="small"
:max="1000000"
:precision="1"
:step="1"
@change="
(e) => (e == null ? (eventForm.eventDurationMax = 1) : null)
"
><template #suffix>
<span>ms</span>
</template></el-input-number
>
</el-form-item>
</el-form>
</el-popover>
</el-form-item>
<el-form-item style="margin-right: -10px">
<el-button size="small" :icon="Search" type="primary" @click="init()"
>查询</el-button
>
<el-button size="small" :icon="RefreshLeft" @click="clearInit()"
>重置</el-button
>
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
style="margin-right: 10px"
>导出
</el-button>
<el-button
style="margin-right: 10px"
v-if="displayValue == 1"
size="small"
:icon="HelpFilled"
type="primary"
@click="handleAggregation"
>溯源</el-button
>
</el-form-item>
</el-form>
<!-- <div style="float: right; margin-bottom: 9px" v-if="displayValue == 1">
<el-button
size="small"
:icon="HelpFilled"
type="primary"
@click="handleAggregation"
>聚合</el-button
>
</div> -->
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
>
<!-- <el-table-column type="index" align="center" label="序号" width="70" /> -->
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="startTime"
align="center"
label="发生时间"
show-overflow-tooltip
width="160"
/>
<el-table-column
prop="stationName"
align="center"
label="变电站"
width="120"
/>
<el-table-column
prop="lineName"
align="center"
label="监测点"
width="120"
/>
<el-table-column
prop="objName"
label="用户"
align="center"
show-overflow-tooltip
>
<template v-slot="scope">
<span>{{ scope.row.objName ? scope.row.objName : "/" }}</span>
</template>
</el-table-column>
<el-table-column
prop="eventType"
align="center"
label="触发类型"
width="90"
/>
<el-table-column
prop="featureAmplitude"
align="center"
label="残余电压(%)"
width="90"
>
<template v-slot="scope">
<span>{{ (scope.row.featureAmplitude * 100).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column
prop="duration"
align="center"
label="持续时间(S)"
width="90"
/>
<el-table-column fixed="right" label="操作" width="80" align="center">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click.stop="trendCharts(scope.row)"
>波形</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<!-- 波形图 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="trendVisible"
v-if="trendVisible"
title="波形"
width="70%"
@close="handleCloseTrend"
>
<waveForm ref="waveFormRef" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, onMounted, watch } from "vue";
import {
getEventList,
processEvents,
loginDeptTree,
getDicDataByTypeCode,
} from "@/api/manage_wx";
import { HelpFilled } from "@element-plus/icons-vue";
import waveForm from "@/components/BX/waveForm.vue";
import {
DArrowRight,
Search,
RefreshLeft,
Download,
} from "@element-plus/icons-vue";
import * as XLSX from "xlsx";
// 定义 emit
const emit = defineEmits(["aggregation-success"]);
const machineVisible = ref(false);
const form = reactive({
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const loading = ref(false);
const tableData = ref([]);
import { useStore } from "vuex";
const store = useStore();
const displayValue = ref();
const trendVisible = ref(false);
const waveFormRef = ref();
const open = async (val) => {
displayValue.value = val;
machineVisible.value = true;
await init();
};
// 查询
const searchValue = ref("");
const deptsIndex = ref("");
const deptsList = ref([]); //部门列表
const eventTypeList = ref([]); //触发类型
const eventForm: any = reactive({
eventValueMin: null,
eventValueMax: null,
eventDurationMin: null,
eventDurationMax: null,
eventType: null,
});
const clearInit = async () => {
searchValue.value = "";
eventForm.eventValueMin = null;
eventForm.eventValueMax = null;
eventForm.eventDurationMin = null;
eventForm.eventDurationMax = null;
eventForm.eventType = null;
deptsIndex.value = null;
await init();
};
// 部门
const initDept = () => {
loginDeptTree({
deptIndex: store.state.deptId,
}).then((res: any) => {
deptsList.value = res.data;
});
};
// 触发类型
const dicDataByTypeCode = () => {
getDicDataByTypeCode({
dictTypeCode: "Event_Statis",
}).then((res: any) => {
eventTypeList.value = res.data;
});
};
const init = async () => {
loading.value = true;
getEventList({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
pageNum: form.pageNum,
pageSize: form.pageSize,
searchValue: searchValue.value,
...eventForm,
eventValueMin:
eventForm.eventValueMin == null ? null : eventForm.eventValueMin / 100,
eventValueMax:
eventForm.eventValueMax == null ? null : eventForm.eventValueMax / 100,
}).then((res) => {
tableData.value = res.data.records;
total.value = res.data.total;
loading.value = false;
});
};
const handleAggregation = (row: any) => {
machineVisible.value = false;
ElMessageBox.confirm(
`是否确认对当前用户部门${store.state.timeValue[0]}${store.state.timeValue[1]}之间的暂降事件进行溯源?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
processEvents({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
}).then((res: any) => {
if (res.code == "A0000") {
ElMessage({
type: "success",
message: res.message,
});
// 通知父组件执行 initialAggregation
emit("aggregation-success");
} else {
ElMessage({
type: "warning",
message: res.message,
});
}
});
})
.catch((error) => {});
};
//点击波形图
const trendCharts = (row: any) => {
row.eventdetail_index = row.eventId;
machineVisible.value = false;
trendVisible.value = true;
setTimeout(() => {
waveFormRef.value?.open({
...row,
bdname: row.stationName,
pointname: row.lineName,
timeid: row.startTime,
eventvalue: row.featureAmplitude,
persisttime: row.duration,
});
}, 500);
};
// 关闭波形图
const handleCloseTrend = () => {
trendVisible.value = false;
machineVisible.value = true;
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
init();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
init();
};
// 暂降溯源导出
const exportTable = async () => {
let columnExpor: any = [
[
"发生时间",
"变电站",
"监测点",
"用户",
"触发类型",
"残余电压(%)",
"持续时间(S)",
],
];
let list = [];
await getEventList({
deptId: store.state.deptId,
searchBeginTime: store.state.timeValue[0],
searchEndTime: store.state.timeValue[1],
searchValue: searchValue.value,
...eventForm,
eventValueMin:
eventForm.eventValueMin == null ? null : eventForm.eventValueMin / 100,
eventValueMax:
eventForm.eventValueMax == null ? null : eventForm.eventValueMax / 100,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.startTime,
item.stationName,
item.lineName,
item.objName,
item.eventType,
(item.featureAmplitude * 100).toFixed(2),
item.duration,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "暂降事件");
// 写出文件
XLSX.writeFile(workbook, "暂降事件" + ".xlsx");
});
};
onMounted(() => {
initDept();
dicDataByTypeCode();
});
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

View File

@@ -0,0 +1,701 @@
<template>
<!-- 配网计算 -->
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="20">
<PointTree :showSelect="false" @node-click="handleNodeClick" @init="handleNodeClick"></PointTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select v-model="form.index" filterable multiple :multiple-limit="5" collapse-tags collapse-tags-tooltip
clearable placeholder="请选择次数" style="width: 200px" size="small">
<el-option v-for="item in 49" :key="item" :label="item + 1 + '次'" :value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="负荷数据">
<el-select v-model="form.loadDataId" clearable filterable placeholder="请选择负荷数据" style="width: 200px"
size="small">
<el-option v-for="item in loadDataOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small">确定</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane v-for="(item, index) in tabList" :key="item" :label="item.label" :name="index"
style="height: 100%">
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker v-model="item.time" class="mr10 ml10" type="daterange" start-placeholder="起始时间"
end-placeholder="结束时间" format="YYYY-MM-DD" date-format="YYYY-MM-DD" time-format="YYYY-MM-DD"
value-format="YYYY-MM-DD" :disabled-date="handleDisabledDate" size="small" />
<el-button type="primary" :icon="CaretRight" @click="execute(item, index)" size="small">
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input v-model="item.form.limit" placeholder="请选择限值" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 0 ? 'frontBox' : '']" @click="setCode(0)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input v-model="item.form.time1" placeholder="请选择时间点一" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 1 ? 'frontBox' : '']" @click="setCode(1)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input v-model="item.form.time2" placeholder="请选择时间点二" disabled style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 2 ? 'frontBox' : '']" @click="setCode(2)" size="small" />
</template>
</el-input>
</el-form-item>
<el-button type="primary" :icon="Document" @click="generateFn" v-if="!item.showDynamic" size="small">
生成动态谐波责任数据
</el-button>
<el-button type="primary" :icon="Document" v-else @click="generateMetrics" size="small">
生成谐波责任指标
</el-button>
</el-form>
</div>
<div class="box" v-loading="loading" element-loading-background="#343849c7">
<MyEChart :options="item.options" v-if="item.showEcahr == 1" @group="group" />
<el-empty description="时间范围内无谐波数据" v-if="item.showEcahr == 2" />
</div>
<!-- 生成动态谐波责任数据 -->
<div class="box boxTab" v-loading="loading1" element-loading-background="#343849c7">
<MyEChart :options="item.dynamicOptions" style="flex: 1" v-if="item.showDynamic" />
<div style="width: 500px" v-if="item.showDynamic" class="tableBox">
<el-table :scrollbar-always-on="true" ref="tableRef" :data="item.dynamicData" height="280px"
size="small" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)" width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) /
10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import PointTree from "@/components/tree/pointTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
userDataList,
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
} from "@/api/manage_wx";
const emit = defineEmits(["setTitle"]); // 打开弹窗
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
loadDataId: "",
});
const loadDataOptions: any = ref([]);
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
setTimeout(() => {
emit("setTitle", data.alias);
}, 0)
}
};
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
// 定义时间边界
const startLimit = new Date(timeFrame.value[0]).getTime() - 86400000; //向前推1天
const endLimit = new Date(timeFrame.value[1]).setHours(23, 59, 59, 999);
// 如果日期不存在(选择今天时可能出现),不禁用
if (!date) return false;
// 禁用 2025-08-01 之前和 2025-08-31 之后的日期
return date.getTime() < startLimit || date.getTime() > endLimit;
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.loadDataId == "") {
return ElMessage.warning("请选择负荷数据");
}
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
let timeList = loadDataOptions.value.filter(
(item: any) => item.id == form.loadDataId
)[0];
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
time: [timeList.startTime, timeList.endTime],
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
timeFrame.value = [timeList.startTime, timeList.endTime];
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
loading1.value = true;
await getDynamicData({
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
userDataId: form.loadDataId,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
userDataList({
pageNum: 1,
pageSize: 10000,
searchValue: "",
}).then((res: any) => {
console.log(res.data);
loadDataOptions.value = res.data.records;
});
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #fffFFF80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
.currentPosition {
position: absolute;
top: -10px;
right: 10px;
font-size: 14px;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,255 @@
<template>
<!--谐波放大事件 -->
<el-dialog
:close-on-click-modal="false"
draggable
v-model="machineVisible"
:title="title"
width="1200px"
>
<div class="tableBox">
<div style="display: flex; justify-content: flex-end">
<el-button
type="primary"
:icon="Download"
@click="exportTable"
size="small"
>导出
</el-button>
</div>
<el-table
:scrollbar-always-on="true"
:data="tableData"
height="443px"
size="small"
v-loading="loading"
element-loading-background="#343849c7"
:header-cell-style="{ textAlign: 'center' }"
border
style="margin-top: 13px"
>
<el-table-column label="序号" align="center" type="index" width="70">
<template #default="scope">
<span>{{
(form.pageNum - 1) * form.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
prop="monitorName"
align="center"
label="监测点名称"
width="120"
show-overflow-tooltip
/>
<el-table-column
prop="objName"
align="center"
label="用户"
show-overflow-tooltip
/>
<el-table-column
prop="startTime"
align="center"
label="开始时间"
width="140"
/>
<el-table-column
prop="endTime"
align="center"
label="结束时间"
width="140"
/>
<el-table-column
prop="harmonicCount"
align="center"
label="次数"
width="60"
/>
<el-table-column prop="phase" align="center" label="相别" width="60" />
<el-table-column
prop="duration"
align="center"
label="持续时间(min)"
width="100"
>
<template #default="scope">
{{ scope.row.duration / 60 }}
</template>
</el-table-column>
<el-table-column
prop="vavgValue"
align="center"
label="电压标准值"
width="80"
/>
<el-table-column
prop="iavgValue"
align="center"
label="电流标准值"
width="80"
>
<template #default="scope">
{{ Math.floor(scope.row.iavgValue * 100) / 100 }}
</template>
</el-table-column>
<el-table-column
prop="upScheme"
align="center"
label="处理措施"
show-overflow-tooltip
/>
<el-table-column prop="address" align="center" label="操作" width="60">
<template #default="scope">
<el-button
size="small"
type="primary"
link
@click="arendChart(scope.row)"
>趋势图</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
size="small"
style="margin-top: 10px"
:currentPage="form.pageNum"
:page-size="form.pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
background
:layout="'sizes,total, ->, prev, pager, next, jumper'"
:total="total"
@size-change="onTableSizeChange"
@current-change="onTableCurrentChange"
></el-pagination>
</el-dialog>
<TrendChart ref="trendChartRef" @close="machineVisible = true"></TrendChart>
</template>
<script lang="ts" setup>
import { ref, reactive, inject, nextTick } from "vue";
import { getDetail } from "@/api/manage_wx";
import TrendChart from "./trendChart.vue";
import { Download } from "@element-plus/icons-vue";
import * as XLSX from "xlsx";
const machineVisible = ref(false);
const title = ref("谐波放大事件");
const form = reactive({
searchBeginTime: "",
lineId: "",
pageNum: 1,
pageSize: 20,
});
const total = ref(0); // 假设总条数为100
const tableData = ref([]);
const loading = ref(false);
const trendChartRef = ref();
//form表单校验规则
const open = (text: string, time?: any, lineId?: any) => {
machineVisible.value = true;
form.lineId = lineId;
form.searchBeginTime = time;
nextTick(() => {
getTableData();
});
};
const onTableSizeChange = (size: number) => {
form.pageSize = size;
getTableData();
};
const onTableCurrentChange = (page: number) => {
form.pageNum = page;
getTableData();
};
const getTableData = async () => {
loading.value = true;
const res: any = await getDetail(form);
if (res.code == "A0000") {
tableData.value = res.data.records;
total.value = res.data.total;
}
loading.value = false;
};
// 趋势图
const arendChart = (row: any) => {
trendChartRef.value.open(row);
machineVisible.value = false;
};
// 导出
const exportTable = async () => {
let columnExpor: any = [
[
"监测点名称",
"用户",
"开始时间",
"结束时间",
"次数",
"相别",
"持续时间(min)",
"电压标准值",
"电流标准值",
],
];
let list = [];
await getDetail({
...form,
pageNum: 1,
pageSize: total.value,
}).then((res) => {
let data = res.data.records.map((item) => {
return [
item.monitorName,
item.objName,
item.startTime,
item.endTime,
item.harmonicCount,
item.phase,
item.duration / 60,
item.vavgValue,
Math.floor(item.iavgValue * 100) / 100,
];
});
list = [...columnExpor, ...data];
// 创建工作表
const worksheet = XLSX.utils.aoa_to_sheet(list);
worksheet["!cols"] = list.map((col) => ({ wch: 20 }));
// 创建工作簿
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "谐波放大事件");
// 写出文件
XLSX.writeFile(workbook, "谐波放大事件" + ".xlsx");
});
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.tableBox {
padding: 0px !important;
}
.formBox {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.formLeft {
display: flex;
align-items: center;
color: #fff;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,667 @@
<template>
<!-- 配网计算 -->
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="20">
<SystemTree :showSelect="false" @node-click="handleNodeClick" @init="handleNodeClick"></SystemTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select v-model="form.index" filterable multiple :multiple-limit="5" collapse-tags
collapse-tags-tooltip clearable placeholder="请选择次数" style="width: 200px" size="small">
<el-option v-for="item in 49" :key="item" :label="item + 1 + '次'" :value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small">确定</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane v-for="(item, index) in tabList" :key="item" :label="item.label" :name="index"
style="height: 100%">
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker v-model="item.time" class="mr10 ml10" type="daterange"
start-placeholder="起始时间" end-placeholder="结束时间" format="YYYY-MM-DD"
date-format="YYYY-MM-DD" time-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:disabled-date="handleDisabledDate" size="small" />
<el-button type="primary" :icon="CaretRight" @click="execute(item, index)" size="small">
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input v-model="item.form.limit" placeholder="请选择限值" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 0 ? 'frontBox' : '']"
@click="setCode(0)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input v-model="item.form.time1" placeholder="请选择时间点一" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 1 ? 'frontBox' : '']"
@click="setCode(1)" size="small" />
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input v-model="item.form.time2" placeholder="请选择时间点二" disabled
style="width: 200px" size="small">
<template #append>
<el-button :icon="Edit" :class="[code == 2 ? 'frontBox' : '']"
@click="setCode(2)" size="small" />
</template>
</el-input>
</el-form-item>
<el-button type="primary" :icon="Document" @click="generateFn" v-if="!item.showDynamic"
size="small">
生成动态谐波责任数据
</el-button>
<el-button type="primary" :icon="Document" v-else @click="generateMetrics" size="small">
生成谐波责任指标
</el-button>
</el-form>
</div>
<div class="box" v-loading="loading" element-loading-background="#343849c7">
<MyEChart :options="item.options" v-if="item.showEcahr == 1" @group="group" />
<el-empty description="时间范围内无谐波数据" v-if="item.showEcahr == 2" />
</div>
<!-- 生成动态谐波责任数据 -->
<div class="box boxTab" v-loading="loading1" element-loading-background="#343849c7">
<MyEChart :options="item.dynamicOptions" style="flex: 1" v-if="item.showDynamic" />
<div style="width: 500px" v-if="item.showDynamic" class="tableBox">
<el-table :scrollbar-always-on="true" ref="tableRef" :data="item.dynamicData"
height="280px" size="small" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)"
width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) /
10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import SystemTree from "@/components/tree/systemTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
} from "@/api/manage_wx";
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
});
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
}
};
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
return date.getTime() > Date.now();
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
loading1.value = true;
await getDynamicData({
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #fffFFF80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
</style>

View File

@@ -0,0 +1,982 @@
<template>
<!-- 配网计算 -->
<splitpanes
style="height: 100%"
class="default-theme"
id="navigation-splitpanes"
>
<pane :size="20">
<PointTree
:showSelect="false"
@node-click="handleNodeClick"
@init="handleNodeClick"
></PointTree>
</pane>
<pane style="background-color: rgba(44, 46, 60, 0.1)" :size="100 - size">
<el-form :model="form" inline label-width="auto">
<el-form-item label="用采用户">
<el-tree-select
v-model="form.userList"
:data="dataTree"
multiple
filterable
show-checkbox
ref="treeRef"
:props="defaultProps"
node-key="id"
size="small"
popper-class="tree-select-popper"
:popper-append-to-body="false"
:default-expanded-keys="expandedKeys"
collapse-tags
collapse-tags-tooltip
clearable
class="wide-tree-select"
/>
</el-form-item>
<el-form-item label="谐波类型">
<el-radio-group v-model="form.type">
<el-radio-button label="谐波电压" size="small" value="1" />
<el-radio-button label="谐波电流" size="small" value="0" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数">
<el-select
v-model="form.index"
filterable
multiple
:multiple-limit="5"
collapse-tags
collapse-tags-tooltip
clearable
placeholder="请选择次数"
style="width: 200px"
size="small"
>
<el-option
v-for="item in 49"
:key="item"
:label="item + 1 + '次'"
:value="item + 1"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="负荷数据">
<el-select v-model="form.loadDataId" clearable filterable placeholder="请选择负荷数据" style="width: 200px"
size="small">
<el-option v-for="item in loadDataOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item> -->
<el-form-item>
<!-- <el-button type="primary" icon="el-icon-Plus" @click="push('/admin/division/aListOfLoadData')">
新增
</el-button> -->
<el-button type="primary" :icon="Select" @click="submit" size="small"
>确定</el-button
>
</el-form-item>
</el-form>
<el-tabs v-model="activeName" v-if="showTabs" style="height: 740px">
<el-tab-pane
v-for="(item, index) in tabList"
:key="item"
:label="item.label"
:name="index"
style="height: 100%"
>
<div>
<div>
<span style="color: #fff">时间范围</span>
<el-date-picker
v-model="item.time"
class="mr10 ml10"
type="daterange"
start-placeholder="起始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD"
date-format="YYYY-MM-DD"
time-format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled-date="handleDisabledDate"
size="small"
/>
<el-button
type="primary"
:icon="CaretRight"
@click="execute(item, index)"
size="small"
>
执行
</el-button>
</div>
<div v-if="item.showEcahr == 1" class="harmonicButton">
<el-form :inline="true" v-model="item.form">
<el-form-item label="限值" v-if="item.showDynamic">
<el-input
v-model="item.form.limit"
placeholder="请选择限值"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 0 ? 'frontBox' : '']"
@click="setCode(0)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点一" v-if="item.showDynamic">
<el-input
v-model="item.form.time1"
placeholder="请选择时间点一"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 1 ? 'frontBox' : '']"
@click="setCode(1)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-form-item label="时间点二" v-if="item.showDynamic">
<el-input
v-model="item.form.time2"
placeholder="请选择时间点二"
disabled
style="width: 200px"
size="small"
>
<template #append>
<el-button
:icon="Edit"
:class="[code == 2 ? 'frontBox' : '']"
@click="setCode(2)"
size="small"
/>
</template>
</el-input>
</el-form-item>
<el-button
type="primary"
:icon="Document"
@click="generateFn"
v-if="!item.showDynamic"
size="small"
>
生成动态谐波责任数据
</el-button>
<el-button
type="primary"
:icon="Document"
v-else
@click="generateMetrics"
size="small"
>
生成谐波责任指标
</el-button>
</el-form>
</div>
<div
class="box"
v-loading="loading"
element-loading-background="#343849c7"
>
<MyEChart
:options="item.options"
v-if="item.showEcahr == 1"
@group="group"
/>
<el-empty
description="时间范围内无谐波数据"
v-if="item.showEcahr == 2"
/>
</div>
<!-- 生成动态谐波责任数据 -->
<div
class="box boxTab"
v-loading="loading1"
element-loading-background="#343849c7"
>
<MyEChart
:options="item.dynamicOptions"
style="flex: 1"
v-if="item.showDynamic"
/>
<div
style="width: 500px"
v-if="item.showDynamic"
class="tableBox"
>
<el-table
:scrollbar-always-on="true"
ref="tableRef"
:data="item.dynamicData"
height="280px"
size="small"
:header-cell-style="{ textAlign: 'center' }"
border
>
<el-table-column
prop="customerName"
align="center"
label="用户名(用户号)"
/>
<el-table-column
prop="responsibilityData"
align="center"
label="责任数据(%)"
width="120"
>
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) / 10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</pane>
</splitpanes>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive, watch } from "vue";
import "splitpanes/dist/splitpanes.css";
import { Splitpanes, Pane } from "splitpanes";
import PointTree from "@/components/tree/pointTree.vue";
import { Edit, Right } from "@element-plus/icons-vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { Select, CaretRight, Document } from "@element-plus/icons-vue";
import {
userDataList,
getHistoryHarmData,
getDynamicData,
getResponsibilityData,
getTerminalTreeForFive,
} from "@/api/manage_wx";
import { useStore } from "vuex";
const store = useStore();
const emit = defineEmits(["setTitle", "init"]); // 打开弹窗
const visible = ref(false);
const size = ref(0);
const dotList: any = ref({});
const form: any = reactive({
type: "1",
index: [],
userList: [],
});
const loadDataOptions: any = ref([]);
const code = ref(3);
const xAxisData = ref([]);
const loading = ref(false);
const loading1 = ref(false);
const tabList: any = ref([]);
const activeName = ref(0);
const xValue = ref("");
const showTabs = ref(false);
// 设置时间
const timeFrame = ref(["", ""]);
const openDialog = () => {
visible.value = true;
};
// 添加已选择的节点ID列表
const selectedNodeIds = ref<string[]>([]);
const handleNodeClick = (data: any) => {
if (data.level == 6) {
dotList.value = data;
setTimeout(() => {
emit("setTitle", data.alias);
}, 0);
// 保存选择的节点ID
if (data.id) {
selectedNodeIds.value = [data.id];
}
// 如果右侧已经选择了这个节点,则从右侧选择中移除
const selectedUserIdIndex = form.userList.indexOf(data.id);
if (selectedUserIdIndex > -1) {
// 创建新数组以触发视图更新
form.userList = form.userList.filter((id: string) => id !== data.id);
}
}
};
// 添加一个方法来更新树节点的禁用状态
const updateTreeDataDisabledState = () => {
if (!dataTree.value || dataTree.value.length === 0) return;
const disableNodes = (nodes: any[]) => {
if (!nodes || nodes.length === 0) return;
nodes.forEach((node) => {
// 如果节点ID在已选择列表中则禁用
if (selectedNodeIds.value.includes(node.id)) {
node.disabled = true;
} else {
// 如果之前被禁用但现在不在选择列表中,则启用
if (node.hasOwnProperty("disabled")) {
node.disabled = false;
}
}
// 递归处理子节点
if (node.children && node.children.length > 0) {
disableNodes(node.children);
}
});
};
disableNodes(dataTree.value);
};
// 监听 selectedNodeIds 的变化,更新树节点状态
watch(
selectedNodeIds,
() => {
updateTreeDataDisabledState();
},
{ deep: true }
);
// 处理日期禁用逻辑
const handleDisabledDate = (date) => {
// 定义时间边界
const startLimit = new Date(timeFrame.value[0]).getTime() - 86400000; //向前推1天
const endLimit = new Date(timeFrame.value[1]).setHours(23, 59, 59, 999);
// 如果日期不存在(选择今天时可能出现),不禁用
if (!date) return false;
// 禁用 2025-08-01 之前和 2025-08-31 之后的日期
return date.getTime() < startLimit || date.getTime() > endLimit;
};
// 这是按钮变色
const setCode = (num: number) => {
if (code.value == num) {
return (code.value = 3);
}
code.value = num;
};
// 确定
const submit = () => {
if (form.index.length == 0) {
return ElMessage.warning("请选择谐波次数");
}
if (form.index.length == 0) {
showTabs.value = false;
} else {
showTabs.value = true;
let list = JSON.parse(JSON.stringify(form.index)).sort((a, b) => a - b);
tabList.value = [];
list.forEach((item: any) => {
tabList.value.push({
label: item + "次谐波",
key: item,
showExecute: false,
form: {
limit: "",
time1: "",
time2: "",
},
showEcahr: 3, //1显示echart 2显示无数据 3什么都没有
options: {},
dynamicOptions: {}, //动态echarts
dynamicList: {}, //动态echarts
showDynamic: false, //动态执行展示
});
});
code.value = 3;
activeName.value = 0;
}
};
// 执行
const execute = async (item: any, index: number) => {
if (item.time == undefined) {
return ElMessage.warning("请选择时间范围");
}
tabList.value[activeName.value].showDynamic = false;
loading.value = true;
await getHistoryHarmData({
searchBeginTime: item.time[0],
searchEndTime: item.time[1],
type: form.type,
time: item.key,
lineId: dotList.value.id,
})
.then((res: any) => {
if (res.code == "A0000") {
let [min, max] = yMethod(
res.data.historyData.map((item: any) => item.value + 0.1)
);
xAxisData.value = res.data.historyData.map((item: any) => item.time);
tabList.value[index].options = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
xValue.value = params[0].value[0];
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
right: 50,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
series: [
{
name: item.key + (form.type == 1 ? "次谐波电压" : "次谐波电流"),
data: res.data.historyData.map((item: any) => [
item.time,
Math.floor(item.value * 10000) / 10000,
]),
type: "line",
symbol: "none",
markLine: {
symbol: "none", // 去除箭头
label: {
show: false, // 隐藏标签
},
data: [
{
yAxis: "",
},
{
xAxis: "",
},
{
xAxis: "",
},
],
// 样式配置
lineStyle: {
color: "red",
type: "dashed", // 虚线
},
},
},
],
};
tabList.value[index].showEcahr = 1; //显示echart
} else {
ElMessage.warning(res.message);
tabList.value[index].showEcahr = 2;
}
loading.value = false;
})
.catch(() => {
tabList.value[index].showEcahr = 2;
loading.value = false;
});
};
const resDataId = ref("");
// 生成动态谐波责任数据
const generateFn = async () => {
if (form.userList.length == 0) {
return ElMessage.warning("请选择用采用户");
}
loading1.value = true;
await getDynamicData({
userDataId: "123",
lineId: dotList.value.id,
searchBeginTime: tabList.value[activeName.value].time[0],
searchEndTime: tabList.value[activeName.value].time[1],
time: tabList.value[activeName.value].key,
type: form.type,
userList: form.userList,
systemType: 1,
})
.then((res: any) => {
if (res.code == "A0000") {
resDataId.value = res.data.responsibilityDataIndex;
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) =>
timeFormat(item)
);
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
} else {
ElMessage.warning(res.message);
}
loading1.value = false;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 生成指标
const generateMetrics = async () => {
if (tabList.value[activeName.value].form.limit == "")
return ElMessage.warning("请选择限值!");
if (tabList.value[activeName.value].form.time1 == "")
return ElMessage.warning("请选择时间一!");
if (tabList.value[activeName.value].form.time2 == "")
return ElMessage.warning("请选择时间二!");
loading1.value = true;
await getResponsibilityData({
limitEndTime: tabList.value[activeName.value].form.time2,
limitStartTime: tabList.value[activeName.value].form.time1,
limitValue: tabList.value[activeName.value].form.limit,
resDataId: resDataId.value,
time: tabList.value[activeName.value].key,
type: form.type,
})
.then((res: any) => {
tabList.value[activeName.value].dynamicData = res.data.responsibilities;
let [min, max] = yMethod(
res.data.datas.map((item: any) => item.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = res.data.timeDatas.map((item: any) => timeFormat(item));
res.data.datas.forEach((item: any) => {
series.push({
name: item.customerName,
data: item.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
smooth: true,
symbol: "none",
});
});
tabList.value[activeName.value].dynamicOptions = {
title: {
text: "",
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
name: form.type == 1 ? "%" : "A",
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
};
tabList.value[activeName.value].showDynamic = true;
})
.catch(() => {
loading1.value = false;
});
loading1.value = false;
};
// 监听echart点击
const group = (chart: any, myChartDom: any) => {
myChartDom.addEventListener("click", function (event: any) {
// 获取点击位置相对于图表容器的坐标
const rect = myChartDom.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const pointInPixel = [x, y];
// 转换为逻辑坐标(相对于图表坐标系)
const pointInGrid = chart.convertFromPixel({ gridIndex: 0 }, pointInPixel);
// 计算X轴和Y轴的对应数据
// 处理X轴数据分类轴
// 处理Y轴数据数值轴
let yValue = pointInGrid[1].toFixed(4);
// xValue = timeFormat(pointInGrid[0].toFixed(0) - 0)
if (code.value == 0) {
tabList.value[activeName.value].form.limit = yValue;
tabList.value[activeName.value].options.series[0].markLine.data[0].yAxis =
yValue;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 1) {
tabList.value[activeName.value].form.time1 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[1].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
} else if (code.value == 2) {
tabList.value[activeName.value].form.time2 = xValue.value;
tabList.value[activeName.value].options.series[0].markLine.data[2].xAxis =
xValue.value;
chart.setOption(tabList.value[activeName.value].options);
}
// 控制台输出详细信息
// console.log('点击事件详情:', {
// X轴数据: xValue,
// Y轴数据: yValue
// })
});
};
onMounted(() => {
const dom = document.getElementById("navigation-splitpanes");
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100);
}
userDataList({
pageNum: 1,
pageSize: 10000,
searchValue: "",
}).then((res: any) => {
console.log(res.data);
loadDataOptions.value = res.data.records;
});
if (!dotList.value || !dotList.value.alias) {
dotList.value = { alias: "请选择监测点位" };
}
});
const formData = ref({
deptIndex: store.state.deptId,
});
const dataTree = ref([]);
const expandedKeys = ref([]);
const defaultProps = {
label: "name",
value: "id",
disabled: "disabled",
};
const treeRef = ref();
const loadData = () => {
let form = JSON.parse(JSON.stringify(formData.value));
getTerminalTreeForFive(form).then((res) => {
console.log(res);
res.data = [
{
name: "电网拓扑",
level: -1,
id: 0,
children: res.data,
},
];
// 查找第一层级的最后一个子节点
const firstLevelChildren = res.data[0].children;
if (firstLevelChildren && firstLevelChildren.length > 0) {
let flag = true;
// 设置节点别名
res.data.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
item4.children.forEach((item5: any) => {
if (item5.level == 7) {
item5.children.forEach((item6: any) => {
item6.disabled = false;
item6.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}>${item6.name}`;
});
} else {
if (flag) {
expandedKeys.value = [item5.id];
treeRef.value.setCurrentKey(item5.id);
emit("init", item5);
flag = false;
}
item5.disabled = false;
item5.alias = `${item.name}>${item2.name}>${item3.name}>${item4.name}>${item5.name}`;
}
});
});
});
});
});
}
dataTree.value = res.data;
// 更新禁用状态
updateTreeDataDisabledState();
});
};
loadData();
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
@use "@/assets/scss/index.scss";
.box {
// height: calc((100vh - 190px) / 2);
height: 300px;
}
.boxTab {
display: flex;
}
.harmonicButton {
height: 42px;
margin-top: 10px;
display: flex;
justify-content: end;
align-items: center;
}
:deep(.el-tabs__content) {
height: calc(100vh - 265px);
}
:deep(.el-input-group__append, .el-input-group__prepend) {
background-color: #ffffff00;
padding: 0 17px;
}
:deep(.frontBox) {
background-color: var(--el-color-primary) !important;
color: #fff !important;
}
::v-deep(.el-tabs__item) {
color: #fff;
}
::v-deep(.el-tabs__item.is-active) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__nav-wrap::after) {
color: var(--el-color-primary);
}
::v-deep(.el-tabs__active-bar) {
color: var(--el-color-primary);
}
::v-deep(.splitpanes__splitter) {
background-color: rgba(44, 46, 60, 0.1) !important;
&:before {
background-color: #fff !important;
}
&:after {
background-color: #ffffff80 !important;
}
}
::v-deep(.el-form-item) {
margin-bottom: 0px;
}
.currentPosition {
position: absolute;
top: -10px;
right: 10px;
font-size: 14px;
font-weight: 600;
}
// 树选择器下拉面板样式
::v-deep(.tree-select-popper) {
.el-select-dropdown__wrap {
width: 400px; /* 设置你想要的宽度 */
}
}
::v-deep(.el-tag__content){
max-width: 90px;
}
/* 树选择器本身的宽度控制 */
::v-deep(.wide-tree-select) {
width: 200px;
}
</style>

View File

@@ -0,0 +1,230 @@
<!-- 详情 -->
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" title="详情" draggable width="1600px"
@close="handleCloseDialog" style="height: 800px">
<div class="default-main">
<div class="title_1">
<span class="monitoring-point">
{{ props.detailsQuery.name || "" }}</span>
</div>
<el-tabs v-model="activeName" @tab-change="generateFn">
<el-tab-pane v-for="(item, index) in tabList" :key="index" :label="item.name + '次谐波'" style="height: 100%"
:name="index" v-loading="loading">
<div style="height: 680px; overflow-y: auto">
<div class="box mb10" v-for="(value, i) in item.dynamicOptions" :key="i">
<div class="boxTab">
<MyEChart :options="item.dynamicOptions[i]" style="flex: 1" :style="{
height: `calc((680px) / ${item.list.length == 0
? 1
: item.list.length > 1
? 2
: item.list.length
} - 21px)`,
}" />
<div style="width: 500px" class="tableBox">
<el-table ref="tableRef" :data="item.list[i]" size="small" :height="`calc((600px) / ${item.list.length == 0
? 1
: item.list.length > 1
? 2
: item.list.length
} - 21px)`" :header-cell-style="{ textAlign: 'center' }" border>
<el-table-column prop="customerName" align="center" label="用户名(用户号)" />
<el-table-column prop="responsibilityData" align="center" label="责任数据(%)" width="120">
<template #default="scope">
{{
Math.floor(scope.row.responsibilityData * 10000) / 10000
}}
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-divider />
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { timeFormat } from "@/utils/common";
import { yMethod } from "@/utils/echartMethod";
import { displayHistoryData } from "@/api/manage_wx";
const props = defineProps<{
detailsQuery?: any; // 根据实际类型调整
}>();
// const height = mainHeight(155);
const activeName = ref(0);
const tabList: any = ref([]);
const visible: any = ref(false);
const loading: any = ref(false);
const init = () => {
let data =
(Array.isArray(props.detailsQuery.time)
? props.detailsQuery.time[0]
: props.detailsQuery.time
)?.split(",") ?? [];
tabList.value = [];
data.forEach((item: any) => {
tabList.value.push({
name: item,
dynamicOptions: [],
list: [],
});
});
activeName.value = 0;
generateFn(0);
};
// 生成动态谐波责任数据
const generateFn = async (e: any) => {
if (tabList.value[e].dynamicOptions.length != 0) return;
loading.value = true;
await displayHistoryData({
id: props.detailsQuery.id,
time: tabList.value[e].name,
})
.then((res: any) => {
res.data.forEach((item: any) => {
tabList.value[e].list.push(item.responsibilities);
let [min, max] = yMethod(
item.datas.map((k: any) => k.valueDatas).flat()
);
let series: any[] = [];
let time: any[] = item.timeDatas.map((k: any) => timeFormat(k));
item.datas.forEach((k: any) => {
series.push({
name: k.customerName,
data: k.valueDatas.map((k: any, i: number) => [
time[i],
Math.floor(k * 10000) / 10000,
]),
type: "line",
symbol: "none",
smooth: true,
});
});
tabList.value[e].dynamicOptions.push({
title: {
text: `时间:${item.limitSTime}${item.limitETime} 限值:${item.limitValue}`,
},
xAxis: {
type: "time",
name: "时间",
axisLabel: {
formatter: {
day: "{MM}-{dd}",
month: "{MM}",
year: "{yyyy}",
},
},
// 刻度线样式
axisTick: {
lineStyle: {
color: "#fff", // 刻度线白色
},
},
// 轴线样式
axisLine: {
lineStyle: {
color: "#fff", // 轴线白色
},
},
},
tooltip: {
formatter(params: any) {
let str = params[0].value[0] + "<br/>";
for (let i = 0; i < params.length; i++) {
str =
str +
params[i].marker +
params[i].seriesName +
"" +
params[i].value[1] +
"<br/>";
}
return str;
},
},
grid: {
top: 30,
},
legend: {
show: false,
},
yAxis: {
min: min,
max: max,
},
toolbox: {
show: false,
},
options: {
series: series,
},
});
});
})
.catch(() => {
loading.value = false;
});
loading.value = false;
};
const openDialog = () => {
visible.value = true;
if (props.detailsQuery) {
nextTick(() => {
init();
});
}
};
const handleCloseDialog = () => {
visible.value = false;
};
// onMounted(() => {
// init();
// });
defineExpose({
openDialog,
});
</script>
<style lang="scss" scoped>
@use "@/assets/scss/index.scss";
.title_1 {
position: absolute;
right: 10px;
top: 65px;
font-size: 14px;
font-weight: 600;
color: #fff;
}
.monitoring-point {
font-size: 14px;
font-weight: 600;
}
.boxTab {
display: flex;
}
</style>

View File

@@ -0,0 +1,653 @@
<!-- 谐波放大表格详情 -->
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" title="趋势图" draggable width="70%"
@close="handleCloseDialog">
<el-radio-group v-model="condition" size="small" @change="init">
<el-radio-button label="谐波电压" value="42" />
<el-radio-button label="谐波电流" value="43" />
</el-radio-group>
<MyEChart v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.8)" :options="list[0]?.option"
:style="`height:670px`" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MyEChart from "@/components/echarts/MyEchart.vue";
import { getHistoryResult } from "@/api/manage_wx/index";
import { yMethod } from "@/utils/echartMethod";
const emit = defineEmits(["close"]);
const visible = ref(false);
const loading = ref(false);
const options = ref<any>(null);
const list = ref<any>([]);
const traceability = ref<any>([]);
const rowData: any = ref({});
const condition: any = ref('42');
const open = async (row: any) => {
rowData.value = row;
condition.value = '42';
visible.value = true;
init()
};
const init = async () => {
list.value = [];
loading.value = true;
await getHistoryResult({
lineId: [rowData.value.monitorId],
searchBeginTime: rowData.value.startTime,
searchEndTime: rowData.value.endTime,
valueType: 4,
harmonic: rowData.value.harmonicCount,
ptType: 0,
condition: [condition.value],
}).then((res) => {
shujuchuli(res);
});
}
const shujuchuli = (res: any) => {
list.value = [];
let shujuData = res.data;
shujuData.forEach((item: any, i: number) => {
//判断是否存在暂降点
if (item.eventDetail == null) {
let [min, max] = yMethod([item.minValue, item.maxValue, condition.value == '42' ? rowData.value.vavgValue : rowData.value.vavgValue.iavgValue]
);
//判断是否有限值(有上下限)
if (item.topLimit !== 0 && item.lowerLimit !== 0) {
item.phaiscType.push("上限");
item.phaiscType.push("下限");
if (item.minValue !== null && item.maxValue !== null) {
//最小值等于下限值
//图列为A,B,C,上限,下限
if (item.phaiscType.length == 5) {
let avalue = [];
let bvalue = [];
let cvalue = [];
let topLimit = [];
let lowerLimit = [];
item.maxValue = item.topLimit;
item.minValue = item.lowerLimit;
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
//上下限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
lowerLimit.push([item.value[j][0], item.lowerLimit.toFixed(3)]);
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
topLimit: topLimit,
lowerLimit: lowerLimit,
};
list.value.push(shuju);
}
//图列为频率等,上限,下限
if (item.phaiscType.length == 3) {
let gvalue = [];
let topLimit = [];
let lowerLimit = [];
item.maxValue = item.topLimit;
item.minValue = item.lowerLimit;
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
//上下限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
lowerLimit.push([item.value[j][0], item.lowerLimit.toFixed(3)]);
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
gvalue: gvalue,
topLimit: topLimit,
lowerLimit: lowerLimit,
chufa: [],
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
//有上限值
if (item.topLimit !== 0 && item.lowerLimit == 0) {
item.phaiscType.push("上限");
if (item.minValue !== null) {
//最小值等于下限值
//图列为A,B,C,上限
if (item.phaiscType.length == 4) {
let avalue = [];
let bvalue = [];
let cvalue = [];
let topLimit = [];
item.maxValue = item.topLimit;
// item.minValue=item.lowerLimit
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
//上限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
maxValue: max,
minValue: min,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
topLimit: topLimit,
};
list.value.push(shuju);
}
//图列为频率等,上限
if (item.phaiscType.length == 2) {
let gvalue = [];
let topLimit = [];
item.maxValue = item.topLimit;
// item.minValue=item.lowerLimit
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
//上限值
topLimit.push([item.value[j][0], item.topLimit.toFixed(3)]);
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
gvalue: gvalue,
topLimit: topLimit,
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
//无限值
if (item.topLimit == 0 && item.lowerLimit == 0) {
if (item.minValue !== null) {
//最小值等于下限值
//图列为A,B,C
if (item.phaiscType.length == 3) {
let avalue = [];
let bvalue = [];
let cvalue = [];
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
avalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
avalue.push([]);
}
//判断存在缺失值b
if (item.value[j][2] != undefined) {
bvalue.push([item.value[j][0], item.value[j][2].toFixed(3)]);
} else {
bvalue.push([]);
}
//判断存在缺失值c
if (item.value[j][3] != undefined) {
cvalue.push([item.value[j][0], item.value[j][3].toFixed(3)]);
} else {
cvalue.push([]);
}
}
//数据为空
} else {
avalue = [];
bvalue = [];
cvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
avalue: avalue,
bvalue: bvalue,
cvalue: cvalue,
};
list.value.push(shuju);
}
//图列为频率等
if (item.phaiscType.length == 1) {
let gvalue = [];
//判断数据是否存在
if (item.value !== null) {
for (let j = 0; j < item.value.length; j++) {
//判断存在缺失值a
if (item.value[j][1] != undefined) {
gvalue.push([item.value[j][0], item.value[j][1].toFixed(3)]);
} else {
gvalue.push([]);
}
}
//数据为空
} else {
gvalue = [];
}
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: min,
maxValue: max,
gvalue: gvalue,
};
list.value.push(shuju);
}
} else {
let shuju = {
id: "qushifenx" + i,
title: item.lineName + "--" + item.targetName,
targetName: item.targetName,
legend: item.phaiscType,
valueName: item.unit[0],
minValue: null,
avalue: [],
bvalue: [],
cvalue: [],
topLimit: [],
lowerLimit: [],
chufa: [],
};
list.value.push(shuju);
}
}
}
});
rendering();
};
const rendering = () => {
list.value.forEach((item: any) => {
let opitonserise: any[] = [];
item.legend.forEach((item2: any) => {
if (
item.avalue !== undefined &&
(item2 == "A相" || item2 == "AB相" || item2 == "零序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
itemStyle: {
normal: {
color: "#DAA520",
},
},
data: item.avalue,
};
opitonserise.push(data);
} else if (
item.bvalue !== undefined &&
(item2 == "B相" || item2 == "BC相" || item2 == "正序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
itemStyle: {
normal: {
color: "#2E8B57",
},
},
data: item.bvalue,
};
opitonserise.push(data);
} else if (
item.cvalue !== undefined &&
(item2 == "C相" || item2 == "CA相" || item2 == "负序电压")
) {
let data = {
name: item2,
symbol: "none",
symbolSize: 5,
type: "line",
smooth: true,
barWidth: 22,
itemStyle: {
normal: {
color: "#A52a2a",
},
},
data: item.cvalue,
};
opitonserise.push(data);
}
});
if (item.valueName == undefined) {
item.valueName = "无";
}
opitonserise[0].markLine = {
itemStyle: {
normal: {
lineStyle: {
type: "dashed", //dotted、solid
color: "#FF33FF",
width: 2,
},
},
},
label: {
normal: {
color: "#fff",
formatter: function (params) {
return `标准值`;
},
},
},
data: [
{
name: "标准值",
yAxis: condition.value == '42' ? rowData.value.vavgValue : rowData.value.iavgValue,
},
],
};
// console.log("🚀 ~ rendering ~ row.value:", rowData.value);
// console.log("🚀 ~ rendering ~ opitonserise:", opitonserise);
item.serise = opitonserise;
});
getEcharts();
};
const getEcharts = () => {
list.value.forEach((item: any, i: number) => {
console.log("🚀 ~ getEcharts ~ item:", item)
item.option = {
backgroundColor: "#fff",
title: {
left: "center",
text: item.title,
},
tooltip: {
top: "10px",
trigger: "axis",
borderColor: "grey",
style: {
color: "#fff",
fontSize: "15px",
padding: 10,
},
formatter: function (params) {
// console.log(params)
let tips = "";
tips += "时刻:" + params[0].data[0] + "</br/>";
for (let i = 0; i < params.length; i++) {
if (params[i].seriesName != "暂降触发点") {
tips +=
params[i].marker +
params[i].seriesName +
":" +
(params[i].value[1] - 0).toFixed(2) +
"<br/>";
}
}
return tips;
},
// axisPointer: {
// type: "cross",
// label: {
// color: "#fff",
// fontSize: 16,
// },
// },
textStyle: {
color: "#fff",
fontStyle: "normal",
opacity: 0.35,
fontSize: 14,
},
backgroundColor: "rgba(0,0,0,0.55)",
borderWidth: 0,
},
legend: {
right: 50,
top: 25,
verticalAlign: "top",
enabled: true,
itemDistance: 5,
textStyle: {
fontSize: "0.6rem",
color: "#fff",
rich: {
a: {
verticalAlign: "middle",
},
},
padding: [0, 0, 0, 0], //[上、右、下、左]
},
},
xAxis: [
{
type: "time",
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: "#fff",
},
},
axisLabel: {
textStyle: {
fontFamily: "dinproRegular",
},
},
},
],
toolbox: {
show: false,
feature: {
dataZoom: {
// bottom: '10px',
yAxisIndex: "none",
},
},
},
yAxis: [
{
type: "value",
min: item.minValue,
max: item.maxValue,
name: item.valueName,
axisLine: {
show: true,
onZero: false, //-----------重点
lineStyle: {
color: "#fff",
},
},
splitLine: {
lineStyle: {
// 使用深浅的间隔色
type: "dashed",
opacity: 0.5,
},
},
},
],
series: item.serise,
};
let aValues = [];
let bValues = [];
let CValues = [];
let ZValues = [];
if (
traceability.value.length > 0 &&
traceability.value[0].value != null &&
traceability.value[0].value.length > 0
) {
for (let i = 0; i < traceability.value[0].value.length; i++) {
let T = traceability.value[0].value[i][0];
let A = traceability.value[0].value[i][1];
let B = traceability.value[0].value[i][2];
let C = traceability.value[0].value[i][3];
let Z = A + B + C;
aValues.push([T, A > 0 ? 1 : A == 0 ? 0 : -1]);
bValues.push([T, B > 0 ? 1 : B == 0 ? 0 : -1]);
CValues.push([T, C > 0 ? 1 : C == 0 ? 0 : -1]);
ZValues.push([T, Z > 0 ? 1 : Z == 0 ? 0 : -1]);
}
}
});
loading.value = false;
};
const handleCloseDialog = () => {
visible.value = false;
options.value = null;
emit("close");
};
defineExpose({
open,
});
</script>

View File

@@ -0,0 +1,583 @@
<template>
<div class="container">
<!-- 使用 v-for 遍历四个角落 -->
<div
v-for="corner in corners"
:key="corner.id"
v-show="corner.show"
:class="['corner', corner.className]"
>
<div class="content">
<div class="title">{{ corner.title }}</div>
<el-descriptions :column="1" size="small" label-width="70px" border>
<el-descriptions-item
v-for="(item, index) in corner.data"
:key="index"
:label="item.label"
>
<!-- {{ item.value }} -->
<div v-html="item.value" v-if="item.label !== '暂降次数'"></div>
<!-- 跑马灯 -->
<div v-else style="display: flex">
<div style="width: 30px">{{ corner.raceLists.length }}</div>
<div class="simple-marquee">
<div class="marquee-content">
<span
style="margin-right: 15px"
v-for="(event, index) in corner.raceLists"
:key="index"
@click="goToRace(event)"
>{{
index +
1 +
"、" +
event.startTime +
"发生" +
event.eventType +
"," +
"残余电压:" +
(event.featureAmplitude * 100).toFixed(2) +
"%" +
"," +
"持续时间:" +
event.duration +
"S"
}}</span
>
</div>
</div>
</div>
</el-descriptions-item>
</el-descriptions>
</div>
<span class="close-btn" @click="closeCorner(corner.id)">
<Close />
</span>
</div>
<DipDetail ref="dipDetail"></DipDetail>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, reactive } from "vue";
import { clickImage, realTimeData } from "@/api/manage_wx";
import { Close } from "@element-plus/icons-vue";
import socketClient from "@/utils/webSocketClient";
import DipDetail from "../eventStatistics/dipDetail.vue";
// 定义接收的 props
const props = defineProps<{
eventList?: [];
}>();
//开始创建webSocket客户端
const dataSocket = reactive({
socketServe: socketClient.Instance,
});
// 定义四个角落的数据
const corners = ref([
{
id: "topLeft",
title: "左上",
className: "top-left",
show: false,
data: [] as { label: string; value: string }[],
elementId: "", // 记录该角落对应的元素ID
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
{
id: "topRight",
title: "右上",
className: "top-right",
show: false,
data: [] as { label: string; value: string }[],
elementId: "",
raceLists: [] as any[], // 为每个角落添加独立的跑马灯数据存储
},
// {
// id: "bottomLeft",
// title: "左下",
// className: "bottom-left",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
// {
// id: "bottomRight",
// title: "右下",
// className: "bottom-right",
// show: false,
// data: [] as { label: string; value: string }[],
// elementId: "",
// },
]);
const steadyStateList = ref([]);
const selectedId = ref("");
// 内部响应式数据
const eventList = ref([]);
// 点击跑马灯展示弹框
const dipDetail = ref(null);
const handleClickImage = async (elementId: string) => {
// 检查 elementId 是否有值,没有值则直接返回空数组
if (!elementId) {
eventList.value = [];
return;
}
try {
// 发送点击图片请求
const res = await clickImage({ lineId: elementId });
// 确保返回的数据是数组格式,并且过滤掉 null/undefined 元素
let dataToStore: any[] = [];
if (Array.isArray(res.data)) {
dataToStore = res.data.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data && Array.isArray(res.data.records)) {
dataToStore = res.data.records.filter(
(item) => item !== null && item !== undefined
);
} else if (res.data) {
// 如果是单个对象且不为 null
if (res.data !== null && res.data !== undefined) {
dataToStore = [res.data];
}
}
eventList.value = dataToStore;
} catch (error) {
console.error("调用 clickImage 接口出错:", error);
// 出错时设置为空数组,避免后续处理出错
eventList.value = [];
}
};
// 监听 props 变化
// watch(
// () => props.eventList,
// (newVal) => {
// if (newVal && Array.isArray(newVal)) {
// eventList.value = [...newVal];
// dataLoaded.value = true;
// } else {
// dataLoaded.value = false;
// }
// },
// { immediate: true, deep: true }
// );
// 记录显示顺序,用于循环替换
const displayOrder = ref<number[]>([]);
// 计算跑马灯动画时长(毫秒)
const calculateMarqueeDuration = (corner) => {
if (!corner.raceLists || corner.raceLists.length === 0) {
return 25000; // 默认25秒
}
// 计算所有事件文本的总长度
let totalTextLength = 0;
corner.raceLists.forEach((event, index) => {
const text = `${index + 1}${event.startTime}发生${
event.eventType
},残余电压:${(event.featureAmplitude * 100).toFixed(2)}%,持续时间:${
event.duration
}S`;
totalTextLength += text.length;
});
// 添加分隔符长度和边距
const separatorLength =
corner.raceLists.length > 1 ? (corner.raceLists.length - 1) * 15 : 0; // margin-right: 15px
totalTextLength += separatorLength;
// 根据文本长度计算时长(减慢速度)
// 将每字符需要的时间从50ms增加到80ms最少8秒最多90秒
const duration = Math.min(Math.max(totalTextLength * 80, 8000), 90000);
return duration;
};
// 更新指定角落数据的函数
const updateCornerData = (
cornerIndex: number,
dataItem: any,
elementId: string
) => {
// 更新标题为 objName
if (dataItem.objName) {
corners.value[cornerIndex].title = dataItem.objName;
} else {
corners.value[cornerIndex].title = dataItem.stationName;
}
// 格式化数据
corners.value[cornerIndex].data = [
{ label: "监测点", value: dataItem.lineName },
// {
// label: "暂降次数",
// value: dataItem.eventIds.length,
// },
{
label: "暂降次数",
value: ``,
},
// { label: "稳态指标", value: "Ua:65.5 Ub:65.02 Uc:65.27 Uac:112.85 Uab:112.67 Ubc:112.85" },
{
label: "稳态指标",
value: ``,
},
];
// 记录该角落对应的元素ID
corners.value[cornerIndex].elementId = elementId;
corners.value[cornerIndex].show = true;
// 设置动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corners.value[cornerIndex]);
const marqueeElement = document.querySelector(
`.${corners.value[cornerIndex].className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
};
// 显示下一个角落的函数
const showNextCorner = (elementId: string) => {
// 检查该元素ID是否已经显示过
const existingCornerIndex = corners.value.findIndex(
(corner) => corner.elementId === elementId && corner.show
);
if (existingCornerIndex !== -1) {
// 如果该元素已经显示过,不更新数据
return;
}
// 确保 eventList.value 是数组并且过滤掉 null/undefined 元素
if (!Array.isArray(eventList.value)) {
console.warn("eventList.value 不是数组格式:", eventList.value);
return;
}
// 过滤掉 null 和 undefined 元素,然后查找匹配项
const validItems = eventList.value.filter(
(item) => item !== null && item !== undefined
);
const dataItem = validItems.find((item) => item.lineId === elementId);
// 如果没有找到匹配的数据项,则不更新数据
if (!dataItem) {
console.warn("未找到匹配的数据项:", elementId);
return;
}
// 查找一个未显示的角落
const availableCornerIndex = corners.value.findIndex(
(corner) => !corner.show
);
if (availableCornerIndex !== -1) {
// 有空闲角落,显示在该角落
updateCornerData(availableCornerIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[availableCornerIndex].raceLists = dataItem.eventList || [];
// 记录显示顺序
displayOrder.value.push(availableCornerIndex);
} else {
// 没有空闲角落,按顺序替换角落
// 获取需要替换的角落索引(循环替换)
const replaceIndex = displayOrder.value.shift() || 0;
updateCornerData(replaceIndex, dataItem, elementId);
// 将事件数据存储到该角落
corners.value[replaceIndex].raceLists = dataItem.eventList || [];
// 将替换的索引重新加入队列末尾
displayOrder.value.push(replaceIndex);
}
};
// 关闭指定角落的函数
const closeCorner = (id: string) => {
const cornerIndex = corners.value.findIndex((c) => c.id === id);
if (cornerIndex !== -1) {
corners.value[cornerIndex].show = false;
corners.value[cornerIndex].elementId = ""; // 清空元素ID记录
// 从显示顺序中移除该角落索引
const orderIndex = displayOrder.value.indexOf(cornerIndex);
if (orderIndex !== -1) {
displayOrder.value.splice(orderIndex, 1);
}
}
send();
};
// 关闭所有角落的函数
const closeAllCorners = () => {
corners.value.forEach((corner) => {
corner.show = false;
corner.elementId = "";
});
displayOrder.value = [];
};
// 组件挂载后初始化监听器
onMounted(() => {
init();
// 初始化时不显示任何内容
});
// 连接webSocket客户端
const init = () => {
if (!dataSocket.socketServe) {
console.error("WebSocket 客户端实例不存在");
return;
}
dataSocket.socketServe.connect(new Date().getTime());
dataSocket.socketServe.registerCallBack("message", (res: any) => {
if (res.type == 1) {
//稳态指标数据
let steadyState = JSON.parse(res.message);
// console.log(steadyState, "8990hhhhh");
if (steadyState == null || steadyState.length == 0) return;
steadyStateList.value = steadyState;
corners.value.forEach((corner, index) => {
let str = ``;
steadyState
.filter((item) => item.lineId == corner.elementId)
.forEach((item) => {
if (item.value == 3.1415926) {
str += `<div>${item.statisticalName}/</div>`;
} else {
str += `<div>${item.statisticalName}${item.value}${item.unit}</div>`;
}
});
corner.data.length > 0
? (corner.data[2].value = `<div style="max-height: 100px;overflow-y: auto;">${str} </div>`)
: "";
// 更新跑马灯动画时长
nextTick(() => {
const duration = calculateMarqueeDuration(corner);
const marqueeElement = document.querySelector(
`.${corner.className} .marquee-content`
);
if (marqueeElement) {
(marqueeElement as HTMLElement).style.setProperty(
"--marquee-duration",
`${duration}ms`
);
}
});
});
}
});
};
const time = ref(null);
// 推送消息
const send = () => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
if (time.value) {
clearTimeout(time.value);
}
if (
corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId).length == 0
)
return;
time.value = setInterval(() => {
dataSocket.socketServe.send({
pageId: selectedId.value,
lineIdList: corners.value
.filter((item) => item.show == true)
.map((corner) => corner.elementId),
});
}, 1000 * 60);
};
const goToRace = (row) => {
console.log(row.measurementPointId, "1222");
dipDetail.value.open(row.measurementPointId);
};
// 监听来自 iframe 的消息
window.addEventListener("message", async function (event) {
// 安全起见可以验证消息来源origin
// if (event.origin !== 'https://trusted-origin.com') return;
// 处理从 iframe 发送过来的消息
if (event.data.action === "coreClick") {
const clickedElementId = event.data.coreId;
selectedId.value = event.data.selectedId;
// 调用接口获取最新数据
await handleClickImage(clickedElementId);
// 根据接收到的元素LineId显示对应数据
await showNextCorner(clickedElementId);
await send();
}
});
// 页面卸载时清除定时器
onBeforeUnmount(() => {
clearTimeout(time.value);
dataSocket.socketServe?.closeWs();
});
</script>
<style>
/* 走马灯样式 - 支持逐条显示 */
.simple-marquee {
width: 230px;
height: 24px;
overflow: hidden;
position: relative;
white-space: nowrap;
}
.marquee-content {
display: inline-block;
padding-left: 100%;
/* animation: marquee-single 15s linear infinite; */
animation: marquee-single var(--marquee-duration, 25s) linear infinite;
line-height: 24px;
text-decoration: underline;
text-decoration-color: #4877f6;
}
@keyframes marquee-single {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
/* 鼠标悬停时暂停动画 */
.simple-marquee:hover .marquee-content {
animation-play-state: paused;
cursor: pointer;
}
</style>
<style scoped lang="scss">
.container {
position: relative;
}
.corner {
width: 340px;
/* height: 135px; */
background-color: #2b2d3a90;
position: absolute;
color: white;
/* font-weight: bold; */
/* 添加弹出动画 */
opacity: 0;
transform: scale(0.3);
transition: all 0.4s ease-out;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* 显示状态的样式 */
.corner:not([style*="display: none"]):not([style*="display:none"]) {
opacity: 1;
transform: scale(1);
}
.top-left {
top: 10px;
left: 10px;
}
.top-right {
top: 10px;
right: 10px;
}
.bottom-left {
top: 170px;
left: 10px;
}
.bottom-right {
top: 170px;
right: 10px;
}
.content {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.title {
font-size: 16px;
/* text-align: center; */
/* margin-bottom: 8px; */
padding: 8px;
/* color: #409eff; */
border-bottom: 1px solid #444;
background-color: #21232b;
border-radius: 8px 8px 0 0;
}
.data-item {
display: flex;
margin-bottom: 4px;
font-size: 12px;
}
.label {
/* font-weight: bold; */
width: 55px;
flex-shrink: 0;
}
.value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 关闭按钮样式 */
.close-btn {
position: absolute;
top: 10px;
right: 10px;
width: 14px;
color: white;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
.indicator {
display: flex;
}
</style>

Some files were not shown because too many files have changed in this diff Show More