10 Commits

Author SHA1 Message Date
466d552165 签出调整 2025-11-26 10:53:52 +08:00
9d23303919 Merge remote-tracking branch 'origin/qr_branch' into qr_branch 2025-11-26 08:57:28 +08:00
83cdc4ba30 签出调整 2025-11-26 08:56:58 +08:00
caozehui
29efbae717 源未知异常处理 2025-11-06 08:42:04 +08:00
caozehui
a5177860de 微调 2025-09-03 14:23:05 +08:00
caozehui
66019e0d3c 闪变添加4000变动频度 2025-08-04 10:02:22 +08:00
caozehui
0ca992134f 微调 2025-08-01 09:44:00 +08:00
caozehui
128b02c145 微调 2025-08-01 08:46:44 +08:00
caozehui
bbc08679b9 微调 2025-08-01 08:42:26 +08:00
caozehui
bf8b8c9f0f NPQS-581表单默认加密、密钥和识别码为万能的 2025-07-31 14:16:27 +08:00
142 changed files with 16901 additions and 23361 deletions

View File

@@ -25,10 +25,10 @@ module.exports = (appInfo) => {
*/
config.windowsOption = {
title: '自动检测平台',
width: 1920 /1.5,
height: 1080 /1.2,
minWidth: 1920 /1.5,
minHeight: 1080 /1.2,
width: 1600,
height: 950,
minWidth: 1600,
minHeight: 950,
webPreferences: {
//webSecurity: false,
contextIsolation: false, // false -> 可在渲染进程中使用electron的apitrue->需要bridge.js(contextBridge)

View File

@@ -9,7 +9,7 @@ module.exports = (appInfo) => {
/**
* 开发者工具
*/
config.openDevTools = false;
config.openDevTools = true;
/**
* 应用程序顶部菜单

View File

@@ -14,10 +14,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@types/event-source-polyfill": "^1.0.5",
"@vue-flow/core": "^1.45.0",
"@vueuse/core": "^10.4.1",
"autofit.js": "^3.2.8",
"axios": "^1.7.3",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.9",
@@ -25,7 +22,6 @@
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
"element-plus": "^2.7.8",
"event-source-polyfill": "^1.0.31",
"html2canvas": "^1.4.1",
"md5": "^2.3.0",
"mitt": "^3.0.1",
@@ -76,11 +72,11 @@
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"unplugin-vue-setup-extend-plus": "^1.0.0",
"vite": "^5.4.19",
"vite": "^5.3.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-node-polyfills": "^0.24.0",
"vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^0.16.5",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^2.0.21"

View File

@@ -1,25 +1,27 @@
<template>
<!--element-plus语言国际化全局修改为中文-->
<el-config-provider :locale="locale" :size="assemblySize" :button="buttonConfig">
<router-view />
</el-config-provider>
<!--element-plus语言国际化全局修改为中文-->
<el-config-provider
:locale='locale'
:size='assemblySize'
:button='buttonConfig'
>
<router-view />
</el-config-provider>
</template>
<script lang="ts" setup>
import autofit from 'autofit.js'
<script lang='ts' setup>
defineOptions({
name: 'App',
})
import { useI18n } from 'vue-i18n'
import { getBrowserLang } from '@/utils'
import { useTheme } from '@/hooks/useTheme'
import { ElConfigProvider } from 'element-plus'
import { type LanguageType } from './stores/interface'
import { LanguageType } from './stores/interface'
import { useGlobalStore } from '@/stores/modules/global'
import en from 'element-plus/es/locale/lang/en'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
defineOptions({
name: 'App'
})
const globalStore = useGlobalStore()
// init theme
@@ -29,26 +31,16 @@ initTheme()
// init language
const i18n = useI18n()
onMounted(() => {
const language = globalStore.language ?? getBrowserLang()
i18n.locale.value = language
globalStore.setGlobalState('language', language as LanguageType)
// 自动适配
autofit.init({
el: '#app',
//dh: 720 * 1,
//dw: 1280 * 1.2,
dw: 1920 / 1.5,
dh: 1080 / 1.2,
resize: true,
limit: 0.1
})
const language = globalStore.language ?? getBrowserLang()
i18n.locale.value = language
globalStore.setGlobalState('language', language as LanguageType)
})
// element language
const locale = computed(() => {
if (globalStore.language == 'zh') return zhCn
if (globalStore.language == 'en') return en
return getBrowserLang() == 'zh' ? zhCn : en
if (globalStore.language == 'zh') return zhCn
if (globalStore.language == 'en') return en
return getBrowserLang() == 'zh' ? zhCn : en
})
// element assemblySize

View File

@@ -1,158 +1,126 @@
export namespace CheckData {
export interface DataCheck {
scriptName: string
errorSysId: string
dataRule: string
deviceName: string
chnNum: string
deviceId: string
num?: string | number
scriptName: string,
errorSysId: string,
dataRule: string,
deviceName: string,
chnNum: string,
}
export interface PhaseCheckResult {
// 检测源定值-标准值
resultData: number
resultData: number,
// 装置原始数据-被检值
data: number
data: number,
// 误差值
errorData: number
errorData: number,
// 第几次谐波
num?: number
num?: number,
//符合、不符合
isData?: number
isData?: number,
//最大误差值
radius?: string
unit?: string
}
export interface DataItem {
num: number
isData: number
data: number
resultData: number
radius: string
errorData: number
unit: string
}
export interface TableRow {
isData: number
harmNum: number
radius: string
dataA: DataItem
dataB: DataItem
dataC: DataItem
dataT: DataItem | null
unit: string
timeDev?: string
uaDev?: string | number
ubDev?: string | number
ucDev?: string | number
utDev?: string | number
uaStdDev?: string | number
ubStdDev?: string | number
ucStdDev?: string | number
utStdDev?: string | number
radius?: string,
unit?: string,
}
/**
* 用于定义 查看(设备)通道检测结果响应数据 类型
*/
export interface ResCheckResult {
dataA?: PhaseCheckResult | null
dataB?: PhaseCheckResult | null
dataC?: PhaseCheckResult | null
dataT?: PhaseCheckResult | null
dataA?: PhaseCheckResult | null,
dataB?: PhaseCheckResult | null,
dataC?: PhaseCheckResult | null,
dataT?: PhaseCheckResult | null,
// 第几次谐波
//num: number | null,
//符合、不符合
isData?: number
isData?: number,
//最大误差值
radius?: string
radius?: string,
//单位
unit?: string
unit?: string,
}
/**
* 用于定义 查看(设备)通道检测结果表格展示数据 类型
*/
export interface CheckResult {
stdA?: string
dataA?: string
errorA?: string
maxErrorA?: string
isDataA?: number
unitA?: string
stdB?: string
dataB?: string
errorB?: string
maxErrorB?: string
isDataB?: number
unitB?: string
stdC?: string
dataC?: string
errorC?: string
maxErrorC?: string
isDataC?: number
unitC?: string
stdT?: string
dataT?: string
errorT?: string
maxErrorT?: string
isDataT?: number
unitT?: string
stdA?: string,
dataA?: string,
errorA?: string,
maxErrorA?: string,
isDataA?: number,
unitA?: string,
stdB?: string,
dataB?: string,
errorB?: string,
maxErrorB?: string,
isDataB?: number,
unitB?: string,
stdC?: string,
dataC?: string,
errorC?: string,
maxErrorC?: string,
isDataC?: number,
unitC?: string,
stdT?: string,
dataT?: string,
errorT?: string,
maxErrorT?: string,
isDataT?: number,
unitT?: string,
//最大误差值
maxError?: string
unit?: string
maxError?: string,
unit?: string,
//符合、不符合
result?: number
result?: number,
}
/**
* 用于定义 具体通道的原始数据类型
*/
export interface RawDataItem {
time?: string
harmNum?: number | null
dataA?: string
dataB?: string
dataC?: string
dataT?: string
time?: string,
harmNum?: number | null,
dataA?: string,
dataB?: string,
dataC?: string,
dataT?: string,
unit?: string | null
}
export interface Device {
deviceId: string //装置序号Id
deviceName: string //设备名称
chnNum: number //设备通道数
deviceId: string; //装置序号Id
deviceName: string; //设备名称
chnNum: number; //设备通道数
planId: string //计划Id
devType: string //设备类型
devVolt: number //设备电压
devCurr: number //设备电流
factorFlag: number //是否支持系数校准
checkResult: number //检测结果
chnNumList: string[] //连线存储数据
planId: string; //计划Id
devType: string; //设备类型
devVolt: number; //设备电压
devCurr: number; //设备电流
factorFlag: number; //是否支持系数校准
checkResult:number; //检测结果
}
// 用来描述检测脚本类型
export interface ScriptItem {
id: string
code: string
scriptName: string
id: string,
code: string,
scriptName: string,
}
// 用来描述 检测数据-左侧树结构
export interface TreeItem {
id: string | null
scriptTypeName: string | null
sourceDesc: string | null
harmNum: number | null
index: number | null
fly: number | null
children: TreeItem[] | null
id: string | null,
scriptTypeName: string | null,
sourceDesc: string | null,
harmNum: number | null,
index: number | null,
fly: number | null,
children: TreeItem[] | null,
}
// 用来描述 通道检测结果
@@ -167,17 +135,16 @@ export namespace CheckData {
}
export interface DeviceCheckResult {
deviceId: string
deviceName: string
code?: string
deviceId: string,
deviceName: string,
chnResult: ChnCheckResultEnum[] //通道检测结果
}
//用来描述 某个脚本测试项对所有通道的检测结果
export interface ScriptChnItem {
scriptType: string
scriptName?: string //可以不要该属性,有点多余
code?: string
scriptName?: string //可以不要该属性,有点多余
// 设备
devices: Array<DeviceCheckResult>
}
@@ -187,7 +154,7 @@ export namespace CheckData {
LOADING = 'var(--el-color-primary)',
SUCCESS = '#91cc75',
WARNING = '#e6a23c',
DANGER = '#f56c6c'
DANGER = '#f56c6c',
}
/**
@@ -202,17 +169,18 @@ export namespace CheckData {
* 用于描述 脚本检测结果展示的按钮类型
*/
export interface ScriptChnViewItem {
scriptType: string
scriptType: string,
scriptName?: string //脚本项名称,可以不要该属性,有点多余
// 设备
devices: Array<{
deviceId: string
deviceName: string
chnResult: ButtonResult[]
deviceId: string,
deviceName: string,
chnResult: ButtonResult[],
}>
}
/**
* 定义检测日志类型
*/
@@ -225,16 +193,13 @@ export namespace CheckData {
* 定义手动检测时,勾选的测试项
*/
export interface SelectTestItem {
preTest: boolean
timeTest: boolean
channelsTest: boolean
preTest: boolean,
timeTest: boolean,
channelsTest: boolean,
test: boolean
}
//描述比对式检测项描述
export interface CompareTestItem {
id: string
code: string
name: string
}
}

View File

@@ -1,22 +1,12 @@
import { pa } from 'element-plus/es/locale/index.mjs';
import http from '@/api'
import {CheckData} from '@/api/check/interface'
import http from "@/api";
import {CheckData} from "@/api/check/interface";
export const getBigTestItem = (params: {
reCheckType: number
planId: string
devIds: string[]
patternId: string
reCheckType: number,
planId: string,
devIds: string[],
}) => {
return http.post(`/adPlan/getBigTestItem`, params, {loading: false})
}
export const getScriptList = (params: {
devId:string,
chnNum:number,
num:number
}) => {
return http.post('/result/getCheckItem', params, {loading: false})
return http.post(`/adPlan/getBigTestItem`, params, {loading: false});
}
/**
@@ -24,12 +14,12 @@ export const getScriptList = (params: {
* @param params 当为scriptType为null时表示查询所有脚本类型否则只查询指定脚本类型。当为chnNum为-1时表示查询所有通道否则只查询指定通道。
*/
export const getFormData = (params: {
planId: string
deviceId: string
chnNum: string
planId: string,
deviceId: string,
chnNum: string,
scriptType: string | null
}) => {
return http.post('/result/formContent/', params, {loading: false})
return http.post("/result/formContent/", params, {loading: false});
}
/**
@@ -37,13 +27,13 @@ export const getFormData = (params: {
* @param params
*/
export const getTreeData = (params: {
scriptId?: string
devId?: string
devNum?: string
scriptType?: string | null
code?: string
scriptId?: string,
devId?: string,
devNum?: string,
scriptType?: string | null,
code?: string,
}) => {
return http.post<CheckData.TreeItem[]>('/result/treeData/', params, {loading: false})
return http.post<CheckData.TreeItem[]>("/result/treeData/", params, {loading: false});
}
/**
@@ -51,25 +41,25 @@ export const getTreeData = (params: {
* @param params
*/
export const getTableData = (params: {
scriptType: string | null
scriptId: string
devId: string
devNum: string
code: string
index: number
scriptType: string | null,
scriptId: string,
devId: string,
devNum: string,
code: string,
index: number,
}) => {
return http.post('/result/resultData/', params, {loading: false})
return http.post("/result/resultData/", params, {loading: false});
}
export const exportRawData = (params: {
scriptType: string | null
scriptId: string
devId: string
devNum: string
code: string
index: number
scriptType: string | null,
scriptId: string,
devId: string,
devNum: string,
code: string,
index: number,
}) => {
return http.download('/result/exportRawData', params, {loading: false})
return http.download("/result/exportRawData", params, {loading: false});
}
/**
@@ -77,46 +67,13 @@ export const exportRawData = (params: {
* @param params
*/
export const reCalculate = (params: {
planId: string
scriptId: string
errorSysId: string
deviceId: string
planId: string,
scriptId: string,
errorSysId: string,
deviceId: string,
code: string
patternId: string
}) => {
return http.post('/result/reCalculate', params, {loading: true})
}
/**
* 获取数据获取基本信息
* @param params
*/
export const getContrastFormContent = (params: {
planId: string
scriptType: string
deviceId: string
chnNum: string
num: number | null
patternId: string
}) => {
return http.post('/result/getContrastFormContent', params, {loading: false})
}
/**
* 获取检测结果
* @param params
*/
export const getContrastResult = (params: {
planId: string
scriptType: string
deviceId: string
chnNum: string | number
num: number | string | null
waveNum: number | null
isWave: boolean
patternId: string
}) => {
return http.post('/result/getContrastResult', params, {loading: true})
return http.post("/result/reCalculate", params, {loading: true});
}
/**
@@ -124,14 +81,13 @@ export const getContrastResult = (params: {
* @param params
*/
export const changeErrorSystem = (params: {
planId: string
scriptId: string
errorSysId: string
deviceId: string
planId: string,
scriptId: string,
errorSysId: string,
deviceId: string,
code: string
patternId: string
}) => {
return http.post('/result/changeErrorSystem', params, {loading: true})
return http.post("/result/changeErrorSystem", params, {loading: true});
}
/**
@@ -140,4 +96,4 @@ export const changeErrorSystem = (params: {
*/
export const deleteTempTable = (code: string) => {
return http.get(`/result/deleteTempTable?code=${code}`, null, {loading: false})
}
}

View File

@@ -266,4 +266,172 @@ const data = [
reCheck_Num: 0, //复检次数
},
]
// const plan_devicedata = [
// {
// id: '1', //装置序号ID
// name: '模拟装置1', //设备名称
// dev_Type: 'PQS882A',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 0, //复检次数
// },
// {
// id: '2', //装置序号ID
// name: '模拟装置2', //设备名称
// dev_Type: 'PQS882B4',//设备类型
// dev_Chns: 4, //设备通道数
// check_Result: '/', //检测结果
// report_State: '未生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'未检',//检测状态
// reCheck_Num: 0, //复检次数
// },
// {
// id: '3', //装置序号ID
// name: '模拟装置3', //设备名称
// dev_Type: 'PQS882B4',//设备类型
// dev_Chns: 4, //设备通道数
// check_Result: '/', //检测结果
// report_State: '未生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测中',//检测状态
// reCheck_Num: 0, //复检次数
// },
// {
// id: '4', //装置序号ID
// name: '模拟装置4', //设备名称
// dev_Type: 'PQS882B4',//设备类型
// dev_Chns: 4, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '未生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 1, //复检次数
// },
// {
// id: '5', //装置序号ID
// name: '中电测试装置', //设备名称
// dev_Type: 'PMC-680M-22-22-00-115ANBC',//设备类型
// dev_Chns: 4, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '未生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 1, //复检次数
// },
// {
// id: '6', //装置序号ID
// name: '易司拓测试装置1', //设备名称
// dev_Type: 'E703A',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 1, //复检次数
// },
// {
// id: '7', //装置序号ID
// name: '易司拓测试装置2', //设备名称
// dev_Type: 'E703A',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 1, //复检次数
// },
// {
// id: '8', //装置序号ID
// name: '山大电力测试装置1', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 1, //复检次数
// },
// {
// id: '9', //装置序号ID
// name: '山大电力测试装置2', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '10', //装置序号ID
// name: '山大电力测试装置2', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '11', //装置序号ID
// name: '山大电力测试装置2', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '12', //装置序号ID
// name: '山大电力测试装置2', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '13', //装置序号ID
// name: '山大电力测试装置2', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '14', //装置序号ID
// name: '山大电力测试装置3', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// {
// id: '15', //装置序号ID
// name: '山大电力测试装置4', //设备名称
// dev_Type: 'SDL-3002C',//设备类型
// dev_Chns: 1, //设备通道数
// check_Result: '不合格', //检测结果
// report_State: '已生成', //报告状态
// document_State: '未归档', //归档状态
// check_State:'检测完成',//检测状态
// reCheck_Num: 2, //复检次数
// },
// ]
export default {data,plan_devicedata}

View File

@@ -1,4 +1,3 @@
import { pa } from 'element-plus/es/locale/index.mjs';
import type {Device} from '@/api/device/interface/device'
import http from '@/api'
@@ -31,8 +30,8 @@ export const exportPqDev = (params: Device.ReqPqDevParams) => {
return http.download(`/pqDev/export`, params)
}
// 下载导入文件模板
export const downloadTemplate = (params: { pattern: string }) => {
return http.download(`/pqDev/downloadTemplate`,params)
export const downloadTemplate = () => {
return http.download(`/pqDev/downloadTemplate`)
}
//导入被检设备
@@ -60,15 +59,8 @@ export const importContrastPqDev = (params: Device.ReqPqDevParams) => {
// return http.uploadExcel(`/pqDev/importCNDev`, params)
// }
export const getPqDevById = (params: Device.ReqPqDevParams) => {
return http.get(`/pqDev/getById?id=${params.id}`)
}
//根据设备类型决定电源、icd、模板、通道数、额定电压、额定电流
export const getPqDev = () => {
return http.get(`/devType/listAll`)
}
export const getSelectOptions = (params:{ pattern: string }) => {
return http.get(`/pqDev/getSelectOptions`, params)
}

View File

@@ -25,7 +25,6 @@ export namespace DevType {
devChns: number; //设备通道数
reportName: string| null;//报告模版名称
state: number;
waveCmd:string| null;//录波指令
createBy?: string| null; //创建用户
createTime?: string| null; //创建时间
updateBy?: string| null; //更新用户

View File

@@ -1,119 +1,97 @@
import type { ReqPage, ResPage } from '@/api/interface'
import type { Monitor } from './monitor'
import type {ReqPage, ResPage} from '@/api/interface'
// 被检设备模块
export namespace Device {
/**
* 被检设备表格分页查询参数
*/
export interface ReqPqDevParams extends ReqPage {
id: string // 装置序号id 必填
name: string //设备名称
devType?: string // 设备名称
createTime?: string //创建时间
pattern: string
id: string; // 装置序号id 必填
name: string; //设备名称
devType?: string; // 设备名称
createTime?: string; //创建时间
pattern: string;
}
/**
* 被检设备表格分页查询参数
*/
export interface ReqDevReportParams extends ReqPage {
planId?: string // 计划id
devId?: string // 装置id
scriptId?: string // 脚本id
planCode?: string
devIdList?: string[] // 装置id列表
planId?: string; // 计划id
devId?: string; // 装置id
scriptId?: string; // 脚本id
planCode?: string;
devIdList?: string[]; // 装置id列表
}
/**
* 被检设备新增、修改、根据id查询返回的对象
*/
export interface ResPqDev {
id: string //装置序号ID
name: string //设备名称
pattern: string //设备模式 模拟 数字 比对
devType: string //设备类型
manufacturer?: string | null //生产厂家
createDate: string //生产日期
createId: string //出厂编号
hardwareVersion: string //固件版本
softwareVersion: string //软件版本
protocol: string //通讯协议
ip: string //IP地址
port: number //端口号
encryptionFlag: number //装置是否为加密版本
series?: string | null //装置识别码3ds加密
devKey?: string | null //装置秘钥3ds加密
sampleId?: string | null //样品编号
arrivedDate?: string //送样日期
cityName?: string | null //所属地市名称
gdName?: string | null //所属供电公司名称
subName?: string | null //所属电站名称
reportPath?: string | null //报告路径
planId?: string //检测计划Id
factorFlag?: number //是否支持系数校准(0:不支持,1:支持)
preinvestmentPlan: string | null //预投计划
delegate: string | null //委托方
inspectChannel?: string[] | string //被检通道
inspectDate?: string | null //定检日期
harmSysId?: string | null //谐波系统设备id
importFlag?: number //是否为导入设备 0否 1是
state: number //状态
createBy?: string | null //创建用户
createTime?: string | null //创建时间
updateBy?: string | null //更新用户
updateTime?: string | null //更新时间
devChns: number //设备通道数
devVolt: number //额定电压V
devCurr: number //额定电流A
icdId: string | null
power: string | null //工作电源
devId?: number
checkState?: number | null //检测状态(0:未检1:检测中2:检测完成 3:归档)
checkResult?: number | null //检测结果(0:不符合1:符合2:未检)
reportState?: number | null //报告状态(0:未生成1:已生成2:未检)
recheckNum: number //复检次数
timeCheckResult?: number //守时检测结果(0:不符合1:符合)
factorCheckResult?: number //系数校准结果(0:不合格1:合格2:未检)
realtimeResult?: number //实时数据结论(0:不符合1:符合2:未检)
statisticsResult?: number //统计数据结论(0:不符合1:符合2:未检)
recordedResult?: number //录波数据结论(0:不符合1:符合2:未检)
checkBy?: string | null //检测人
checkTime?: string | null //检测时间
preDetectTime?: number //预检测耗时
coefficientTime?: number //系数校准耗时
formalCheckTime?: number //正式检测耗时
boundPlanName?: string | null
assign?: number ////是否分配给检测人员 0否 1是
monitorList: Monitor.ResPqMon[]
checked: boolean // 是否已选择
disabled: boolean // 是否禁用
id: string; //装置序号ID
name: string; //设备名称
pattern: string; //设备模式 模拟 数字 比对
devType: string;//设备类型
devChns: number; //设备通道数
devVolt: number; //额定电压V
devCurr: number; //额定电流A
manufacturer?: string | null;//生产厂家
createDate: string; //生产日期
createId: string; //出厂编号
hardwareVersion: string; //固件版本
softwareVersion: string; //软件版本
protocol: string; //通讯协议
ip: string; //IP地址
port: number; //端口号
encryptionFlag: number; //装置是否为加密版本
series?: string | null; //装置识别码3ds加密
devKey?: string | null; //装置秘钥3ds加密
sampleId?: string | null; //样品编号
arrivedDate?: string; //送样日期
cityName?: string | null; //所属地市名称
gDName?: string | null; //所属供电公司名称
subName?: string | null; //所属电站名称
checkState?: number | null; //检测状态
checkResult?: number | null; //检测结果
reportState?: number | null; //报告状态
reportPath?: string | null; //报告路径
qRCode?: string | null; //设备关键信息二维码
reCheckNum: number; //复检次数
planId?: string;//检测计划Id
timeCheckResult?: number;//守时检测结果(0:不符合1:符合)
factorFlag?: number;//是否支持系数校准(0:不支持,1:支持)
factorCheckResult?: number;//系数校准结果(0:不合格1:合格2/表示没有做系数校准)
state: number; //状态
createBy?: string | null; //创建用户
createTime?: string | null; //创建时间
updateBy?: string | null; //更新用户
updateTime?: string | null; //更新时间
icdId: string | null;
power: string | null;//工作电源
preinvestmentPlan: string | null;
delegate: string | null; //委托方
}
export interface SelectOption {
label: string
value: string | number
}
export interface ResDev {
id: string
name: string
icd: string
power: string
devVolt: number
devCurr: number
devChns: number
id: string;
name: string,
icd: string,
power: string,
devVolt: number,
devCurr: number,
devChns: number,
}
export interface ResTH {
temperature: number | null //温度
humidity: number | null //湿度
temperature :number | null;//温度
humidity:number | null;//湿度
}
/**
* 被检设备表格查询分页返回的对象;
*/
export interface ResPqDevPage extends ResPage<ResPqDev> {}
export interface ResPqDevPage extends ResPage<ResPqDev> {
}
}

View File

@@ -24,8 +24,6 @@ export namespace ICD {
createTime?: string| null; //创建时间
updateBy?: string| null; //更新用户
updateTime?: string| null; //更新时间
angle: number; // 是否支持电压相角、电流相角指标
usePhaseIndex: number; // 角型接线时是否使用相别的指标来进行检测
}
/**

View File

@@ -1,36 +1,33 @@
import type { ReqPage, ResPage } from '@/api/interface'
// 监测点模块
// 被检设备模块
export namespace Monitor {
/**
* 监测点表格分页查询参数
* 电能质量指标字典数据表格分页查询参数
*/
export interface ReqPqMonParams extends ReqPage {
id: string; // 装置序号id 必填
name?: string; // 设备名称
devType?: string; // 设备名称
createTime?: string; //创建时间
}
/**
* 监测点新增、修改、根据id查询返回的对象
* 被检设备新增、修改、根据id查询返回的对象
*/
export interface ResPqMon {
id: string; //监测点ID
code: string; //默认与谐波系统监测点ID相同
devId: string; //所属设备ID
busbar: string;//所属母线
name: string; //监测点名称
num: number; //线路号,监测点序号
pt: string; //PT变比
ct: string; //CT变比
connection: string; //接线方式,字典表
statInterval: number; //统计间隔
harmSysId: string; //默认与谐波系统监测点ID相同
checkFlag: number;//是否参与检测0否1是
name: string; //所属母线
num: number; //监测点序号
pt: number; //PT变比
ct: number; //CT变比
ptType: string; //接线方式,字典表
}
/**
* 监测点表格查询分页返回的对象;
* 被检设备表格查询分页返回的对象;
*/
export interface ResPqMonPage extends ResPage<ResPqMon> {

View File

@@ -1,47 +0,0 @@
import type { ReqPage, ResPage } from '@/api/interface'
// 标准设备模块
export namespace StandardDevice {
/**
* 标准设备表格分页查询参数
*/
export interface ReqPqStandardDeviceParams extends ReqPage {
id: string; // 装置序号id 必填
name: string; //设备名称
devType?: string; // 设备名称
createTime?: string; //创建时间
}
/**
* 标准设备新增、修改、根据id查询返回的对象
*/
export interface ResPqStandardDevice {
id: string; //装置序号ID
name: string; //设备名称
devType: string;//设备类型
manufacturer?: string | null;//生产厂家
protocol: string;//通讯协议
ip: string; //IP地址
port: number; //端口号
inspectChannel:string[] |string;//可检通道数
encryptionFlag: number; //装置是否为加密版本
series?: string | null; //装置识别码3ds加密
devKey?: string | null; //装置秘钥3ds加密
state: number; //状态
createBy?: string | null; //创建用户
createTime?: string | null; //创建时间
updateBy?: string | null; //更新用户
updateTime?: string | null; //更新时间
disabled?: boolean;
}
/**
* 标准设备表格查询分页返回的对象;
*/
export interface ResPqStandardDevicePage extends ResPage<ResPqStandardDevice> {
}
}

View File

@@ -1,3 +1,4 @@
import type { Monitor } from '@/api/device/interface/monitor'
import http from '@/api'
/**
@@ -5,9 +6,23 @@ import http from '@/api'
*/
//获取监测点
export const getPqMonList = (param:any) => {
return http.post(`/pqMonitor/list`, param)
export const getPqMonList = (params: Monitor.ReqPqMonParams) => {
//return http.post(`/pqMon/list`, params)
}
//添加监测点
export const addPqMon = (params: Monitor.ResPqMon) => {
//return http.post(`/pqMon/add`, params)
}
//编辑监测点
export const updatePqMon = (params: Monitor.ResPqMon) => {
//return http.post(`/pqMon/update`, params)
}
//删除监测点
export const deletePqMon = (params: string[]) => {
//return http.post(`/pqMon/delete`, params)
}

View File

@@ -1,55 +0,0 @@
import type { StandardDevice } from '@/api/device/interface/standardDevice'
import http from '@/api'
/**
* @name 标准设备管理模块
*/
//获取标准设备
export const getPqStandardDevList = (params: StandardDevice.ReqPqStandardDeviceParams) => {
return http.post(`/pqStandardDev/list`, params)
}
//根据id查询标准设备
export const getPqStandardDevById = (params: StandardDevice.ReqPqStandardDeviceParams) => {
return http.get(`/pqStandardDev/getById?id=${params.id}`)
}
//添加标准设备
export const addPqStandardDev = (params: StandardDevice.ResPqStandardDevice) => {
return http.post(`/pqStandardDev/add`, params)
}
//编辑标准设备
export const updatePqStandardDev = (params: StandardDevice.ResPqStandardDevice) => {
return http.post(`/pqStandardDev/update`, params)
}
//删除标准设备
export const deletePqStandardDev = (params: string[]) => {
return http.post(`/pqStandardDev/delete`, params)
}
//导出标准设备
export const exportPqStandardDev = (params: StandardDevice.ReqPqStandardDeviceParams) => {
return http.download(`/pqStandardDev/export`, params)
}
// 下载导入文件模板
export const downloadTemplate = () => {
return http.download(`/pqStandardDev/downloadTemplate`)
}
//导入标准设备
export const importPqStandardDev = (params: StandardDevice.ReqPqStandardDeviceParams) => {
return http.uploadExcel(`/pqStandardDev/import`, params)
}
//获取所有标准设备
export const getAllPqStandardDev = () => {
return http.get(`/pqStandardDev/getAll`)
}
//获取可以绑定的标准设备
export const canBindingList = () => {
return http.get(`/pqStandardDev/canBindingList`)
}

View File

@@ -1,242 +1,190 @@
import { ElMessage } from 'element-plus'
import axios, {
AxiosError,
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
type InternalAxiosRequestConfig
} from 'axios'
import { ElMessage, ElTreeSelect } from 'element-plus';
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
import { LOGIN_URL } from '@/config'
import { type ResultData } from '@/api/interface'
import { ElMessage } from 'element-plus'
import { ResultData } from '@/api/interface'
import { ResultEnum } from '@/enums/httpEnum'
import { checkStatus } from './helper/checkStatus'
import { useUserStore } from '@/stores/modules/user'
import router from '@/routers'
import { refreshToken } from '@/api/user/login'
import { EventSourcePolyfill } from 'event-source-polyfill'
import {refreshToken} from '@/api/user/login'
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
loading?: boolean
loading?: boolean;
}
const config = {
// 默认地址请求地址,可在 .env.** 文件中修改
baseURL: import.meta.env.VITE_API_URL as string,
// 设置超时时间
timeout: ResultEnum.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true,
// post请求指定数据类型以及编码
headers: { 'Content-Type': 'application/json;charset=utf-8' }
// 默认地址请求地址,可在 .env.** 文件中修改
baseURL: import.meta.env.VITE_API_URL as string,
// 设置超时时间
timeout: ResultEnum.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true,
// post请求指定数据类型以及编码
headers: { 'Content-Type': 'application/json;charset=utf-8' },
}
class RequestHttp {
service: AxiosInstance
service: AxiosInstance
public constructor(config: AxiosRequestConfig) {
// 创建实例
this.service = axios.create(config)
/**
* @description 请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
* token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
*/
this.service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
isFirst = true
const userStore = useUserStore()
// 当前请求不需要显示 loading在 api 服务中通过指定的第三个参数: { loading: false } 来控制
config.loading ?? (config.loading = true)
config.loading && showFullScreenLoading()
if (config.headers && typeof config.headers.set === 'function') {
config.headers.set('Authorization', 'Bearer ' + userStore.accessToken)
config.headers.set('Is-Refresh-Token', userStore.isRefreshToken + '')
}
return config
},
(error: AxiosError) => {
return Promise.reject(error)
}
)
let isFirst = true
/**
* @description 响应拦截器
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
*/
this.service.interceptors.response.use(
async (response: AxiosResponse) => {
const { data } = response
const userStore = useUserStore()
tryHideFullScreenLoading()
if (data.code === ResultEnum.ACCESSTOKEN_EXPIRED) {
// 用长token去换短token
userStore.setAccessToken(userStore.refreshToken)
userStore.setIsRefreshToken(true)
const result = await refreshToken()
if (result) {
//获取新token成功的话
// 有新的token后重新请求
userStore.setAccessToken(result.data.accessToken)
userStore.setRefreshToken(result.data.refreshToken)
userStore.setIsRefreshToken(false)
userStore.setExp(1000 * 60 * 60 * 24 * 30)
response.config.headers.Authorization = `Bearer ${result.data.accessToken}` //重新请求前需要将更新后的新token更换掉之前无效的token,不然会死循环
const resp = await this.service.request(response.config)
return resp
} else {
// 刷新失效,跳转登录页
}
}
// 登陆失效
if (data.code === ResultEnum.OVERDUE) {
console.log('登陆失效')
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setIsRefreshToken(false)
userStore.setUserInfo({ id: '', name: '' })
userStore.setExp(0)
await router.replace(LOGIN_URL)
if (isFirst) {
//临时处理token失效弹窗多次
ElMessage.error(data.message)
isFirst = false
}
return Promise.reject(data)
}
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
if (data.code && data.code !== ResultEnum.SUCCESS) {
if (data.message.includes('&')) {
let formattedMessage = data.message.split('&').join('<br>')
if (data.message.includes(':')) {
formattedMessage = formattedMessage.replace(':', '')
}
ElMessage.error({ message: formattedMessage, dangerouslyUseHTMLString: true })
return Promise.reject(data)
}
ElMessage.error(data.message)
return Promise.reject(data)
}
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
if (userStore.exp <= Date.now() && userStore.exp !== 0) {
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setIsRefreshToken(false)
userStore.setUserInfo({ id: '', name: '' })
userStore.setExp(0)
ElMessage.error('登录已过期,请重新登录!')
await router.replace(LOGIN_URL)
return Promise.reject(data)
}
// 对于blob类型的响应返回完整的response对象以保留响应头
if (response.config.responseType === 'blob') {
return response
}
return data
},
async (error: AxiosError) => {
const { response } = error
tryHideFullScreenLoading()
console.log('error', error.message)
// 请求超时 && 网络错误单独判断,没有 response
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
// 根据服务器响应的错误状态码,做不同的处理
if (response) checkStatus(response.status)
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
if (!window.navigator.onLine) router.replace('/500')
return Promise.reject(error)
}
)
}
public constructor(config: AxiosRequestConfig) {
// 创建实例
this.service = axios.create(config)
/**
* @description 常用请求方法封装
* @description 请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
* token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
*/
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object })
}
post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
return this.service.post(url, params, _object)
}
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.put(url, params, _object)
}
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
return this.service.delete(url, { params, ..._object })
}
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, { ..._object, responseType: 'blob' }).then(res => res.data)
}
downloadWithHeaders(url: string, params?: object, _object = {}): Promise<AxiosResponse<Blob>> {
return this.service.post(url, params, { ..._object, responseType: 'blob' })
}
upload(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, {
..._object,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
/**
* 针对excel的上传默认返回的是blob类型Excel没问题时返回json特殊处理
*/
uploadExcel(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service
.post(url, params, {
..._object,
headers: { 'Content-Type': 'multipart/form-data' },
responseType: 'blob'
})
.then(res => res.data)
}
// 添加SSE连接方法
sse(url: string, params?: any): EventSource {
this.service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
isFirst = true
const userStore = useUserStore()
// 构造带参数的URL
let requestUrl = config.baseURL + url
if (params) {
const searchParams = new URLSearchParams()
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
searchParams.append(key, String(params[key]))
}
}
requestUrl += '?' + searchParams.toString()
// 当前请求不需要显示 loading在 api 服务中通过指定的第三个参数: { loading: false } 来控制
config.loading ?? (config.loading = true)
config.loading && showFullScreenLoading()
if (config.headers && typeof config.headers.set === 'function') {
config.headers.set('Authorization', 'Bearer ' + userStore.accessToken)
config.headers.set('Is-Refresh-Token', userStore.isRefreshToken+"")
}
return config
},
(error: AxiosError) => {
return Promise.reject(error)
},
)
// 创建EventSource连接
const eventSource = new EventSourcePolyfill(requestUrl, {
headers: {
Authorization: 'Bearer ' + userStore.accessToken
},
// 增加超时时间到1200秒
heartbeatTimeout: 1200000
})
let isFirst = true
/**
* @description 响应拦截器
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
*/
this.service.interceptors.response.use(
async (response: AxiosResponse) => {
const { data } = response
const userStore = useUserStore()
tryHideFullScreenLoading()
if(data.code === ResultEnum.ACCESSTOKEN_EXPIRED){
// 用长token去换短token
userStore.setAccessToken(userStore.refreshToken)
userStore.setIsRefreshToken(true)
const result = await refreshToken()
if (result) { //获取新token成功的话
// 有新的token后重新请求
userStore.setAccessToken(result.data.accessToken)
userStore.setRefreshToken(result.data.refreshToken)
userStore.setIsRefreshToken(false)
userStore.setExp(1000 * 60 * 60 * 24 * 30)
response.config.headers.Authorization = `Bearer ${result.data.accessToken}`//重新请求前需要将更新后的新token更换掉之前无效的token,不然会死循环
const resp = await this.service.request(response.config)
return resp
} else {
// 刷新失效,跳转登录页
}
}
// 登陆失效
if (data.code === ResultEnum.OVERDUE) {
console.log("登陆失效")
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setIsRefreshToken(false)
userStore.setUserInfo({ id:'',name: '' })
userStore.setExp(0)
await router.replace(LOGIN_URL)
if(isFirst){//临时处理token失效弹窗多次
ElMessage.error(data.message)
isFirst = false
}
return Promise.reject(data)
}
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
if (data.code && data.code !== ResultEnum.SUCCESS) {
if(data.message.includes('&')){
let formattedMessage = data.message.split('&').join('<br>');
if (data.message.includes(':')) {
formattedMessage = formattedMessage.replace(':', '')
}
ElMessage.error({ message: formattedMessage, dangerouslyUseHTMLString: true });
return Promise.reject(data)
}
ElMessage.error(data.message)
return Promise.reject(data)
}
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
if (userStore.exp <= Date.now() && userStore.exp !== 0) {
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setIsRefreshToken(false)
userStore.setUserInfo({ id:'',name: '' })
userStore.setExp(0)
ElMessage.error('登录已过期,请重新登录!')
await router.replace(LOGIN_URL)
return Promise.reject(data)
}
return data
},
async (error: AxiosError) => {
const { response } = error
tryHideFullScreenLoading()
console.log('error', error.message)
// 请求超时 && 网络错误单独判断,没有 response
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
// 根据服务器响应的错误状态码,做不同的处理
if (response) checkStatus(response.status)
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
if (!window.navigator.onLine) router.replace('/500')
return Promise.reject(error)
},
)
}
/**
* @description 常用请求方法封装
*/
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object })
}
post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
return this.service.post(url, params, _object)
}
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.put(url, params, _object)
}
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
return this.service.delete(url, { params, ..._object })
}
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, { ..._object, responseType: 'blob' })
}
upload(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, {
..._object,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
/**
* 针对excel的上传默认返回的是blob类型Excel没问题时返回json特殊处理
*/
uploadExcel(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, {
..._object,
headers: { 'Content-Type': 'multipart/form-data' },
responseType: 'blob',
})
}
// 设置默认的Authorization头部
eventSource.addEventListener('open', function () {
console.log('SSE连接已建立')
})
// 添加错误处理
eventSource.addEventListener('error', function (err) {
console.error('SSE连接错误:', err)
})
return eventSource
}
}
export default new RequestHttp(config)

View File

@@ -15,7 +15,6 @@ export interface Result {
* 请求响应参数包含data
*/
export interface ResultData<T = any> extends Result {
map(arg0: (item: any) => { label: any; value: any; }): { label: string; value: string; }[] | { label: string; value: string; }[];
data: T;
}

View File

@@ -26,19 +26,7 @@ export namespace Plan {
associateReport:number;//是否关联报告模板 0否 1是
reportTemplateName:string;
reportTemplateVersion:string;
dataRule:string;//数据处理原则
standardDevIds:string[];
standardDevMap:Map<string,number>;//标准设备
testItems:string[];//测试项
Check_By?:string;//计划检测人
progress?: number; // 进度百分比,例如 75
children?: ResPlan[];
testConfig?: PlanTestConfig;
importFlag?: number; // 导入标识0-否1-是
leader?: string; // 负责人
memberIds?: string | string[]; //成员
members?: string; //成员字符串
dataRule:string //数据处理原则
}
// 检测计划 + 分页
@@ -47,27 +35,15 @@ export namespace Plan {
}
export interface ReqPlan extends ResPlan {
datasourceIds:string | string[];
sourceIds: string | null;
datasourceIds:string;
sourceIds: string;
planId:string;
scriptName: string ;
errorSysName: string;
sourceName: string ;
standardDevNameStr: string;
testItemNameStr:string;
devIds: string[];
}
export interface PlanTestConfig {
planId: string;
waveRecord: number;
realTime: number;
statistics: number;
flicker: number;
maxTime: number;
}
}

View File

@@ -2,53 +2,54 @@ import type { Plan } from './interface'
import http from '@/api'
import type { ErrorSystem } from '../device/interface/error'
import type { Device } from '../device/interface/device'
import { ReqDevReportParams } from '@/api/device/interface/device'
/**
* @name 检测计划管理模块
*/
// 获取检测计划列表
export const getPlanList = (params: Plan.ReqPlanParams) => {
return http.post(`/adPlan/list`, params)
return http.post(`/adPlan/list`, params)
}
// 新增检测计划
export const addPlan = (params: any) => {
return http.post(`/adPlan/add`, params)
return http.post(`/adPlan/add`, params)
}
// 编辑检测计划
export const updatePlan = (params: any) => {
return http.post(`/adPlan/update`, params)
return http.post(`/adPlan/update`, params)
}
// 删除检测计划
export const deletePlan = (params: { id: string[]; pattern: string }) => {
return http.post(`/adPlan/delete?pattern=${params.pattern}`, params.id)
export const deletePlan = (params: { id: string[] }) => {
return http.post(`/adPlan/delete`, params)
}
// 获取指定模式下所有检测源
// 获取指定模式下所有检测源
export const getTestSourceList = (params: Plan.ReqPlan) => {
return http.get(`/pqSource/getAll?patternId=${params.pattern}`)
return http.get(`/pqSource/getAll?patternId=${params.pattern}`)
}
// 获取指定模式下所有检测脚本
export const getPqScriptList = (params: Plan.ReqPlan) => {
return http.get(`/pqScript/getAll?patternId=${params.pattern}`)
return http.get(`/pqScript/getAll?patternId=${params.pattern}`)
}
//获取所有误差体系
export const getPqErrSysList = () => {
return http.get<ErrorSystem.ErrorSystemList>(`/pqErrSys/getAll`)
return http.get<ErrorSystem.ErrorSystemList>(`/pqErrSys/getAll`)
}
//获取指定模式下所有未绑定的设备
export const getUnboundPqDevList = (params: { pattern: string}) => {
return http.get(`/pqDev/listUnbound?pattern=${params.pattern}`)
export const getUnboundPqDevList = (params: Plan.ReqPlan) => {
return http.get(`/pqDev/listUnbound?pattern=${params.pattern}`)
}
//根据检测计划id查询出所有已绑定的设备
export const getBoundPqDevList = (params: any) => {
return http.post(`/adPlan/listByPlanId`, params)
return http.post(`/pqDev/listByPlanId`, params)
}
//检测计划绑定设备
@@ -58,105 +59,34 @@ export const getBoundPqDevList = (params: any) => {
// 按照模式查询检测计划(用于首页展示)
export const getPlanListByPattern = (params: Plan.ReqPlan) => {
return http.get(`/adPlan/listByPattern?pattern=${params.pattern}`)
return http.get(`/adPlan/listByPattern?pattern=${params.pattern}`)
}
// 导出检测计划
export const exportPlan = (params: Device.ReqPqDevParams) => {
return http.download(`/adPlan/export`, params)
return http.download(`/adPlan/export`, params)
}
// 下载模板
export const downloadTemplate = (params: { patternId: string }) => {
return http.download(`/adPlan/downloadTemplate`, params)
return http.download(`/adPlan/downloadTemplate`, params)
}
// 导入检测计划
export const importPlan = (params: Device.ReqPqDevParams) => {
return http.uploadExcel(`/adPlan/import`, params)
return http.uploadExcel(`/adPlan/import`, params)
}
// 装置检测报告生成
export const generateDevReport = (params: Device.ReqDevReportParams) => {
return http.post(`/report/generateReport`, params)
return http.post(`/report/generateReport`, params)
}
// 装置检测报告下载
export const downloadDevData = (params: Device.ReqDevReportParams) => {
return http.download(`/report/downloadReport`, params)
}
// 装置检测报告下载(带响应头)
export const downloadDevDataWithHeaders = (params: Device.ReqDevReportParams) => {
return http.downloadWithHeaders(`/report/downloadReport`, params)
return http.download(`/report/downloadReport`, params)
}
export const staticsAnalyse = (params: { id: string[] }) => {
return http.download('/adPlan/analyse', params)
return http.download('/adPlan/analyse', params)
}
//根据计划id分页查询被检设
export const getDevListByPlanId = (params: any) => {
return http.post(`/adPlan/listDevByPlanId`, params)
}
//修改子计划名称
export const updateSubPlanName = (params: Plan.ReqPlan) => {
return http.get(`/adPlan/updateSubPlanName?planId=${params.id}&name=${params.name}`)
}
//子计划绑定/解绑标准设备
export const subPlanBindStandardDevList = (params: Plan.ReqPlan) => {
return http.post(`/adPlan/updateBindStandardDev`, params)
}
//子计划绑定/解绑被检设备
export const subPlanBindDev = (params: Plan.ReqPlan) => {
return http.post(`/adPlan/updateBindDev`, params)
}
//根据父计划ID获取未被子计划绑定的标准设备
export const getUnboundStandardDevList = (params: Plan.ResPlan) => {
return http.get(`/adPlan/getUnBoundStandardDev?fatherPlanId=${params.fatherPlanId}`)
}
//根据计划ID获取已绑定的标准设备
export const getBoundStandardDevList = (params: Plan.ResPlan) => {
return http.get(`/adPlan/getBoundStandardDev?planId=${params.id}`)
}
//根据计划ID获取已绑定的所有标准设备
export const getBoundStandardDevAllList = (params: { id: string }) => {
return http.get(`/adPlan/getBoundStandardDev?planId=${params.id}&all=1`)
}
// 导出子计划
export const exportSubPlan = (params: Plan.ResPlan) => {
return http.download(`/adPlan/exportSubPlan?planId=${params.id}`)
}
// 导入子检测计划
export const importSubPlan = (params: Plan.ResPlan) => {
return http.upload(`/adPlan/importSubPlan`, params)
}
// 导出计划检测结果数据
export const exportPlanCheckData = (params: any) => {
return http.post(
`/adPlan/exportPlanCheckData?planId=${params.id}&devIds=${params.devIds}&report=${params.report}`
)
}
//根据误差体系id获取测试项
export const getPqErrSysTestItemList = (params: {errorSysId : string}) => {
return http.get(`/pqErrSys/getTestItems?id=${params.errorSysId}`)
}
// 获取计划项目成员
export const getMemberList = (params: {id : string}) => {
return http.get(`/adPlan/getMemberList?planId=${params.id}`)
}
// 导入并合并子检测计划检测结果数据
export const importAndMergePlanCheckData = (params: Plan.ResPlan) => {
return http.upload(`/adPlan/importAndMergePlanCheckData`, params)
}

View File

@@ -1,51 +0,0 @@
export interface MonitorResult {
/**
* 监测点id
*/
monitorId: string;
/**
* 监测点序号
*/
monitorNum: number;
/**
* 总检测次数
*/
totalNum: number;
/**
* 合格检测次数
*/
qualifiedNum: number;
/**
* 不合格检测次数
*/
unQualifiedNum: number;
/**
* 误差体系名称
*/
errorSysName: string;
/**
* 检测结果
*/
checkResult: number;
/**
* 哪次
*/
whichTime: string;
/**
* 结论来源
*/
resultOrigin: string;
/**
* 来源类型
*/
resultType: string;
}

View File

@@ -1,7 +0,0 @@
import http from '@/api'
export const getMonitorResult = (devId: string) => http.post(`/result/getMonitorResult?devId=${devId}`)
export const getMonitorDataSourceResult = (monitorId: string) =>
http.get(`/result/getMonitorDataSourceResult?monitorId=${monitorId}`)
export const updateMonitorResult = (data: any) => http.post('/result/updateMonitorResult', data)

View File

@@ -32,15 +32,3 @@ export const pauseTest = () => {
export const resumeTest = (params) => {
return http.post(`/prepare/restartTemTest/`, params, {loading: false})
}
/**
* 比对式通道配对
* @param params
*/
export const contrastTest = (params: any) => {
return http.post(`/prepare/startContrastTest`,params)
}
export const exportAlignData= () => {
return http.download(`/prepare/exportAlignData`)
}

View File

@@ -1,11 +1,12 @@
import http from '@/api'
import { type VersionRegister } from '@/api/system/versionRegister/interface'
import {type VersionRegister} from '@/api/system/versionRegister/interface'
//获取有效数据配置
export const getRegRes = (params: { type: string }) => {
export const getRegRes = (params: VersionRegister.ResSys_Reg_Res) => {
return http.get(`/sysRegRes/getRegResByType?id=${params.type}`)
}
//编辑有效数据配置
export const updateRegRes = (params: VersionRegister.Sys_Reg_Res) => {
return http.post(`/sysRegRes/update`, params)

View File

@@ -1,23 +1,23 @@
// 登录模块
import type { ReqPage, ResPage } from '@/api/interface'
import type { ReqPage,ResPage } from '@/api/interface'
export namespace Login {
export interface ReqLoginForm {
username: string
password: string
checked: boolean
}
export interface ResLogin {
accessToken: string
refreshToken: string
userInfo: {
id: string
name: string
}
}
export interface ResAuthButtons {
[key: string]: string[]
export interface ReqLoginForm {
username: string;
password: string;
checked: boolean;
}
export interface ResLogin {
accessToken: string;
refreshToken: string;
userInfo:{
id: string;
name: string;
}
}
export interface ResAuthButtons {
[key: string]: string[];
}
}
@@ -52,8 +52,6 @@ export namespace User {
updateTime?: string;//更新时间
roleIds?: string[]; //
roleNames?:string[]; //
roleCodes?:string[]; //
disabled?: boolean;
}
// 用户接口

View File

@@ -1,4 +1,3 @@
import { pa } from 'element-plus/es/locale/index.mjs';
import type {Login} from '@/api/user/interface/user'
import {ADMIN as rePrefix} from '@/api/system/config/serviceName'
import http from '@/api'

View File

@@ -37,7 +37,3 @@ export const updatePassWord = (params: User.ResPassWordUser) => {
return http.post(`/sysUser/updatePassword`,params)
}
// 获取所有用户
export const getAllUser= () => {
return http.get(`/sysUser/getAll`)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,20 +1,19 @@
<template>
<div class="not-container">
<img src="@/assets/images/403.png" class="not-img" alt="403" />
<div class="not-detail">
<h2>403</h2>
<h4>抱歉您无权访问该页面~🙅🙅</h4>
<el-button type="primary" @click="router.back">返回上一页</el-button>
</div>
<div class="not-container">
<img src="@/assets/images/403.png" class="not-img" alt="403" />
<div class="not-detail">
<h2>403</h2>
<h4>抱歉您无权访问该页面~🙅🙅</h4>
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
</div>
</div>
</template>
<script setup lang="ts" name="403">
import { useRouter } from 'vue-router'
const router = useRouter()
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,20 +1,19 @@
<template>
<div class="not-container">
<img src="@/assets/images/404.png" class="not-img" alt="404" />
<div class="not-detail">
<h2>404</h2>
<h4>抱歉您访问的页面不存在~🤷🤷</h4>
<el-button type="primary" @click="router.back">返回上一页</el-button>
</div>
<div class="not-container">
<img src="@/assets/images/404.png" class="not-img" alt="404" />
<div class="not-detail">
<h2>404</h2>
<h4>抱歉您访问的页面不存在~🤷🤷</h4>
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
</div>
</div>
</template>
<script setup lang="ts" name="404">
import { useRouter } from 'vue-router'
const router = useRouter()
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,20 +1,19 @@
<template>
<div class="not-container">
<img src="@/assets/images/500.png" class="not-img" alt="500" />
<div class="not-detail">
<h2>500</h2>
<h4>抱歉您的网络不见了~🤦🤦</h4>
<el-button type="primary" @click="router.back">返回上一页</el-button>
</div>
<div class="not-container">
<img src="@/assets/images/500.png" class="not-img" alt="500" />
<div class="not-detail">
<h2>500</h2>
<h4>抱歉您的网络不见了~🤦🤦</h4>
<el-button type="primary" @click="router.back"> 返回上一页 </el-button>
</div>
</div>
</template>
<script setup lang="ts" name="500">
import { useRouter } from 'vue-router'
const router = useRouter()
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,69 +1,58 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="`批量添加${parameter.title}`"
:destroy-on-close="true"
width="580px"
draggable
>
<el-form class="drawer-multiColumn-form" label-width="100px">
<el-form-item label="模板下载 :">
<el-button type="primary" :icon="Download" @click="downloadTemp">点击下载</el-button>
</el-form-item>
<el-form-item label="文件上传 :">
<el-upload
action="#"
class="upload"
:drag="true"
:limit="excelLimit"
:multiple="true"
:show-file-list="true"
:http-request="uploadExcel"
:before-upload="beforeExcelUpload"
:on-exceed="handleExceed"
:accept="parameter.fileType!.join(',')"
>
<slot name="empty">
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
</slot>
<template #tip>
<slot name="tip">
<div class="el-upload__tip">
请上传 .xls , .xlsx 标准格式文件文件最大为 {{ parameter.fileSize }}M
</div>
</slot>
</template>
</el-upload>
</el-form-item>
<el-form-item v-if="parameter.showCover" label="数据覆盖 :">
<el-switch v-model="isCover" />
</el-form-item>
</el-form>
</el-dialog>
<el-dialog v-model='dialogVisible' :title='`批量添加${parameter.title}`' :destroy-on-close='true' width='580px'
draggable>
<el-form class='drawer-multiColumn-form' label-width='100px'>
<el-form-item label='模板下载 :'>
<el-button type='primary' :icon='Download' @click='downloadTemp'> 点击下载</el-button>
</el-form-item>
<el-form-item label='文件上传 :'>
<el-upload
action='#'
class='upload'
:drag='true'
:limit='excelLimit'
:multiple='true'
:show-file-list='true'
:http-request='uploadExcel'
:before-upload='beforeExcelUpload'
:on-exceed='handleExceed'
:accept="parameter.fileType!.join(',')"
>
<slot name='empty'>
<el-icon class='el-icon--upload'>
<upload-filled />
</el-icon>
<div class='el-upload__text'>将文件拖到此处<em>点击上传</em></div>
</slot>
<template #tip>
<slot name='tip'>
<div class='el-upload__tip'>请上传 .xls , .xlsx 标准格式文件文件最大为 {{ parameter.fileSize }}M</div>
</slot>
</template>
</el-upload>
</el-form-item>
<el-form-item v-if='parameter.showCover' label='数据覆盖 :'>
<el-switch v-model='isCover' />
</el-form-item>
</el-form>
</el-dialog>
</template>
<script setup lang="ts" name="ImportExcel">
<script setup lang='ts' name='ImportExcel'>
import { ref } from 'vue'
import { useDownload } from '@/hooks/useDownload'
import { Download } from '@element-plus/icons-vue'
import { ElMessage, ElNotification, UploadRawFile, UploadRequestOptions } from 'element-plus'
import { ElNotification, UploadRequestOptions, UploadRawFile, ElMessage } from 'element-plus'
export interface ExcelParameterProps {
title: string // 标题
showCover?: boolean // 是否显示”数据覆盖“选项
patternId?: string // 模式ID
planId?: string | null //计划ID
fileSize?: number // 上传文件的大小
fileType?: File.ExcelMimeType[] // 上传文件的类型
tempApi?: (params: any) => Promise<any> // 下载模板的Api
importApi?: (params: any) => Promise<any> // 批量导入的Api
getTableList?: () => void // 获取表格数据的Api
title: string; // 标题
showCover?: boolean; // 是否显示”数据覆盖“选项
patternId?: string; // 模式ID
fileSize?: number; // 上传文件的大小
fileType?: File.ExcelMimeType[]; // 上传文件的类型
tempApi?: (params: any) => Promise<any>; // 下载模板的Api
importApi?: (params: any) => Promise<any>; // 批量导入的Api
getTableList?: () => void; // 获取表格数据的Api
}
// 是否覆盖数据
@@ -74,77 +63,68 @@ const excelLimit = ref(1)
const dialogVisible = ref(false)
// 父组件传过来的参数
const parameter = ref<ExcelParameterProps>({
title: '',
fileSize: 5,
fileType: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
title: '',
fileSize: 5,
fileType: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
})
const emit = defineEmits<{
(e: 'result', data: boolean): void
}>()
// 接收父组件参数
const acceptParams = (params: ExcelParameterProps) => {
parameter.value = { ...parameter.value, ...params }
dialogVisible.value = true
parameter.value = { ...parameter.value, ...params }
dialogVisible.value = true
}
// Excel 导入模板下载
const downloadTemp = () => {
if (!parameter.value.tempApi) return
useDownload(parameter.value.tempApi, `${parameter.value.title}模板`, { pattern: parameter.value.patternId }, false)
if (!parameter.value.tempApi) return
useDownload(parameter.value.tempApi, `${parameter.value.title}模板`, {'patternId':parameter.value.patternId}, false)
}
// 文件上传
const uploadExcel = async (param: UploadRequestOptions) => {
let excelFormData = new FormData()
excelFormData.append('file', param.file)
if (parameter.value.patternId) {
excelFormData.append('patternId', parameter.value.patternId)
}
excelFormData.append('planId', parameter.value.planId)
isCover.value && excelFormData.append('isCover', isCover.value as unknown as Blob)
//await parameter.value.importApi!(excelFormData);
await parameter.value.importApi!(excelFormData).then(res => handleImportResponse(res))
parameter.value.getTableList && parameter.value.getTableList()
dialogVisible.value = false
let excelFormData = new FormData()
excelFormData.append('file', param.file)
if (parameter.value.patternId) {
excelFormData.append('patternId', parameter.value.patternId)
}
isCover.value && excelFormData.append('isCover', isCover.value as unknown as Blob)
//await parameter.value.importApi!(excelFormData);
await parameter.value.importApi!(excelFormData)
.then(res => handleImportResponse(res))
parameter.value.getTableList && parameter.value.getTableList()
dialogVisible.value = false
}
async function handleImportResponse(res: any) {
console.log(res)
if (res.type === 'application/json') {
const fileReader = new FileReader()
fileReader.onloadend = () => {
try {
const jsonData = JSON.parse(fileReader.result)
if (jsonData.code === 'A0000') {
ElMessage.success('导入成功')
} else {
ElMessageBox.alert(jsonData.message, {
title: '导入结果',
type: 'error'
})
}
emit('result', jsonData.data)
} catch (err) {
console.log(err)
}
async function handleImportResponse(res: any) {
console.log(res)
if (res.type === 'application/json') {
const fileReader = new FileReader()
fileReader.onloadend = () => {
try {
const jsonData = JSON.parse(fileReader.result)
if (jsonData.code === 'A0000') {
ElMessage.success('导入成功')
} else {
ElMessage.error(jsonData.message)
}
fileReader.readAsText(res)
} else {
emit('result', false)
ElMessage.error('导入失败,请查看下载附件!')
let blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '导入失败数据'
document.body.appendChild(link)
link.click()
link.remove()
} catch (err) {
console.log(err)
}
}
fileReader.readAsText(res)
} else {
ElMessage.error('导入失败,请查看下载附件!')
let blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '导入失败数据'
document.body.appendChild(link)
link.click()
link.remove()
}
}
/**
@@ -152,32 +132,32 @@ async function handleImportResponse(res: any) {
* @param file 上传的文件
* */
const beforeExcelUpload = (file: UploadRawFile) => {
const isExcel = parameter.value.fileType!.includes(file.type as File.ExcelMimeType)
const fileSize = file.size / 1024 / 1024 < parameter.value.fileSize!
if (!isExcel)
ElNotification({
title: '温馨提示',
message: '上传文件只能是 xls / xlsx 格式!',
type: 'warning'
})
if (!fileSize)
setTimeout(() => {
ElNotification({
title: '温馨提示',
message: `上传文件大小不能超过 ${parameter.value.fileSize}MB`,
type: 'warning'
})
}, 0)
return isExcel && fileSize
const isExcel = parameter.value.fileType!.includes(file.type as File.ExcelMimeType)
const fileSize = file.size / 1024 / 1024 < parameter.value.fileSize!
if (!isExcel)
ElNotification({
title: '温馨提示',
message: '上传文件只能是 xls / xlsx 格式!',
type: 'warning',
})
if (!fileSize)
setTimeout(() => {
ElNotification({
title: '温馨提示',
message: `上传文件大小不能超过 ${parameter.value.fileSize}MB`,
type: 'warning',
})
}, 0)
return isExcel && fileSize
}
// 文件数超出提示
const handleExceed = () => {
ElNotification({
title: '温馨提示',
message: '最多只能上传一个文件!',
type: 'warning'
})
ElNotification({
title: '温馨提示',
message: '最多只能上传一个文件!',
type: 'warning',
})
}
// 上传错误提示
@@ -199,9 +179,9 @@ const handleExceed = () => {
// }
defineExpose({
acceptParams
acceptParams,
})
</script>
<style lang="scss" scoped>
@use './index.scss';
<style lang='scss' scoped>
@import "./index.scss";
</style>

View File

@@ -1,3 +0,0 @@
.upload {
width: 80%;
}

View File

@@ -1,241 +0,0 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="parameter.title"
:destroy-on-close="true"
width="450px"
:close-on-click-modal="!parameter.progressBar"
:show-close="!disable"
draggable
>
<el-upload
ref="uploadRef"
action="#"
class="upload"
:limit="1"
:http-request="uploadZip"
accept=".zip"
:auto-upload="!parameter.confirmMessage"
:on-change="handleChange"
:on-remove="handleRemove"
:disabled="fileDisabled"
>
<slot name="empty">
<el-button type="primary" :disabled="fileDisabled" icon="Upload">点击上传</el-button>
</slot>
<template #tip>
<slot name="tip">
<div class="el-upload__tip">请上传 .zip 标准格式文件</div>
</slot>
</template>
</el-upload>
<el-text v-if="parameter.progressBar && progressData.status === 'exception'" size="small" type="danger">
{{ progressData.message }}
</el-text>
<el-text v-if="parameter.progressBar && progressData.status === 'success'" size="small" type="success">
{{ progressData.message }}
</el-text>
<el-text v-if="parameter.progressBar && progressData.status === ''" size="small" type="info">
{{ progressData.message }}
</el-text>
<el-progress
style="margin-top: 10px; margin-bottom: 10px"
v-if="parameter.progressBar"
:status="progressData.status"
:percentage="progressData.percentage"
:stroke-width="10"
striped
striped-flow
></el-progress>
<template #footer v-if="parameter.confirmMessage">
<el-button :disabled="disable" type="primary" @click="uploadSubmit">开始导入</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ImportZip">
import { ref } from 'vue'
import type { UploadInstance, UploadProps, UploadRequestOptions } from 'element-plus'
import http from '@/api'
export interface ZipParameterProps {
title: string // 标题
patternId?: string // 模式ID
planId?: string // 计划ID
importApi?: (params: any) => Promise<any> // 批量导入的Api
confirmMessage?: string // 提示信息
progressBar?: boolean // 进度条
}
// dialog状态
const dialogVisible = ref(false)
const disable = ref(true)
const fileDisabled = ref(false)
const uploadRef = ref<UploadInstance>()
// 父组件传过来的参数
const parameter = ref<ZipParameterProps>({
title: ''
})
const emit = defineEmits<{
(e: 'result', data: boolean): void
}>()
// 接收父组件参数
const acceptParams = (params: ZipParameterProps) => {
parameter.value = { ...parameter.value, ...params }
dialogVisible.value = true
}
// 文件上传
const uploadZip = (param: UploadRequestOptions) => {
let zipFormData = new FormData()
zipFormData.append('file', param.file)
if (parameter.value.patternId) {
zipFormData.append('patternId', parameter.value.patternId)
}
if (parameter.value.planId) {
zipFormData.append('planId', parameter.value.planId)
}
if (parameter.value.progressBar) {
initSSE()
}
setTimeout(() => {
parameter.value.importApi!(zipFormData)
.then(res => handleImportResponse(res))
.catch(err => {
fileDisabled.value = false
disable.value = false
})
}, 1000)
}
const handleImportResponse = (res: any) => {
if (!parameter.value.progressBar) {
if (res.code === 'A0000') {
ElMessage.success('导入成功')
emit('result', true)
dialogVisible.value = false
} else {
ElMessage.error(res.message)
fileDisabled.value = false
disable.value = false
}
} else {
if (res.code !== 'A0000') {
ElMessage.error(res.message)
}
}
}
const uploadSubmit = () => {
if (!uploadRef.value) {
return ElMessage.warning('请选择文件!')
}
progressData.value = {
percentage: 0,
status: '',
message: ''
}
ElMessageBox.confirm(parameter.value.confirmMessage, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
disable.value = true
fileDisabled.value = true
uploadRef.value?.submit()
})
.catch(() => {
disable.value = false
fileDisabled.value = false
})
}
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
disable.value = uploadFiles.length === 0
progressData.value = {
percentage: 0,
status: '',
message: ''
}
}
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
disable.value = uploadFiles.length === 0
progressData.value = {
percentage: 0,
status: '',
message: ''
}
}
const progressData = ref({
percentage: 0,
status: '',
message: ''
})
const eventSource = ref<EventSource | null>(null)
const initSSE = () => {
eventSource.value = http.sse('/sse/createSse')
eventSource.value.onmessage = event => {
console.log('收到消息内容是:', event.data)
const res = JSON.parse(event.data)
progressData.value.percentage = res.data
progressData.value.message = res.message
if (res.code === 'A0002') {
fileDisabled.value = false
disable.value = false
progressData.value.status = 'exception'
ElMessage.error(res.message)
}
if (progressData.value.percentage === 100) {
progressData.value.status = 'success'
eventSource.value!.close()
ElMessage.success('导入成功')
emit('result', true)
dialogVisible.value = false
}
}
eventSource.value.onerror = error => {
console.warn('SSE 连接出错:', error)
eventSource.value!.close()
}
}
// 添加一个手动关闭EventSource的函数
const closeEventSource = () => {
if (eventSource.value) {
eventSource.value.close()
eventSource.value = null
console.log('SSE连接已关闭')
}
}
// 监听 dialogVisible 的变化,确保在对话框关闭时清理资源
watch(dialogVisible, newVal => {
if (!newVal) {
// 延迟执行,确保在组件完全关闭后清理
setTimeout(() => {
closeEventSource()
fileDisabled.value = false
disable.value = false
progressData.value = {
percentage: 0,
status: '',
message: ''
}
}, 100)
}
})
onUnmounted(() => {
closeEventSource()
})
defineExpose({
acceptParams
})
</script>
<style lang="scss" scoped>
@use './index.scss';
</style>

View File

@@ -9,5 +9,5 @@
<script setup lang="ts" name="Loading"></script>
<style scoped lang="scss">
@use "./index.scss";;
@import "./index.scss";
</style>

View File

@@ -119,7 +119,6 @@ import TableColumn from './components/TableColumn.vue'
import Sortable from 'sortablejs'
export interface ProTableProps {
columns: ColumnProps[]; // 列配置项 ==> 必传
data?: any[]; // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传
requestApi?: (params: any) => Promise<any>; // 请求表格数据的 api ==> 非必传

View File

@@ -1,49 +1,49 @@
<template>
<div class="icon-box">
<el-input
ref="inputRef"
v-model="valueIcon"
v-bind="$attrs"
:placeholder="placeholder"
:clearable="clearable"
@clear="clearIcon"
@click="openDialog"
>
<template #append>
<el-button :icon="customIcons[iconValue]" />
</template>
</el-input>
<el-dialog v-model="dialogVisible" :title="placeholder" top="5%" width="30%">
<el-input v-model="inputValue" placeholder="搜索图标" size="large" :prefix-icon="Icons.Search" />
<el-scrollbar v-if="Object.keys(iconsList).length">
<div class="icon-list">
<div v-for="item in iconsList" :key="item" class="icon-item" @click="selectIcon(item)">
<component :is="item"></component>
<span>{{ item.name }}</span>
</div>
</div>
</el-scrollbar>
<el-empty v-else description="未搜索到您要找的图标~" />
</el-dialog>
</div>
<div class='icon-box' >
<el-input
ref='inputRef'
v-model='valueIcon'
v-bind='$attrs'
:placeholder='placeholder'
:clearable='clearable'
@clear='clearIcon'
@click='openDialog'
>
<template #append>
<el-button :icon='customIcons[iconValue]' />
</template>
</el-input>
<el-dialog v-model='dialogVisible' :title='placeholder' top='5%' width='30%' >
<el-input v-model='inputValue' placeholder='搜索图标' size='large' :prefix-icon='Icons.Search' />
<el-scrollbar v-if='Object.keys(iconsList).length'>
<div class='icon-list'>
<div v-for='item in iconsList' :key='item' class='icon-item' @click='selectIcon(item)'>
<component :is='item'></component>
<span>{{ item.name }}</span>
</div>
</div>
</el-scrollbar>
<el-empty v-else description='未搜索到您要找的图标~' />
</el-dialog>
</div>
</template>
<script lang="ts" setup name="SelectIcon">
<script lang='ts' setup name='SelectIcon'>
import * as Icons from '@element-plus/icons-vue'
import { computed, ref } from 'vue'
import { computed, ref } from 'vue';
interface SelectIconProps {
iconValue: string | undefined
title?: string
clearable?: boolean
placeholder?: string
iconValue: string| undefined;
title?: string;
clearable?: boolean;
placeholder?: string;
}
const props = withDefaults(defineProps<SelectIconProps>(), {
iconValue: '',
title: '请选择图标',
clearable: true,
placeholder: '请选择图标'
iconValue: '',
title: '请选择图标',
clearable: true,
placeholder: '请选择图标',
})
// 重新接收一下,防止打包后 clearable 报错
@@ -55,36 +55,37 @@ const openDialog = () => (dialogVisible.value = true)
// 选择图标(触发更新父组件数据)
const emit = defineEmits<{
'update:iconValue': [value: string]
'update:iconValue': [value: string];
}>()
const selectIcon = (item: any) => {
dialogVisible.value = false
valueIcon.value = item.name
emit('update:iconValue', item.name)
setTimeout(() => inputRef.value.blur(), 0)
dialogVisible.value = false
valueIcon.value = item.name
emit('update:iconValue', item.name)
setTimeout(() => inputRef.value.blur(), 0)
}
// 清空图标
const inputRef = ref()
const clearIcon = () => {
valueIcon.value = ''
emit('update:iconValue', '')
setTimeout(() => inputRef.value.blur(), 0)
valueIcon.value = ''
emit('update:iconValue', '')
setTimeout(() => inputRef.value.blur(), 0)
}
// 监听搜索框值
const inputValue = ref('')
const customIcons: { [key: string]: any } = Icons
const iconsList = computed((): { [key: string]: any } => {
if (!inputValue.value) return Icons
let result: { [key: string]: any } = {}
for (const key in customIcons) {
if (key.toLowerCase().indexOf(inputValue.value.toLowerCase()) > -1) result[key] = customIcons[key]
}
return result
if (!inputValue.value) return Icons
let result: { [key: string]: any } = {}
for (const key in customIcons) {
if (key.toLowerCase().indexOf(inputValue.value.toLowerCase()) > -1) result[key] = customIcons[key]
}
return result
})
</script>
<style scoped lang="scss">
@use './index.scss';
<style scoped lang='scss'>
@import "./index.scss";
</style>

View File

@@ -1,84 +1,97 @@
<template>
<div class="time-control">
<el-select class="select" v-model="timeUnit" placeholder="选择时间单位" @change="handleChange">
<!-- 采用 v-for 动态渲染 -->
<el-option v-for="unit in timeUnits" :key="unit.value" :label="unit.label" :value="unit.value"></el-option>
</el-select>
<div class='time-control'>
<el-select
class='select'
v-model='timeUnit'
placeholder='选择时间单位'
@change='handleChange'
>
<!-- 采用 v-for 动态渲染 -->
<el-option
v-for='unit in timeUnits'
:key='unit.value'
:label='unit.label'
:value='unit.value'
></el-option>
</el-select>
<!-- 禁用时间选择器 -->
<div class="date-display">
<el-date-picker
class="date-picker"
v-model="startDate"
type="date"
placeholder="起始时间"
@change="emitDateChange"
:disabled-date="disableStartDate"
:readonly="timeUnit != '自定义'"
></el-date-picker>
<el-text>~</el-text>
<el-date-picker
class="date-picker"
v-model="endDate"
type="date"
placeholder="结束时间"
@change="emitDateChange"
:disabled-date="disableEndDate"
:readonly="timeUnit !== '自定义'"
></el-date-picker>
</div>
<div class="date-display" v-if="timeUnit !== '自定义'">
<el-button
style="width: 10px"
class="triangle-button"
type="primary"
@click="prevPeriod"
@change="emitDateChange"
>
<div class="left_triangle"></div>
</el-button>
<el-button class="triangle-button" type="primary" @click="goToCurrent">当前</el-button>
<el-button
style="width: 10px"
class="triangle-button"
type="primary"
@click="nextPeriod"
:disabled="isNextDisabled"
>
<div class="right_triangle"></div>
</el-button>
</div>
<!-- 禁用时间选择器 -->
<div class='date-display'>
<el-date-picker
class='date-picker'
v-model='startDate'
type='date'
placeholder='起始时间'
@change='emitDateChange'
:disabled-date='disableStartDate'
:readonly="timeUnit != '自定义'"
></el-date-picker>
<el-text>~</el-text>
<el-date-picker
class='date-picker'
v-model='endDate'
type='date'
placeholder='结束时间'
@change='emitDateChange'
:disabled-date='disableEndDate'
:readonly="timeUnit !== '自定义'"
></el-date-picker>
</div>
<div class='date-display' v-if="timeUnit !== '自定义'">
<el-button
style='width: 10px;'
class='triangle-button'
type='primary'
@click='prevPeriod'
@change='emitDateChange'
>
<div class='left_triangle'></div>
</el-button>
<el-button class='triangle-button' type='primary' @click='goToCurrent'>
当前
</el-button>
<el-button
style='width: 10px;'
class='triangle-button'
type='primary'
@click='nextPeriod'
:disabled='isNextDisabled'
>
<div class='right_triangle'></div>
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
<script setup lang='ts'>
import { ref, onMounted, defineProps, defineEmits } from 'vue'
// 定义时间单位的类型
interface TimeUnit {
label: string
value: string
label: string;
value: string;
}
// 定义组件的props包含包括和排除的时间单位
const props = defineProps({
include: {
type: Array as () => string[],
default: () => ['日', '周', '月', '季度', '年', '自定义']
},
exclude: {
type: Array as () => string[],
default: () => []
},
default: {
type: String,
default: '月'
}
include: {
type: Array as () => string[],
default: () => ['日', '周', '月', '季度', '年', '自定义'],
},
exclude: {
type: Array as () => string[],
default: () => [],
},
default: {
type: String,
default: '月',
},
})
// 定义事件
const emit = defineEmits<{
(e: 'update-dates', startDate: string, endDate: string): void
(e: 'update-dates', startDate: string, endDate: string): void;
}>()
const timeUnit = ref<string>(props.default) // 默认选择
const startDate = ref<Date>(new Date()) // 起始日期
@@ -87,206 +100,219 @@ const isNextDisabled = ref<boolean>(false) // 控制下一周期按钮的禁用
const today = ref<Date>(new Date()) // 当前日期
// 过滤出可用的时间单位
const timeUnits = ref<TimeUnit[]>(
props.include
.filter(unit => !props.exclude.includes(unit))
.map(unit => ({
label: unit,
value: unit
}))
props.include.filter(unit => !props.exclude.includes(unit)).map(unit => ({
label: unit,
value: unit,
})),
)
// 发出日期变化事件
const emitDateChange = () => {
emit('update-dates', formatDate(startDate.value), formatDate(endDate.value))
emit('update-dates', formatDate(startDate.value), formatDate(endDate.value))
}
// 在组件挂载时更新日期范围
onMounted(() => {
updateDateRange()
updateDateRange()
})
const handleChange = (unit: string) => {
// 根据选择的时间单位处理日期变化
if (unit !== '自定义') {
updateDateRange()
} else {
// 自定义选项逻辑
startDate.value = new Date(new Date().setDate(new Date().getDate() - 1))
endDate.value = new Date()
}
timeUnit.value = unit
// 根据选择的时间单位处理日期变化
if (unit !== '自定义') {
updateDateRange()
} else {
// 自定义选项逻辑
startDate.value = new Date(new Date().setDate(new Date().getDate() - 1))
endDate.value = new Date()
}
timeUnit.value = unit
// 确保开始时间和结束时间不为空
if (!startDate.value) {
startDate.value = new Date()
}
if (!endDate.value) {
endDate.value = new Date()
}
startDate.value = new Date()
}
if (!endDate.value) {
endDate.value = new Date()
}
emitDateChange() // 变化时也发出更新事件
updateNextButtonStatus()
emitDateChange() // 变化时也发出更新事件
updateNextButtonStatus()
}
const updateDateRange = () => {
// 根据选择的时间单位计算起始和结束日期
if (timeUnit.value === '日') {
startDate.value = today.value
endDate.value = today.value
} else if (timeUnit.value === '周') {
startDate.value = getStartOfWeek(today.value)
endDate.value = getEndOfWeek(today.value)
} else if (timeUnit.value === '月') {
// 获取本月的开始和结束日期
startDate.value = new Date(today.value.getFullYear(), today.value.getMonth(), 1)
endDate.value = new Date(today.value.getFullYear(), today.value.getMonth() + 1, 0)
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
} else if (timeUnit.value === '季度') {
const quarter = Math.floor(today.value.getMonth() / 3)
startDate.value = new Date(today.value.getFullYear(), quarter * 3, 1)
endDate.value = new Date(today.value.getFullYear(), quarter * 3 + 3, 0)
// 根据选择的时间单位计算起始和结束日期
if (timeUnit.value === '日') {
startDate.value = today.value
endDate.value = today.value
} else if (timeUnit.value === '周') {
startDate.value = getStartOfWeek(today.value)
endDate.value = getEndOfWeek(today.value)
} else if (timeUnit.value === '月') {
// 获取本月的开始和结束日期
startDate.value = new Date(today.value.getFullYear(), today.value.getMonth(), 1);
endDate.value = new Date(today.value.getFullYear(), today.value.getMonth() + 1, 0);
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
} else if (timeUnit.value === '年') {
startDate.value = new Date(today.value.getFullYear(), 0, 1)
endDate.value = new Date(today.value.getFullYear(), 11, 31)
} else if (timeUnit.value === '季度') {
const quarter = Math.floor(today.value.getMonth() / 3);
startDate.value = new Date(today.value.getFullYear(), quarter * 3, 1);
endDate.value = new Date(today.value.getFullYear(), quarter * 3 + 3, 0);
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
}
// 确保开始时间和结束时间不为空
if (!startDate.value) {
startDate.value = new Date()
}
if (!endDate.value) {
endDate.value = new Date()
}
} else if (timeUnit.value === '年') {
startDate.value = new Date(today.value.getFullYear(), 0, 1);
endDate.value = new Date(today.value.getFullYear(), 11, 31);
updateNextButtonStatus()
// // 确保结束日期不超过今天
// if (endDate.value > today.value) {
// endDate.value = new Date(today.value);
// endDate.value.setHours(23, 59, 59, 999); // 设置结束时间为今天的23:59:59.999
// }
}
// 确保开始时间和结束时间不为空
if (!startDate.value) {
startDate.value = new Date()
}
if (!endDate.value) {
endDate.value = new Date()
}
updateNextButtonStatus()
}
const getStartOfWeek = (date: Date) => {
const startOfWeek = new Date(date)
const day = startOfWeek.getDay()
const diff = day === 0 ? -6 : 1 - day // 星期天的情况
startOfWeek.setDate(startOfWeek.getDate() + diff)
return startOfWeek
const startOfWeek = new Date(date)
const day = startOfWeek.getDay()
const diff = day === 0 ? -6 : 1 - day // 星期天的情况
startOfWeek.setDate(startOfWeek.getDate() + diff)
return startOfWeek
}
const getEndOfWeek = (date: Date) => {
const endOfWeek = new Date(date)
const day = endOfWeek.getDay()
const diff = day === 0 ? 0 : 7 - day // 星期天的情况
endOfWeek.setDate(endOfWeek.getDate() + diff)
const endOfWeek = new Date(date)
const day = endOfWeek.getDay()
const diff = day === 0 ? 0 : 7 - day // 星期天的情况
endOfWeek.setDate(endOfWeek.getDate() + diff)
// 获取今天的日期
const today = new Date()
today.setHours(23, 59, 59, 999) // 设置今天的结束时间23:59:59.999
// 获取今天的日期
const today = new Date();
today.setHours(23, 59, 59, 999); // 设置今天的结束时间23:59:59.999
// 返回不超过今天的结束时间
//return endOfWeek > today ? today : endOfWeek;
return endOfWeek
// 返回不超过今天的结束时间
//return endOfWeek > today ? today : endOfWeek;
return endOfWeek
}
const prevPeriod = () => {
const prevStartDate = new Date(startDate.value)
const prevEndDate = new Date(endDate.value)
const prevStartDate = new Date(startDate.value)
const prevEndDate = new Date(endDate.value)
if (timeUnit.value === '日') {
prevStartDate.setDate(prevStartDate.getDate() - 1)
prevEndDate.setDate(prevEndDate.getDate() - 1)
} else if (timeUnit.value === '周') {
prevStartDate.setDate(prevStartDate.getDate() - 7)
prevEndDate.setDate(prevEndDate.getDate() - 7)
} else if (timeUnit.value === '月') {
prevStartDate.setMonth(prevStartDate.getMonth() - 1)
prevEndDate.setMonth(prevEndDate.getMonth() - 1)
} else if (timeUnit.value === '季度') {
prevStartDate.setMonth(prevStartDate.getMonth() - 3)
prevEndDate.setMonth(prevEndDate.getMonth() - 3)
} else if (timeUnit.value === '年') {
prevStartDate.setFullYear(prevStartDate.getFullYear() - 1)
prevEndDate.setFullYear(prevEndDate.getFullYear() - 1)
}
if (timeUnit.value === '日') {
prevStartDate.setDate(prevStartDate.getDate() - 1)
prevEndDate.setDate(prevEndDate.getDate() - 1)
} else if (timeUnit.value === '周') {
prevStartDate.setDate(prevStartDate.getDate() - 7)
prevEndDate.setDate(prevEndDate.getDate() - 7)
} else if (timeUnit.value === '月') {
startDate.value = prevStartDate
endDate.value = prevEndDate
updateNextButtonStatus()
prevStartDate.setMonth(prevStartDate.getMonth() - 1)
prevEndDate.setMonth(prevEndDate.getMonth() - 1)
} else if (timeUnit.value === '季度') {
prevStartDate.setMonth(prevStartDate.getMonth() - 3)
prevEndDate.setMonth(prevEndDate.getMonth() - 3)
} else if (timeUnit.value === '年') {
prevStartDate.setFullYear(prevStartDate.getFullYear() - 1)
prevEndDate.setFullYear(prevEndDate.getFullYear() - 1)
}
startDate.value = prevStartDate
endDate.value = prevEndDate
updateNextButtonStatus()
}
const goToCurrent = () => {
if (timeUnit.value !== '自定义') {
updateDateRange() // 更新为当前选择时间单位的时间范围
}
if (timeUnit.value !== '自定义') {
updateDateRange() // 更新为当前选择时间单位的时间范围
}
}
const nextPeriod = () => {
const nextStartDate = new Date(startDate.value)
const nextEndDate = new Date(endDate.value)
const nextStartDate = new Date(startDate.value)
const nextEndDate = new Date(endDate.value)
if (timeUnit.value === '日') {
nextStartDate.setDate(nextStartDate.getDate() + 1)
nextEndDate.setDate(nextEndDate.getDate() + 1)
} else if (timeUnit.value === '周') {
nextStartDate.setDate(nextStartDate.getDate() + 7)
nextEndDate.setDate(nextEndDate.getDate() + 7)
} else if (timeUnit.value === '月') {
nextStartDate.setMonth(nextStartDate.getMonth() + 1)
nextEndDate.setMonth(nextEndDate.getMonth() + 1)
} else if (timeUnit.value === '季度') {
nextStartDate.setMonth(nextStartDate.getMonth() + 3)
nextEndDate.setMonth(nextStartDate.getMonth() + 3)
} else if (timeUnit.value === '年') {
nextStartDate.setFullYear(nextStartDate.getFullYear() + 1)
nextEndDate.setFullYear(nextEndDate.getFullYear() + 1)
}
if (timeUnit.value === '日') {
nextStartDate.setDate(nextStartDate.getDate() + 1)
nextEndDate.setDate(nextEndDate.getDate() + 1)
} else if (timeUnit.value === '周') {
nextStartDate.setDate(nextStartDate.getDate() + 7)
nextEndDate.setDate(nextEndDate.getDate() + 7)
} else if (timeUnit.value === '月') {
nextStartDate.setMonth(nextStartDate.getMonth() + 1)
nextEndDate.setMonth(nextEndDate.getMonth() + 1)
} else if (timeUnit.value === '季度') {
nextStartDate.setMonth(nextStartDate.getMonth() + 3)
nextEndDate.setMonth(nextStartDate.getMonth() + 3)
} else if (timeUnit.value === '年') {
nextStartDate.setFullYear(nextStartDate.getFullYear() + 1)
nextEndDate.setFullYear(nextEndDate.getFullYear() + 1)
}
startDate.value = nextStartDate
endDate.value = nextEndDate
updateNextButtonStatus()
startDate.value = nextStartDate
endDate.value = nextEndDate
updateNextButtonStatus()
}
const updateNextButtonStatus = () => {
// 更新下一个按钮的禁用状态
const maxDate = new Date() // 假设最新日期为今天
// 将 maxDate 设置为当天的开始时间
maxDate.setHours(0, 0, 0, 0)
// 将 endDate 设置为当天的开始时间并进行比较
const endDateAdjusted = new Date(endDate.value)
endDateAdjusted.setHours(0, 0, 0, 0)
// 仅比较日期部分
isNextDisabled.value = endDateAdjusted >= maxDate
emitDateChange() // 变化时也发出更新事件
// 更新下一个按钮的禁用状态
const maxDate = new Date() // 假设最新日期为今天
// 将 maxDate 设置为当天的开始时间
maxDate.setHours(0, 0, 0, 0)
// 将 endDate 设置为当天的开始时间并进行比较
const endDateAdjusted = new Date(endDate.value)
endDateAdjusted.setHours(0, 0, 0, 0)
// 仅比较日期部分
isNextDisabled.value = endDateAdjusted >= maxDate
emitDateChange() // 变化时也发出更新事件
}
// 限制开始日期不能选择超过当前日期
const disableStartDate = (date: Date) => {
return date > today.value
return date > today.value
}
// 限制结束日期不能超过当前日期且必须大于开始日期
const disableEndDate = (date: Date) => {
if (timeUnit.value !== '自定义') return false // 如果不是自定义时间单位,则不限制
const start = new Date(startDate.value)
return date > today.value || (start && date <= start)
if (timeUnit.value !== '自定义') return false // 如果不是自定义时间单位,则不限制
const start = new Date(startDate.value)
return date > today.value || (start && date <= start)
}
// 格式化日期yyyy-mm-dd
function formatDate(date: Date | null): string {
if (!date) {
return ''
}
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}`
if (!date) {
return '';
}
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}`;
}
</script>
<style scoped lang="scss">
@use './index.scss';
<style scoped lang='scss'>
@import "./index.scss";
</style>

View File

@@ -5,7 +5,7 @@
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { onBeforeUnmount, onMounted, ref, defineExpose, watch, nextTick } from 'vue'
// import echarts from './echarts'
import * as echarts from 'echarts' // 全引入
// import 'echarts/lib/component/dataZoom'

View File

@@ -6,24 +6,21 @@ import { useAuthStore } from '@/stores/modules/auth'
import type { Directive, DirectiveBinding } from 'vue'
const auth: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
//console.log('binding',binding)
const { value, modifiers } = binding
let currentPageRoles = []
const authStore = useAuthStore()
if (modifiers && Object.keys(modifiers).length) {
currentPageRoles = authStore.authButtonListGet[Object.keys(modifiers)[0]] ?? []
} else {
currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? []
}
console.log('currentPageRoles', currentPageRoles)
if (value instanceof Array && value.length) {
const hasPermission = value.every(item => currentPageRoles.includes(item))
if (!hasPermission) el.remove()
} else {
if (!currentPageRoles.includes(value)) el.remove()
}
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding
const authStore = useAuthStore()
const currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? []
// console.log('1234',authStore.routeName)
// console.log('123',currentPageRoles)
if (value instanceof Array && value.length) {
//console.log('123456',value)
const hasPermission = value.every(item => currentPageRoles.includes(item))
if (!hasPermission) el.remove()
} else {
//console.log('12345',value)
if (!currentPageRoles.includes(value)) el.remove()
}
},
}
export default auth

View File

@@ -1,5 +1,4 @@
import { ElNotification } from "element-plus";
import type { AxiosResponse } from "axios";
/**
* @description 接收数据流生成 blob创建链接下载文件
@@ -9,55 +8,6 @@ import type { AxiosResponse } from "axios";
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fileType 导出的文件格式 (默认为.xlsx)
* */
/**
* 从 Content-Disposition 头解析文件名
*/
const getFileNameFromContentDisposition = (contentDisposition: string | undefined | null): string | null => {
if (!contentDisposition) return null;
// 优先匹配 filename*=UTF-8'' 格式RFC 5987
const filenameStarRegex = /filename\*\s*=\s*UTF-8''([^;]+)/i;
const starMatches = filenameStarRegex.exec(contentDisposition);
if (starMatches && starMatches[1]) {
try {
return decodeURIComponent(starMatches[1]);
} catch (e) {
console.warn('解码 filename* 失败:', e);
}
}
// 其次匹配 filename="文件名" 或 filename=文件名 格式
const filenameRegex = /filename\s*=\s*([^;]+)/i;
const matches = filenameRegex.exec(contentDisposition);
if (matches && matches[1]) {
let filename = matches[1].trim();
// 去除引号
filename = filename.replace(/^["']|["']$/g, "");
// 尝试解码 URL 编码的文件名
try {
// 检查是否包含 URL 编码字符
if (filename.includes('%') || filename.includes('UTF-8')) {
// 处理可能的 UTF-8 前缀
if (filename.startsWith("UTF-8''") || filename.startsWith("utf-8''")) {
filename = filename.substring(7);
}
filename = decodeURIComponent(filename);
}
} catch (e) {
// 如果解码失败,返回原始文件名
console.warn('解码文件名失败,使用原始值:', e);
}
return filename;
}
return null;
};
export const useDownload = async (
api: (param: any) => Promise<any>,
tempName: string,
@@ -89,73 +39,6 @@ export const useDownload = async (
document.body.removeChild(exportFile);
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
}
};
/**
* @description 支持服务器文件名的下载方法,会从 Content-Disposition 头获取文件名
* @param {Function} api 导出表格的api方法 (必传)
* @param {String} fallbackName 备用文件名,当服务器未提供时使用 (可选)
* @param {Object} params 导出的参数 (默认{})
* @param {Boolean} isNotify 是否有导出消息提示 (默认为 true)
* @param {String} fallbackFileType 备用文件格式,当服务器未提供时使用 (默认为.xlsx)
*/
export const useDownloadWithServerFileName = async (
api: (param: any) => Promise<AxiosResponse<Blob> | any>,
fallbackName: string = "",
params: any = {},
isNotify: boolean = true,
fallbackFileType: string = ".xlsx"
) => {
if (isNotify) {
ElNotification({
title: "温馨提示",
message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!",
type: "info",
duration: 3000
});
}
try {
const res = await api(params);
// 检查响应是否包含 data 属性AxiosResponse还是直接是 Blob
const blob = res.data ? new Blob([res.data]) : new Blob([res]);
// 尝试从响应头获取文件名(如果存在)
let serverFileName: string | null = null;
if (res.headers) {
const headers = res.headers || {};
const contentDisposition = headers['content-disposition'] || headers['Content-Disposition'];
serverFileName = getFileNameFromContentDisposition(contentDisposition);
}
// 确定最终使用的文件名
let finalFileName: string;
if (serverFileName) {
finalFileName = serverFileName;
} else if (fallbackName) {
finalFileName = `${fallbackName}${fallbackFileType}`;
} else {
finalFileName = `download${fallbackFileType}`;
}
// 兼容 edge 不支持 createObjectURL 方法
if ("msSaveOrOpenBlob" in navigator) {
return window.navigator.msSaveOrOpenBlob(blob, finalFileName);
}
const blobUrl = window.URL.createObjectURL(blob);
const exportFile = document.createElement("a");
exportFile.style.display = "none";
exportFile.download = finalFileName;
exportFile.href = blobUrl;
document.body.appendChild(exportFile);
exportFile.click();
// 去除下载对 url 的影响
document.body.removeChild(exportFile);
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error('文件下载失败:', error);
}
};

View File

@@ -78,7 +78,6 @@ export const useTable = (
});
dataCallBack && (data = dataCallBack(data));
state.tableData = isPageable ? data.records : data;
//console.log(data);
// 解构后台返回的分页数据 (如果有分页更新分页信息)
if (isPageable) {
state.resPageable.total = data.total;
@@ -86,7 +85,6 @@ export const useTable = (
state.resPageable.size = data.size;
}
} catch (error) {
//console.log('123');
requestError && requestError(error);
}
};

View File

@@ -1,62 +1,63 @@
<!-- 经典布局 -->
<template>
<el-container class="layout">
<el-header>
<div class="header-lf mask-image">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
<span class="logo-text">{{ title }}</span>
</div>
<ToolBarLeft />
</div>
<div class="header-ri">
<ToolBarRight />
</div>
</el-header>
<el-container class="classic-content">
<el-aside>
<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="menuList" />
</el-menu>
</el-scrollbar>
</div>
</el-aside>
<el-container class="classic-main">
<Main />
</el-container>
</el-container>
<el-container class="layout">
<el-header>
<div class="header-lf mask-image">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
<span class="logo-text">{{ title }}</span>
</div>
<ToolBarLeft />
</div>
<div class="header-ri">
<ToolBarRight />
</div>
</el-header>
<el-container class="classic-content">
<el-aside>
<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="menuList" />
</el-menu>
</el-scrollbar>
</div>
</el-aside>
<el-container class="classic-main">
<Main />
</el-container>
</el-container>
</el-container>
</template>
<script setup lang="ts" name="layoutClassic">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/modules/auth'
import { useGlobalStore } from '@/stores/modules/global'
import Main from '@/layouts/components/Main/index.vue'
import SubMenu from '@/layouts/components/Menu/SubMenu.vue'
import ToolBarLeft from '@/layouts/components/Header/ToolBarLeft.vue'
import ToolBarRight from '@/layouts/components/Header/ToolBarRight.vue'
import { computed } from "vue";
import { useRoute } from "vue-router";
import { useAuthStore } from "@/stores/modules/auth";
import { useGlobalStore } from "@/stores/modules/global";
import Main from "@/layouts/components/Main/index.vue";
import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
const title = import.meta.env.VITE_GLOB_APP_TITLE
const title = import.meta.env.VITE_GLOB_APP_TITLE;
const route = useRoute()
const authStore = useAuthStore()
const globalStore = useGlobalStore()
const accordion = computed(() => globalStore.accordion)
const isCollapse = computed(() => globalStore.isCollapse)
const menuList = computed(() => authStore.showMenuListGet)
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
const route = useRoute();
const authStore = useAuthStore();
const globalStore = useGlobalStore();
const accordion = computed(() => globalStore.accordion);
const isCollapse = computed(() => globalStore.isCollapse);
const menuList = computed(() => authStore.showMenuListGet);
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,105 +1,103 @@
<!-- 分栏布局 -->
<template>
<el-container class="layout">
<div class="aside-split">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
</div>
<el-scrollbar>
<div class="split-list">
<div
v-for="item in menuList"
:key="item.path"
class="split-item"
:class="{
'split-active': splitActive === item.path || `/${splitActive.split('/')[1]}` === item.path
}"
@click="changeSubMenu(item)"
>
<el-icon>
<component :is="item.meta.icon"></component>
</el-icon>
<span class="title">{{ item.meta.title }}</span>
</div>
</div>
</el-scrollbar>
<el-container class="layout">
<div class="aside-split">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
</div>
<el-scrollbar>
<div class="split-list">
<div
v-for="item in menuList"
:key="item.path"
class="split-item"
:class="{ 'split-active': splitActive === item.path || `/${splitActive.split('/')[1]}` === item.path }"
@click="changeSubMenu(item)"
>
<el-icon>
<component :is="item.meta.icon"></component>
</el-icon>
<span class="title">{{ item.meta.title }}</span>
</div>
</div>
<el-aside :class="{ 'not-aside': !subMenuList.length }" :style="{ width: isCollapse ? '65px' : '210px' }">
<div class="logo flx-center">
<span v-show="subMenuList.length" class="logo-text">{{ isCollapse ? 'G' : title }}</span>
</div>
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="subMenuList" />
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header>
<ToolBarLeft />
<ToolBarRight />
</el-header>
<Main />
</el-container>
</el-scrollbar>
</div>
<el-aside :class="{ 'not-aside': !subMenuList.length }" :style="{ width: isCollapse ? '65px' : '210px' }">
<div class="logo flx-center">
<span v-show="subMenuList.length" class="logo-text">{{ isCollapse ? "G" : title }}</span>
</div>
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="subMenuList" />
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<el-header>
<ToolBarLeft />
<ToolBarRight />
</el-header>
<Main />
</el-container>
</el-container>
</template>
<script setup lang="ts" name="layoutColumns">
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/modules/auth'
import { useGlobalStore } from '@/stores/modules/global'
import Main from '@/layouts/components/Main/index.vue'
import ToolBarLeft from '@/layouts/components/Header/ToolBarLeft.vue'
import ToolBarRight from '@/layouts/components/Header/ToolBarRight.vue'
import SubMenu from '@/layouts/components/Menu/SubMenu.vue'
import { ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useAuthStore } from "@/stores/modules/auth";
import { useGlobalStore } from "@/stores/modules/global";
import Main from "@/layouts/components/Main/index.vue";
import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
const title = import.meta.env.VITE_GLOB_APP_TITLE
const title = import.meta.env.VITE_GLOB_APP_TITLE;
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const globalStore = useGlobalStore()
const accordion = computed(() => globalStore.accordion)
const isCollapse = computed(() => globalStore.isCollapse)
const menuList = computed(() => authStore.showMenuListGet)
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const globalStore = useGlobalStore();
const accordion = computed(() => globalStore.accordion);
const isCollapse = computed(() => globalStore.isCollapse);
const menuList = computed(() => authStore.showMenuListGet);
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
const subMenuList = ref<Menu.MenuOptions[]>([])
const splitActive = ref('')
const subMenuList = ref<Menu.MenuOptions[]>([]);
const splitActive = ref("");
watch(
() => [menuList, route],
() => {
// 当前菜单没有数据直接 return
if (!menuList.value.length) return
splitActive.value = route.path
const menuItem = menuList.value.filter((item: Menu.MenuOptions) => {
return route.path === item.path || `/${route.path.split('/')[1]}` === item.path
})
if (menuItem[0].children?.length) return (subMenuList.value = menuItem[0].children)
subMenuList.value = []
},
{
deep: true,
immediate: true
}
)
() => [menuList, route],
() => {
// 当前菜单没有数据直接 return
if (!menuList.value.length) return;
splitActive.value = route.path;
const menuItem = menuList.value.filter((item: Menu.MenuOptions) => {
return route.path === item.path || `/${route.path.split("/")[1]}` === item.path;
});
if (menuItem[0].children?.length) return (subMenuList.value = menuItem[0].children);
subMenuList.value = [];
},
{
deep: true,
immediate: true
}
);
// change SubMenu
const changeSubMenu = (item: Menu.MenuOptions) => {
splitActive.value = item.path
if (item.children?.length) return (subMenuList.value = item.children)
subMenuList.value = []
router.push(item.path)
}
splitActive.value = item.path;
if (item.children?.length) return (subMenuList.value = item.children);
subMenuList.value = [];
router.push(item.path);
};
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,78 +1,85 @@
<!-- 横向布局 -->
<template>
<el-container class="layout">
<el-header>
<div class="logo flx-center">
<!-- <img class="logo-img" src="@/assets/images/logo.svg" alt="logo" /> -->
<img class="logo-img" src="@/assets/images/cn_pms9100_logo.png" alt="logo" />
<span class="logo-text">{{ title }}</span>
</div>
<el-menu v-if="showMenuFlag" trigger="click" mode="horizontal" :router="false" :default-active="activeMenu">
<!-- 不能直接使用 SubMenu 组件无法触发 el-menu 隐藏省略功能 -->
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu
v-if="subItem.children?.length"
:key="subItem.path"
:index="subItem.path + 'el-sub-menu'"
>
<template #title>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<span>{{ subItem.meta.title }}</span>
</template>
<SubMenu :menu-list="subItem.children" />
</el-sub-menu>
<el-menu-item
v-else
:key="subItem.path + 'el-menu-item'"
:index="subItem.path"
@click="handleClickMenu(subItem)"
>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-menu>
<ToolBarRight />
</el-header>
<Main />
</el-container>
<el-container class="layout">
<el-header>
<div class="logo flx-center">
<!-- <img class="logo-img" src="@/assets/images/logo.svg" alt="logo" /> -->
<img
class="logo-img"
src="@/assets/images/cn_pms9100_logo.png"
alt="logo"
/>
<span class="logo-text">{{ title }} </span>
</div>
<el-menu v-if="showMenuFlag" trigger="click" mode="horizontal" :router="false" :default-active="activeMenu">
<!-- 不能直接使用 SubMenu 组件无法触发 el-menu 隐藏省略功能 -->
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu
v-if="subItem.children?.length"
:key="subItem.path"
:index="subItem.path + 'el-sub-menu'"
>
<template #title>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<span>{{ subItem.meta.title }}</span>
</template>
<SubMenu :menu-list="subItem.children" />
</el-sub-menu>
<el-menu-item
v-else
:key="subItem.path + 'el-menu-item'"
:index="subItem.path"
@click="handleClickMenu(subItem)"
>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem.meta.title }}</span>
</template>
</el-menu-item>
</template>
</el-menu>
<ToolBarRight />
</el-header>
<Main />
</el-container>
</template>
<script setup lang="ts" name="layoutTransverse">
import { computed } from 'vue'
import { useAuthStore } from '@/stores/modules/auth'
import { useRoute, useRouter } from 'vue-router'
import Main from '@/layouts/components/Main/index.vue'
import ToolBarRight from '@/layouts/components/Header/ToolBarRight.vue'
import SubMenu from '@/layouts/components/Menu/SubMenu.vue'
import { computed } from "vue";
import { useAuthStore } from "@/stores/modules/auth";
import { useRoute, useRouter } from "vue-router";
import Main from "@/layouts/components/Main/index.vue";
import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
const title = import.meta.env.VITE_GLOB_APP_TITLE
const title = import.meta.env.VITE_GLOB_APP_TITLE;
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const menuList = computed(() => authStore.showMenuListGet)
const showMenuFlag = computed(() => authStore.showMenuFlagGet)
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const menuList = computed(() => authStore.showMenuListGet);
const showMenuFlag=computed(()=>authStore.showMenuFlagGet)
const activeMenu = computed(
() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string
);
const handleClickMenu = (subItem: Menu.MenuOptions) => {
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
router.push(subItem.path)
}
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");
router.push(subItem.path);
};
</script>
<style scoped lang="scss">
@use './index.scss';
.logo {
margin-right: 0 !important;
@import "./index.scss";
.logo{
margin-right: 0 !important;
}
.logo-text {
letter-spacing: 2px;
letter-spacing: 2px;
}
</style>

View File

@@ -1,56 +1,56 @@
<!-- 纵向布局 -->
<template>
<el-container class="layout">
<el-aside>
<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
<span v-show="!isCollapse" class="logo-text">{{ title }}</span>
</div>
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="menuList" />
</el-menu>
</el-scrollbar>
</div>
</el-aside>
<el-container>
<el-header>
<ToolBarLeft />
<ToolBarRight />
</el-header>
<Main />
</el-container>
<el-container class="layout">
<el-aside>
<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
<div class="logo flx-center">
<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
<span v-show="!isCollapse" class="logo-text">{{ title }}</span>
</div>
<el-scrollbar>
<el-menu
:router="false"
:default-active="activeMenu"
:collapse="isCollapse"
:unique-opened="accordion"
:collapse-transition="false"
>
<SubMenu :menu-list="menuList" />
</el-menu>
</el-scrollbar>
</div>
</el-aside>
<el-container>
<el-header>
<ToolBarLeft />
<ToolBarRight />
</el-header>
<Main />
</el-container>
</el-container>
</template>
<script setup lang="ts" name="layoutVertical">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/modules/auth'
import { useGlobalStore } from '@/stores/modules/global'
import Main from '@/layouts/components/Main/index.vue'
import ToolBarLeft from '@/layouts/components/Header/ToolBarLeft.vue'
import ToolBarRight from '@/layouts/components/Header/ToolBarRight.vue'
import SubMenu from '@/layouts/components/Menu/SubMenu.vue'
import { computed } from "vue";
import { useRoute } from "vue-router";
import { useAuthStore } from "@/stores/modules/auth";
import { useGlobalStore } from "@/stores/modules/global";
import Main from "@/layouts/components/Main/index.vue";
import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
const title = import.meta.env.VITE_GLOB_APP_TITLE
const title = import.meta.env.VITE_GLOB_APP_TITLE;
const route = useRoute()
const authStore = useAuthStore()
const globalStore = useGlobalStore()
const accordion = computed(() => globalStore.accordion)
const isCollapse = computed(() => globalStore.isCollapse)
const menuList = computed(() => authStore.showMenuListGet)
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
const route = useRoute();
const authStore = useAuthStore();
const globalStore = useGlobalStore();
const accordion = computed(() => globalStore.accordion);
const isCollapse = computed(() => globalStore.isCollapse);
const menuList = computed(() => authStore.showMenuListGet);
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string);
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -1,104 +1,115 @@
<template>
<div class="footer flx-align-center pl10">
<el-dropdown>
<!-- <span class="el-dropdown-link">
<div class="footer flx-align-center pl10">
<el-dropdown>
<!-- <span class="el-dropdown-link">
{{ title }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span> -->
<!-- <el-button dictType="primary"> -->
<div class="change_mode">
{{ title }}
<el-icon class="el-icon--right change_mode_down"><arrow-down /></el-icon>
<el-icon class="el-icon--right change_mode_up"><arrow-up /></el-icon>
</div>
<!-- </el-button> -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handelOpen('模拟式')">模拟式模块</el-dropdown-item>
<el-dropdown-item @click="handelOpen('数字式')">数字式模块</el-dropdown-item>
<el-dropdown-item @click="handelOpen('比对式')">比对式模块</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<p style="margin: 0">
<a href="http://www.shining-electric.com/" target="_blank">2024 © 南京灿能电力自动化股份有限公司</a>
</p>
</div>
<!-- <el-button dictType="primary"> -->
<div class="change_mode">
{{ title }}
<el-icon class="el-icon--right change_mode_down"
><arrow-down
/></el-icon>
<el-icon class="el-icon--right change_mode_up"><arrow-up /></el-icon>
</div>
<!-- </el-button> -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handelOpen('模拟式')"
>模拟式模块</el-dropdown-item
>
<el-dropdown-item @click="handelOpen('数字式')"
>数字式模块</el-dropdown-item
>
<el-dropdown-item @click="handelOpen('比对式')"
disabled>比对式模块</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<p style="margin: 0;" >
<a href="http://www.shining-electric.com/" target="_blank">
2024 © 南京灿能电力自动化股份有限公司
</a>
</p>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useAuthStore } from '@/stores/modules/auth'
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
import { useRouter } from 'vue-router'
const router = useRouter()
const authStore = useAuthStore()
const modeStore = useModeStore()
import { ref, reactive, computed, onMounted, watch } from "vue";
import { useAuthStore } from "@/stores/modules/auth";
import { useModeStore } from '@/stores/modules/mode'; // 引入模式 store
import { useRouter } from "vue-router";
const router = useRouter();
const authStore = useAuthStore();
const modeStore = useModeStore();
const title = computed(() => {
return modeStore.currentMode === '' ? '模拟式模块' : modeStore.currentMode + '模块'
})
return modeStore.currentMode=== ''? '模拟式模块' : modeStore.currentMode+'模块';
});
const handelOpen = async (item: string) => {
await authStore.setShowMenu()
modeStore.setCurrentMode(item) // 将模式code存入 store
await authStore.setShowMenu();
modeStore.setCurrentMode(item); // 将模式code存入 store
//if (router.currentRoute.value.path === '/home/index') {
// 强制刷新页面
window.location.reload();
//} else {
// router.push({ path: '/home/index' });
//}
};
//if (router.currentRoute.value.path === '/home/index') {
// 强制刷新页面
window.location.reload()
//} else {
// router.push({ path: '/home/index' });
//}
}
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
.footer {
position: relative;
background-color: var(--el-color-primary);
// .el-button:hover {
// background-color: var(--el-color-primary) !important;
// border: none !important;
// outline: none !important;
// }
.change_mode {
color: #fff;
display: flex;
align-items: center;
justify-content: flex-start;
height: 100%;
width: auto;
font-size: 14px;
.change_mode_down {
display: block;
}
.change_mode_up {
display: none;
}
position: relative;
background-color: var(--el-color-primary);
// .el-button:hover {
// background-color: var(--el-color-primary) !important;
// border: none !important;
// outline: none !important;
// }
.change_mode {
color: #fff;
display: flex;
align-items: center;
justify-content: flex-start;
height: 100%;
width: auto;
font-size: 14px;
.change_mode_down {
display: block;
}
.change_mode:hover {
.change_mode_down {
display: none;
}
.change_mode_up {
display: block;
}
.change_mode_up {
display: none;
}
.el-dropdown {
z-index: 1001;
}
.change_mode:hover {
.change_mode_down {
display: none;
}
p {
position: absolute;
width: 100%;
height: 100%;
text-align: right;
line-height: 40px;
a {
color: #fff;
margin-right: 25px; // 增加右边距
}
.change_mode_up {
display: block;
}
}
.el-dropdown {
z-index: 1001;
}
p {
position: absolute;
width: 100%;
height: 100%;
text-align: right;
line-height: 40px;
a {
color: #fff;
margin-right: 25px; // 增加右边距
}
}
}
</style>

View File

@@ -48,7 +48,6 @@
active: item.value === appSceneStore.currentScene
}"
@click="changeScene(item.value?? '')"
:disabled = "item.value === appSceneStore.currentScene"
>
{{ item.name }}
</el-dropdown-item>

View File

@@ -1,81 +1,84 @@
<template>
<Maximize v-show="maximize" />
<Tabs v-if="tabs && showMenuFlag" />
<el-main>
<router-view v-slot="{ Component, route }" style="height: 100%">
<!-- {{ keepAliveName}} -->
<!-- <transition name="slide-right" mode="out-in"> -->
<keep-alive :include="tabsMenuList">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<!-- </transition> -->
</router-view>
</el-main>
<el-footer>
<Footer />
</el-footer>
<Maximize v-show="maximize" />
<Tabs v-if="tabs && showMenuFlag" />
<el-main>
<router-view v-slot="{ Component, route }" style="height:100%;">
<!-- {{ keepAliveName}} -->
<!-- <transition name="slide-right" mode="out-in"> -->
<keep-alive :include="tabsMenuList" >
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<!-- </transition> -->
</router-view>
</el-main>
<el-footer>
<Footer />
</el-footer>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, provide, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useDebounceFn } from '@vueuse/core'
import { useGlobalStore } from '@/stores/modules/global'
import { useKeepAliveStore } from '@/stores/modules/keepAlive'
import Maximize from './components/Maximize.vue'
import Tabs from '@/layouts/components/Tabs/index.vue'
import Footer from '@/layouts/components/Footer/index.vue'
import { useAuthStore } from '@/stores/modules/auth'
import { ref, onBeforeUnmount, provide, watch, computed } from "vue";
import { storeToRefs } from "pinia";
import { useDebounceFn } from "@vueuse/core";
import { useGlobalStore } from "@/stores/modules/global";
import { useKeepAliveStore } from "@/stores/modules/keepAlive";
import Maximize from "./components/Maximize.vue";
import Tabs from "@/layouts/components/Tabs/index.vue";
import Footer from "@/layouts/components/Footer/index.vue";
import { useAuthStore } from "@/stores/modules/auth";
import { useTabsStore } from '@/stores/modules/tabs'
const tabStore = useTabsStore()
const globalStore = useGlobalStore()
const globalStore = useGlobalStore();
const tabsMenuList = computed(() => tabStore.tabsMenuList.map(item => item.name))
const authStore = useAuthStore()
const { maximize, isCollapse, layout, tabs, footer } = storeToRefs(globalStore)
const keepAliveStore = useKeepAliveStore()
const { keepAliveName } = storeToRefs(keepAliveStore)
const authStore = useAuthStore();
const { maximize, isCollapse, layout, tabs, footer } = storeToRefs(globalStore);
const keepAliveStore = useKeepAliveStore();
const { keepAliveName } = storeToRefs(keepAliveStore);
// console.log("🚀 ~ keepAliveName:", keepAliveName)
//是否显示导航栏
const showMenuFlag = computed(() => authStore.showMenuFlagGet)
const showMenuFlag = computed(() => authStore.showMenuFlagGet);
// 注入刷新页面方法
const isRouterShow = ref(true)
const refreshCurrentPage = (val: boolean) => (isRouterShow.value = val)
provide('refresh', refreshCurrentPage)
const isRouterShow = ref(true);
const refreshCurrentPage = (val: boolean) => (isRouterShow.value = val);
provide("refresh", refreshCurrentPage);
// 监听当前页面是否最大化,动态添加 class
watch(
() => maximize.value,
() => {
const app = document.getElementById('app') as HTMLElement
if (maximize.value) app.classList.add('main-maximize')
else app.classList.remove('main-maximize')
},
{ immediate: true }
)
() => maximize.value,
() => {
const app = document.getElementById("app") as HTMLElement;
if (maximize.value) app.classList.add("main-maximize");
else app.classList.remove("main-maximize");
},
{ immediate: true }
);
// 监听布局变化,在 body 上添加相对应的 layout class
watch(
() => layout.value,
() => {
const body = document.body as HTMLElement
body.setAttribute('class', layout.value)
},
{ immediate: true }
)
() => layout.value,
() => {
const body = document.body as HTMLElement;
body.setAttribute("class", layout.value);
},
{ immediate: true }
);
// 监听窗口大小变化,折叠侧边栏
const screenWidth = ref(0)
const screenWidth = ref(0);
const listeningWindow = useDebounceFn(() => {
screenWidth.value = document.body.clientWidth
if (!isCollapse.value && screenWidth.value < 1200) globalStore.setGlobalState('isCollapse', true)
if (isCollapse.value && screenWidth.value > 1200) globalStore.setGlobalState('isCollapse', false)
}, 100)
window.addEventListener('resize', listeningWindow, false)
screenWidth.value = document.body.clientWidth;
if (!isCollapse.value && screenWidth.value < 1200)
globalStore.setGlobalState("isCollapse", true);
if (isCollapse.value && screenWidth.value > 1200)
globalStore.setGlobalState("isCollapse", false);
}, 100);
window.addEventListener("resize", listeningWindow, false);
onBeforeUnmount(() => {
window.removeEventListener('resize', listeningWindow)
})
window.removeEventListener("resize", listeningWindow);
});
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -20,17 +20,13 @@
</template>
</template>
<script setup lang="ts">
import { onBeforeMount } from "vue";
import { useRouter } from "vue-router";
defineProps<{ menuList: Menu.MenuOptions[] }>();
const router = useRouter();
const handleClickMenu = (subItem: Menu.MenuOptions) => {
//console.log('1456----------------',subItem);
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, "_blank");
router.push(subItem.path);
};
</script>
<style lang="scss">
.el-sub-menu .el-sub-menu__title:hover {

View File

@@ -1,88 +1,81 @@
<template>
<el-dropdown trigger="click" :teleported="false">
<div class="more-button">
<i :class="'iconfont icon-xiala'"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refresh">
<el-icon><Refresh /></el-icon>
{{ $t('tabs.refresh') }}
</el-dropdown-item>
<el-dropdown-item @click="maximize">
<el-icon><FullScreen /></el-icon>
{{ $t('tabs.maximize') }}
</el-dropdown-item>
<el-dropdown-item divided @click="closeCurrentTab">
<el-icon><Remove /></el-icon>
{{ $t('tabs.closeCurrent') }}
</el-dropdown-item>
<el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'left')">
<el-icon><DArrowLeft /></el-icon>
{{ $t('tabs.closeLeft') }}
</el-dropdown-item>
<el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'right')">
<el-icon><DArrowRight /></el-icon>
{{ $t('tabs.closeRight') }}
</el-dropdown-item>
<el-dropdown-item divided @click="tabStore.closeMultipleTab(route.fullPath)">
<el-icon><CircleClose /></el-icon>
{{ $t('tabs.closeOther') }}
</el-dropdown-item>
<el-dropdown-item @click="closeAllTab">
<el-icon><FolderDelete /></el-icon>
{{ $t('tabs.closeAll') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click" :teleported="false">
<div class="more-button">
<i :class="'iconfont icon-xiala'"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="refresh">
<el-icon><Refresh /></el-icon>{{ $t("tabs.refresh") }}
</el-dropdown-item>
<el-dropdown-item @click="maximize">
<el-icon><FullScreen /></el-icon>{{ $t("tabs.maximize") }}
</el-dropdown-item>
<el-dropdown-item divided @click="closeCurrentTab">
<el-icon><Remove /></el-icon>{{ $t("tabs.closeCurrent") }}
</el-dropdown-item>
<el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'left')">
<el-icon><DArrowLeft /></el-icon>{{ $t("tabs.closeLeft") }}
</el-dropdown-item>
<el-dropdown-item @click="tabStore.closeTabsOnSide(route.fullPath, 'right')">
<el-icon><DArrowRight /></el-icon>{{ $t("tabs.closeRight") }}
</el-dropdown-item>
<el-dropdown-item divided @click="tabStore.closeMultipleTab(route.fullPath)">
<el-icon><CircleClose /></el-icon>{{ $t("tabs.closeOther") }}
</el-dropdown-item>
<el-dropdown-item @click="closeAllTab">
<el-icon><FolderDelete /></el-icon>{{ $t("tabs.closeAll") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { inject, nextTick } from 'vue'
import { HOME_URL } from '@/config'
import { useTabsStore } from '@/stores/modules/tabs'
import { useGlobalStore } from '@/stores/modules/global'
import { useKeepAliveStore } from '@/stores/modules/keepAlive'
import { useRoute, useRouter } from 'vue-router'
import { inject, nextTick } from "vue";
import { HOME_URL } from "@/config";
import { useTabsStore } from "@/stores/modules/tabs";
import { useGlobalStore } from "@/stores/modules/global";
import { useKeepAliveStore } from "@/stores/modules/keepAlive";
import { useRoute, useRouter } from "vue-router";
const route = useRoute()
const router = useRouter()
const tabStore = useTabsStore()
const globalStore = useGlobalStore()
const keepAliveStore = useKeepAliveStore()
const route = useRoute();
const router = useRouter();
const tabStore = useTabsStore();
const globalStore = useGlobalStore();
const keepAliveStore = useKeepAliveStore();
// refresh current page
const refreshCurrentPage: Function = inject('refresh') as Function
const refreshCurrentPage: Function = inject("refresh") as Function;
const refresh = () => {
setTimeout(() => {
keepAliveStore.removeKeepAliveName(route.name as string)
refreshCurrentPage(false)
nextTick(() => {
keepAliveStore.addKeepAliveName(route.name as string)
refreshCurrentPage(true)
})
}, 0)
}
setTimeout(() => {
keepAliveStore.removeKeepAliveName(route.name as string);
refreshCurrentPage(false);
nextTick(() => {
keepAliveStore.addKeepAliveName(route.name as string);
refreshCurrentPage(true);
});
}, 0);
};
// maximize current page
const maximize = () => {
globalStore.setGlobalState('maximize', true)
}
globalStore.setGlobalState("maximize", true);
};
// Close Current
const closeCurrentTab = () => {
if (route.meta.isAffix) return
tabStore.removeTabs(route.fullPath)
}
if (route.meta.isAffix) return;
tabStore.removeTabs(route.fullPath);
};
// Close All
const closeAllTab = () => {
tabStore.closeMultipleTab()
router.push(HOME_URL)
}
tabStore.closeMultipleTab();
router.push(HOME_URL);
};
</script>
<style scoped lang="scss">
@use '../index.scss';
@import "../index.scss";
</style>

View File

@@ -24,12 +24,12 @@
<script setup lang="ts">
import Sortable from 'sortablejs'
import { computed, onMounted, ref, watch } from 'vue'
import { ref, computed, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useGlobalStore } from '@/stores/modules/global'
import { useTabsStore } from '@/stores/modules/tabs'
import { useAuthStore } from '@/stores/modules/auth'
import { TabPaneName, TabsPaneContext } from 'element-plus'
import { TabsPaneContext, TabPaneName } from 'element-plus'
import MoreButton from './components/MoreButton.vue'
const route = useRoute()
@@ -105,15 +105,18 @@ const tabsDrop = () => {
// Tab Click
const tabClick = (tabItem: TabsPaneContext) => {
const fullPath = tabItem.props.name as string
// console.log("🚀 ~ tabClick ~ fullPath:", tabItem)
router.push(fullPath)
}
// Remove Tab
const tabRemove = (fullPath: TabPaneName) => {
tabStore.removeTabs(fullPath as string, fullPath == route.fullPath || '/machine/testScriptAdd' == route.fullPath)
}
</script>
<style scoped lang="scss">
@use './index.scss';
@import './index.scss';
</style>

View File

@@ -1,198 +1,186 @@
<template>
<el-drawer v-model="drawerVisible" title="布局设置" size="290px">
<!-- 布局样式 -->
<el-divider class="divider" content-position="center">
<el-icon><Notification /></el-icon>
布局样式
</el-divider>
<div class="layout-box">
<el-tooltip effect="dark" content="纵向" placement="top" :show-after="200">
<div
:class="['layout-item layout-vertical', { 'is-active': layout == 'vertical' }]"
@click="setLayout('vertical')"
>
<div class="layout-dark"></div>
<div class="layout-container">
<div class="layout-light"></div>
<div class="layout-content"></div>
</div>
<el-icon v-if="layout == 'vertical'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
<el-tooltip effect="dark" content="经典" placement="top" :show-after="200">
<div
:class="['layout-item layout-classic', { 'is-active': layout == 'classic' }]"
@click="setLayout('classic')"
>
<div class="layout-dark"></div>
<div class="layout-container">
<div class="layout-light"></div>
<div class="layout-content"></div>
</div>
<el-icon v-if="layout == 'classic'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
<el-tooltip effect="dark" content="横向" placement="top" :show-after="200">
<div
:class="['layout-item layout-transverse', { 'is-active': layout == 'transverse' }]"
@click="setLayout('transverse')"
>
<div class="layout-dark"></div>
<div class="layout-content"></div>
<el-icon v-if="layout == 'transverse'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
<el-tooltip effect="dark" content="分栏" placement="top" :show-after="200">
<div
:class="['layout-item layout-columns', { 'is-active': layout == 'columns' }]"
@click="setLayout('columns')"
>
<div class="layout-dark"></div>
<div class="layout-light"></div>
<div class="layout-content"></div>
<el-icon v-if="layout == 'columns'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
<el-drawer v-model="drawerVisible" title="布局设置" size="290px">
<!-- 布局样式 -->
<el-divider class="divider" content-position="center">
<el-icon><Notification /></el-icon>
布局样式
</el-divider>
<div class="layout-box">
<el-tooltip effect="dark" content="纵向" placement="top" :show-after="200">
<div :class="['layout-item layout-vertical', { 'is-active': layout == 'vertical' }]" @click="setLayout('vertical')">
<div class="layout-dark"></div>
<div class="layout-container">
<div class="layout-light"></div>
<div class="layout-content"></div>
</div>
<el-icon v-if="layout == 'vertical'">
<CircleCheckFilled />
</el-icon>
</div>
<div class="theme-item">
<span>
侧边栏反转色
<el-tooltip effect="dark" content="侧边栏颜色变为深色模式" placement="top">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
<el-switch v-model="asideInverted" @change="setAsideTheme" />
</el-tooltip>
<el-tooltip effect="dark" content="经典" placement="top" :show-after="200">
<div :class="['layout-item layout-classic', { 'is-active': layout == 'classic' }]" @click="setLayout('classic')">
<div class="layout-dark"></div>
<div class="layout-container">
<div class="layout-light"></div>
<div class="layout-content"></div>
</div>
<el-icon v-if="layout == 'classic'">
<CircleCheckFilled />
</el-icon>
</div>
<div class="theme-item mb50">
<span>
头部反转色
<el-tooltip effect="dark" content="头部颜色变为深色模式" placement="top">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
<el-switch v-model="headerInverted" @change="setHeaderTheme" />
</el-tooltip>
<el-tooltip effect="dark" content="横向" placement="top" :show-after="200">
<div :class="['layout-item layout-transverse', { 'is-active': layout == 'transverse' }]" @click="setLayout('transverse')">
<div class="layout-dark"></div>
<div class="layout-content"></div>
<el-icon v-if="layout == 'transverse'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
<el-tooltip effect="dark" content="分栏" placement="top" :show-after="200">
<div :class="['layout-item layout-columns', { 'is-active': layout == 'columns' }]" @click="setLayout('columns')">
<div class="layout-dark"></div>
<div class="layout-light"></div>
<div class="layout-content"></div>
<el-icon v-if="layout == 'columns'">
<CircleCheckFilled />
</el-icon>
</div>
</el-tooltip>
</div>
<div class="theme-item">
<span>
侧边栏反转色
<el-tooltip effect="dark" content="侧边栏颜色变为深色模式" placement="top">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
<el-switch v-model="asideInverted" @change="setAsideTheme" />
</div>
<div class="theme-item mb50">
<span>
头部反转色
<el-tooltip effect="dark" content="头部颜色变为深色模式" placement="top">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
<el-switch v-model="headerInverted" @change="setHeaderTheme" />
</div>
<!-- 全局主题 -->
<el-divider class="divider" content-position="center">
<el-icon><ColdDrink /></el-icon>
全局主题
</el-divider>
<div class="theme-item">
<span>主题颜色</span>
<el-color-picker v-model="primary" :predefine="colorList" @change="changePrimary" />
</div>
<div class="theme-item">
<span>暗黑模式</span>
<SwitchDark />
</div>
<div class="theme-item">
<span>灰色模式</span>
<el-switch v-model="isGrey" @change="changeGreyOrWeak('grey', !!$event)" />
</div>
<div class="theme-item mb40">
<span>色弱模式</span>
<el-switch v-model="isWeak" @change="changeGreyOrWeak('weak', !!$event)" />
</div>
<!-- 全局主题 -->
<el-divider class="divider" content-position="center">
<el-icon><ColdDrink /></el-icon>
全局主题
</el-divider>
<div class="theme-item">
<span>主题颜色</span>
<el-color-picker v-model="primary" :predefine="colorList" @change="changePrimary" />
</div>
<div class="theme-item">
<span>暗黑模式</span>
<SwitchDark />
</div>
<div class="theme-item">
<span>灰色模式</span>
<el-switch v-model="isGrey" @change="changeGreyOrWeak('grey', !!$event)" />
</div>
<div class="theme-item mb40">
<span>色弱模式</span>
<el-switch v-model="isWeak" @change="changeGreyOrWeak('weak', !!$event)" />
</div>
<!-- 界面设置 -->
<el-divider class="divider" content-position="center">
<el-icon><Setting /></el-icon>
界面设置
</el-divider>
<div class="theme-item">
<span>菜单折叠</span>
<el-switch v-model="isCollapse" />
</div>
<div class="theme-item">
<span>菜单手风琴</span>
<el-switch v-model="accordion" />
</div>
<div class="theme-item">
<span>面包屑</span>
<el-switch v-model="breadcrumb" />
</div>
<div class="theme-item">
<span>面包屑图标</span>
<el-switch v-model="breadcrumbIcon" />
</div>
<div class="theme-item">
<span>标签栏</span>
<el-switch v-model="tabs" />
</div>
<div class="theme-item">
<span>标签栏图标</span>
<el-switch v-model="tabsIcon" />
</div>
<div class="theme-item">
<span>页脚</span>
<el-switch v-model="footer" />
</div>
</el-drawer>
<!-- 界面设置 -->
<el-divider class="divider" content-position="center">
<el-icon><Setting /></el-icon>
界面设置
</el-divider>
<div class="theme-item">
<span>菜单折叠</span>
<el-switch v-model="isCollapse" />
</div>
<div class="theme-item">
<span>菜单手风琴</span>
<el-switch v-model="accordion" />
</div>
<div class="theme-item">
<span>面包屑</span>
<el-switch v-model="breadcrumb" />
</div>
<div class="theme-item">
<span>面包屑图标</span>
<el-switch v-model="breadcrumbIcon" />
</div>
<div class="theme-item">
<span>标签栏</span>
<el-switch v-model="tabs" />
</div>
<div class="theme-item">
<span>标签栏图标</span>
<el-switch v-model="tabsIcon" />
</div>
<div class="theme-item">
<span>页脚</span>
<el-switch v-model="footer" />
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useTheme } from '@/hooks/useTheme'
import { useGlobalStore } from '@/stores/modules/global'
import { LayoutType } from '@/stores/interface'
import { DEFAULT_PRIMARY } from '@/config'
import mittBus from '@/utils/mittBus'
import SwitchDark from '@/components/SwitchDark/index.vue'
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { useTheme } from "@/hooks/useTheme";
import { useGlobalStore } from "@/stores/modules/global";
import { LayoutType } from "@/stores/interface";
import { DEFAULT_PRIMARY } from "@/config";
import mittBus from "@/utils/mittBus";
import SwitchDark from "@/components/SwitchDark/index.vue";
const { changePrimary, changeGreyOrWeak, setAsideTheme, setHeaderTheme } = useTheme()
const { changePrimary, changeGreyOrWeak, setAsideTheme, setHeaderTheme } = useTheme();
const globalStore = useGlobalStore()
const globalStore = useGlobalStore();
const {
layout,
primary,
isGrey,
isWeak,
asideInverted,
headerInverted,
isCollapse,
accordion,
breadcrumb,
breadcrumbIcon,
tabs,
tabsIcon,
footer
} = storeToRefs(globalStore)
layout,
primary,
isGrey,
isWeak,
asideInverted,
headerInverted,
isCollapse,
accordion,
breadcrumb,
breadcrumbIcon,
tabs,
tabsIcon,
footer
} = storeToRefs(globalStore);
// 预定义主题颜色
const colorList = [
DEFAULT_PRIMARY,
'#daa96e',
'#0c819f',
'#409eff',
'#27ae60',
'#ff5c93',
'#e74c3c',
'#fd726d',
'#f39c12',
'#9b59b6'
]
DEFAULT_PRIMARY,
"#daa96e",
"#0c819f",
"#409eff",
"#27ae60",
"#ff5c93",
"#e74c3c",
"#fd726d",
"#f39c12",
"#9b59b6"
];
// 设置布局方式
const setLayout = (val: LayoutType) => {
globalStore.setGlobalState('layout', val)
setAsideTheme()
}
globalStore.setGlobalState("layout", val);
setAsideTheme();
};
// 打开主题设置
const drawerVisible = ref(false)
mittBus.on('openThemeDrawer', () => (drawerVisible.value = true))
const drawerVisible = ref(false);
mittBus.on("openThemeDrawer", () => (drawerVisible.value = true));
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -8,9 +8,6 @@ import {
} from "@/utils";
import { useRouter } from "vue-router";
import { AUTH_STORE_KEY } from "@/stores/constant";
import {useModeStore} from '@/stores/modules/mode'
export const useAuthStore = defineStore({
id: AUTH_STORE_KEY,
state: (): AuthState => ({
@@ -46,16 +43,9 @@ export const useAuthStore = defineStore({
},
// Get AuthMenuList
async getAuthMenuList() {
const modeStore = useModeStore()
const { data } = await getAuthMenuListApi();
this.authMenuList = data;
const { data: menuData } = await getAuthMenuListApi();
// 根据不同模式过滤菜单
const filteredMenu = modeStore.currentMode === '比对式'
? filterMenuByExcludedNames(menuData, ['testSource', 'testScript', 'controlSource'])
: filterMenuByExcludedNames(menuData, ['standardDevice']);
this.authMenuList = filteredMenu;
},
// Set RouteName
async setRouteName(name: string) {
@@ -83,21 +73,3 @@ export const useAuthStore = defineStore({
},
},
});
/**
* 通用菜单过滤函数
* @param menuList 菜单列表
* @param excludedNames 需要排除的菜单名称数组
* @returns 过滤后的菜单列表
*/
function filterMenuByExcludedNames(menuList: any[], excludedNames: string[]): any[] {
return menuList.filter(menu => {
// 如果当前项有 children递归处理子项
if (menu.children && menu.children.length > 0) {
menu.children = filterMenuByExcludedNames(menu.children, excludedNames);
}
// 过滤掉在排除列表中的菜单项
return !excludedNames.includes(menu.name);
});
}

View File

@@ -3,21 +3,25 @@ import {CHECK_STORE_KEY} from "@/stores/constant";
import type {CheckData} from "@/api/check/interface";
import type {Plan} from '@/api/plan/interface'
import {useAppSceneStore} from "@/stores/modules/mode";
import { set } from "lodash";
const AppSceneStore = useAppSceneStore()
export const useCheckStore = defineStore("check", {
id: CHECK_STORE_KEY,
export const useCheckStore = defineStore(CHECK_STORE_KEY, {
state: () => ({
devices: [] as CheckData.Device[],
plan: {} as Plan.ResPlan,
selectTestItems: {preTest: true, timeTest: false, channelsTest: false, test: true} as CheckData.SelectTestItem,
checkType: 1, // 0:手动检测 1:自动检测
devices: Array<CheckData.Device>(),
plan: Object<Plan.ResPlan>(),
selectTestItems: Object<CheckData.SelectTestItem>({preTest: true, timeTest: false, channelsTest: false, test: true}),
checkType:1, // 0:手动检测 1:自动检测
reCheckType: 1, // 0:不合格项复检 1:全部复检
showDetailType: 0, // 0:数据查询 1:误差体系跟换 2正式检测
showDetailType: 0 ,// 0:数据查询 1:误差体系跟换 2正式检测
temperature: 0,
humidity: 0,
chnNumList: [],//连线数据
nodesConnectable: true,//设置是能可以连线
humidity: 0
}),
getters: {},
actions: {
addDevices(device: CheckData.Device[]) {
this.devices.push(...device);
@@ -29,9 +33,8 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
this.devices = [];
},
initSelectTestItems() {
const appSceneStore = useAppSceneStore()
this.selectTestItems.preTest = true
if (appSceneStore.currentScene === '1') {
if (AppSceneStore.currentScene === '1') {
this.selectTestItems.channelsTest = true
} else {
this.selectTestItems.channelsTest = false
@@ -55,13 +58,6 @@ export const useCheckStore = defineStore(CHECK_STORE_KEY, {
},
setHumidity(humidity: number) {
this.humidity = humidity
},
setChnNum(chnNumList: string[]) {
this.chnNumList = chnNumList
},
setNodesConnectable(nodesConnectable: boolean) {
this.nodesConnectable = nodesConnectable
},
}
}
});

View File

@@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { type GlobalState } from "@/stores/interface";
import { GlobalState } from "@/stores/interface";
import { DEFAULT_PRIMARY} from "@/config";
import piniaPersistConfig from "@/stores/helper/persist";
import {GLOBAL_STORE_KEY} from "@/stores/constant";
@@ -49,6 +49,7 @@ export const useGlobalStore = defineStore({
// Set GlobalState
setGlobalState(...args: ObjToKeyValArray<GlobalState>) {
this.$patch({ [args[0]]: args[1] });
console.log(DEFAULT_PRIMARY);
}
},
persist: piniaPersistConfig(GLOBAL_STORE_KEY)

View File

@@ -140,9 +140,3 @@ body,
:-webkit-any(article, aside, nav, section) h1 {
font-size: 2em;
}
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/core@1.45.0/dist/style.css';
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/core@1.45.0/dist/theme-default.css';
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/controls@latest/dist/style.css';
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/minimap@latest/dist/style.css';
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/node-resizer@latest/dist/style.css';

View File

@@ -1,234 +0,0 @@
import { useUserStore } from "@/stores/modules/user";
// JWT Token解析后的载荷接口
export interface JwtPayload {
userId?: string;
loginName?: string;
exp?: number;
iat?: number;
[key: string]: any;
}
// Token信息摘要接口
export interface TokenInfo {
userId: string | null;
loginName: string | null;
expiration: string | null;
isExpired: boolean;
remainingTime: string;
}
/**
* JWT工具类
* 提供JWT token的解析、验证等功能
*/
export class JwtUtil {
/**
* Base64URL解码
* @param str Base64URL编码的字符串
*/
private static base64UrlDecode(str: string): string {
try {
// Base64URL转Base64
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
// 补齐padding
while (base64.length % 4) {
base64 += '=';
}
// Base64解码
const decoded = atob(base64);
// 处理UTF-8编码
return decodeURIComponent(
decoded
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
} catch (error) {
throw new Error('Base64URL解码失败: ' + error);
}
}
/**
* 解析JWT Token获取载荷信息
* @param token JWT token字符串如果不传则从store中获取
*/
static parseToken(token?: string): JwtPayload | null {
try {
let targetToken = token;
// 如果没有传入token从store中获取
if (!targetToken) {
const userStore = useUserStore();
targetToken = userStore.accessToken;
}
if (!targetToken) {
console.warn('Token不存在');
return null;
}
// JWT token由三部分组成用.分割header.payload.signature
const parts = targetToken.split('.');
if (parts.length !== 3) {
console.error('无效的JWT token格式');
return null;
}
// 解码payload部分第二部分
const payload = parts[1];
const decodedPayload = this.base64UrlDecode(payload);
const tokenData: JwtPayload = JSON.parse(decodedPayload);
return tokenData;
} catch (error) {
console.error('解析JWT Token失败:', error);
return null;
}
}
/**
* 获取指定字段的值
* @param field 字段名
* @param token JWT token字符串可选
*/
static getField<T = any>(field: string, token?: string): T | null {
const tokenData = this.parseToken(token);
return tokenData?.[field] || null;
}
/**
* 获取用户ID
* @param token JWT token字符串可选
*/
static getUserId(token?: string): string | null {
return this.getField<string>('userId', token);
}
/**
* 获取登录名
* @param token JWT token字符串可选
*/
static getLoginName(token?: string): string | null {
return this.getField<string>('loginName', token);
}
/**
* 获取Token过期时间戳
* @param token JWT token字符串可选
*/
static getExpiration(token?: string): number | null {
return this.getField<number>('exp', token);
}
/**
* 获取Token签发时间戳
* @param token JWT token字符串可选
*/
static getIssuedAt(token?: string): number | null {
return this.getField<number>('iat', token);
}
/**
* 检查Token是否过期
* @param token JWT token字符串可选
*/
static isExpired(token?: string): boolean {
const exp = this.getExpiration(token);
if (!exp) return true;
// JWT中的exp是秒级时间戳需要转换为毫秒
const expTime = exp * 1000;
return Date.now() >= expTime;
}
/**
* 获取Token剩余有效时间毫秒
* @param token JWT token字符串可选
*/
static getRemainingTime(token?: string): number {
const exp = this.getExpiration(token);
if (!exp) return 0;
const expTime = exp * 1000;
const remaining = expTime - Date.now();
return Math.max(0, remaining);
}
/**
* 格式化剩余时间为可读字符串
* @param ms 毫秒数
*/
static formatRemainingTime(ms: number): string {
if (ms <= 0) return '已过期';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}${hours % 24}小时`;
if (hours > 0) return `${hours}小时 ${minutes % 60}分钟`;
if (minutes > 0) return `${minutes}分钟 ${seconds % 60}`;
return `${seconds}`;
}
/**
* 获取完整的Token信息摘要
* @param token JWT token字符串可选
*/
static getTokenInfo(token?: string): TokenInfo {
const exp = this.getExpiration(token);
const remainingMs = this.getRemainingTime(token);
return {
userId: this.getUserId(token),
loginName: this.getLoginName(token),
expiration: exp ? new Date(exp * 1000).toLocaleString() : null,
isExpired: this.isExpired(token),
remainingTime: this.formatRemainingTime(remainingMs)
};
}
/**
* 验证Token是否有效格式正确且未过期
* @param token JWT token字符串可选
*/
static isValid(token?: string): boolean {
const tokenData = this.parseToken(token);
return tokenData !== null && !this.isExpired(token);
}
/**
* 获取Token的头部信息
* @param token JWT token字符串可选
*/
static getHeader(token?: string): any | null {
try {
let targetToken = token;
if (!targetToken) {
const userStore = useUserStore();
targetToken = userStore.accessToken;
}
if (!targetToken) return null;
const parts = targetToken.split('.');
if (parts.length !== 3) return null;
const header = parts[0];
const decodedHeader = this.base64UrlDecode(header);
return JSON.parse(decodedHeader);
} catch (error) {
console.error('解析JWT Header失败:', error);
return null;
}
}
}
// 导出单例方法,方便直接调用
export const jwtUtil = JwtUtil;

View File

@@ -1,4 +1,5 @@
import mitt from "mitt";
const mittBus = mitt();
export default mittBus;

View File

@@ -1,697 +1,185 @@
/**
* WebSocket客户端服务
* 提供WebSocket连接管理、心跳机制、消息处理等功能
* 集成JWT token解析支持自动获取用户登录名
*
* @author hongawen
* @version 2.0
*/
import {ElMessage} from "element-plus";
import { ElMessage } from "element-plus";
import { jwtUtil } from "./jwtUtil";
// ============================================================================
// 类型定义 (Types & Interfaces)
// ============================================================================
/**
* WebSocket消息接口定义对应后端WebSocketVO结构
*/
interface WebSocketMessage<T = any> {
type: string; // 消息类型
requestId?: string; // 请求ID
operateCode?: string; // 操作代码
code?: number; // 状态码
desc?: string; // 描述信息
data?: T; // 泛型数据
}
/**
* 回调函数类型定义
*/
type CallbackFunction<T = any> = (message: WebSocketMessage<T>) => void;
/**
* WebSocket配置接口
*/
interface SocketConfig {
url: string; // WebSocket服务器地址
heartbeatInterval?: number; // 心跳间隔时间(ms)
reconnectDelay?: number; // 重连延迟时间(ms)
maxReconnectAttempts?: number; // 最大重连次数
timeout?: number; // 超时时间(ms)
}
/**
* 连接状态枚举
*/
enum ConnectionStatus {
DISCONNECTED = 'disconnected', // 未连接
CONNECTING = 'connecting', // 连接中
CONNECTED = 'connected', // 已连接
RECONNECTING = 'reconnecting', // 重连中
ERROR = 'error' // 连接错误
}
/**
* 常用的WebSocket消息类型定义命名空间
*/
namespace WebSocketMessageTypes {
/**
* 预检测相关消息
*/
export interface PreTestMessage {
deviceId?: string;
status?: string;
progress?: number;
errorInfo?: string;
}
/**
* 系数校准相关消息
*/
export interface CoefficientMessage {
deviceId: string;
channel: number;
voltage?: string;
current?: string;
calibrationResult?: boolean;
}
/**
* 正式检测相关消息
*/
export interface TestMessage {
deviceId: string;
testType: string;
testResult?: 'success' | 'failed' | 'processing';
testData?: any;
}
/**
* 通用响应消息
*/
export interface CommonResponse {
success: boolean;
message?: string;
timestamp?: number;
}
}
// ============================================================================
// 导出类型
// ============================================================================
export type {
WebSocketMessage,
CallbackFunction,
SocketConfig,
WebSocketMessageTypes
};
export {
ConnectionStatus
};
// ============================================================================
// 主要服务类
// ============================================================================
/**
* WebSocket服务类
* 单例模式实现提供完整的WebSocket连接管理功能
*/
export default class SocketService {
// ========================================================================
// 静态属性和方法 (Static)
// ========================================================================
/**
* 单例实例
*/
private static instance: SocketService | null = null;
/**
* 获取单例实例
* @returns SocketService实例
*/
static get Instance(): SocketService {
static instance = null;
static get Instance() {
if (!this.instance) {
this.instance = new SocketService();
}
return this.instance;
}
// 和服务端连接的socket对象
ws = null;
// 存储回调函数
callBackMapping = {};
// 标识是否连接成功
connected = false;
// 记录重试的次数
sendRetryCount = 0;
// 重新连接尝试的次数
connectRetryCount = 0;
work:any;
workerBlobUrl:any;
lastActivityTime= 0; // 上次活动时间戳
lastResponseHeartTime = Date.now();//最后一次收到心跳回复时间
// ========================================================================
// 实例属性 (Properties)
// ========================================================================
reconnectDelay= 5000; // 重新连接延迟,单位毫秒
/**
* WebSocket连接实例
*/
private ws: WebSocket | null = null;
/**
* 消息回调函数映射表
*/
private callBackMapping: Record<string, CallbackFunction<any>> = {};
/**
* 当前连接状态
*/
private connectionStatus: ConnectionStatus = ConnectionStatus.DISCONNECTED;
/**
* 发送消息重试计数器
*/
private sendRetryCount: number = 0;
/**
* 连接重试计数器
*/
private connectRetryCount: number = 0;
/**
* 心跳Worker实例
*/
private heartbeatWorker: Worker | null = null;
/**
* Worker脚本的Blob URL
*/
private workerBlobUrl: string | null = null;
/**
* 最后一次收到心跳响应的时间戳
*/
private lastResponseHeartTime: number = Date.now();
/**
* WebSocket连接配置
*/
private config: SocketConfig = {
// url: 'ws://127.0.0.1:7777/hello',
url: 'ws://192.168.1.124:7777/hello',
heartbeatInterval: 9000, // 9秒心跳间隔
reconnectDelay: 5000, // 5秒重连延迟
maxReconnectAttempts: 5, // 最多重连5次
timeout: 30000 // 30秒超时
};
// 定义连接服务器的方法
connect() {
// ========================================================================
// 构造函数 (Constructor)
// ========================================================================
/**
* 私有构造函数,防止外部直接实例化
*/
private constructor() {
this.initializeProperties();
}
/**
* 初始化属性
*/
private initializeProperties(): void {
this.lastResponseHeartTime = Date.now();
}
// ========================================================================
// Getter属性 (Computed)
// ========================================================================
/**
* 获取连接状态
* @returns 是否已连接
*/
get connected(): boolean {
return this.connectionStatus === ConnectionStatus.CONNECTED;
}
// ========================================================================
// 公共方法 (Public Methods)
// ========================================================================
/**
* 配置WebSocket连接参数
* @param config 部分配置对象
*/
public configure(config: Partial<SocketConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 连接WebSocket服务器同步方式保持向后兼容
*/
public connect(): Promise<void> | void {
// 检查浏览器支持
// 连接服务器
if (!window.WebSocket) {
console.log('您的浏览器不支持WebSocket');
return;
return console.log('您的浏览器不支持WebSocket');
}
// 防止重复连接
if (this.connectionStatus === ConnectionStatus.CONNECTING || this.connected) {
console.warn('WebSocket已连接或正在连接中');
return;
}
this.connectionStatus = ConnectionStatus.CONNECTING;
try {
this.ws = new WebSocket(this.buildWebSocketUrl());
this.setupEventHandlersLegacy();
} catch (error) {
this.connectionStatus = ConnectionStatus.ERROR;
console.error('WebSocket连接失败:', error);
}
}
/**
* 异步连接WebSocket服务器
* @returns Promise<void>
*/
public connectAsync(): Promise<void> {
return new Promise((resolve, reject) => {
// 检查浏览器支持
if (!window.WebSocket) {
const error = '您的浏览器不支持WebSocket';
console.error(error);
reject(new Error(error));
return;
}
// 防止重复连接
if (this.connectionStatus === ConnectionStatus.CONNECTING || this.connected) {
console.warn('WebSocket已连接或正在连接中');
resolve();
return;
}
this.connectionStatus = ConnectionStatus.CONNECTING;
try {
this.ws = new WebSocket(this.buildWebSocketUrl());
this.setupEventHandlers(resolve, reject);
} catch (error) {
this.connectionStatus = ConnectionStatus.ERROR;
reject(error);
}
});
}
/**
* 注册消息回调函数(支持泛型)
* @param messageType 消息类型
* @param callback 回调函数
*/
public registerCallBack<T = any>(messageType: string, callback: CallbackFunction<T>): void {
if (!messageType || typeof callback !== 'function') {
console.error('注册回调函数参数无效');
return;
}
this.callBackMapping[messageType] = callback;
console.log(`注册消息处理器: ${messageType}`);
}
/**
* 注销消息回调函数
* @param messageType 消息类型
*/
public unRegisterCallBack(messageType: string): void {
if (this.callBackMapping[messageType]) {
delete this.callBackMapping[messageType];
console.log(`注销消息处理器: ${messageType}`);
}
}
/**
* 发送数据到WebSocket服务器
* @param data 要发送的数据
* @returns Promise<void>
*/
public send(data: any): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.connected || !this.ws) {
// 未连接时的重试机制
if (this.sendRetryCount < 3) {
this.sendRetryCount++;
setTimeout(() => {
this.send(data).then(resolve).catch(reject);
}, this.sendRetryCount * 500);
return;
} else {
reject(new Error('WebSocket未连接且重试失败'));
return;
}
}
try {
// 重置重试计数
this.sendRetryCount = 0;
// 尝试发送JSON数据失败则发送原始数据
const message = typeof data === 'string' ? data : JSON.stringify(data);
this.ws.send(message);
console.log('发送消息:', message);
resolve();
} catch (error) {
console.error('发送消息失败:', error);
reject(error);
}
});
}
/**
* 关闭WebSocket连接
*/
public closeWs(): void {
console.log('正在关闭WebSocket连接...');
// 清理心跳
this.clearHeartbeat();
// 关闭连接
if (this.ws) {
this.ws.close(1000, '主动关闭连接');
this.ws = null;
}
// 更新状态
this.connectionStatus = ConnectionStatus.DISCONNECTED;
this.connectRetryCount = 0;
this.sendRetryCount = 0;
console.log('WebSocket连接已关闭');
}
/**
* 获取当前连接状态
* @returns 连接状态枚举值
*/
public getConnectionStatus(): ConnectionStatus {
return this.connectionStatus;
}
/**
* 获取连接统计信息
* @returns 连接统计对象
*/
public getConnectionStats(): {
status: ConnectionStatus;
connectRetryCount: number;
lastResponseHeartTime: number;
} {
return {
status: this.connectionStatus,
connectRetryCount: this.connectRetryCount,
lastResponseHeartTime: this.lastResponseHeartTime
};
}
// let token = $.cookie('123');
// let token = '4E6EF539AAF119D82AC4C2BC84FBA21F';
// ========================================================================
// 私有方法 (Private Methods)
// ========================================================================
/**
* 构建完整的WebSocket URL
* 自动从JWT token中获取loginName作为name参数
* @returns 完整的WebSocket URL
*/
private buildWebSocketUrl(): string {
const { url } = this.config;
// 直接从JWT token中获取loginName作为name参数
const loginName = jwtUtil.getLoginName();
if (loginName) {
const separator = url.includes('?') ? '&' : '?';
return `${url}${separator}name=${encodeURIComponent(loginName)}`;
}
// 如果无法获取loginName返回原始URL并输出警告
console.warn('无法从JWT token中获取loginNameWebSocket连接可能会失败');
return url;
}
/**
* 设置WebSocket事件处理器异步版本
* @param resolve Promise resolve回调
* @param reject Promise reject回调
*/
private setupEventHandlers(resolve: () => void, reject: (error: Error) => void): void {
if (!this.ws) return;
// 连接成功事件
this.ws.onopen = () => {
ElMessage.success("WebSocket连接服务端成功");
console.log('WebSocket连接成功');
this.connectionStatus = ConnectionStatus.CONNECTED;
this.connectRetryCount = 0;
this.startHeartbeat();
resolve();
};
// 连接关闭事件
this.ws.onclose = (event: CloseEvent) => {
console.log('WebSocket连接关闭', event.code, event.reason);
this.connectionStatus = ConnectionStatus.DISCONNECTED;
this.clearHeartbeat();
// 非正常关闭且未超过最大重连次数,尝试重连
if (event.code !== 1000 && this.connectRetryCount < this.config.maxReconnectAttempts!) {
this.attemptReconnect();
}
};
// 连接错误事件
this.ws.onerror = (error: Event) => {
console.error('WebSocket连接错误:', error);
ElMessage.error("WebSocket连接异常");
this.connectionStatus = ConnectionStatus.ERROR;
reject(new Error('WebSocket连接失败'));
};
// 消息接收事件
this.ws.onmessage = (event: MessageEvent) => {
this.handleMessage(event);
};
}
/**
* 设置WebSocket事件处理器兼容版本
*/
private setupEventHandlersLegacy(): void {
if (!this.ws) return;
// 连接成功事件
const url = 'ws://127.0.0.1:7777/hello?name=cdf'
this.ws = new WebSocket(url);
// 连接成功的事件
this.ws.onopen = () => {
ElMessage.success("webSocket连接服务端成功了");
console.log('连接服务端成功了');
this.connectionStatus = ConnectionStatus.CONNECTED;
this.connected = true;
// 重置重新连接的次数
this.connectRetryCount = 0;
this.updateLastActivityTime();
this.startHeartbeat();
};
// 连接关闭事件
this.ws.onclose = (event: CloseEvent) => {
// 1.连接服务端失败
// 2.当连接成功之后, 服务器关闭的情况
this.ws.onclose = () => {
console.log('连接webSocket服务端关闭');
this.connectionStatus = ConnectionStatus.DISCONNECTED;
this.connected = false;
this.connectRetryCount++;
this.clearHeartbeat();
// 保持原有的重连逻辑(被注释掉的)
// this.connectRetryCount++;
/* setTimeout(() => {
this.connect();
}, 500 * this.connectRetryCount);*/
this.connect();
}, 500 * this.connectRetryCount);*/
};
// 连接错误事件
this.ws.onerror = () => {
ElMessage.error("webSocket连接异常");
this.connectionStatus = ConnectionStatus.ERROR;
};
// 消息接收事件
this.ws.onmessage = (event: MessageEvent) => {
this.handleMessage(event);
};
}
/**
* 处理接收到的消息
* 支持心跳响应、JSON消息和普通文本消息
* @param event WebSocket消息事件
*/
private handleMessage(event: MessageEvent): void {
// console.log('Received message:', event.data);
// 心跳响应处理
if (event.data === 'over') {
console.log(`${new Date().toLocaleTimeString()} - 收到心跳响应`);
this.lastResponseHeartTime = Date.now();
return;
}
// 得到服务端发送过来的数据
this.ws.onmessage = (event) => {
// console.log('🚀 ~ SocketService ~ connect ~ event:', event)
if(event.data == 'over') {
//心跳消息处理
this.lastResponseHeartTime = Date.now();
this.updateLastActivityTime(); // 收到心跳响应时更新活动时间
}else {
let message: { [key: string]: any };
try {
console.log('Received message:',event.data)
message = JSON.parse(event.data);
} catch (e) {
return console.error("消息解析失败", event.data, e);
}
// 检查消息是否为空或无效
if (!event.data || event.data.trim() === '') {
console.warn('收到空消息,跳过处理');
return;
}
// 业务消息处理
try {
// 检查是否为JSON格式
if (typeof event.data === 'string' && (event.data.startsWith('{') || event.data.startsWith('['))) {
const message: WebSocketMessage = JSON.parse(event.data);
/* 通过接受服务端发送的type字段来回调函数 */
if (message?.type && this.callBackMapping[message.type]) {
this.callBackMapping[message.type](message);
} else {
console.warn('未找到对应的消息处理器:', message.type);
}
} else {
// 非JSON格式的消息作为普通文本处理
console.log('收到非JSON格式消息:', event.data);
// 可以添加文本消息的处理逻辑
if (this.callBackMapping['text']) {
this.callBackMapping['text']({
type: 'text',
data: event.data
});
console.log("抛弃====>")
console.log(event.data)
/* 丢弃或继续写你的逻辑 */
}
}
} catch (error) {
console.error('消息解析失败:', event.data, error);
console.error('消息类型:', typeof event.data);
console.error('消息长度:', event.data?.length || 0);
}
};
}
// ========================================================================
// 重连机制相关方法
// ========================================================================
/**
* 尝试重新连接WebSocket
*/
private attemptReconnect(): void {
if (this.connectionStatus === ConnectionStatus.RECONNECTING) {
return;
}
this.connectionStatus = ConnectionStatus.RECONNECTING;
this.connectRetryCount++;
const delay = this.config.reconnectDelay! * this.connectRetryCount;
console.log(`尝试第${this.connectRetryCount}次重连,${delay}ms后开始...`);
setTimeout(() => {
try {
const result = this.connect();
if (result instanceof Promise) {
result.catch((error: any) => {
console.error('重连失败:', error);
});
}
} catch (error: any) {
console.error('重连失败:', error);
}
}, delay);
}
// ========================================================================
// 心跳机制相关方法
// ========================================================================
/**
* 启动心跳机制
*/
private startHeartbeat(): void {
startHeartbeat() {
this.lastResponseHeartTime = Date.now();
this.createHeartbeatWorker();
const _this = this
_this.workerBlobUrl = window.URL.createObjectURL(new Blob(['(function(e){setInterval(function(){this.postMessage(null)},9000)})()']));
this.work = new Worker(_this.workerBlobUrl);
this.work.onmessage = function(e){
//判断多久没收到心跳响应
if(_this.lastActivityTime - _this.lastResponseHeartTime > 30000){
//说明已经三轮心跳没收到回复了,关闭检测,提示用户。
ElMessage.error("业务主体模块发生未知异常,请尝试重新启动!");
_this.clearHeartbeat();
return;
}
_this.sendHeartbeat();
}
}
sendHeartbeat() {
console.log(new Date()+"进入心跳消息发送。。。。。。。。。。。。。")
this.ws.send('alive');
this.updateLastActivityTime(); // 发送心跳后更新活动时间
}
/**
* 创建心跳Worker
* 使用Worker在独立线程中处理心跳定时器避免主线程阻塞
*/
private createHeartbeatWorker(): void {
try {
// 创建Worker脚本
const workerScript = `
setInterval(function() {
postMessage('heartbeat');
}, ${this.config.heartbeatInterval});
`;
this.workerBlobUrl = window.URL.createObjectURL(
new Blob([workerScript], { type: 'application/javascript' })
);
this.heartbeatWorker = new Worker(this.workerBlobUrl);
// 心跳Worker消息处理
this.heartbeatWorker.onmessage = (event: MessageEvent) => {
this.handleHeartbeatTick();
};
// Worker错误处理
this.heartbeatWorker.onerror = (error: ErrorEvent) => {
console.error('心跳Worker错误:', error);
this.clearHeartbeat();
};
} catch (error) {
console.error('创建心跳Worker失败:', error);
updateLastActivityTime() {
this.lastActivityTime = Date.now();
}
clearHeartbeat() {
const _this = this
if (_this.work) {
_this.work.terminate();
_this.work = null;
}
if (_this.workerBlobUrl) {
window.URL.revokeObjectURL(_this.workerBlobUrl); // 释放临时的Blob URL
_this.workerBlobUrl = null;
}
}
/**
* 处理心跳定时器事件
* 检查连接超时并发送心跳消息
*/
private handleHeartbeatTick(): void {
// 检查是否超时(距离上次收到心跳响应的时间)
const timeSinceLastResponse = Date.now() - this.lastResponseHeartTime;
if (timeSinceLastResponse > this.config.timeout!) {
console.error(`WebSocket心跳超时: ${timeSinceLastResponse}ms > ${this.config.timeout}ms`);
ElMessage.error("WebSocket连接超时请检查网络连接");
this.clearHeartbeat();
this.closeWs();
return;
}
this.sendHeartbeat();
}
/**
* 发送心跳消息到服务器
*/
private sendHeartbeat(): void {
if (this.connected && this.ws) {
console.log(`${new Date().toLocaleTimeString()} - 发送心跳消息`);
this.ws.send('alive');
// 回调函数的注册
registerCallBack(socketType, callBack) {
this.callBackMapping[socketType] = callBack;
}
// 取消某一个回调函数
unRegisterCallBack(socketType) {
this.callBackMapping[socketType] = null;
}
// 发送数据的方法
send(data) {
// 判断此时此刻有没有连接成功
if (this.connected) {
this.sendRetryCount = 0;
try {
this.ws.send(JSON.stringify(data));
} catch (e) {
this.ws.send(data);
}
} else {
this.sendRetryCount++;
setTimeout(() => {
this.send(data);
}, this.sendRetryCount * 500);
}
}
/**
* 清理心跳机制
* 终止Worker并清理相关资源
*/
private clearHeartbeat(): void {
if (this.heartbeatWorker) {
this.heartbeatWorker.terminate();
this.heartbeatWorker = null;
}
if (this.workerBlobUrl) {
window.URL.revokeObjectURL(this.workerBlobUrl);
this.workerBlobUrl = null;
// 断开方法
closeWs() {
if (this.connected) {
this.ws.close()
}
console.log('执行WS关闭命令..');
}
}
}

View File

@@ -1,222 +1,222 @@
<template>
<el-dialog :title="dialogTitle" :model-value="dialogVisible" @close="close" v-bind="dialogMiddle" align-center>
<el-form :model="formContent" ref="dialogFormRef" :rules="rules" class="form-two">
<el-form-item label="上级菜单" prop="pid" :label-width="100">
<el-tree-select
v-model="displayPid"
:data="functionList"
check-strictly
:render-after-expand="false"
show-checkbox
check-on-click-node
node-key="id"
:props="defaultProps"
/>
</el-form-item>
<el-form-item label="名称" prop="name" :label-width="100">
<el-input v-model="formContent.name" maxlength="32" show-word-limit />
</el-form-item>
<el-form-item label="编码" prop="code" :label-width="100">
<el-input v-model="formContent.code" maxlength="32" show-word-limit />
</el-form-item>
<el-form-item v-if="!formContent.type" label="图标" prop="icon" :label-width="100">
<IconSelect
v-model="formContent.icon"
:iconValue="formContent.icon"
@update:icon-value="iconValue => (formContent.icon = iconValue)"
placeholder="选择一个图标"
/>
</el-form-item>
<el-form-item v-if="!formContent.type" label="路由地址" prop="path" :label-width="100">
<el-input v-model="formContent.path" maxlength="32" show-word-limit />
</el-form-item>
<el-form-item v-if="!formContent.type" label="组件地址" prop="component" :label-width="100">
<el-input v-model="formContent.component" maxlength="32" show-word-limit />
</el-form-item>
<el-form-item label="排序" prop="sort" :label-width="100">
<el-input-number v-model="formContent.sort" :min="1" :max="999" />
</el-form-item>
<el-form-item label="类型" prop="type" :label-width="100">
<el-select v-model="formContent.type" clearable placeholder="请选择资源类型">
<el-option label="菜单" :value="0"></el-option>
<el-option label="按钮" :value="1"></el-option>
<!-- <el-option label="公共资源" :value="2"></el-option>
<el-dialog :title="dialogTitle" :model-value="dialogVisible" @close="close" v-bind="dialogMiddle">
<el-form :model="formContent" ref='dialogFormRef' :rules='rules' class="form-two">
<el-form-item label="上级菜单" prop="pid" :label-width="100">
<el-tree-select
v-model="displayPid"
:data="functionList"
check-strictly
:render-after-expand="false"
show-checkbox
check-on-click-node
node-key="id"
:props="defaultProps"
/>
</el-form-item>
<el-form-item label="名称" prop="name" :label-width="100">
<el-input v-model="formContent.name" maxlength="32" show-word-limit/>
</el-form-item>
<el-form-item label="编码" prop="code" :label-width="100">
<el-input v-model="formContent.code" maxlength="32" show-word-limit/>
</el-form-item>
<el-form-item v-if="!formContent.type" label="图标" prop="icon" :label-width="100">
<IconSelect
v-model="formContent.icon"
:iconValue="formContent.icon"
@update:icon-value="iconValue => formContent.icon = iconValue"
placeholder="选择一个图标"
/>
</el-form-item>
<el-form-item v-if="!formContent.type" label="路由地址" prop="path" :label-width="100" >
<el-input v-model="formContent.path" maxlength="32" show-word-limit/>
</el-form-item>
<el-form-item v-if="!formContent.type" label="组件地址" prop="component" :label-width="100">
<el-input v-model="formContent.component" maxlength="32" show-word-limit/>
</el-form-item>
<el-form-item label="排序" prop="sort" :label-width="100">
<el-input-number v-model="formContent.sort" :min='1' :max='999' />
</el-form-item>
<el-form-item label='类型' prop='type' :label-width="100">
<el-select v-model="formContent.type" clearable placeholder="请选择资源类型">
<el-option label="菜单" :value="0"></el-option>
<el-option label="按钮" :value="1"></el-option>
<!-- <el-option label="公共资源" :value="2"></el-option>
<el-option label="服务间调用资源" :value="3"></el-option> -->
</el-select>
</el-form-item>
<el-form-item label="描述" prop="remark" :label-width="100">
<el-input v-model="formContent.remark" :rows="2" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="close()">取 消</el-button>
<el-button type="primary" @click="save()">确 定</el-button>
</div>
</template>
</el-select>
</el-form-item>
<el-form-item label="描述" prop="remark" :label-width="100">
<el-input v-model="formContent.remark" :rows="2" type="textarea"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="close()">取 消</el-button>
<el-button type="primary" @click="save()">确 定</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup name="ResourceDialog">
import { computed, type Ref, ref, watch } from 'vue'
import { dialogMiddle } from '@/utils/elementBind'
import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
import { useDictStore } from '@/stores/modules/dict'
import type { Function } from '@/api/user/interface/function'
import { addFunction, getFunctionListNoButton, updateFunction } from '@/api/user/function/index'
import IconSelect from '@/components/SelectIcon/index.vue'
const value = ref()
// 树形节点配置
const defaultProps = {
</template>
<script lang="ts" setup name="ResourceDialog">
import { defineProps, defineEmits,watch,ref, type Ref, computed } from 'vue';
import { dialogMiddle } from '@/utils/elementBind'
import { ElMessage, type FormInstance, type FormItemRule } from 'element-plus'
import { useDictStore } from '@/stores/modules/dict'
import type { Function } from "@/api/user/interface/function"
import {addFunction,updateFunction,getFunctionListNoButton} from '@/api/user/function/index'
import IconSelect from '@/components/SelectIcon/index.vue'
const value = ref()
// 树形节点配置
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
const functionList = ref<Function.ResFunction[]>([])
const dictStore = useDictStore()
// 定义弹出组件元信息
const dialogFormRef = ref()
function useMetaInfo() {
};
const functionList = ref<Function.ResFunction[]>([])
const dictStore = useDictStore()
// 定义弹出组件元信息
const dialogFormRef = ref()
function useMetaInfo() {
const dialogVisible = ref(false)
const titleType = ref('add')
const formContent = ref<Function.ResFunction>({
id: '', //资源表Id
pid: '', //节点0为根节点
pids: '', //节点上层所有节点
name: '', //名称
code: '', //资源标识
path: '', //路径
component: '',
icon: undefined as string | undefined, // 图标
sort: 100, //排序
type: 0, //资源类型0-菜单、1-按钮、2-公共资源、3-服务间调用资源
remark: '', //权限资源描述
state: 1 //权限资源状态
id: '',//资源表Id
pid:'',//节点0为根节点
pids:'',//节点上层所有节点
name: '',//名称
code:'',//资源标识
path:'',//路径
component:'',
icon:undefined as string | undefined, // 图标
sort:100,//排序
type:0,//资源类型0-菜单、1-按钮、2-公共资源、3-服务间调用资源
remark: '',//权限资源描述
state:1,//权限资源状态
})
return { dialogVisible, titleType, formContent }
}
}
const { dialogVisible, titleType, formContent } = useMetaInfo()
const { dialogVisible, titleType, formContent } = useMetaInfo()
// 清空formContent
const resetFormContent = () => {
formContent.value = {
id: '', //资源表Id
pid: '', //节点0为根节点
pids: '', //节点上层所有节点
name: '', //名称
code: '', //资源标识
path: '', //路径
component: '',
icon: undefined, //图标
sort: 100, //排序
type: 0, //资源类型0-菜单、1-按钮、2-公共资源、3-服务间调用资源
remark: '', //权限资源描述
state: 1 //权限资源状态
id: '',//资源表Id
pid:'',//节点0为根节点
pids:'',//节点上层所有节点
name: '',//名称
code:'',//资源标识
path:'',//路径
component:'',
icon:undefined,//图标
sort:100,//排序
type:0,//资源类型0-菜单、1-按钮、2-公共资源、3-服务间调用资源
remark:'',//权限资源描述
state:1,//权限资源状态
}
}
let dialogTitle = computed(() => {
}
let dialogTitle = computed(() => {
return titleType.value === 'add' ? '新增菜单' : '编辑菜单'
})
})
// 定义规则
const formRuleRef = ref<FormInstance>()
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
name: [{ required: true, trigger: 'blur', message: '菜单名称必填!' }],
code: [{ required: true, trigger: 'blur', message: '编码必填!' }]
})
watch(
() => formContent.value.type,
newVal => {
if (newVal === 1) {
// 选择按钮时,路由地址和组件地址无需校验
rules.value.path = []
rules.value.component = []
} else {
// 其他情况下,路由地址和组件地址需要校验
rules.value.path = [{ required: true, trigger: 'blur', message: '路由地址必填!' }]
rules.value.component = [{ required: true, trigger: 'blur', message: '组件地址必填!' }]
}
}
)
// 定义规则
const formRuleRef = ref<FormInstance>()
const rules : Ref<Record<string, Array<FormItemRule>>> = ref({
name :[{required:true,trigger:'blur',message:'菜单名称必填!'}],
code :[{required:true,trigger:'blur',message:'编码必填!'}]
})
// 关闭弹窗
watch(() => formContent.value.type, (newVal) => {
if (newVal === 1) {
// 选择按钮时,路由地址和组件地址无需校验
rules.value.path = [];
rules.value.component = [];
} else {
// 其他情况下,路由地址和组件地址需要校验
rules.value.path = [{ required: true, trigger: 'blur', message: '路由地址必填!' }];
rules.value.component = [{ required: true, trigger: 'blur', message: '组件地址必填!' }];
}
});
// 关闭弹窗
const close = () => {
dialogVisible.value = false
// 清空dialogForm中的值
resetFormContent()
// 重置表单
dialogFormRef.value?.resetFields()
}
}
// 计算属性,用于控制显示的 pid
const displayPid = computed({
get: () => {
return formContent.value.pid === '0' ? '' : formContent.value.pid
},
set: value => {
formContent.value.pid = value
}
})
get: () => {
return formContent.value.pid === '0' ? '' : formContent.value.pid;
},
set: (value) => {
formContent.value.pid = value;
}
});
// 保存数据
const save = () => {
// 保存数据
const save = () => {
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (formContent.value.pid === undefined || formContent.value.pid === null || formContent.value.pid === '') {
formContent.value.pid = '0'
}
if (
formContent.value.pids === undefined ||
formContent.value.pids === null ||
formContent.value.pids === ''
) {
formContent.value.pids = '0'
}
if (valid) {
if (formContent.value.id) {
await updateFunction(formContent.value)
} else {
await addFunction(formContent.value)
}
ElMessage.success({ message: `${dialogTitle.value}成功!` })
close()
// 刷新表格
await props.refreshTable!()
}
})
} catch (err) {
console.error('验证过程中出现错误', err)
}
}
// 打开弹窗,可能是新增,也可能是编辑
const open = async (sign: string, data: Function.ResFunction) => {
if (formContent.value.pid === undefined || formContent.value.pid === null || formContent.value.pid === '') {
formContent.value.pid = '0';
}
if (formContent.value.pids === undefined || formContent.value.pids === null || formContent.value.pids === '') {
formContent.value.pids = '0';
}
if (valid) {
if (formContent.value.id) {
await updateFunction(formContent.value);
} else {
await addFunction(formContent.value);
}
ElMessage.success({ message: `${dialogTitle.value}成功!` })
close()
// 刷新表格
await props.refreshTable!()
}
})
} catch (err) {
console.error('验证过程中出现错误', err)
}
}
// 打开弹窗,可能是新增,也可能是编辑
const open = async (sign: string, data: Function.ResFunction) => {
// 重置表单
dialogFormRef.value?.resetFields()
const response = await getFunctionListNoButton()
functionList.value = response.data as unknown as Function.ResFunction[]
titleType.value = sign
dialogVisible.value = true
rules.value.path = [{ required: true, trigger: 'blur', message: '路由地址必填!' }]
rules.value.component = [{ required: true, trigger: 'blur', message: '组件地址必填!' }]
rules.value.path = [{ required: true, trigger: 'blur', message: '路由地址必填!' }];
rules.value.component = [{ required: true, trigger: 'blur', message: '组件地址必填!' }];
if (formContent.value.pid === '0') {
formContent.value.pid = ''
if (formContent.value.pid ==='0') {
formContent.value.pid = '';
}
if (data.id) {
formContent.value = { ...data }
formContent.value = { ...data }
} else {
resetFormContent()
resetFormContent()
}
}
// 对外映射
defineExpose({ open })
const props = defineProps<{
refreshTable: (() => Promise<void>) | undefined
}>()
</script>
}
// 对外映射
defineExpose({ open })
const props = defineProps<{
refreshTable: (() => Promise<void>) | undefined;
}>()
</script>

View File

@@ -1,6 +1,6 @@
<template>
<!-- 基础信息弹出框 -->
<el-dialog :model-value="dialogVisible" :title="dialogTitle" v-bind="dialogSmall" @close="close" align-center>
<el-dialog :model-value="dialogVisible" :title="dialogTitle" v-bind="dialogSmall" @close="close" >
<div>
<el-form :model="formContent"

View File

@@ -1,6 +1,6 @@
<template>
<!-- 基础信息弹出框 -->
<el-dialog :model-value="dialogVisible" :title="dialogTitle" v-bind="dialogMiddle" @close="close" align-center>
<el-dialog :model-value="dialogVisible" :title="dialogTitle" v-bind="dialogMiddle" @close="close" >
<div>
<el-form :model="formContent" ref='dialogFormRef'>
<el-tree

View File

@@ -1,6 +1,6 @@
<template>
<!-- 基础信息弹出框 -->
<el-dialog v-model='dialogVisible' :title="dialogTitle" v-bind="dialogSmall" @close="close" align-center>
<el-dialog v-model='dialogVisible' :title="dialogTitle" v-bind="dialogSmall" @close="close">
<div>
<el-form :model="formContent"
ref='dialogFormRef'

View File

@@ -1,6 +1,6 @@
<template>
<!-- 基础信息弹出框 -->
<el-dialog v-model='dialogVisible' :title="dialogTitle" v-bind="dialogSmall" @close="close" align-center>
<el-dialog v-model='dialogVisible' :title="dialogTitle" v-bind="dialogSmall" @close="close" >
<div>
<el-form :model="formContent"
ref='dialogFormRef'

View File

@@ -1,88 +0,0 @@
<script setup>
import { BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow } from '@vue-flow/core'
import { computed } from 'vue'
import { useCheckStore } from '@/stores/modules/check'
const checkStore = useCheckStore()
const props = defineProps({
id: {
type: String,
required: true
},
sourceX: {
type: Number,
required: true
},
sourceY: {
type: Number,
required: true
},
targetX: {
type: Number,
required: true
},
targetY: {
type: Number,
required: true
},
sourcePosition: {
type: String,
required: true
},
targetPosition: {
type: String,
required: true
},
markerEnd: {
type: String,
required: false
},
style: {
type: Object,
required: false
}
})
const { removeEdges } = useVueFlow()
const path = computed(() => getBezierPath(props))
const edgeStyle = computed(() => ({
...props.style,
strokeWidth: 3
}))
</script>
<script>
export default {
inheritAttrs: false
}
</script>
<template>
<BaseEdge :id="id" :style="edgeStyle" :path="path[0]" :marker-end="markerEnd" />
<EdgeLabelRenderer>
<div
:style="{
pointerEvents: 'all',
position: 'absolute',
transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`
}"
class="nodrag nopan"
>
<el-popconfirm v-if="checkStore.nodesConnectable" title="确定要删除这条连线吗?" @confirm="removeEdges(id)">
<template #reference>
<el-icon class="edge-icon"><Delete /></el-icon>
</template>
</el-popconfirm>
</div>
</EdgeLabelRenderer>
</template>
<style scoped>
.edge-icon {
background: white;
border-radius: 50%;
padding: 2px;
cursor: pointer;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
}
</style>

View File

@@ -1,557 +0,0 @@
<template>
<div>
<div class="flow-container" style="overflow: hidden; position: relative"
:style="{ height: vueFlowElement + 'px' }">
<!-- <el-button @click="logConnections">打印当前配对</el-button> -->
<VueFlow :nodes="nodes" :edges="edges" :connection-radius="30" :nodes-draggable="false" :dragging="false"
:zoom-on-scroll="false" :pan-on-drag="false" :disable-zoom-pan-on-connect="true"
:prevent-scrolling="true" :fit-view="true" :min-zoom="1" :max-zoom="1"
:nodesConnectable="checkStore.nodesConnectable" :elements-selectable="false" auto-connect
@connect="handleConnect" @connect-start="handleConnectStart" @connect-end="handleConnectEnd"
@pane-ready="onPaneReady" v-on:pane-mouse-move="false"></VueFlow>
</div>
<!-- 底部操作按钮 -->
<!-- <template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleNext">下一步</el-button>
</div>
</template> -->
<!-- 手动检测-勾选检测项弹窗 -->
<!-- <SelectTestItemPopup ref="selectTestItemPopupRef" @openTestDialog="openTestDialog"></SelectTestItemPopup> -->
</div>
</template>
<script lang="ts" setup>
import { h, ref, onMounted, PropType } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { dialogBig } from '@/utils/elementBind'
import { Platform, Flag } from '@element-plus/icons-vue'
import { Device } from '@/api/device/interface/device'
import { StandardDevice } from '@/api/device/interface/standardDevice'
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
import { ElMessage, stepProps } from 'element-plus'
import CustomEdge from './RemoveableEdge.vue' // 导入自定义连接线组件
import { jwtUtil } from '@/utils/jwtUtil'
import { useCheckStore } from '@/stores/modules/check'
const vueFlowElement = ref(442)
const checkStore = useCheckStore()
const dialogVisible = ref(false)
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
const testPopup = ref()
const dialogTitle = ref('手动检测')
const prop = defineProps({
devIdList: {
type: Array as any,
default: []
},
pqStandardDevList: {
type: Array as any,
default: []
},
planIdKey: {
type: String,
default: ''
},
deviceMonitor: {
type: Map as PropType<Map<string, any[]>>,
default: () => new Map()
}
})
// 计算对话框高度
const dialogHeight = ref(600)
// 初始化 VueFlow注册自定义连线类型
const { edges, setViewport } = useVueFlow({
edgeTypes: {
default: CustomEdge
}
})
// 初始化时锁定画布位置
const onPaneReady = () => {
setViewport({ x: 0, y: 0, zoom: 1 })
}
// 提取公共的label渲染函数
const createLabel = (text: string, type: string, Key: number) => {
return h(
'div',
{
style: {
display: 'flex',
// flexDirection: 'column',
alignItems: 'center',
fontSize: '15px',
height: '75px',
// textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
// h(Platform, {
// style: {
// height: '40px',
// marginBottom: '4px',
// color: '#526ade'
// }
// }),
h('img', {
src:
Key == 2
? new URL('@/assets/images/inspected1.jpg', import.meta.url).href
: new URL('@/assets/images/inspected2.png', import.meta.url).href,
// alt: '设备图标',
style: {
width: '50px',
marginRight: '5px'
// 保持原有的颜色风格,如果需要可以调整滤镜
// filter: 'invert(35%) sepia(65%) saturate(300%) hue-rotate(210deg)'
}
}),
h('div', { style: { textAlign: 'left' } }, ['设备名称:' + text, h('br'), '设备类型:' + type])
// h('div', null, '设备名称:' + text),
// h('div', null, '设备类型:' + type)
]
) as any
}
const createLabel3 = (text: string) => {
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
fontSize: '15px',
height: '35px',
textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
// padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
h('div', {
style: {
width: '8px',
marginRight: '4px',
color: '#526ade'
}
}),
text
]
) as any
}
const handleConnectStart = (params: any) => {
onPaneReady()
}
const handleConnectEnd = (params: any) => {
onPaneReady()
}
const handleConnect = (params: any) => {
console.log('连接信息:', params)
const sourceNode = nodes.value.find(node => node.id === params.source)
const targetNode = nodes.value.find(node => node.id === params.target)
// 连接规则验证
const isValidConnection = sourceNode?.type === 'input' && targetNode?.type === 'output'
if (!isValidConnection) {
removeEdge(params)
ElMessage.warning('只能从被检通道连接到标准通道')
return
}
// 过滤掉当前连接,检查是否还有重复的
const existingEdges = edges.value.filter(edge => edge.source === params.source || edge.target === params.target)
// 如果同源或同目标的连接超过1个说明有重复
if (existingEdges.length > 1) {
const duplicateSource = existingEdges.filter(edge => edge.source === params.source).length > 1
const duplicateTarget = existingEdges.filter(edge => edge.target === params.target).length > 1
if (duplicateSource) {
removeEdge(params)
ElMessage.warning('该被检通道已经连接,不能重复连接')
return
}
if (duplicateTarget) {
removeEdge(params)
ElMessage.warning('该标准通道已经连接,不能重复连接')
return
}
}
}
// 删除不合法连接
const removeEdge = (params: any) => {
const edgeIndex = edges.value.findIndex(edge => edge.source === params.source && edge.target === params.target)
if (edgeIndex !== -1) {
edges.value.splice(edgeIndex, 1)
}
}
const nodes = ref([])
const planId = ref('')
const devIds = ref<string[]>()
const standardDevIds = ref<string[]>()
const open = async () => {
console.log('开始打开通道配对')
edges.value = []
devIds.value = prop.devIdList.map(d => d.id)
standardDevIds.value = prop.pqStandardDevList.map(d => d.id)
planId.value = prop.planIdKey
nodes.value = createNodes(prop.devIdList, prop.pqStandardDevList, prop.deviceMonitor)
dialogVisible.value = true
onPaneReady()
}
onMounted(() => {
open()
})
const handleNext = async () => {
if (edges.value.length === 0) {
ElMessage.warning('请先完成通道配对')
return false
}
// const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_');
let chnNumList: string[] = []
await edges.value.forEach(edge => {
const match = edge.source.split('-')
if (match) {
chnNumList.push(match[2])
}
})
const connections = edges.value.reduce(
(map, edge) => {
// 从source中提取设备ID和通道号: 被检通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_')
// 从target中提取设备ID和通道号: 标准通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const targetValue = edge.target.replace('标准通道-', '').replace('-', '_')
map[sourceKey] = targetValue
return map
},
{} as Record<string, string>
)
await generateChannelMapping()
await checkStore.setChnNum(chnNumList)
return {
title: dialogTitle.value,
mapping: channelMapping.value,
plan: planId.value,
login: jwtUtil.getLoginName(),
devIdsArray: devIds.value,
standardDevIdsArray: standardDevIds.value,
pair: connections
}
}
const openTestDialog = async () => {
// 转换连接信息只保留设备ID和通道号
const connections = edges.value.reduce(
(map, edge) => {
// 从source中提取设备ID和通道号: 被检通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_')
// 从target中提取设备ID和通道号: 标准通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const targetValue = edge.target.replace('标准通道-', '').replace('-', '_')
map[sourceKey] = targetValue
return map
},
{} as Record<string, string>
)
generateChannelMapping()
setTimeout(() => {
testPopup.value?.open(
dialogTitle.value,
channelMapping.value,
planId.value,
jwtUtil.getLoginName(),
devIds.value,
standardDevIds.value,
connections
)
}, 100)
}
// 转换 edges.value 为 channelMapping 格式
const channelMapping = ref<Record<string, Record<string, string>>>({})
// 生成映射关系的方法
const generateChannelMapping = () => {
const mapping: Record<string, Record<string, string>> = {}
edges.value.forEach(edge => {
// 解析 source 节点信息(被检通道)
const sourceParts = edge.source.split('-')
const sourceDeviceId = sourceParts[1]
const sourceChannel = sourceParts[2]
// 解析 target 节点信息(标准通道)
const targetParts = edge.target.split('-')
const targetDeviceId = targetParts[1]
const targetChannel = targetParts[2]
// 查找对应的节点以获取显示名称
const sourceDeviceNode = nodes.value.find(node => node.id === sourceDeviceId)
const targetDeviceNode = nodes.value.find(node => node.id === targetDeviceId)
if (sourceDeviceNode && targetDeviceNode) {
// 提取设备显示文本
const sourceDeviceText = sourceDeviceNode.data.label.children[1].children[0].children
const targetDeviceText = targetDeviceNode.data.label.children[1].children[0].children
// 构造键名 - 现在以标准设备为键
const targetKey = `${targetDeviceText}`.replace('设备名称:', '')
const sourceValue = `${sourceDeviceText}通道${sourceChannel}`.replace('设备名称:', '')
// 初始化对象
if (!mapping[targetKey]) {
mapping[targetKey] = {}
}
// 添加映射关系 - 标准设备通道 -> 被检设备信息
mapping[targetKey][`通道${targetChannel}`] = sourceValue
}
})
channelMapping.value = mapping
}
const createNodes = (device: Device.ResPqDev[], standardDev: StandardDevice.ResPqStandardDevice[], deviceMonitor: Map<string, any[]>) => {
const channelCounts: Record<string, number> = {}
device.forEach(device => {
channelCounts[device.id] = device.devChns || 0
})
const inspectionDevices = device.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const channelCounts2: Record<string, number> = {}
standardDev.forEach(dev => {
const channelList = dev.inspectChannel ? dev.inspectChannel.split(',') : []
channelCounts2[dev.id] = channelList.length
})
const standardDevices = standardDev.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const newNodes: any[] = []
const deviceChannelGroups: { deviceId: string; centerY: number }[] = []
const standardChannelGroups: any[] = []
const deviceWidth = 50
const inputChannelX = 350
const outputChannelX = 1050
const standardWidth = 1170
// 添加被检通道
// let currentYPosition = 50; // 初始Y位置
let actualChannelsTotalLength = 0;
for (const [deviceId, count] of Object.entries(channelCounts)) {
// 直接计算当前设备的通道数并累加,无需完整构建数组
actualChannelsTotalLength += deviceMonitor.has(deviceId)
? (deviceMonitor.get(deviceId) || []).length
: count;
}
let currentYPosition = (vueFlowElement.value - 60 * actualChannelsTotalLength) / 2; // 初始Y位置
const deviceSpacing = 30; // 设备间的垂直间距
Object.entries(channelCounts).forEach(([deviceId, count]) => {
// 从deviceMonitor中获取实际通道信息
let actualChannels = []; // 存储实际的通道号
// 如果deviceMonitor中有该设备的数据则使用实际监测点信息
if (deviceMonitor.has(deviceId)) {
const monitorPoints = deviceMonitor.get(deviceId) || [];
// 提取监测点的num值作为通道号
actualChannels = monitorPoints.map(point => point.num);
//console.log('deviceId', deviceId, '实际通道号:', actualChannels, '监测点:', monitorPoints);
} else {
// 如果没有monitor数据默认使用连续的通道号
actualChannels = Array.from({ length: count }, (_, i) => i + 1);
}
const yPosition = currentYPosition;
// 遍历实际通道号而不是连续的数字
actualChannels.forEach((channelNum, index) => {
const channelId = `被检通道-${deviceId}-${channelNum}`;
newNodes.push({
id: channelId,
type: 'input',
data: { label: createLabel3(`被检通道${channelNum}`) },
position: { x: inputChannelX, y: yPosition + index * 50 },
sourcePosition: 'right',
style: { width: '120px', border: 'none', boxShadow: 'none' }
});
deviceChannelGroups.push({
deviceId,
centerY: 0
});
});
// 更新currentYPosition为下一台设备留出空间
// 每台设备需要的空间 = 实际通道数 * 50 + 设备间距
currentYPosition += actualChannels.length * 50 + deviceSpacing;
});
// 添加标准通道
// let currentYPosition2 = 50; // 初始Y位置
let totalCount = 0;
// 遍历所有条目并累加 count 值
Object.entries(channelCounts2).forEach(([deviceId, count]) => {
totalCount += count;
});
let currentYPosition2 = (vueFlowElement.value - 60 * totalCount) / 2; // 初始Y位置; // 初始Y位置
const standardDeviceSpacing = 30; // 标准设备间的垂直间距
Object.entries(channelCounts2).forEach(([deviceId, count]) => {
const yPosition2 = currentYPosition2;
for (let i = 1; i <= count; i++) {
const channelId = `标准通道-${deviceId}-${i}`;
newNodes.push({
id: channelId,
type: 'output',
data: { label: createLabel3(`标准通道${i}`) },
position: { x: outputChannelX, y: yPosition2 + (i - 1) * 50 },
targetPosition: 'left',
style: { width: '120px', border: 'none', boxShadow: 'none' }
});
standardChannelGroups.push({
deviceId,
centerY: 0
});
}
// 更新currentYPosition2为下一台标准设备留出空间
currentYPosition2 += count * 50 + standardDeviceSpacing;
});
// 添加被检设备
// let deviceCurrentYPosition = 50; // 与通道计算保持一致的初始位置
let deviceCurrentYPosition = (vueFlowElement.value - 60 * actualChannelsTotalLength) / 2; // 与通道计算保持一致的初始位置
let lastDeviceId = ''; // 记录上一个处理的设备ID
deviceChannelGroups.forEach(({ deviceId, centerY }) => {
const device = inspectionDevices.find(d => d.id === deviceId)
if (device) {
// 只有当设备ID变化时才计算新位置
if (lastDeviceId !== deviceId) {
// 计算该设备对应的实际通道数量
let actualChannelCount = channelCounts[deviceId] || 0;
// 如果deviceMonitor中有该设备的数据则使用实际监测点数量
if (deviceMonitor.has(deviceId)) {
const monitorPoints = deviceMonitor.get(deviceId) || [];
actualChannelCount = monitorPoints.length;
}
// 计算设备高度居中位置 - 基于该设备组的实际位置
const deviceCenterY = deviceCurrentYPosition + (actualChannelCount * 50) / 2 - 50
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType, 1) },
position: { x: deviceWidth, y: deviceCenterY },
class: 'no-handle-node',
style: { width: '300px', border: 'none', boxShadow: 'none' }
})
// 更新位置为下一台设备的起始位置
deviceCurrentYPosition += actualChannelCount * 50 + 30
// 更新上一个设备ID
lastDeviceId = deviceId
}
}
})
// 添加标准设备
// let standardDeviceCurrentYPosition = 50; // 与标准通道计算保持一致的初始位置
let standardDeviceCurrentYPosition = (vueFlowElement.value - 60 * totalCount) / 2; // 与标准通道计算保持一致的初始位置
let lastStandardDeviceId = ''; // 记录上一个处理的标准设备ID
standardChannelGroups.forEach(({ deviceId, centerY }) => {
const device = standardDevices.find(d => d.id === deviceId)
if (device) {
// 只有当设备ID变化时才计算新位置
if (lastStandardDeviceId !== deviceId) {
// 计算该标准设备对应的通道数量
const channelCount = channelCounts2[deviceId] || 0
// 计算设备高度居中位置 - 基于该设备组的实际位置
const deviceCenterY = standardDeviceCurrentYPosition + (channelCount * 50) / 2 - 50
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType, 2) },
position: { x: standardWidth, y: deviceCenterY },
class: 'no-handle-node',
style: { width: '300px', border: 'none', boxShadow: 'none' }
})
// 更新位置为下一台标准设备的起始位置
standardDeviceCurrentYPosition += channelCount * 50 + 30
// 更新上一个标准设备ID
lastStandardDeviceId = deviceId
}
}
})
//页面高度取决于设备通道
// dialogHeight.value = Math.max(yPosition.value, yPosition2.value)
return newNodes
}
defineExpose({ open, handleNext })
</script>
<style>
.flow-container {
width: 100%;
overflow: hidden;
}
.vue-flow__node.no-handle-node .vue-flow__handle {
display: none !important;
}
</style>

View File

@@ -0,0 +1,999 @@
<template>
<el-dialog v-model='dialogVisible' title="系数校准" v-bind="dialogBig" width="1550px" @close="handleCancel" :before-close="beforeClose">
<div class="test-dialog">
<div class="dialog-content">
<div class="right-title">
<!-- <div>系数校准表</div> -->
<div>{{ outputDsc }}</div>
<div>
<span style=" font-size: 18px;font-weight: 600;">
设备已合格 <span style="color: #91cc75">{{ qualified }}</span> / <span style="color: green">{{ total }}</span>
</span>
<!-- <el-button type="primary" loading
v-if="activeIndex > 0 && activeIndex < activeTotalNum">通道系数已校准3台/共3台</el-button>
<el-button type="primary" :disabled="true" v-if="activeIndex === activeTotalNum">通道系数已校准3台/共3台</el-button> -->
</div>
</div>
<div class="container">
<div class="dialog-left">
<el-steps direction="vertical" :active="active" :process-status="currentStepStatus" finish-status="success">
<el-step title="开始"/>
<el-step>
<template #title>
<span>大电压/电流系数下装</span><br/>
<span class="spanStyle">源输出为</span><br/>
<span class="spanStyle" v-if="active > 0">{{ big_V_Download }}</span><br/>
<span class="spanStyle" v-if="active > 0">{{ big_I_Download }}</span>
<el-icon v-if="active === 1 " class="loading-box">
<el-icon-loading/>
</el-icon>
</template>
</el-step>
<el-step>
<template #title>
<span>小电压/电流系数下装</span><br/>
<span class="spanStyle">源输出为</span><br/>
<span class="spanStyle" v-if="active > 1">{{ small_V_Download }}</span><br/>
<span class="spanStyle" v-if="active > 1">{{ small_I_Download }}</span>
<el-icon v-if="active === 2" class="loading-box">
<el-icon-loading/>
</el-icon>
</template>
</el-step>
<el-step>
<template #title>
<span>大电压/电流校准</span><br/>
<span class="spanStyle">源输出为</span><br/>
<span class="spanStyle" v-if="active > 2">{{ big_V_Adjust }}</span><br/>
<span class="spanStyle" v-if="active > 2">{{ big_I_Adjust }}</span>
<el-icon v-if="active === 3" class="loading-box">
<el-icon-loading/>
</el-icon>
</template>
</el-step>
<el-step>
<template #title>
<span>小电压/电流校准</span><br/>
<span class="spanStyle">源输出为</span><br/>
<span class="spanStyle" v-if="active > 3">{{ small_V_Adjust }}</span><br/>
<span class="spanStyle" v-if="active > 3">{{ small_I_Adjust }}</span>
<el-icon v-if="active === 4" class="loading-box">
<el-icon-loading/>
</el-icon>
</template>
</el-step>
<el-step title="结束"/>
</el-steps>
</div>
<div class="right-content">
<el-tabs type="border-card" v-model="editableTabsValue" :active-index="String(activeIndex)">
<el-tab-pane v-for="(device, index) in name" :key="index" :label="device">
<template #label>
<span class="custom-tabs-label">
<span>{{ device }}</span>
<el-icon v-if="errorStates[index]" class="icon-style">
<Failed/>
</el-icon>
</span>
</template>
<channelsTestTable
:tableData="getTableDataForChannel(index)"
:big_V_loading="big_V_loadingStates"
:curV="CurV">
</channelsTestTable>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit" :disabled="isButtonDisabled">开始系数校准</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="tsx" setup name="channelsTest">
import {type Device} from '@/api/device/interface/device';
import {Failed} from '@element-plus/icons-vue'
import {type Ref, ref, toRef, watch} from 'vue'
import {dialogBig} from '@/utils/elementBind'
import {ElMessageBox} from 'element-plus';
import {getCoefficientCheck} from '@/api/home/channelsTest/index'
import type {ChannelsTest} from '@/api/home/interface/channelsTest';
import type {Plan} from '@/api/plan/interface';
import {useUserStore} from "@/stores/modules/user";
const activeIndex = ref(0)
const activeTotalNum = ref(4)
const qualified = ref(0)
const outputDsc = ref('电压误差为±0.1Un% 电流误差为±0.5%')
const total = ref(0)
const dialogVisible = ref(false)
const active = ref(0)
let timer1: NodeJS.Timeout | null = null; // 声明并初始化 timer1
let timer2: NodeJS.Timeout | null = null; // 同样声明并初始化 timer2
const name = ref<string[]>([])//系数校准所选设备名字数组
const channel = ref<number[]>([])//系数校准所选设备通道数组
const devIdArray = ref<string[]>([])//系数校准所选设备ID数组
const select_Plan = ref<Plan.ReqPlan>()
const planId = ref('')
const isButtonDisabled = ref(false);
const CurV = ref<number>()//额定电压
// 在 setup 函数中
const errorStates = ref(new Array(name.value.length).fill(false));
//const loadingStates = ref(new Array(name.value.length).fill(false)); // 初始化 loading 状态
const big_V_loadingStates = ref(false); // 初始化 大电压大电流下装loading 状态
const small_V_loadingStates = ref(false); // 初始化 小电压小电流下装loading 状态
const big_V_loadingStates2 = ref(false); // 初始化 大电压大电流校准loading 状态
const small_V_loadingStates2 = ref(false); // 初始化 小电压小电流校准loading 状态
const editableTabsValue = ref('0')
const big_V_Download = ref('')
const big_I_Download = ref('')
const small_V_Download = ref('')
const small_I_Download = ref('')
const big_V_Adjust = ref('')
const big_I_Adjust = ref('')
const small_V_Adjust = ref('')
const small_I_Adjust = ref('')
const props = defineProps({
webMsgSend: {
type: Object,
default: () => ({})
}
})
const userStore = useUserStore()
const tableDataMap = new Map<number, Ref<ChannelsTest.CoefficientVO[]>>([]);
const currentStepStatus = ref<'error' | 'finish' | 'wait' | 'success' | 'process'>('finish');
const webMsgSend = toRef(props, 'webMsgSend');
watch(webMsgSend, function (newValue, oldValue) {
if (newValue.code == 10520) {
ElMessageBox.alert('报文解析异常!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10521) {
ElMessageBox.alert('程控源參数有误!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10522) {
ElMessageBox.alert('测试项解析有误!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10523) {
ElMessageBox.alert('源连接失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10524) {
ElMessageBox.alert('获取源控制权失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10525) {
ElMessageBox.alert('重置源失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10527) {
ElMessageBox.alert('源未进行初始化!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10528) {
ElMessageBox.alert('目标源有误(该用户已控制其他源,在关闭前无法操作新的源)', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10529) {
ElMessageBox.alert('源状态有误,无法响应报文(例如源处于输出状态,无法响应初始化报文)', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10550) {
ElMessageBox.alert(`${newValue.data}设备连接异常!`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10551) {
ElMessageBox.alert(`${newValue.data}设备触发报告异常!`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10552) { //todo 10552之后还会发送消息吗
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else {
switch (newValue.requestId) {
case 'yjc_ytxjy':
switch (newValue.operateCode) {
case'INIT_GATHER':
if (newValue.code == -1) {
ElMessageBox.alert('源未知异常', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10523) {
ElMessageBox.alert('源连接失败', '源连接失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
}
break;
case 'YJC_xujy':
switch (newValue.operateCode) {
case 'OPER_GATHER':
if (newValue.code == 10552) {
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10520) {
ElMessageBox.alert('解析报文异常,执行自动关闭,请重新发起检测', '解析报文异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
break;
case 'DATA_REQUEST$02':
if (newValue.code == 25003) {
ElMessageBox.alert('相序校验未通过,执行自动关闭,请重新发起检测', '相序校验未通过', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
break;
}
break;
case 'yjc_sbtxjy':
switch (newValue.operateCode) {
case 'INIT_GATHER$01':
if (newValue.code == 10550) {
ElMessageBox.alert('设备连接异常', '设备连接异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10551) {
ElMessageBox.alert('设备触发报告异常', '设备触发报告异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10552) {
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10520) {
ElMessageBox.alert('解析报文异常,执行自动关闭,请重新发起检测', '解析报文异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
break;
}
break;
case 'yjc_xyjy':
switch (newValue.operateCode) {
case 'VERIFY_MAPPING$01':
if (newValue.code == 10200) {
let data = JSON.parse(newValue.data)
ElMessageBox.alert(`脚本与icd检验失败! icd名称${data['icdType']} -> 校验项:${data['dataType']}`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
if (newValue.code == 10500) {
ElMessageBox.alert(`装置中未找到该icd`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
break;
}
break;
case 'Coefficient_Check':
console.log("Coefficient_Checkactive", active.value);
switch (newValue.operateCode) {
case 'big_start'://大电压,电流下装
big_V_Download.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
big_I_Download.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'big_end'://大电压,电流下装
active.value++;
tableLoading('small', '系数下装')
break;
}
switch (newValue.operateCode) {
case 'small_start'://小电压,电流下装
small_V_Download.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
small_I_Download.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'small_end'://小电压,电流下装
active.value++;
tableLoading('big', '系数校准')
break;
}
switch (newValue.operateCode) {
case 'big_comp_start'://大电压,电流校准
big_V_Adjust.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
big_I_Adjust.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'big_comp_end'://大电压,电流校准
active.value++;
tableLoading('small', '系数校准')
break;
}
switch (newValue.operateCode) {
case 'small_comp_start'://小电压,电流校准
small_V_Adjust.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
small_I_Adjust.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'small_comp_end'://小电压,电流校准
active.value++;
active.value++;
for (let i = 0; i < name.value.length; i++) {
const currentDataRef = tableDataMap.get(i);
if (currentDataRef) {
const currentData = currentDataRef.value;
// 检查当前数据中有无不合格字段
const hasError = checkForErrors(currentData);
if (hasError) {
} else {
qualified.value++;
}
updateErrorState(i, hasError);
}
}
//editableTabsValue.value = (tabNumber.value).toString();//显示下一个tab
isButtonDisabled.value = false; // 恢复按钮
break;
}
switch (newValue.operateCode) {
case 'DATA_CHNFACTOR$02'://表格
// 输出 key 为 0 的数组中的第一条 ChannelsTest.CoefficientVO 对象
for (let i = 0; i < name.value.length; i++) {
const targetArrayRef = tableDataMap.get(i);
if (targetArrayRef) {
const targetArray = targetArrayRef.value;
if (targetArray.length > 0) {
const firstCoefficientVO = targetArray.find(item => item.monitorNum === newValue.data.monitorNum &&
item.type === newValue.data.type &&
item.desc === newValue.data.desc &&
item.devName === newValue.data.devName);
if (firstCoefficientVO) { // 检查 firstCoefficientVO 是否存在
firstCoefficientVO.aVuData = parseFloat(newValue.data.aVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.aVuXi)) && isFinite(newValue.data.aVuXi)) {
firstCoefficientVO.aVuXi = (parseFloat(newValue.data.aVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.aVuXi = newValue.data.aVuXi;
}
firstCoefficientVO.bVuData = parseFloat(newValue.data.bVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.bVuXi)) && isFinite(newValue.data.bVuXi)) {
firstCoefficientVO.bVuXi = (parseFloat(newValue.data.bVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.bVuXi = newValue.data.bVuXi;
}
firstCoefficientVO.cVuData = parseFloat(newValue.data.cVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.cVuXi)) && isFinite(newValue.data.cVuXi)) {
firstCoefficientVO.cVuXi = (parseFloat(newValue.data.cVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.cVuXi = newValue.data.cVuXi;
}
firstCoefficientVO.aIeData = parseFloat(newValue.data.aIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.aIeXi)) && isFinite(newValue.data.aIeXi)) {
firstCoefficientVO.aIeXi = (parseFloat(newValue.data.aIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.aIeXi = newValue.data.aIeXi;
}
firstCoefficientVO.bIeData = parseFloat(newValue.data.bIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.bIeXi)) && isFinite(newValue.data.bIeXi)) {
firstCoefficientVO.bIeXi = (parseFloat(newValue.data.bIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.bIeXi = newValue.data.bIeXi;
}
firstCoefficientVO.cIeData = parseFloat(newValue.data.cIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.cIeXi)) && isFinite(newValue.data.cIeXi)) {
firstCoefficientVO.cIeXi = (parseFloat(newValue.data.cIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.cIeXi = newValue.data.cIeXi;
}
firstCoefficientVO.aV = newValue.data.aV;
firstCoefficientVO.bV = newValue.data.bV;
firstCoefficientVO.cV = newValue.data.cV;
firstCoefficientVO.aI = newValue.data.aI;
firstCoefficientVO.bI = newValue.data.bI;
firstCoefficientVO.cI = newValue.data.cI;
//console.log(newValue.data.devName + '对象:', firstCoefficientVO);
activeIndex.value++;
} else {
//console.log('未找到匹配的'+ newValue.data.devName+'对象');
}
} else {
//console.log(newValue.data.devName + '数组为空');
}
} else {
//console.log('未找到'+newValue.data.devName+'对应的数组');
}
}
break;
}
break;
case 'socket_timeout':
switch (newValue.operateCode) {
case 'VOLTAGE':
ElMessageBox.alert('连接超时!', '连接超时', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
break;
}
break;
case 'connect':
switch (newValue.operateCode) {
case "Source":
ElMessageBox.alert('源服务端连接失败', '源服务端连接失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
break;
case "Dev":
ElMessageBox.alert('设备服务端连接失败', '设备服务端连接失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
break;
}
break;
}
}
})
//出错系数检测初始化
const TableInit = () => {
console.log("TableInitactive", active.value);
isButtonDisabled.value = false; // 恢复按钮
for (let i = 0; i < channel.value.length; i++) {
const currentTableData = initializeTableData(dataTemplates, i);
tableDataMap.set(i, currentTableData)
// const targetArrayRef = tableDataMap.get(i);
// if (targetArrayRef) {
// const targetArray = targetArrayRef.value;
// if (targetArray.length > 0) {
// targetArray.forEach(item => item.loading =false)
// }
// }
}
activeIndex.value = 0
qualified.value = 0
active.value = 0
}
//按行图标转动
const tableLoading = (type: string, desc: string) => {
for (let i = 0; i < channel.value.length; i++) {
const targetArrayRef = tableDataMap.get(i);
if (targetArrayRef) {
const targetArray = targetArrayRef.value;
if (targetArray.length > 0) {
for (let j = 0; j < channel.value[i]; j++) {
const firstCoefficientVO = targetArray.find(item => item.monitorNum === (j + 1).toString() &&
item.type === type &&
item.desc === desc);
if (firstCoefficientVO) {
firstCoefficientVO.loading = true;
}
}
}
}
}
}
const dataTemplates: ChannelsTest.CoefficientVO[] = [
{
monitorNum: '1',
desc: '系数下装',
type: 'big',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: '',
aV: '—',
bV: '—',
cV: '—',
aI: '—',
bI: '—',
cI: '—',
},
{
monitorNum: '2',
desc: '系数下装',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: '',
aV: '—',
bV: '—',
cV: '—',
aI: '—',
bI: '—',
cI: '—',
},
{
monitorNum: '3',
desc: '系数校准',
type: 'big',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: '',
aV: '—',
bV: '—',
cV: '—',
aI: '—',
bI: '—',
cI: '—',
},
{
monitorNum: '4',
desc: '系数校准',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: '',
aV: '—',
bV: '—',
cV: '—',
aI: '—',
bI: '—',
cI: '—',
},
];
const dataTemplates2: ChannelsTest.CoefficientVO[] = [
{
monitorNum: '1',
desc: '系数下装',
type: 'big',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: ''
},
{
monitorNum: '2',
desc: '系数下装',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: ''
},
{
monitorNum: '3',
desc: '系数校准',
type: 'big',
aVuData: '—',
aVuXi: '不合格',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: ''
},
{
monitorNum: '4',
desc: '系数校准',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: ''
},
];
// 更新错误状态的方法
const updateErrorState = (index: number, hasError: boolean) => {
errorStates.value[index] = hasError;
};
// 打开弹窗,可能是新增,也可能是编辑
const open = (selection: Device.ResPqDev[], plan: Plan.ReqPlan) => {
CurV.value = selection[0]?.devVolt || 57.74;
isButtonDisabled.value = false; // 恢复按钮
select_Plan.value = plan
planId.value = selection[0]?.planId || '';
devIdArray.value = selection.map(item => item.id);
name.value = selection.map(item => item.name)
channel.value = selection.map(item => item.devChns)
dialogVisible.value = true;
total.value = name.value.length
// 初始化 loadingStates 为 false
// loadingStates.value = new Array(selection.length).fill(false);
errorStates.value = new Array(selection.length).fill(false);
for (let i = 0; i < channel.value.length; i++) {
const currentTableData = initializeTableData(dataTemplates, i);
tableDataMap.set(i, currentTableData)
}
//console.log('tableDataMap',tableDataMap);
}
const emit = defineEmits<{
(e: 'quitClicked'): void;
(e: 'submitClicked', callback: (resolve: (value: boolean) => void) => void): void;
}>();
const beforeClose = (done: () => void) => {
ElMessageBox.confirm('是否退出系数校准?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
handleCancel()
})
}
const handleCancel = () => {
//dataSocket.socketServe.closeWs()
// 清空 name, channel, total
name.value = [];
channel.value = [];
total.value = 0;
activeIndex.value = 0
qualified.value = 0
active.value = 0
dialogVisible.value = false
editableTabsValue.value = '0'
emit('quitClicked'); // 触发事件
}
const getTableDataForChannel = (index: number): any[] => {
const data = tableDataMap.get(index);
return data ? data.value : [];
}
watch(activeIndex, function (newValue, oldValue) {
if (activeIndex.value === 1) {
outputDsc.value = "电压误差为±0.1Un% 电流误差为±0.5%";
// 当前源输出为Ua=Ub=Uc=57.74V Ia=Ib=Ic=1A"
}
})
// 示例的 checkForErrors 函数,根据实际需求进行调整
const checkForErrors = (data: ChannelsTest.CoefficientVO[]): boolean => {
// 这里假设不合格字段的标准是 status 为 '不合格' 或 isValid 为 false
return data.some(item =>
item.aVuXi === '不合格' ||
item.bVuXi === '不合格' ||
item.cVuXi === '不合格' ||
item.aIeXi === '不合格' ||
item.bIeXi === '不合格' ||
item.cIeXi === '不合格'
);
};
const handleSubmit = async () => {
// 创建一个 Promise 来等待父组件的回调
const response = await new Promise<boolean>((resolve) => {
emit('submitClicked', resolve);
});
if (!response) {
return;
}
isButtonDisabled.value = true; // 禁用按钮
tableLoading('big', '系数下装')
await getCoefficientCheck({
userPageId: "cdf",
devIds: devIdArray.value,
planId: planId.value,
errorSysId: select_Plan.value?.errorSysId,
scriptId: select_Plan.value?.scriptId,
operateType: '0', // '0'为预检测、1为正式检测
userId: userStore.userInfo.id
})
active.value++;
// 初始化 loadingStates 为 true
// loadingStates.value = new Array(name.value.length).fill(true);
return;
// 初始化 currentTableData
// let isTimer2Completed = false;
// //"80b4b4f52a4c4064a18319525f8ac13c",
// for (let i = 0; i < channel.value.length; i++) {
// // 重置状态变量
// active.value = 0;
// //activeIndex.value = 0;
// editableTabsValue.value = i.toString();
// // 初始化并填充 currentTableData
// const currentTableData = initializeTableData(dataTemplates2, i);
// tableDataMap.set(i, currentTableData);
// //activeIndex.value++;
// // 清除之前的 timer1
// clearInterval(timer1);
// // 启动 timer1
// timer1 = setInterval(() => {
// active.value++;
// if (active.value > 5) {
// clearInterval(timer1);
// }
// }, 3000);
// // 清除之前的 timer2
// clearInterval(timer2);
// // 启动 timer2
// timer2 = setInterval(() => {
// // 初始化并填充 currentTableData
// const currentTableData = initializeTableData(i > 0 ? dataTemplates2 : dataTemplates2, i);
// tableDataMap.set(i, currentTableData);
// activeIndex.value++;
// clearInterval(timer2);
// const currentDataRef = tableDataMap.get(i);
// if (currentDataRef) {
// const currentData = currentDataRef.value;
// // 检查当前数据中有无不合格字段
// const hasError = checkForErrors(currentData);
// if (hasError) {
// } else {
// qualified.value++;
// }
// updateErrorState(i, hasError);
// }
// // 设置标志变量为 true表示 timer2 已经完成
// isTimer2Completed = true;
// }, 3000);
// // 等待 timer2 完成
// while (!isTimer2Completed) {
// // 这里可以添加一个短暂的等待,避免死循环
// await new Promise(resolve => setTimeout(resolve, 100));
// }
// // 重置标志变量
// isTimer2Completed = false;
// }
};
// 提取初始化并填充 currentTableData 的函数
const initializeTableData = (templates: ChannelsTest.CoefficientVO[], index: number): Ref<ChannelsTest.CoefficientVO[]> => {
const currentTableData = ref<ChannelsTest.CoefficientVO[]>([]);
for (let j = 0; j < channel.value[index]; j++) {
templates.forEach((template) => {
// 使用解构赋值排除 id 和 MonitorIdx 属性
const {devName, monitorNum: __, ...rest} = template;
currentTableData.value.push({
monitorNum: (j + 1).toString(),
devName: name.value[index],
...rest,
});
});
}
return currentTableData;
};
// 对外映射
defineExpose({open})
</script>
<style scoped>
/* 确保 el-icon-loading 的动画效果没有被覆盖 */
.loading-box {
animation: rotate 2s linear infinite;
font-size: 30px; /* 增大图标的大小 */
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.right-title {
display: flex;
flex-direction: row;
/* 横向排列 */
justify-content: space-between;
margin-bottom: 10px;
}
.custom-tabs-label .el-icon {
vertical-align: middle;
}
.custom-tabs-label span {
vertical-align: middle;
margin-left: 4px;
}
.dialog-content {
height: 510px;
}
.el-tabs--border-card {
height: 470px;
}
/* .el-icon svg {
color: #ff7171;
} */
.icon-style {
color: #ff7171;
}
.container {
display: flex;
}
.dialog-left {
flex: 1;
}
.right-content {
flex: 6;
}
.spanStyle {
font-size: 14px;
}
</style>

View File

@@ -1,386 +1,348 @@
<template>
<div class="table-container table-main">
<el-table
:data="tableData"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
style="width: 100%"
max-height="400px"
:span-method="objectSpanMethod"
>
<el-table-column prop="monitorNum" label="监测点序号" width="80" />
<el-table-column prop="desc" label="描述" width="90" />
<el-table-column label="电压通道">
<el-table-column prop="Ua" label="A相">
<el-table-column prop="aVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.aVuData === '—' && scope.row.loading" class="loading-box">
<div class="table-container table-main">
<el-table :data="tableData"
:header-cell-style="{ textAlign: 'center' } "
:cell-style="{ textAlign: 'center' }"
style="width: 100%"
max-height="400px"
:span-method="objectSpanMethod">
<el-table-column prop="monitorNum" label="监测点序号" width="80"/>
<el-table-column prop="desc" label="描述" width="90"/>
<el-table-column label="电压通道" >
<el-table-column prop="Ua" label="A相">
<el-table-column prop="aVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.aVuData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.aVuData }}
</span>
</template>
</el-table-column>
<el-table-column prop="aVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="aVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.aVuXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.aV + 'V' }}
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4)+ 'V'}}<br/>
误差值{{ scope.row.aV + 'V'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.aVuXi }}
{{ scope.row.aVuXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.aVuXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.aV + 'V' }}
>
<template #content>
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4)+ 'V'}}<br/>
误差值{{ scope.row.aV + 'V'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.aVuXi }}</el-tag>
</el-tooltip>
<!-- <el-tag type="danger" v-if="scope.row.aVuXi === '不合格'">
</el-tooltip>
<!-- <el-tag type="danger" v-if="scope.row.aVuXi === '不合格'">
{{ scope.row.aVuXi }}
</el-tag> -->
<el-icon v-else-if="scope.row.aVuXi === '—' && scope.row.loading" class="loading-box">
<el-icon v-else-if="scope.row.aVuXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<!-- <span v-else>
</el-icon>
<!-- <span v-else>
{{ scope.row.aVuXi }}
</span> -->
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ub" label="B相">
<el-table-column prop="bVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.bVuData === '—' && scope.row.loading" class="loading-box">
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ub" label="B相">
<el-table-column prop="bVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.bVuData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.bVuData }}
</span>
</template>
</el-table-column>
<el-table-column prop="bVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="bVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.bVuXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.bV + 'V' }}
>
<template #content>
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4)+ 'V'}}<br/>
误差值{{ scope.row.bV + 'V'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.bVuXi }}
{{ scope.row.bVuXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.bVuXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.bV + 'V' }}
>
<template #content>
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4)+ 'V'}}<br/>
误差值{{ scope.row.bV + 'V'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.bVuXi }}</el-tag>
</el-tooltip>
<el-icon v-else-if="scope.row.bVuXi === '—' && scope.row.loading" class="loading-box">
</el-tooltip>
<el-icon v-else-if="scope.row.bVuXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Uc" label="C相">
<el-table-column prop="cVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.cVuData === '—' && scope.row.loading" class="loading-box">
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Uc" label="C相">
<el-table-column prop="cVuData" label="被检值(V)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.cVuData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.cVuData }}
</span>
</template>
</el-table-column>
<el-table-column prop="cVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="cVuXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.cVuXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.cV + 'V' }}
>
<template #content>
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4)+ 'V'}}<br/>
误差值{{ scope.row.cV + 'V'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.cVuXi }}
{{ scope.row.cVuXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.cVuXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围
{{
(-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'
}}
<br />
误差值{{ scope.row.cV + 'V' }}
>
<template #content>
误差范围 {{ (-0.001 * props.curV).toFixed(4) + 'V~' + (0.001 * props.curV).toFixed(4) + 'V'}}<br/>
误差值{{ scope.row.cV + 'V'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.cVuXi }}</el-tag>
</el-tooltip>
<el-icon v-else-if="scope.row.cVuXi === '—' && scope.row.loading" class="loading-box">
</el-tooltip>
<el-icon v-else-if="scope.row.cVuXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
</template>
</el-icon>
</template>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column label="电流通道">
<el-table-column prop="Ia" label="A相">
<el-table-column prop="aIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.aIeData === '—' && scope.row.loading" class="loading-box">
<el-table-column label="电流通道" >
<el-table-column prop="Ia" label="A相">
<el-table-column prop="aIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.aIeData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.aIeData }}
</span>
</template>
</el-table-column>
<el-table-column prop="aIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="aIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.aIeXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.aI + '%' }}
>
<template #content>
误差范围 {{-0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.aI + '%'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.aIeXi }}
{{ scope.row.aIeXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.aIeXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.aI + '%' }}
>
<template #content>
误差范围 {{ -0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.aI + '%'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.aIeXi }}</el-tag>
</el-tooltip>
<el-icon v-else-if="scope.row.aIeXi === '—' && scope.row.loading" class="loading-box">
</el-tooltip>
<el-icon v-else-if="scope.row.aIeXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ib" label="B相">
<el-table-column prop="bIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.bIeData === '—' && scope.row.loading" class="loading-box">
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ib" label="B相">
<el-table-column prop="bIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.bIeData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.bIeData }}
</span>
</template>
</el-table-column>
<el-table-column prop="bIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="bIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.bIeXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.bI + '%' }}
>
<template #content>
误差范围 {{ -0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.bI + '%'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.bIeXi }}
{{ scope.row.bIeXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.bIeXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.bI + '%' }}
>
<template #content>
误差范围 {{ -0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.bI + '%'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.bIeXi }}</el-tag>
</el-tooltip>
<el-icon v-else-if="scope.row.bIeXi === '—' && scope.row.loading" class="loading-box">
</el-tooltip>
<el-icon v-else-if="scope.row.bIeXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ic" label="C相">
<el-table-column prop="cIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.cIeData === '—' && scope.row.loading" class="loading-box">
</el-icon>
</template>
</el-table-column>
</el-table-column>
<el-table-column prop="Ic" label="C相">
<el-table-column prop="cIeData" label="被检值(A)" width="100px">
<template #default="scope">
<el-icon v-if="scope.row.cIeData === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
<span v-else>
</el-icon>
<span v-else>
{{ scope.row.cIeData }}
</span>
</template>
</el-table-column>
<el-table-column prop="cIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
</span>
</template>
</el-table-column>
<el-table-column prop="cIeXi" label="检测结果" width="90px">
<template #default="scope">
<el-tooltip
v-if="scope.row.cIeXi === '不合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.cI + '%' }}
>
<template #content>
误差范围 {{ -0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.cI + '%'}}
</template>
<el-tag type="danger" class="tooltip-content">
{{ scope.row.cIeXi }}
{{ scope.row.cIeXi }}
</el-tag>
</el-tooltip>
<el-tooltip
</el-tooltip>
<el-tooltip
v-else-if="scope.row.cIeXi === '合格'"
class="item"
effect="dark"
placement="bottom-start"
>
<template #content>
误差范围 {{ -0.5 + '%~' + 0.5 + '%' }}
<br />
误差值{{ scope.row.cI + '%' }}
>
<template #content>
误差范围 {{ -0.5 + "%~" +0.5 + "%" }}<br/>
误差值{{ scope.row.cI + '%'}}
</template>
<el-tag type="success" class="tooltip-content">{{ scope.row.cIeXi }}</el-tag>
</el-tooltip>
<el-icon v-else-if="scope.row.cIeXi === '—' && scope.row.loading" class="loading-box">
</el-tooltip>
<el-icon v-else-if="scope.row.cIeXi === '—'&& scope.row.loading" class="loading-box">
<el-icon-loading />
</el-icon>
</template>
</el-icon>
</template>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</div>
</el-table>
</div>
</template>
<script lang="ts" setup name="ErrorSystemDialog">
import { ElIcon, ElTag } from 'element-plus'
import type { ChannelsTest } from '@/api/home/interface/channelsTest'
<script lang="ts" setup name="ErrorSystemDialog">
import { defineProps, type PropType } from 'vue';
import { ElIcon, ElLoading, ElTag } from 'element-plus';
import type { ChannelsTest } from '@/api/home/interface/channelsTest';
import { number } from 'echarts';
interface Props {
tableData: ChannelsTest.CoefficientVO[]
big_V_loading: boolean
curV: number
tableData: ChannelsTest.CoefficientVO[];
big_V_loading: boolean;
curV: number;
}
const props = withDefaults(defineProps<Props>(), {
tableData: () => [],
big_V_loading: false,
curV: 0
})
tableData: () => [],
big_V_loading: false,
curV: 0,
});
function objectSpanMethod({
row,
column,
rowIndex,
columnIndex
}: {
row: any
column: any
rowIndex: number
columnIndex: number
}) {
function objectSpanMethod({ row, column, rowIndex, columnIndex }: { row: any, column: any, rowIndex: number, columnIndex: number }) {
if (columnIndex === 0) {
if (rowIndex % 4 === 0) {
return {
rowspan: 4,
colspan: 1
}
colspan: 1,
};
} else {
return {
rowspan: 0,
colspan: 0
}
colspan: 0,
};
}
}
if (columnIndex === 1) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1
}
colspan: 1,
};
} else {
return {
rowspan: 0,
colspan: 0
}
colspan: 0,
};
}
}
}
</script>
<style scoped>
</script>
<style scoped>
.form-grid {
display: flex;
flex-direction: row; /* 横向排列 */
@@ -402,40 +364,40 @@ function objectSpanMethod({
margin-bottom: 20px; /* 添加底部边距 */
}
.el-table th,
.el-table td {
text-align: center; /* 所有单元格文字居中 */
.el-table th, .el-table td {
text-align: center; /* 所有单元格文字居中 */
}
.table-container {
max-height: 400px; /* 根据需要调整高度 */
overflow-y: auto; /* 允许垂直滚动 */
overflow-x: hidden; /* 隐藏水平滚动条 */
max-height: 400px; /* 根据需要调整高度 */
overflow-y: auto; /* 允许垂直滚动 */
overflow-x: hidden; /* 隐藏水平滚动条 */
}
/* 确保 el-icon-loading 的动画效果没有被覆盖 */
.loading-box {
animation: rotate 2s linear infinite;
animation: rotate 2s linear infinite;
}
.icon-margin {
margin-left: 0px;
margin-left: 0px;
}
.icon-align {
vertical-align: 0px;
vertical-align: 0px;
}
.tooltip-content {
cursor: pointer;
cursor: pointer;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,63 +0,0 @@
<template>
<div class="table-main">
<el-table
:data="prop.tableData"
stripe
border
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
height="368px"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="70" fixed="left"/>
<el-table-column prop="dataA" :label="'被检设备'">
<el-table-column prop="timeDev" label="数据时间" width="200"/>
<el-table-column prop="uaDev" :label="'A相'+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.uaDev != null"/>
<el-table-column prop="ubDev" :label="setB+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.ubDev != null"/>
<el-table-column prop="ucDev" :label="'C相'+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.ucDev != null"/>
<el-table-column prop="utDev" :label="setT+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData[0]?.utDev != null"/>
</el-table-column>
<el-table-column prop="dataA" :label="'标准设备'">
<el-table-column prop="timeStdDev" label="数据时间" width="200"/>
<el-table-column prop="uaStdDev" :label="'A相'+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.uaStdDev != null"/>
<el-table-column prop="ubStdDev" :label="setB+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.ubStdDev != null"/>
<el-table-column prop="ucStdDev" :label="'C相'+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData.length==0||prop.tableData[0]?.ucStdDev != null"/>
<el-table-column prop="utStdDev" :label="setT+(outerUnit==''?'':''+outerUnit+'')" v-if="prop.tableData[0]?.utStdDev != null"/>
</el-table-column>
</el-table>
</div>
</template>
<script lang="tsx" setup>
import { computed } from 'vue'
import { CheckData } from '@/api/check/interface'
const prop = defineProps({
tableData: {
type: Array as () => CheckData.TableRow[],
default: []
},
currentCheckItem: {
type: String,
default: ''
}
})
const outerUnit = computed(() => {
return prop.tableData.length > 0 ? prop.tableData[0].unit : '';
})
const setB = computed(() => {
return prop.currentCheckItem == '三相电流不平衡度'
? '负序不平衡度'
: prop.currentCheckItem == '三相电压不平衡度'
? '负序不平衡度'
: 'B相'
})
const setT = computed(() => {
return prop.currentCheckItem == '频率' ? '频率' : 'T相'
})
</script>
<style scoped></style>

View File

@@ -1,196 +0,0 @@
<template>
<div class="table-main">
<el-table
:data="prop.tableData"
height="368px"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
>
<!-- <el-table-column type="index" label="序号" width="70" fixed="left" />-->
<el-table-column label="A相" v-if="prop.tableData.length==0|| prop.tableData[0]?.dataA">
<el-table-column prop="stdA" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataA.data }}
</template>
</el-table-column>
<el-table-column prop="dataA" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataA.resultData }}
</template>
</el-table-column>
<el-table-column prop="isDataA" label="检测结果">
<template #default="{ row }">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ addPercentSigns(row.dataA.radius, row.dataA.unit) }}
<br/>
误差值{{ row.dataA.errorData }}{{ row.dataA.unit }}
</template>
<el-tag type="success" v-if="row.dataA.isData === 1">符合</el-tag>
<el-tag type="danger" v-if="row.dataA.isData === 2">不符合</el-tag>
<el-tag type="warning" v-if="row.dataA.isData === 4">/</el-tag>
<el-tag type="info" v-if="row.dataA.isData === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="setB" v-if="prop.tableData.length==0|| prop.tableData[0]?.dataB">
<el-table-column prop="stdB" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataB.data }}
</template>
</el-table-column>
<el-table-column prop="dataB" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataB.resultData }}
</template>
</el-table-column>
<el-table-column prop="isDataB" label="检测结果">
<template #default="{ row }">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ addPercentSigns(row.dataB.radius, row.dataB.unit) }}
<br/>
误差值{{ row.dataB.errorData }}{{ row.dataB.unit }}
</template>
<el-tag type="success" v-if="row.dataB.isData === 1">符合</el-tag>
<el-tag type="danger" v-if="row.dataB.isData === 2">不符合</el-tag>
<el-tag type="warning" v-if="row.dataB.isData === 4">/</el-tag>
<el-tag type="info" v-if="row.dataB.isData === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="C相" v-if="prop.tableData.length==0|| prop.tableData[0]?.dataC">
<el-table-column prop="stdC" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataC.data }}
</template>
</el-table-column>
<el-table-column prop="dataC" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataC.resultData }}
</template>
</el-table-column>
<el-table-column prop="isDataC" label="检测结果">
<template #default="{ row }">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ addPercentSigns(row.dataC.radius, row.dataC.unit) }}
<br/>
误差值{{ row.dataC.errorData }}{{ row.dataC.unit }}
</template>
<el-tag type="success" v-if="row.dataC.isData === 1">符合</el-tag>
<el-tag type="danger" v-if="row.dataC.isData === 2">不符合</el-tag>
<el-tag type="warning" v-if="row.dataC.isData === 4">/</el-tag>
<el-tag type="info" v-if="row.dataC.isData === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="setT" v-if="prop.tableData[0].dataT">
<el-table-column prop="stdT" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataT.data }}
</template>
</el-table-column>
<el-table-column prop="dataT" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')">
<template #default="{ row }">
{{ row.dataT.resultData }}
</template>
</el-table-column>
<el-table-column prop="isDataT" label="检测结果">
<template #default="{ row }">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ addPercentSigns(row.dataT.radius, row.dataT.unit) }}
<br/>
误差值{{ row.dataT.errorData }}{{ row.dataT.unit }}
</template>
<el-tag type="success" v-if="row.dataT.isData === 1">符合</el-tag>
<el-tag type="danger" v-if="row.dataT.isData === 2">不符合</el-tag>
<el-tag type="warning" v-if="row.dataT.isData === 4">/</el-tag>
<el-tag type="info" v-if="row.dataT.isData === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script lang="tsx" setup>
import {computed} from 'vue'
import {CheckData} from '@/api/check/interface'
const prop = defineProps({
tableData: {
type: Array as () => CheckData.TableRow[],
default: []
},
currentCheckItem: {
type: String,
default: ''
}
})
// 添加单位
const outerUnit = computed(() => {
return prop.tableData.length > 0 ? prop.tableData[0].unit : '';
})
const addPercentSigns = (text: string, unit: string) => {
return text
.split('~')
.map(part => `${part}${unit}`)
.join('~')
}
const setB = computed(() => {
return prop.currentCheckItem == '三相电流不平衡度'
? '三相电流不平衡度'
: prop.currentCheckItem == '三相电压不平衡度'
? '三相电压不平衡度'
: 'B相'
})
const setT = computed(() => {
return prop.currentCheckItem == '频率' ? '频率' : 'T相'
})
</script>
<style scoped>
.form-grid {
display: flex;
flex-direction: row; /* 横向排列 */
flex-wrap: wrap; /* 允许换行 */
}
.form-grid .el-form-item {
flex: 1 1 30%; /* 控件宽度 */
margin-right: 20px; /* 控件间距 */
}
.form-grid .el-form-item:last-child {
margin-right: 0; /* 最后一个控件不需要右边距 */
}
.dialog-footer {
display: flex;
justify-content: flex-start;
margin-bottom: 10px; /* 调整这里的值以增加或减少间距 */
}
.el-tabs {
margin-bottom: 20px; /* 添加底部边距 */
}
.el-table th,
.el-table td {
text-align: center; /* 所有单元格文字居中 */
}
</style>

View File

@@ -1,662 +0,0 @@
<template>
<el-dialog
:append-to-body="appendToBody"
class="dialog"
title="数据查询"
:model-value="visible"
@close="close"
v-bind="dialogBig"
:draggable="false"
width="1400px"
>
<div class="data-check-dialog">
<div>
<el-form :model="formContent" label-width="auto" class="form-three">
<el-form-item label="误差体系">
<el-select
:disabled="checkStore.showDetailType === 2 || checkStore.showDetailType === 0"
v-model="formContent.errorSysId"
placeholder="请选择误差体系"
autocomplete="off"
@change="handleErrorSysChange"
>
<el-option
v-for="option in pqErrorList"
:key="option.id"
:label="option.name"
:value="option.id"
/>
</el-select>
</el-form-item>
<el-form-item label="数据原则">
<el-input v-model="formContent.dataRule" :disabled="true" />
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="formContent.deviceName" :disabled="true" />
</el-form-item>
<el-form-item label="通道号">
<el-select v-model="formContent.chnNum" @change="handleChnNumChange" :disabled="sourceKey == 1">
<el-option v-for="item in chnList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="检测次数">
<el-select
v-model="formContent.num"
clearable
@change="handleNumChange"
:disabled="sourceKey == 1"
>
<el-option
v-for="item in chnMapList[formContent.chnNum]"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item v-if="checkStore.showDetailType === 1">
<el-button type="primary" :icon="Postcard">报告生成</el-button>
</el-form-item>
<el-form-item v-if="checkStore.showDetailType === 0">
<el-button type="primary" :icon="Histogram" @click="handleReCalculate">重新计算</el-button>
</el-form-item>
</el-form>
</div>
<div class="data-check-body">
<div class="content-left-tree" v-if="sourceKey == 2">
<el-tree
style="width: 200px"
:data="scriptData"
:props="defaultProps"
highlight-current
node-key="id"
ref="treeRef"
@node-click="handleNodeClick"
></el-tree>
</div>
<div class="content-right">
<div class="content-right-title">
<div style="width: 840px">
<span class="content-right-title-text">当前检测项目</span>
<!-- 当code为'wave_data'时显示下拉框 -->
<el-select
v-if="isWaveData"
v-model="selectedScriptName"
style="width: 200px"
@change="handleScriptNameChange"
>
<el-option
v-for="item in scriptNameOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- 否则显示原来的文本 -->
<span v-else style="color: var(--el-color-primary)">{{ rowList.scriptName }}</span>
</div>
<el-form-item
style="
margin: 0 auto;
margin-bottom: 0px !important;
width: 200px;
position: absolute;
left: 50%;
transform: translateX(-50%);
"
label="录波次数"
v-if="isWaveData"
>
<el-select v-model="waveNumber" @change="handleWaveNumberChange">
<el-option v-for="i in waveNumCount" :key="i" :label="i" :value="i" />
</el-select>
</el-form-item>
<el-form-item
style="margin-left: 280px; margin-bottom: 0px !important; width: 300px"
label="测试项"
>
<el-select v-model="currentCheckItem">
<el-option
v-for="item in tesList"
:key="item"
:label="item.replace(/\.0$/, '')"
:value="item"
/>
</el-select>
</el-form-item>
</div>
<div class="content-right-Tabs">
<el-tabs type="border-card" v-model="activeTab">
<el-tab-pane label="检测结果" name="resultTab">
<CompareDataCheckResultTable
:tableData="checkResultData.length == 0 ? [] : currentCheckResultData"
:currentCheckItem="currentCheckItem"
:currentScriptTypeName="currentScriptTypeName"
v-if="activeTab === 'resultTab'"
/>
</el-tab-pane>
<el-tab-pane label="原始数据" name="rawDataTab">
<CompareDataCheckRawDataTable
v-if="activeTab === 'rawDataTab'"
:tableData="rawTableData.length == 0 ? [] : currentRawTableData"
:currentCheckItem="currentCheckItem"
:currentScriptTypeName="currentScriptTypeName"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { dialogBig } from '@/utils/elementBind'
import { computed, reactive, ref } from 'vue'
import CompareDataCheckResultTable from './compareDataCheckResultTable.vue'
import CompareDataCheckRawDataTable from './compareDataCheckRawDataTable.vue'
import { CheckData } from '@/api/check/interface'
import { useCheckStore } from '@/stores/modules/check'
import { Histogram, Postcard } from '@element-plus/icons-vue'
import { getPqErrSysList } from '@/api/plan/plan'
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
import { useDictStore } from '@/stores/modules/dict'
import {
changeErrorSystem,
deleteTempTable,
getContrastFormContent,
getContrastResult,
getScriptList,
reCalculate
} from '@/api/check/test'
import { ElMessage } from 'element-plus'
import { ResultEnum } from '@/enums/httpEnum'
const { appendToBody = true } = defineProps<{
appendToBody: boolean
}>()
const checkStore = useCheckStore()
const modeStore = useModeStore()
const dictStore = useDictStore()
const visible = ref(false)
const treeRef = ref()
const pqErrorList = reactive<{ id: string; name: string }[]>([])
const activeTab = ref('resultTab')
const currentCheckItem = ref<any>()
const rowList: any = ref([])
let scriptType: string | null = null
const defaultProps = {
children: 'children',
label: 'scriptName'
}
const chnMapList: any = ref({})
const waveNumCount = ref(0)
const waveNumber = ref(1)
const selectedScriptName = ref('')
const pattern = ref('')
// 添加以下内容
const isWaveData = ref(false)
const scriptNameOptions = ref<{ label: string; value: string }[]>([])
// 表单数据
const formContent = reactive<CheckData.DataCheck>({
scriptName: '',
errorSysId: '',
dataRule: '',
deviceName: '',
chnNum: '',
deviceId: '',
num: ''
})
const sourceKey = ref(1) //1:正式检测进入页面 2:检测数据查询进入
// 通道下拉列表
const chnList: any = ref([])
// 当前检测项目名称
const currentScriptTypeName = ref('')
// 检测结果表格数据
const checkResultData = ref<CheckData.CheckResult[]>([])
// 检测脚本配置数据
const scriptData = ref<CheckData.ScriptItem[]>([])
//录波对应当前检测项下拉框
const selectScript = ref<CheckData.ScriptItem[]>([])
// 原始数据表格数据
const rawTableData = ref<CheckData.RawDataItem[]>([])
const tesList: any = ref([])
// 检测结果
const currentCheckResultData = computed(() => {
const data = checkResultData.value[currentCheckItem.value]
return Array.isArray(data) ? data : []
})
const currentRawTableData = computed(() => {
const data = rawTableData.value[currentCheckItem.value]
return Array.isArray(data) ? data : []
})
const open = async (row: any, chnNum: string, deviceId: string | null, source: number) => {
isWaveData.value = false
pattern.value = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? '' //获取数据字典中对应的id
rowList.value = {}
formContent.deviceId = deviceId || ''
formContent.chnNum = chnNum
sourceKey.value = source
// 获取基本信息
await getBasicInformation(row.scriptType)
if (source == 1) {
// 正式检测进入页面 - 创建新的对象避免引用共享
rowList.value = {
scriptName: row.scriptName,
scriptType: row.scriptType,
// 复制其他需要的属性
devices: row.devices ? [...row.devices] : []
}
}
// 检测数据查询进入---不区分检测数据查询和正式检测
await initScriptData()
visible.value = true
scriptType = null
formContent.errorSysId = checkStore.plan.errorSysId
pqErrorList.length = 0
// 获取误差体系
let { data: resPqErrorList } = await getPqErrSysList()
Object.assign(pqErrorList, resPqErrorList)
initGetResult()
}
const initGetResult = async () => {
// 判断是否为录波数据
const isLuoboData =
(sourceKey.value == 1 && rowList.value.scriptName == '录波数据') ||
(sourceKey.value == 2 && scriptData.value[0]?.code == 'wave_data')
if (isLuoboData) {
isWaveData.value = true
// 设置录波数据相关的选项
scriptNameOptions.value = selectScript.value.map(item => ({
label: item.scriptName,
value: item.scriptName
}))
// 默认选中第一个选项
if (scriptNameOptions.value.length > 0) {
selectedScriptName.value = scriptNameOptions.value[0].value
// 更新rowList以匹配选中的script
const selectedItem = selectScript.value.find(item => item.scriptName === selectedScriptName.value)
if (selectedItem) {
rowList.value.scriptName = selectedScriptName.value
rowList.value.scriptType = selectedItem.id
}
}
await getResults('wave_data')
} else {
await getResults('')
}
}
// 查询大项树
const initScriptData = async () => {
let response: any = await getScriptList({
devId: formContent.deviceId,
chnNum: formContent.chnNum,
num: formContent.num
})
// 格式化脚本数据
let temp = response.data.map((item: any) => {
return {
...item,
scriptName: item.scriptName
}
})
// 保存脚本数据
scriptData.value = temp
// 查找code为"录波"的项
let luoboItem = response.data.find((item: any) => item.code === 'wave_data')
// 如果找到了"录波"项则使用其subitems否则使用空数组
let temp2 = []
if (luoboItem && luoboItem.subItems) {
// 格式化脚本数据
temp2 = luoboItem.subItems.map((item: any) => {
return {
...item,
scriptName: item.scriptName
}
})
}
selectScript.value = temp2
// 只有在sourceKey == 2时才设置rowList和tree相关属性
if (sourceKey.value === 2 && temp.length > 0) {
rowList.value.scriptName = temp[0].scriptName
rowList.value.scriptType = temp[0].id
selectedScriptName.value = temp[0].scriptName
setTimeout(() => {
treeRef.value?.setCurrentKey(temp[0].id)
}, 0)
}
}
// 获取基本信息
const getBasicInformation = async (scriptType: any) => {
checkResultData.value = []
rawTableData.value = []
const scriptType2 = ref('')
if (sourceKey.value == 1) {
scriptType2.value = scriptType
} else {
scriptType2.value = ''
}
//确保scriptData已初始化
// if (scriptData.value.length === 0) {
// await initScriptData()
// }
try {
const res: any = await getContrastFormContent({
planId: checkStore.plan.id,
scriptType: scriptType2.value,
deviceId: formContent.deviceId,
chnNum: formContent.chnNum,
num: formContent.num == '' ? null : parseInt(formContent.num),
patternId: pattern.value
})
formContent.dataRule = res.data.dataRule
formContent.deviceName = res.data.deviceName
formContent.errorSysId = res.data.errorSysId
chnMapList.value = res.data.chnMap
let chnMap: string[] = []
for (let key in res.data.chnMap) {
chnMap.push(key)
}
chnList.value = chnMap
formContent.chnNum = formContent.chnNum == null ? chnList.value[0] : formContent.chnNum
// 设置检测次数默认值为chnMap数组的最后一位
if (chnMapList.value[formContent.chnNum] && chnMapList.value[formContent.chnNum].length > 0) {
// 获取当前通道号对应的检测次数数组,并设置为最后一个值(最大值)
const numList = chnMapList.value[formContent.chnNum]
formContent.num = numList[numList.length - 1]
}
waveNumCount.value = res.data.waveNumTotal
} catch (error) {
console.error('获取基本信息失败:', error)
}
}
const handleChnNumChange = async (value: string) => {
formContent.chnNum = value
// 更新检测次数为当前通道的最后一条记录
updateCheckNumForChn(value)
// 执行通用变更处理逻辑
await handleCommonChange()
}
// 处理检测次数变更
const handleNumChange = async (value: string) => {
formContent.num = value
// 执行通用变更处理逻辑
await handleCommonChange()
}
// 通用的变更处理函数
const handleCommonChange = async () => {
// 重新初始化脚本数据(更新左侧树)
await initScriptData()
// 触发当前选中节点的点击事件,保持界面状态一致
if (sourceKey.value === 2 && scriptData.value.length > 0) {
// 查找当前选中的节点
const currentNode = scriptData.value.find((item: any) => item.id === rowList.value.scriptType)
if (currentNode) {
// 如果找到了当前节点,则触发点击事件
handleNodeClick(currentNode)
} else {
// 如果没有找到当前节点,则默认触发第一个节点的点击事件
handleNodeClick(scriptData.value[0])
}
} else if (sourceKey.value === 1 && rowList.value.scriptType) {
// 对于正式检测进入的情况创建一个临时节点对象来触发handleNodeClick
const tempNode = {
scriptName: rowList.value.scriptName,
id: rowList.value.scriptType,
code: rowList.value.scriptName === '录波数据' ? 'wave_data' : ''
}
handleNodeClick(tempNode)
}
}
// 更新检测次数为指定通道的最后一条记录
const updateCheckNumForChn = (chnNum: string) => {
if (chnMapList.value[chnNum] && chnMapList.value[chnNum].length > 0) {
// 获取当前通道号对应的检测次数数组,并设置为最后一个值(最大值)
const numList = chnMapList.value[chnNum]
formContent.num = numList[numList.length - 1]
}
}
// 左边树变化
const handleNodeClick = (data: any) => {
rowList.value.scriptName = data.scriptName
rowList.value.scriptType = data.id
// 判断是否为录波数据
if (data.code === 'wave_data') {
isWaveData.value = true
// 过滤掉"录波"选项,设置下拉框数据
scriptNameOptions.value = selectScript.value
//.filter(item => item.code !== 'wave_data' && item.code !== 'FREQ')
.map(item => ({
label: item.scriptName,
value: item.scriptName
}))
// 每次选中录波数据时都重置为第一个选项并触发getResults
if (scriptNameOptions.value.length > 0) {
selectedScriptName.value = scriptNameOptions.value[0].value
// 更新rowList并触发getResults
rowList.value.scriptName = selectedScriptName.value
const selectedItem = selectScript.value.find(item => item.scriptName === selectedScriptName.value)
if (selectedItem) {
rowList.value.scriptType = selectedItem.id
getResults('wave_data')
}
}
} else {
isWaveData.value = false
getResults(data.code)
}
}
// 获取结果
const getResults = async (code: any) => {
checkResultData.value = []
rawTableData.value = []
// 判断是否为录波数据请求
const isWaveDataRequest = code === 'wave_data' || isWaveData.value
console.log('isWaveDataRequest:', rowList.value.scriptType)
getContrastResult({
planId: checkStore.plan.id,
scriptType: rowList.value.scriptType,
deviceId: formContent.deviceId,
chnNum: formContent.chnNum,
num: formContent.num == '' ? null : formContent.num,
waveNum: isWaveDataRequest ? waveNumber.value : null,
isWave: isWaveDataRequest,
patternId: pattern.value
}).then((res: any) => {
let list: string[] = []
for (let key in res.data.resultMap) {
list.push(key)
}
currentCheckItem.value = list[0]
tesList.value = list
checkResultData.value = res.data.resultMap
rawTableData.value = res.data.rawDataMap
})
}
// 添加处理scriptName变化的方法
const handleScriptNameChange = (value: string) => {
selectedScriptName.value = value
rowList.value.scriptName = value
// 查找选中项的scriptType
const selectedItem = selectScript.value.find(item => item.scriptName === value)
if (selectedItem) {
rowList.value.scriptType = selectedItem.id
getResults('wave_data')
}
}
// 添加处理waveNumber变化的方法
const handleWaveNumberChange = (value: number) => {
waveNumber.value = value
// 当录波次数改变时,重新获取结果
getResults('wave_data')
}
const close = async () => {
visible.value = false
formContent.num = ''
// 可以在这里添加其他清理逻辑
if (checkStore.showDetailType === 1) {
await deleteTempTable(checkStore.plan.code + '')
}
}
const handleErrorSysChange = async () => {
changeErrorSystem({
planId: checkStore.plan.id,
scriptId: '',
errorSysId: formContent.errorSysId,
deviceId: formContent.deviceId,
code: checkStore.plan.code + '',
patternId: dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? ''
}).then(res => {
if (res.code === ResultEnum.SUCCESS) {
ElMessage.success('切换误差体系成功')
handleChnNumChange(formContent.chnNum)
}
})
}
const handleReCalculate = async () => {
reCalculate({
planId: checkStore.plan.id,
scriptId: '',
errorSysId: formContent.errorSysId,
deviceId: formContent.deviceId,
code: checkStore.plan.code + '',
patternId: dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? ''
}).then(res => {
if (res.code === ResultEnum.SUCCESS) {
ElMessage.success('重新计算成功!')
handleChnNumChange(formContent.chnNum)
}
})
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.dialog {
display: flex;
flex-direction: column;
.data-check-dialog {
display: flex;
flex-direction: column;
.data-check-head {
display: flex;
flex-direction: row;
width: 100%;
}
.data-check-body {
height: 500px;
width: 100%;
display: flex;
flex-direction: row;
.content-left-tree {
width: 18%;
display: flex;
flex-direction: column;
align-items: center;
max-height: 495px;
padding: 10px 0.5% 0px 0.5%;
border: 1px solid #ccc;
overflow-y: auto;
overflow-x: auto;
margin-right: 10px;
.content-tree {
width: 100%;
height: 100%;
margin-top: 10px;
.custom-tree-node {
overflow-x: hidden !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
}
}
}
.content-right {
width: 82%;
flex: 1;
.content-right-title {
display: flex;
padding: 10px 0;
margin-top: 0px;
line-height: 1.5;
.content-right-title-text {
font-size: 14px;
font-weight: bold;
}
}
}
.content-right-Tabs {
box-sizing: border-box;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
.el-tabs {
width: 100%;
}
}
.content-left {
height: 100%;
border: 1px solid #e0e0e0;
padding: 10px;
margin-right: 10px;
height: 410px;
overflow-y: auto;
}
}
}
}
:deep(.el-tabs--border-card > .el-tabs__content) {
height: 367px;
}
</style>

View File

@@ -1,601 +0,0 @@
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane label="预检测项目">
<div class="form-grid">
<el-checkbox v-for="(item, index) in detectionOptions" v-model="item.selected" :key="index"
:label="item.name" disabled></el-checkbox>
</div>
</el-tab-pane>
</el-tabs>
<div class="test-dialog">
<div class="dialog-left">
<el-steps direction="vertical" :active="activeIndex" :process-status="currentStepStatus"
finish-status="success">
<el-step :status="step1" title="设备通讯校验"/>
<el-step :status="step2" title="模型一致性校验"/>
<el-step :status="step3" title="实时数据对齐验证" v-if="!props.onlyWave"/>
<el-step :status="step4" title="相序校验"/>
<!-- <el-step :status="step6" title="遥控录波功能验证"/> -->
<el-step :status="step5" :title="ts === 'error'? '检测失败':ts === 'process'? '检测中':ts === 'success'? '检测成功':'待检测'"/>
</el-steps>
</div>
<div class="dialog-right">
<el-collapse v-model="collapseActiveName" accordion>
<el-collapse-item title="设备通讯校验" name="1">
<div class="div-log">
<p v-for="(item, index) in step1InitLog" :key="index"
:style="{ color: item.type === 'error' ? '#F56C6C' : 'var(--el-text-color-regular)' }">
{{ item.log }} <br/>
</p>
</div>
</el-collapse-item>
<el-collapse-item title="模型一致性校验" name="2">
<div class="div-log">
<p v-for="(item, index) in step2InitLog" :key="index"
:style="{ color: item.type === 'error' ? '#F56C6C' : 'var(--el-text-color-regular)' }">
{{ item.log }} <br/>
</p>
</div>
</el-collapse-item>
<el-collapse-item name="3" v-if="!props.onlyWave">
<template #title>
实时数据对齐验证
<el-icon class="title-icon" @click="openDialog" v-if="isShowDialog"><InfoFilled/></el-icon>
</template>
<div class="div-log">
<p v-for="(item, index) in step3InitLog" :key="index"
:style="{ color: item.type === 'error' ? '#F56C6C' : 'var(--el-text-color-regular)' }">
{{ item.log }}
<br/>
</p>
</div>
</el-collapse-item>
<el-collapse-item title="相序校验" name="4">
<div class="div-log">
<p v-for="(item, index) in step4InitLog" :key="index"
:style="{ color: item.type === 'error' ? '#F56C6C' : 'var(--el-text-color-regular)' }">
{{ item.log }} <br/>
</p>
</div>
</el-collapse-item>
<!-- <el-collapse-item title="遥控录波功能验证" name="3">
<div class="div-log">
<p v-for="(item, index) in step3InitLog" :key="index"
:style="{ color: item.type === 'error' ? '#F56C6C' : 'var(--el-text-color-regular)' }">
{{ item.log.split('&&')[0] }}
<br v-if="item.log.includes('&&')"/>
&nbsp;&nbsp;&nbsp;&nbsp;{{ item.log.split('&&')[1] }}
<br v-if="item.log.includes('&&')"/>
&nbsp;&nbsp;&nbsp;&nbsp;{{ item.log.split('&&')[2] }}
</p>
</div>
</el-collapse-item> -->
</el-collapse>
</div>
</div>
</div>
<RealTimeData ref="realTimeDataRef" />
</template>
<script lang="tsx" setup name="preTest">
import { ElMessage, ElMessageBox, StepProps } from 'element-plus'
import { computed, PropType, ref, toRef, watch } from 'vue'
import RealTimeData from './realTimeDataAlign.vue'
const realTimeDataRef = ref()
const step1InitLog = ref([
{
type: 'info',
log: '暂无数据,等待检测开始',
},
])
const step2InitLog = ref([
{
type: 'info',
log: '暂无数据,等待检测开始',
},
])
const step3InitLog = ref([
{
type: 'info',
log: '暂无数据,等待检测开始',
},
])
const step4InitLog = ref([
{
type: 'info',
log: '暂无数据,等待检测开始',
},
])
const isShowDialog = ref(false)
const collapseActiveName = ref('1')
const activeIndex = ref(0)
const activeTotalNum = computed(() => {
let count = 4; // 基础步骤数:设备通讯校验、模型一致性校验、相序校验、最终状态
if (props.onlyWave) {
count++; // 添加实时数据对齐验证步骤
}
return count;
});
const step1 = ref<StepProps['status']>('wait')
const step2 = ref<StepProps['status']>('wait')
const step3 = ref<StepProps['status']>('wait')
const step4 = ref<StepProps['status']>('wait')
const step5 = ref<StepProps['status']>('wait')
//定义与预检测配置数组
const detectionOptions = ref([
{
id: 0,
name: "设备通讯校验",
selected: true,
},
{
id: 1,
name: "模型一致性校验",
selected: true,
},
{
id: 2,
name: "实时数据对齐验证",
selected: true,
},
{
id: 3,
name: "相序校验",
selected: true,
},
]);
const currentStepStatus = ref<'error' | 'finish' | 'wait' | 'success' | 'process'>('finish');
const props = defineProps({
testStatus: {
type: String,
default: 'wait'
},
webMsgSend: {
type: Object,
default: () => ({})
},
mapping: {
type: Object as PropType<Record<string, Record<string, string>>>,
default: () => ({})
},
onlyWave: {
type: Boolean,
default: false
}
})
const testStatus = toRef(props, 'testStatus');
const webMsgSend = toRef(props, 'webMsgSend');
const ts = ref('');
// 在 script setup 中定义接口
interface ChannelData {
devNum: string;
standardDevInfo: string;
dataList: {
timeDev: string | null;
uaDev: number | null;
ubDev: number | null;
ucDev: number | null;
timeStdDev: string | null;
uaStdDev: number | null;
ubStdDev: number | null;
ucStdDev: number | null;
}[];
}
interface DeviceData {
devName: string;
channelDataList: ChannelData[];
}
// 修改 testDataStructure 的类型声明
const testDataStructure = ref<Record<string, DeviceData>>({});
watch(webMsgSend, function (newValue, oldValue) {
if(testStatus.value == 'success' || testStatus.value == 'error'){
return
}
if (testStatus.value !== 'waiting') {
if(newValue.code == 25004){
ElMessage.error('接收数据超时!')
ts.value = 'error'
step5.value = 'error'
}else if(newValue.code == 10550){
ElMessage.error('设备连接异常!')
ts.value = 'error'
step5.value = 'error'
}
switch (newValue.requestId) {
case 'yjc_sbtxjy':
if (newValue.code == 10200) {
step1InitLog.value.push({
type: 'info',
log: newValue.data,
})
}
if (newValue.code == 10201) {
step1.value = 'process'
step1InitLog.value = [{
type: 'wait',
log: '正在进行设备通讯校验.....',
}];
}
if (newValue.code == 10551) {
step1InitLog.value.push({
type: 'error',
log: newValue.data + '设备触发报告异常!',
})
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
if (newValue.code == 10552) {
step1InitLog.value = [{
type: 'error',
log: '存在已经初始化步骤,执行自动关闭,请重新发起检测!',
}];
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
if (newValue.code == 25001) {
step1InitLog.value.push({
type: 'info',
log: newValue.data,
})
activeIndex.value = 1
step1.value = 'success'
step2.value = 'process'
}
break;
case 'record_wave_step1':
if (newValue.code == 25002) { //某一路录波校验失败
step1InitLog.value.push({
type: 'error',
log: newValue.data,
})
} else if (newValue.code == 25003) { //最终失败
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
break;
case 'yjc_mxyzxjy':
if (newValue.code == 10200) { //单个监测点成功
step2InitLog.value.push({
type: 'info',
log: newValue.data + '模型一致性检验成功!',
})
}
if (newValue.code == 10201) {
step2.value = 'process'
step2InitLog.value = [{
type: 'wait',
log: '正在进行模型一致性校验.....',
}];
}
if (newValue.code == 25002) { //单个监测点失败
step2InitLog.value.push({
type: 'error',
log: newValue.data + '模型一致性检验失败!',
})
}
if (newValue.code == 25001) { //最终成功
step2.value = 'success'
step3.value = 'process'
activeIndex.value = 2
}
if (newValue.code == 25003) { //最终失败
step2.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
break;
case 'yjc_align':
if (newValue.code == 10200) { //单个监测点成功
step3InitLog.value.push({
type: 'info',
log: newValue.data + '数据对齐检验成功!',
})
}
if (newValue.code == 10201) {
step3.value = 'process'
step3InitLog.value = [{
type: 'wait',
log: '正在进行数据对齐检验.....',
}];
}
if (newValue.code == 25002) { //单个监测点失败
step3InitLog.value.push({
type: 'error',
log: newValue.data + '数据对齐检验失败!',
})
}
if (newValue.code == 25001 && newValue.data) { //最终成功
isShowDialog.value = true
step3.value = 'success'
step4.value = 'process'
activeIndex.value = 3
testDataStructure.value = newValue.data
}
if (newValue.code == 25003) { //最终失败
isShowDialog.value = true
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
testDataStructure.value = newValue.data
}
break;
case 'YJC_xujy':
if (newValue.code == 10200) {
step4InitLog.value.push({
type: 'info',
log: newValue.data,
})
}
if (newValue.code == 10201) {
step4.value = 'process'
step4InitLog.value = [{
type: 'wait',
log: '正在进行相序性检.....',
}];
}
if (newValue.code == 25002) {
step4InitLog.value.push({
type: 'error',
log: newValue.data,
})
}
if (newValue.code == 25003) {
step4InitLog.value.push({
type: 'error',
log: newValue.data,
})
step4.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
if (newValue.code == 25001) {
step4.value = 'success'
step5.value = 'success'
ts.value = 'success'
activeIndex.value = 4
}
break;
case 'quit':
break;
case 'connect':
switch (newValue.operateCode) {
case "Contrast_Dev":
step1.value = 'error'
step1InitLog.value = [{
type: 'error',
log: '设备服务端连接失败!',
}];
ts.value = 'error'
step5.value = 'error'
break;
}
break;
case 'unknown_operate':
break;
case 'error_flow_end':
ElMessageBox.alert(`当前流程存在异常结束!`, '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
ts.value = 'error'
step5.value = 'error'
break;
case 'socket_timeout':
ElMessageBox.alert(`设备连接异常,请检查设备连接情况!`, '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
ts.value = 'error'
step5.value = 'error'
break;
case 'server_error':
ElMessageBox.alert('服务端主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
ts.value = 'error'
step5.value = 'error'
break;
case 'device_error':
ElMessageBox.alert('设备主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
ts.value = 'error'
step5.value = 'error'
break;
case 'yjc_xyjy' :
switch (newValue.operateCode) {
case 'INIT_GATHER$03':
if (newValue.code == 10552) {
ElMessageBox.alert('重复的初始化操作!', '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
ts.value = 'error'
step5.value = 'error'
}
}
break;
}
}
})
// watch(activeIndex, function (newValue, oldValue) {
// if (newValue <= activeTotalNum.value - 2) {
// collapseActiveName.value = (newValue + 1).toString()
// } else {
// collapseActiveName.value = newValue.toString()
// }
// })
watch(activeIndex, function (newValue, oldValue) {
if(props.onlyWave === true)
{
if (Number(collapseActiveName.value) < activeTotalNum.value - 2) {
if(newValue == 2){
collapseActiveName.value = '4'
}else{
collapseActiveName.value = (newValue + 1).toString()
}
}
} else
{
if (Number(collapseActiveName.value) < activeTotalNum.value) {
collapseActiveName.value = (newValue + 1).toString()
}
}
})
//监听goods_sn的变化
watch(testStatus, function (newValue, oldValue) {
ts.value = props.testStatus;
if (ts.value === 'start') {
ts.value = 'process'
} else if (ts.value === 'waiting') {
activeIndex.value = 0
step1InitLog.value = [
{
type: 'info',
log: '暂无数据,等待检测开始',
},
]
step1.value = 'finish'
step2.value = 'wait'
step3.value = 'wait'
step4.value = 'wait'
step5.value = 'wait'
}
})
const emit = defineEmits(['update:testStatus']);
//监听sn
watch(ts, function (newValue, oldValue) {
//修改父组件
emit('update:testStatus', ts.value)
})
// 定义一个初始化参数的方法
function initializeParameters() {
activeIndex.value = 0
step1.value = 'wait'
step2.value = 'wait'
step3.value = 'wait'
step4.value = 'wait'
step5.value = 'wait'
step1InitLog.value = [
{
type: 'info',
log: '暂无数据,等待检测开始',
},
]
step2InitLog.value = [
{
type: 'info',
log: '暂无数据,等待检测开始',
},
]
step3InitLog.value = [
{
type: 'info',
log: '暂无数据,等待检测开始',
},
]
step4InitLog.value = [
{
type: 'info',
log: '暂无数据,等待检测开始',
},
]
// 清空实时数据对齐验证的数据
testDataStructure.value = {}
isShowDialog.value = false
}
const openDialog = () => {
realTimeDataRef.value.open(props.mapping,testDataStructure.value)
}
defineExpose({
initializeParameters,
});
</script>
<style scoped lang="scss">
.title-icon {
margin-left: 10px;
cursor: pointer;
font-size: 16px;
vertical-align: middle;
}
.test-dialog {
height: 350px;
display: flex;
flex-direction: row;
/* 横向排列 */
margin-top: 20px;
}
.dialog-left {
width: 15%;
margin-left: 20px;
}
.dialog-right {
margin-left: 20px;
width: 80%;
height: 100%;
}
.dialog-right :deep(.el-collapse-item__header) {
font-size: 16px;
}
.div-log {
height: 145px;
overflow-y: auto;
padding-left: 10px;
p {
margin: 5px 0;
font-size: 14px;
}
}
</style>

View File

@@ -1,917 +0,0 @@
<template>
<div>
<div class="dialog" v-bind="dialogBig">
<div class="dialog-title">
<div class="timeView">
<el-icon style="margin: 0px 5px">
<Clock/>
</el-icon>
<span>检测用时{{ timeView }}</span>
</div>
<el-progress style="width: 82%; margin-right: 3%" :percentage="percentage" :color="customColors"/>
<el-button style="width: 10%" type="text" :icon="InfoFilled" @click="showTestLog">检测项进度</el-button>
</div>
<div class="dialog-content">
<el-table
:data="checkResultView"
row-key="scriptType"
height="450px"
:header-cell-style="{ background: 'var(--el-color-primary)', color: '#eee', textAlign: 'center' }"
style="width: 100%"
border
>
<el-table-column
fixed
prop="scriptName"
label="检测项目"
width="150px"
align="center"
></el-table-column>
<template v-if="chnSum <= MAX_CHN_SUM">
<el-table-column
v-for="(item, index1) in deviceList"
:key="item.deviceId"
:label="item.deviceName"
:min-width="110"
align="center"
>
<el-table-column
v-for="(chnItem, index2) in checkStore.chnNumList"
:key="`${item.deviceId}${chnItem}`"
:label="'通道' + chnItem"
align="center"
>
<template #default="{ row }">
<el-tooltip
:content="
row.devices[index1].chnResult[index2].icon === 'More'
? '暂无数据'
: row.devices[index1].chnResult[index2].icon === 'CircleCheckFilled'
? '符合'
: row.devices[index1].chnResult[index2].icon === 'Close'
? '不符合'
: row.devices[index1].chnResult[index2].icon ===
'WarnTriangleFilled'
? '数据异常'
: row.devices[index1].chnResult[index2].icon === 'Loading'
? '检测中'
: '连接中断'
"
placement="right"
>
<el-button
:disabled="
row.devices[index1].chnResult[index2].color ===
CheckData.ButtonColorEnum.INFO ||
row.devices[index1].chnResult[index2].color ===
CheckData.ButtonColorEnum.LOADING
"
:color="row.devices[index1].chnResult[index2].color"
size="small"
@click="handleClick(row, chnItem, row.scriptType)"
style="align-self: center"
>
<el-icon
v-if="row.devices[index1].chnResult[index2].icon === 'Loading'"
class="loading-box"
style="color: #fff"
>
<component :is="Loading"/>
</el-icon>
<el-icon v-else style="color: #fff">
<component :is="row.devices[index1].chnResult[index2].icon"/>
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</template>
</el-table>
</div>
</div>
<div class="drawer-container">
<el-drawer v-model="drawer" title="检测项进度" direction="btt" :size="'38%'">
<div ref="scrollContainerRef" style="height: 100%; overflow-y: auto">
<p
v-for="(item, index) in testLogList"
:key="index"
:style="{
color:
item.type === 'error'
? '#F56C6C'
: item.type === 'warning'
? '#e6a23c'
: 'var(--el-text-color-regular)'
}"
>
{{ item.log }}
<br/>
</p>
</div>
</el-drawer>
</div>
<CompareDataCheckSingleChannelSingleTestPopup
ref="dataCheckSingleChannelSingleTestPopupRef"
:append-to-body="true"
/>
</div>
</template>
<script lang="tsx" setup name="test">
import {InfoFilled, Loading} from '@element-plus/icons-vue'
import CompareDataCheckSingleChannelSingleTestPopup from './compareDataCheckSingleChannelSingleTestPopup.vue'
import {computed, ComputedRef, nextTick, onBeforeMount, onMounted, reactive, ref, toRef, watch} from 'vue'
import {dialogBig} from '@/utils/elementBind'
import {CheckData} from '@/api/check/interface'
import {useCheckStore} from '@/stores/modules/check'
import {ElMessage, ElMessageBox} from 'element-plus'
import {getBigTestItem} from '@/api/check/test'
import {getAutoGenerate} from '@/api/user/login'
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
import {useDictStore} from '@/stores/modules/dict'
import { ca } from 'element-plus/es/locale'
const checkStore = useCheckStore()
const modeStore = useModeStore()
const dictStore = useDictStore()
// 最大通道数
const MAX_CHN_SUM = 12
// 总测试项数
let checkTotal = 0
const props = defineProps({
testStatus: {
type: String,
default: 'waiting'
},
stepsActive: {
type: Number
},
webMsgSend: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits([
'update:testStatus',
'update:webMsgSend',
'sendPause',
'sendResume',
'sendReCheck',
'closeWebSocket'
])
// 用来保存测试项进度抽屉是否打开
const drawer = ref(false)
// 进度条颜色
const customColors = [{color: '#91cc75', percentage: 100}]
// 检测脚本数据
let scriptData: CheckData.ScriptItem[] = []
// 用来保存被检设备
const deviceList = reactive<CheckData.Device[]>([])
// 当前进行的测试项索引
let activeIndex = 0
// 百分比
const percentage = ref(0)
// 时间计数器
let timer: any = null
const timeCount = ref(0)
const timeView = ref('00:00:00')
//测试项开始检测时间(或继续检测时间)
const startData = ref(new Date())
//测试项检测结束时间(或暂停时的时间)
const endData = ref(new Date())
const timeDifference = ref(0)
// 真正的检测结果(详细到通道)
const checkResult = reactive<CheckData.ScriptChnItem[]>([])
// 用来存放检测出现失败的测试项id。只要有一个通道检测不合格则该检测项的id会被加入该数组。
let errorCheckItem: Array<{ scriptType: string; type: CheckData.ChnCheckResultEnum }> = []
// 用来存放检测日志
const testLogList = reactive<CheckData.LogItem[]>([{type: 'info', log: '暂无数据,等待检测开始'}])
// 添加一个响应式变量来跟踪是否需要显示录波项目
const showWaveItem = ref(false)
const testStatus = toRef(props, 'testStatus')
const webMsgSend = toRef(props, 'webMsgSend')
const scrollContainerRef = ref()
const dataCheckSingleChannelSingleTestPopupRef =
ref<InstanceType<typeof CompareDataCheckSingleChannelSingleTestPopup>>()
// 总通道数
const chnSum = computed(() => {
let sum = 0
deviceList.forEach(item => {
sum += item.chnNum
})
return sum
})
// 用来展示的检测结果
const checkResultView: ComputedRef<CheckData.ScriptChnViewItem[]> = computed(() => {
let result: CheckData.ScriptChnViewItem[] = checkResult.map(item => {
let temp: CheckData.ScriptChnViewItem = {
scriptType: item.scriptType,
scriptName: item.scriptName,
devices: []
}
item.devices.forEach(device => {
let tempChnBtnResult: CheckData.ButtonResult[] = []
if (chnSum.value <= MAX_CHN_SUM) {
for (let j = 0; j < device.chnResult.length; j++) {
switch (device.chnResult[j]) {
case CheckData.ChnCheckResultEnum.UNKNOWN:
tempChnBtnResult.push({color: CheckData.ButtonColorEnum.INFO, icon: 'More'})
break
case CheckData.ChnCheckResultEnum.LOADING:
tempChnBtnResult.push({color: CheckData.ButtonColorEnum.LOADING, icon: 'Loading'})
break
case CheckData.ChnCheckResultEnum.SUCCESS:
tempChnBtnResult.push({
color: CheckData.ButtonColorEnum.SUCCESS,
icon: 'CircleCheckFilled'
})
break
case CheckData.ChnCheckResultEnum.FAIL:
tempChnBtnResult.push({color: CheckData.ButtonColorEnum.DANGER, icon: 'Close'})
break
case CheckData.ChnCheckResultEnum.TIMEOUT:
tempChnBtnResult.push({color: CheckData.ButtonColorEnum.WARNING, icon: 'Link'})
break
case CheckData.ChnCheckResultEnum.ERRORDATA:
tempChnBtnResult.push({
color: CheckData.ButtonColorEnum.WARNING,
icon: 'WarnTriangleFilled'
})
break
case CheckData.ChnCheckResultEnum.NOT_PART_IN_ERROR:
tempChnBtnResult.push({color: CheckData.ButtonColorEnum.INFO, icon: 'Minus'})
break
default:
break
}
}
}
temp.devices.push({
deviceId: device.deviceId,
deviceName: device.deviceName,
chnResult: tempChnBtnResult
})
})
return temp
})
return result
})
watch(testStatus, function (newValue, oldValue) {
if (newValue == 'start' || newValue == 'waiting') {
if (!checkStore.selectTestItems.preTest && !checkStore.selectTestItems.channelsTest) {
ElMessage.success('初始化开始!')
emit('update:testStatus', 'test_init')
if (checkStore.selectTestItems.test && checkStore.selectTestItems.preTest) {
testLogList.push({type: 'info', log: `${new Date().toLocaleString()}:初始化开始!`})
}
} else {
emit('update:testStatus', 'process')
}
// 开始计时
startTimeCount()
showTestLog()
//startTimer() // todo 可移除
startData.value = new Date()
timeDifference.value = 0
} else if (newValue == 'error') {
stopTimeCount()
}
})
watch(
webMsgSend,
function (newValue, oldValue) {
switch (newValue.requestId) {
case 'record_wave_step1':
switch (newValue.code) {
case 10200:
setLogList('info', newValue.data)
break
case 25002:
setLogList('error', newValue.data)
break
case 25003:
ElMessageBox.alert('录波对齐失败!', {
confirmButtonText: '确定',
type: 'error'
})
stopTimeCount()
break
case 25001: {
// 当录波校验完成时,更新录波项目的按钮状态
setLogList('info', '录波校验完成!')
// 解析返回的数据
const waveData = JSON.parse(newValue.data)
// 找到录波项目并更新其状态
const waveResultItem = checkResult.find(item => item.scriptType === 'wave_data')
// if (waveResultItem) {
// // 将录波项目状态设置为SUCCESS
// waveResultItem.devices.forEach(device => {
// device.chnResult.fill(CheckData.ChnCheckResultEnum.SUCCESS)
// })
// }
if (waveResultItem) {
// 根据返回的chnResult更新各个设备的通道状态
waveResultItem.devices.forEach(device => {
const deviceData = waveData.find((d: any) => d.deviceId === device.deviceId)
if (deviceData) {
// 根据实际返回的chnResult更新状态
deviceData.chnResult.forEach((result: number, index: number) => {
// 创建数字到枚举的映射
const resultMap: { [key: number]: CheckData.ChnCheckResultEnum } = {
1: CheckData.ChnCheckResultEnum.SUCCESS,
2: CheckData.ChnCheckResultEnum.FAIL,
3: CheckData.ChnCheckResultEnum.TIMEOUT,
4: CheckData.ChnCheckResultEnum.ERRORDATA,
5: CheckData.ChnCheckResultEnum.NOT_PART_IN_ERROR
}
// 使用映射关系设置状态如果没有对应的映射则设为UNKNOWN
device.chnResult[index] = resultMap[result] || CheckData.ChnCheckResultEnum.UNKNOWN
})
}
})
}
// 触发响应式更新
checkResult.splice(0, 0)
stopTimeCount()
updatePercentage()
break
}
}
break
case 'connect':
switch (newValue.operateCode) {
case 'Contrast_Dev':
setLogList('error', '设备服务端连接失败!')
stopTimeCount()
break
}
break
case 'unknown_operate':
break
case 'error_flow_end':
ElMessageBox.alert(`当前流程存在异常结束!`, '检测失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '当前流程存在异常结束!')
stopTimeCount()
break
case 'socket_timeout':
ElMessageBox.alert(`设备连接异常,请检查设备连接情况!`, '检测失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '设备连接异常,请检查设备连接情况!')
stopTimeCount()
break
case 'server_error':
ElMessageBox.alert('服务端主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '服务端主动关闭连接!')
stopTimeCount()
break
case 'device_error':
ElMessageBox.alert('设备主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error'
})
setLogList('error', '设备主动关闭连接!')
stopTimeCount()
break
case 'yjc_xyjy' :
if (newValue.code == 10552) {
ElMessageBox.alert('重复的初始化操作!', '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
setLogList('error', '重复的初始化操作!')
stopTimeCount()
}
break;
case 'yjc_sbtxjy' :
ElMessageBox.alert('重复的初始化操作!', '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
setLogList('error', '重复的初始化操作!')
stopTimeCount()
break;
}
if (checkStore.selectTestItems.preTest == false && newValue.requestId != 'formal_real') {
if (testLogList[0].log == '正在检测,请稍等...' || testLogList[0].log == '暂无数据,等待检测开始') {
testLogList.shift()
setLogList('info', '初始化开始!')
}
let str =
newValue.requestId == 'yjc_sbtxjy'
? '设备通讯校验'
: newValue.requestId == 'yjc_mxyzxjy'
? '模型一致性检验'
: newValue.requestId == 'yjc_align'
? '数据对齐检验'
: newValue.requestId == 'YJC_xujy'
? '相序校验'
: ''
// 预检测处理
switch (newValue.code) {
case 25001:
// 成功
if (newValue.data != undefined) return
setLogList('info', str + '成功!')
if (newValue.requestId == 'YJC_xujy') setLogList('info', '初始化成功!')
break
case 25003:
// 失败
if (newValue.data != undefined) return
setLogList('error', str + '失败!')
emit('update:testStatus', 'error')
stopTimeCount()
if (newValue.requestId == 'YJC_xujy') setLogList('info', '初始化失败!')
break
}
}
// 预监测 正式检测
else if (newValue.requestId == 'formal_real') {
if (testLogList[0].log == '正在检测,请稍等...' || testLogList[0].log == '暂无数据,等待检测开始') {
testLogList.shift()
}
switch (newValue.code) {
case 25001:
case 25006:
case 25005:
case 25007:// 添加闪变处理
{
let result: CheckData.ScriptChnItem[] = []
let message = JSON.parse(newValue.data)
// 当收到 25005/25006 消息时录波项目开始loading
if (newValue.code == 25005 || newValue.code == 25006) {
// 设置录波项目为LOADING状态
const waveResultItem = checkResult.find(item => item.code === 'wave_data')
if (waveResultItem) {
waveResultItem.devices.forEach(device => {
device.chnResult.fill(CheckData.ChnCheckResultEnum.LOADING)
})
}
}
if (newValue.code == 25007) {
// 设置闪变项目为LOADING状态
const flickerResultItem = checkResult.find(item => item.code === 'flicker_data')
if (flickerResultItem) {
flickerResultItem.devices.forEach(device => {
device.chnResult.fill(CheckData.ChnCheckResultEnum.LOADING)
})
}
}
scriptData.forEach(item => {
// 处理当前节点的数据
const temp: CheckData.ScriptChnItem = {
scriptType: item.id,
scriptName: item.scriptName,
devices: []
}
// 特殊处理录波项目 - 如果是25005消息且当前项目是录波项目则使用已设置的状态
if ((newValue.code == 25005 || newValue.code == 25006) && item.code === 'wave_data') {
const existingWaveItem = checkResult.find(checkItem => checkItem.scriptType === 'wave_data')
if (existingWaveItem) {
temp.devices = [...existingWaveItem.devices] // 保留已设置的devices
}
} // 特殊处理闪变项目 - 如果是25007消息且当前项目是闪变项目则使用已设置的状态
else if (newValue.code == 25007 && item.code === 'PST') {
const existingFlickerItem = checkResult.find(checkItem => checkItem.scriptType === 'PST')
if (existingFlickerItem) {
temp.devices = [...existingFlickerItem.devices] // 保留已设置的devices
}
}
else {
// 找到message中所有scriptName与当前item.code匹配的项
const matchedDevices = message
.filter((msg: any) => msg.scriptName === item.code)
.map((msg: any) => ({
deviceId: msg.deviceId,
deviceName: msg.deviceName,
chnResult: msg.chnResult
}))
// 添加匹配到的设备
temp.devices.push(...matchedDevices)
// 对于未匹配到的设备,也要添加占位符(特别是录波项目)
if (item.code === 'wave_data' || item.code === 'PST') {
deviceList.forEach(device => {
const isDeviceExist = matchedDevices.some((matchedDevice: any) => matchedDevice.deviceId === device.deviceId)
if (!isDeviceExist) {
// 对于录波项目或未匹配到的设备,添加默认状态
temp.devices.push({
deviceId: device.deviceId,
deviceName: device.deviceName,
chnResult: new Array(checkStore.chnNumList.length).fill(
item.code === 'wave_data' ?
CheckData.ChnCheckResultEnum.UNKNOWN :
CheckData.ChnCheckResultEnum.UNKNOWN
)
})
}
})
}
}
result.push(temp)
})
Object.assign(checkResult, result)
if (newValue.code == 25001) {
setLogList('info', '检测完成!')
stopTimeCount()
updatePercentage()
}
if(newValue.code == 25005){
setLogList("error", '实时数据校验失败!开始录波校验...')
}
if(newValue.code == 25006){
setLogList("error", '统计数据校验失败!开始录波校验...')
}
break
}
case 25003:
setLogList('error', '检测失败!')
stopTimeCount()
updatePercentage()
break
default:
if (newValue.code != 10201) {
setLogList(newValue.code == 10200 ? 'info' : 'error', newValue.data)
}
break
}
}
},
{deep: true}
)
const setLogList = (state: 'error' | 'info' | 'warning', text: string) => {
testLogList.push({
type: state,
log: `${new Date().toLocaleString()}` + text
})
}
// 更新进度条
const updatePercentage = async () => {
if (testLogList.length - 1 < checkStore.chnNumList.length) {
percentage.value = Math.trunc(((testLogList.length - 1) / checkStore.chnNumList.length) * 100)
} else {
percentage.value = 100
emit('update:testStatus', 'success')
let {data: autoGenerate} = await getAutoGenerate()
if (autoGenerate == 1) {
//调用自动生成报告接口
let devIdList = checkStore.devices.map(item => {
return item.deviceId
})
// await generateDevReport({
// planId: checkStore.plan.id,
// devIdList: devIdList,
// scriptId: checkStore.plan.scriptId,
// planCode: checkStore.plan.code + ''
// })
}
stopTimeCount()
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
{
confirmButtonText: '确定'
}
)
// 关闭WebSocket连接
emit('closeWebSocket')
//clear();
}
}
// ========== 时间计数器管理函数 ==========
// 开始计时
const startTimeCount = () => {
if (!timer) {
timer = setInterval(() => {
timeCount.value = timeCount.value + 1
timeView.value = secondToTime(timeCount.value)
}, 1000)
}
nextTick(() => {
setTimeout(() => {
initCheckResult(CheckData.ChnCheckResultEnum.LOADING)
}, 500)
})
}
// 停止计时
const stopTimeCount = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
// 将秒数转换为 HH:MM:SS 格式
const secondToTime = (second: number) => {
let h: string | number = Math.floor(second / 3600) // 小时
let m: string | number = Math.floor((second - h * 3600) / 60) // 分钟
let s: string | number = Math.floor(second % 60) // 秒
// 补齐前导零
h = h < 10 ? '0' + h : h
m = m < 10 ? '0' + m : m
s = s < 10 ? '0' + s : s
return h + ':' + m + ':' + s
}
onBeforeMount(async () => {
})
const showTestLog = () => {
drawer.value = true
}
// 初始化检测脚本数据
const initScriptData = async () => {
scriptData = []
const pattern = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? ''
let response: any = await getBigTestItem({
reCheckType: checkStore.reCheckType,
planId: checkStore.plan.id,
devIds: checkStore.devices.map(item => item.deviceId),
patternId: pattern
})
// 格式化脚本数据,初始化时排除录波项目
let temp = response.data
.map((item: any) => {
return {
...item,
scriptName: item.scriptName
}
})
// 保存脚本数据并设置总数
scriptData.push(...temp)
checkTotal = scriptData.length
console.log('shul',checkTotal)
}
// 初始化设备列表
const initDeviceList = () => {
Object.assign(deviceList, checkStore.devices)
}
// 初始化检测结果 (详细到通道)
const initCheckResult = (defaultValue: CheckData.ChnCheckResultEnum) => {
let result: CheckData.ScriptChnItem[] = []
scriptData.forEach(item => {
let temp: CheckData.ScriptChnItem = {
scriptType: item.id,
code: item.code,
scriptName: item.scriptName,
devices: []
}
for (let i = 0; i < deviceList?.length; i++) {
let tempChnResult: CheckData.ChnCheckResultEnum[] = []
for (let j = 0; j < checkStore.chnNumList.length; j++) {
// 录波项目初始化为UNKNOWN状态其他项目使用传入的默认值
if ((item.code === 'wave_data' || item.code === 'PST')&& checkTotal > 1) {
tempChnResult.push(CheckData.ChnCheckResultEnum.UNKNOWN)
} else {
tempChnResult.push(defaultValue)
}
}
temp.devices.push({
deviceId: deviceList[i].deviceId,
deviceName: deviceList[i].deviceName,
chnResult: tempChnResult
})
}
result.push(temp)
})
Object.assign(checkResult, result)
}
const scrollToBottom = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollTop = scrollContainerRef.value.scrollHeight + 70
}
}
watch(
testLogList,
() => {
scrollToBottom()
},
{deep: true}
)
// 点击查看设备通道检测详情。参数1设备信息参数2通道号-1代表查看全部通道
const handleClick = (item: any, chnNum: number, scriptType: string) => {
let checkResultItem = checkResult.find(obj => obj.scriptType === scriptType)
let flag = -1
if (checkResultItem) {
let device = checkResultItem.devices.find(obj => obj.deviceId === item.deviceId)
if (device) {
let chnResult = device.chnResult
if (chnNum === -1) {
if (chnResult.findIndex(obj => obj === CheckData.ChnCheckResultEnum.TIMEOUT) !== -1) {
flag = 0
}
if (chnResult.findIndex(obj => obj === CheckData.ChnCheckResultEnum.ERRORDATA) !== -1) {
flag = 1
}
} else {
if (chnResult[chnNum - 1] === CheckData.ChnCheckResultEnum.TIMEOUT) {
flag = 0
}
if (chnResult[chnNum - 1] === CheckData.ChnCheckResultEnum.ERRORDATA) {
flag = 1
}
}
}
}
if (flag === 0) {
ElMessageBox.alert('连接超时,请检查设备通讯是否正常', '连接超时', {
confirmButtonText: '确定',
type: 'warning'
})
}
if (flag === -1 || flag === 1) {
checkStore.setShowDetailType(2)
dataCheckSingleChannelSingleTestPopupRef.value?.open(item, chnNum + '', item.devices[0].deviceId, 1)
}
}
const handlePause = () => {
//emit('sendPause')
testLogList.push({
type: 'error',
log: `${new Date().toLocaleString()}:当前测试小项正在执行中,将在该小项执行结束后暂停...`
})
}
const initializeParameters = async () => {
await initScriptData()
initDeviceList()
initCheckResult(CheckData.ChnCheckResultEnum.UNKNOWN)
percentage.value = 0
timeCount.value = 0
timeView.value = '00:00:00'
testLogList.splice(0, testLogList.length, {
type: 'info',
log: checkStore.selectTestItems.preTest ? '正在检测,请稍等...' : '暂无数据,等待检测开始'
})
}
//
onMounted(() => {
if (!checkStore.selectTestItems.preTest) {
// 判断是否预检测
initializeParameters()
}
})
defineExpose({
initializeParameters,
handlePause,
showTestLog,
startTimeCount
})
</script>
<style scoped lang="scss">
:deep(.el-table .header-row) {
background-color: #f5f7fa;
}
:deep(.el-table .warning-row) {
color: red;
}
.el-table .success-row {
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
.dialog {
display: flex;
flex-direction: column;
overflow-y: hidden;
overflow-x: hidden;
}
.dialog-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-right: 10px;
margin-bottom: 10px;
.timeView {
display: flex;
align-items: center;
color: #91cc75;
width: 28%;
margin-right: 0px;
text-align: left;
font-size: 26px;
font-weight: bold;
}
}
.dialog-content {
max-height: 450px;
overflow-y: hidden;
}
:deep(.el-collapse-item__header) {
height: 30px;
}
.dialog-log {
height: 50px;
overflow-y: hidden;
p {
margin: 5px 0;
font-size: 14px;
}
}
.drawer-container {
:deep(header.el-drawer__header) {
color: #fff !important;
background-color: var(--el-color-primary) !important;
.el-drawer__close-btn svg:hover {
color: #ccc !important;
}
.el-drawer__title {
color: #fff !important;
}
}
}
.loading-box {
animation: loading 1.5s linear infinite;
}
@keyframes loading {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
<style lang="scss" scoped>
:deep(.el-button--small) {
height: 20px !important;
width: 20px !important;
}
:deep(.el-table--default td) {
padding: 5px 0 !important;
}
</style>

View File

@@ -1,686 +0,0 @@
<template>
<el-dialog
:title="dialogTitle"
width="1550px"
:model-value="dialogVisible"
:before-close="beforeClose"
@close="handleClose"
height="1000px"
draggable
>
<div class="steps-container">
<el-steps
v-if="showSteps"
class="test-head-steps"
simple
:active="stepsActiveIndex"
process-status="finish"
finish-status="success"
>
<el-step
title="通道配对"
:icon="stepsActive > 0 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Switch"
@click="handleStepClick(0)"
/>
<el-step
v-if="preTestSelected"
title="预检测"
:icon="stepsActive > 1 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Edit"
@click="handleStepClick(1)"
/>
<el-step
v-if="testSelected"
title="正式检测"
:icon="stepsActive > 2 || stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Coin"
@click="handleStepClick(2)"
/>
<el-step title="检测完成" :icon="stepsActiveIndex > stepsTotalNum - 1 ? SuccessFilled : Key" />
</el-steps>
</div>
<keep-alive>
<ChannelPairing
v-if="stepsActiveView == 0"
ref="channelPairingRef"
:devIdList="prop.devIdList"
:pqStandardDevList="prop.pqStandardDevList"
:planIdKey="prop.planIdKey"
:deviceMonitor="deviceMonitor2"
/>
</keep-alive>
<keep-alive>
<ComparePreTest
v-if="preTestSelected && stepsActiveView == 1"
ref="preTestRef"
v-model:testStatus="preTestStatus"
:webMsgSend="webMsgSend"
:mapping="channelMapping"
:onlyWave="onlyWave"
/>
</keep-alive>
<keep-alive>
<CompareTest
v-if="testSelected && stepsActiveView == 2"
ref="testRef"
:webMsgSend="webMsgSend"
v-model:testStatus="TestStatus"
:stepsActive="stepsActive"
/>
</keep-alive>
<template #footer>
<div>
<el-button
type="primary"
:icon="VideoPlay"
v-if="ActiveStatue === 'waiting' && !(testSelected && stepsActiveIndex == 2)"
@click="handleSubmitFast"
>
开始检测
</el-button>
<!-- <el-button type="primary" v-if="TestStatus === 'test_init'" disabled>
<el-icon class="loading-box" style="color: #fff; margin-right: 8px">
<component :is="Refresh" />
</el-icon>
初始化中
</el-button> -->
<el-button type="primary" v-if="TestStatus == 'process'" :icon="VideoPause" @click="handlePause()">
停止检测
</el-button>
<el-button type="warning" v-if="TestStatus === 'paused_ing'" disabled>
<el-icon class="loading-box" style="color: #fff; margin-right: 8px">
<component :is="Refresh" />
</el-icon>
暂停中
</el-button>
<el-button type="warning" v-if="TestStatus == 'paused'" :icon="VideoPlay" @click="sendResume">
继续检测
</el-button>
<el-button
type="warning"
:icon="VideoPlay"
v-if="
nextStepText !== '下一步' &&
(ActiveStatue === 'success' ||
ActiveStatue === 'error' ||
ActiveStatue === 'test_init_fail' ||
ActiveStatue === 'connect_timeout' ||
ActiveStatue === 'pause_timeout' ||
ActiveStatue === 'waiting')
"
@click="handleSubmitAgain"
>
重新检测
</el-button>
<el-button
:type="ActiveStatue === 'success' ? 'primary' : 'danger'"
:icon="Right"
v-if="
nextStepText !== '下一步' &&
(ActiveStatue === 'success' ||
ActiveStatue === 'error' ||
ActiveStatue === 'test_init_fail' ||
ActiveStatue === 'connect_timeout' ||
ActiveStatue === 'pause_timeout')
"
@click="nextStep"
>
{{ nextStepText }}
</el-button>
<el-button type="primary" @click="handleQuit" v-else>退出检测</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="tsx" setup name="testPopup">
import { nextTick, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Coin, Edit, Key, Refresh, Right, SuccessFilled, Switch, VideoPause, VideoPlay } from '@element-plus/icons-vue'
import ComparePreTest from './comparePreTest.vue'
import ChannelPairing from './channelPairing.vue'
import CompareTest from './compareTest.vue'
import socketClient from '@/utils/webSocketClient'
import { useCheckStore } from '@/stores/modules/check'
import { contrastTest, pauseTest, resumeTest, startPreTest } from '@/api/socket/socket'
import { useUserStore } from '@/stores/modules/user'
import { JwtUtil } from '@/utils/jwtUtil'
const userStore = useUserStore()
const checkStore = useCheckStore()
const nextStepText = ref('下一步')
const dialogVisible = ref(false)
const channelPairingRef = ref()
const showSteps = ref(false)
const stepsTotalNum = ref(-1) //步骤总数
const stepsActiveIndex = ref(0) //当前正在执行的步骤索引
const stepsActiveView = ref(1) //当前正在执行的步骤在(预处理、守时校验、系数校准、正式检测)中的排序,仅用于页面显示
const stepsActive = ref(-1) //当前正在执行的步骤在(预处理、守时校验、系数校准、正式检测)中的排序,实际记录步骤的状态,用于切换步骤
const ActiveStatue = ref('waiting') //当前步骤状态
const preTestStatus = ref('waiting') //预检测执行状态
const TestStatus = ref('waiting') //正式检测执行状态
const webMsgSend = ref() //webSocket推送的数据
const dialogTitle = ref('')
const showComponent = ref(true)
const preTestRef = ref<InstanceType<typeof ComparePreTest> | null>(null)
const testRef: any = ref(null)
const prop = defineProps({
devIdList: {
type: Array as any,
default: []
},
pqStandardDevList: {
type: Array as any,
default: []
},
planIdKey: {
type: String,
default: ''
}
})
const dataSocket = reactive({
socketServe: socketClient.Instance
})
// 勾选的检测内容
const preTestSelected = ref(true)
const testSelected = ref(false)
const initOperate = () => {
ActiveStatue.value = 'waiting'
preTestStatus.value = 'waiting'
TestStatus.value = 'waiting'
stepsActiveIndex.value = 0
showComponent.value = true
// 初始化勾选的检测内容
preTestSelected.value = checkStore.selectTestItems.preTest
testSelected.value = checkStore.selectTestItems.test
let count = 0
for (let key in checkStore.selectTestItems) {
if (checkStore.selectTestItems[key]) {
count++
}
}
stepsTotalNum.value = count + 1
if (preTestSelected.value) {
stepsActiveView.value = 0
stepsActive.value = 0
return
}
if (testSelected.value) {
stepsActiveView.value = 0
stepsActive.value = 0
return
}
}
const channelMapping = ref<Record<string, Record<string, string>>>({})
const planId = ref('')
const loginName = ref('')
const devIds = ref<[]>()
const standardDevIds = ref<[]>()
const pairs = ref<any>()
const testAgain = ref(false) //重新检测按钮是否显示
const checkNumber = ref(0) //检测次数
const deviceMonitor2 = ref<Map<string, any[]>>()
const onlyWave = ref(false) //计划数据源是否只有录波
const open = async (
title: string,
mapping: any,
plan: string,
login: string,
devIdsArray: [],
standardDevIdsArray: [],
pair: any,
deviceMonitor: Map<string, any[]>,
planIsOnlyWave: boolean
) => {
if (checkStore.selectTestItems.preTest && !checkStore.selectTestItems.test) {
testAgain.value = true
}
deviceMonitor2.value = deviceMonitor
onlyWave.value = planIsOnlyWave
checkStore.setNodesConnectable(true)
dialogTitle.value = title
channelMapping.value = mapping
planId.value = plan
loginName.value = login
devIds.value = devIdsArray
standardDevIds.value = standardDevIdsArray
pairs.value = pair
showSteps.value = true
initOperate()
dialogTitle.value = title
dialogVisible.value = true
// 等待组件渲染完成
await nextTick()
if (preTestRef.value) {
preTestRef.value.initializeParameters()
}
//开始创建webSocket客户端
socketClient.Instance.connect()
dataSocket.socketServe = socketClient.Instance
dataSocket.socketServe.registerCallBack('aaa', (res: { code: number }) => {
// console.log('Received message:', res)
// 处理来自服务器的消息
if (res.code === 20000) {
//ElMessage.error(message.message)
// loading.close()
} else {
webMsgSend.value = res
}
})
}
//预检测-重新检测
const handleSubmitAgain = async () => {
if (checkStore.selectTestItems.preTest) {
stepsActiveIndex.value = 1
stepsActiveView.value = 1
stepsActive.value = 1
} else {
stepsActiveIndex.value = 1
stepsActiveView.value = 2
stepsActive.value = 2
}
let count = 0
for (let key in checkStore.selectTestItems) {
if (checkStore.selectTestItems[key]) {
count++
}
}
stepsTotalNum.value = count + 1
// 通知子组件清空并重新初始化
if (preTestRef.value) {
preTestRef.value.initializeParameters()
}
testRef.value && testRef.value.initializeParameters()
// 重置状态
nextStepText.value = '下一步'
ActiveStatue.value = 'waiting'
preTestStatus.value = 'waiting'
TestStatus.value = 'waiting'
await contrastTest({
planId: planId.value,
loginName: loginName.value,
devIds: devIds.value,
standardDevIds: standardDevIds.value,
pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id
})
preTestStatus.value = 'start'
}
//开始检测
const handleSubmitFast = async () => {
if (channelPairingRef.value) {
const res = await channelPairingRef.value.handleNext()
if (!res) return
dialogTitle.value = res.title
channelMapping.value = res.mapping
planId.value = res.plan
loginName.value = res.login
devIds.value = res.devIdsArray
standardDevIds.value = res.standardDevIdsArray
pairs.value = res.pair
checkStore.setNodesConnectable(false)
// nodesConnectable.value = false
}
if (!dataSocket.socketServe.connected) {
ElMessage.error('webSocket连接中断')
return
}
if (checkStore.selectTestItems.preTest) {
stepsActiveIndex.value = 1
stepsActiveView.value = 1
stepsActive.value = 1
} else {
stepsActiveIndex.value = 1
stepsActiveView.value = 2
stepsActive.value = 2
}
switch (stepsActive.value) {
case 1:
if (preTestStatus.value == 'waiting') {
if (checkStore.selectTestItems.preTest) {
await contrastTest({
planId: planId.value,
loginName: loginName.value,
devIds: devIds.value,
standardDevIds: standardDevIds.value,
pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id
})
preTestStatus.value = 'start'
if (checkStore.selectTestItems.test) {
console.log(111111)
testRef.value.initializeParameters()
testRef.value.showTestLog()
}
}
}
break
case 2:
if (TestStatus.value == 'waiting') {
// 如果没有预检测,直接进行正式检测需要先初始化
if (!checkStore.selectTestItems.preTest && checkStore.selectTestItems.test) {
await contrastTest({
planId: planId.value,
loginName: loginName.value,
devIds: devIds.value,
standardDevIds: standardDevIds.value,
pairs: pairs.value,
testItemList: [checkStore.selectTestItems.preTest, false, checkStore.selectTestItems.test],
userId: userStore.userInfo.id
})
}
TestStatus.value = 'start'
} else if (TestStatus.value == 'paused') {
// 发送继续指令
//sendResume()
}
break
default:
break
}
}
const emit = defineEmits<{
(e: 'quitClicked'): void
}>()
watch(preTestStatus, function (newValue, oldValue) {
console.log('预检测状态', newValue, oldValue)
ActiveStatue.value = newValue
})
watch(TestStatus, function (newValue, oldValue) {
console.log('正式检测状态', newValue, oldValue)
ActiveStatue.value = newValue
})
watch(stepsActiveIndex, function (newValue, oldValue) {
if (checkStore.selectTestItems.test && checkStore.selectTestItems.preTest && newValue == 2) {
setTimeout(() => {
testRef.value.initializeParameters()
testRef.value.showTestLog()
testRef.value.startTimeCount()
}, 500)
}
console.log('步骤索引', newValue, oldValue)
})
watch(ActiveStatue, function (newValue, oldValue) {
console.log('当前步骤状态-----', newValue)
console.log('stepsActiveIndex-----', stepsActiveIndex.value)
console.log('stepsTotalNum----', stepsTotalNum.value)
if (newValue === 'error') {
stepsActiveIndex.value = stepsTotalNum.value + 1
nextStepText.value = '检测失败'
}
if (newValue === 'success' && stepsActiveIndex.value === stepsTotalNum.value - 1) {
stepsActiveIndex.value += 2
nextStepText.value = '检测完成'
}
if (newValue === 'test_init_fail') {
stepsActiveIndex.value += 2
nextStepText.value = '初始化失败'
}
if (newValue === 'connect_timeout') {
stepsActiveIndex.value += 2
nextStepText.value = '连接超时'
}
if (newValue === 'pause_timeout') {
stepsActiveIndex.value += 2
// nextStepText.value = '结束测试'
nextStepText.value = '暂停超时'
}
if (newValue === 'success' && stepsActiveIndex.value < stepsTotalNum.value - 1) {
nextStep() // 实现自动点击,进入下一个测试内容
//handleSubmitFast()
}
})
const handleQuit = () => {
console.log('handleQuit', ActiveStatue.value)
if (
ActiveStatue.value !== 'success' &&
ActiveStatue.value !== 'waiting' &&
ActiveStatue.value !== 'paused' &&
ActiveStatue.value !== 'test_init_fail' &&
ActiveStatue.value !== 'connect_timeout' &&
ActiveStatue.value !== 'pause_timeout'
) {
beforeClose(() => {})
} else {
handleClose()
}
}
const handlePause = () => {
sendPause()
testRef.value?.handlePause()
}
const sendPause = () => {
console.log('发起暂停请求')
TestStatus.value = 'paused_ing'
pauseTest()
}
const sendResume = () => {
console.log('发起继续检测请求')
resumeTest({
userPageId: JwtUtil.getLoginName(),
devIds: checkStore.devices.map(item => item.deviceId),
planId: checkStore.plan.id,
reCheckType: '2', // 0:'系数校验''1'为预检测、2为正式检测、'8'为不合格项复检
userId: userStore.userInfo.id,
temperature: checkStore.temperature,
humidity: checkStore.humidity
})
Object.assign(webMsgSend.value, {
requestId: 'Resume_Success'
})
}
const sendReCheck = () => {
console.log('发送重新检测指令')
startPreTest({
userPageId: JwtUtil.getLoginName(),
devIds: checkStore.devices.map(item => item.deviceId),
planId: checkStore.plan.id,
reCheckType: '2', // 0:'系数校验''1'为预检测、2为正式检测、'8'为不合格项复检
userId: userStore.userInfo.id,
temperature: checkStore.temperature,
humidity: checkStore.humidity,
testItemList: [
checkStore.selectTestItems.preTest,
checkStore.selectTestItems.channelsTest,
checkStore.selectTestItems.test
]
}).then(res => {
console.log(res)
if (res.code === 'A001014') {
ElMessageBox.alert('装置配置异常', '初始化失败', {
confirmButtonText: '确定',
type: 'error'
})
TestStatus.value = 'test_init_fail'
}
})
TestStatus.value = 'start'
}
const closeWebSocket = () => {
dataSocket.socketServe.closeWs()
}
const nextStep = () => {
if (
stepsActiveIndex.value == stepsTotalNum.value + 1 ||
ActiveStatue.value === 'error' ||
ActiveStatue.value === 'test_init_fail' ||
ActiveStatue.value === 'connect_timeout' ||
ActiveStatue.value === 'pause_timeout'
) {
handleClose()
return
}
if (ActiveStatue.value != 'error') {
ActiveStatue.value = 'waiting'
let tempStep = stepsActiveIndex.value
let idx = -1
stepsActiveIndex.value++
for (let selectTestItemsKey in checkStore.selectTestItems) {
if (tempStep == 0 && checkStore.selectTestItems[selectTestItemsKey]) {
console.log('selectTestItemsKey1')
stepsActiveView.value = idx
stepsActive.value = idx
return
}
if (checkStore.selectTestItems[selectTestItemsKey] && tempStep != 0) {
console.log('selectTestItemsKey2')
tempStep--
}
console.log('selectTestItemsKey3', idx)
idx++
}
}
}
const handleStepClick = (step: number) => {
if (step > stepsActive.value) {
return
} else {
stepsActiveView.value = step
}
}
function clearData() {
stepsTotalNum.value = -1
stepsActiveIndex.value = 1
stepsActiveView.value = -1
preTestStatus.value = 'waiting'
TestStatus.value = 'waiting'
ActiveStatue.value = 'waiting'
nextStepText.value = '下一步'
}
const beforeClose = (done: () => void) => {
if (stepsActiveIndex.value < stepsTotalNum.value && ActiveStatue.value != 'error') {
ElMessageBox.confirm('检测未完成,是否退出当前检测流程?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
handleClose()
})
} else {
handleClose()
}
}
const handleClose = () => {
showSteps.value = false
dataSocket.socketServe.closeWs()
dialogVisible.value = false
clearData()
showComponent.value = false
emit('quitClicked') // 触发事件
}
// 对外映射
defineExpose({ open })
</script>
<style scoped lang="scss">
.test-head-steps {
:deep(.el-step) {
.el-step__head.is-success {
color: #91cc75;
}
.el-step__title.is-success {
color: #91cc75;
}
}
}
:deep(.dialog-big .el-dialog__body) {
max-height: 840px !important;
}
.steps-container :deep(.test-head-steps) {
height: 80px;
margin-bottom: 10px;
}
.steps-container :deep(.el-step__title) {
font-size: 20px !important;
/* 设置标题字体大小 */
vertical-align: baseline !important;
display: inline-block;
/* 确保文字和图标在同一行 */
line-height: 1;
/* 调整行高以确保底部对齐 */
}
.steps-container :deep(.el-step__icon-inner) {
font-size: 18px !important;
vertical-align: baseline !important;
display: inline-block;
/* 确保文字和图标在同一行 */
line-height: 1;
/* 调整行高以确保底部对齐 */
}
.steps-container :deep(.el-step__icon) {
font-size: 18px !important;
vertical-align: baseline !important;
display: inline-block;
/* 确保文字和图标在同一行 */
line-height: 1;
/* 调整行高以确保底部对齐 */
}
.loading-box {
animation: loading 1.5s linear infinite;
}
@keyframes loading {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,144 +1,138 @@
<template>
<div class="table-main">
<el-table
v-if="tableData.length > 0"
:data="tableData"
height="357px"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
>
<!-- <el-table-column prop="chnNum" label="通道号" width="80">-->
<!-- <template #default="{row}">-->
<!-- {{ '通道' + row.chnNum }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="A相" v-if="phaseA === 1">
<el-table-column prop="stdA" :label="'标准值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="dataA" :label="'被检值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="isDataA" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ scope.row.maxErrorA }}
<br />
误差值{{ scope.row.errorA }} {{ scope.row.errorA !== '/' ? innerUnitA : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataA === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataA === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataA === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="B相" v-if="phaseB === 1">
<el-table-column prop="stdB" :label="'标准值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="dataB" :label="'被检值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="isDataB" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ scope.row.maxErrorB }}
<br />
误差值{{ scope.row.errorB }} {{ scope.row.errorB !== '/' ? innerUnitB : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataB === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataB === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataB === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="C相" v-if="phaseC === 1">
<el-table-column prop="stdC" :label="'标准值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="dataC" :label="'被检值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="isDataC" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围 {{ scope.row.maxErrorC }}
<br />
误差值{{ scope.row.errorC }} {{ scope.row.errorC !== '/' ? innerUnitC : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataC === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataC === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataC === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="tableHeader" v-if="phaseT === 1">
<el-table-column prop="stdT" :label="'标准值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="dataT" :label="'被检值' + (outerUnit == '' ? '' : '' + outerUnit + '')" />
<el-table-column prop="isDataT" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围 {{ scope.row.maxErrorT }}
<br />
误差值{{ scope.row.errorT }} {{ scope.row.errorT !== '/' ? innerUnitT : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataT === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataT === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataT === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
<div class="table-main">
<el-table v-if="tableData.length > 0" :data="tableData" height="357px" :header-cell-style="{ textAlign: 'center' } "
:cell-style="{ textAlign: 'center' }">
<!-- <el-table-column prop="chnNum" label="通道号" width="80">-->
<!-- <template #default="{row}">-->
<!-- {{ '通道' + row.chnNum }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="A相" v-if="phaseA === 1">
<el-table-column prop="stdA" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="dataA" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="isDataA" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ scope.row.maxErrorA }} <br/>
误差值{{ scope.row.errorA }} {{ scope.row.errorA !== '/' ? innerUnitA : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataA === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataA === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataA === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="B相" v-if="phaseB === 1">
<el-table-column prop="stdB" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="dataB" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="isDataB" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围{{ scope.row.maxErrorB }}<br/>
误差值{{ scope.row.errorB }} {{ scope.row.errorB !== '/' ? innerUnitB : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataB === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataB === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataB === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="C相" v-if="phaseC === 1">
<el-table-column prop="stdC" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="dataC" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="isDataC" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围 {{ scope.row.maxErrorC }}<br/>
误差值{{ scope.row.errorC }} {{ scope.row.errorC !== '/' ? innerUnitC : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataC === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataC === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataC === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="tableHeader" v-if="phaseT === 1">
<el-table-column prop="stdT" :label="'标准值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="dataT" :label="'被检值'+(outerUnit==''?'':''+outerUnit+'')"/>
<el-table-column prop="isDataT" label="检测结果">
<template #default="scope">
<el-tooltip effect="dark" placement="bottom">
<template #content>
误差范围 {{ scope.row.maxErrorT }}<br/>
误差值{{ scope.row.errorT }} {{ scope.row.errorT !== '/' ? innerUnitT : '' }}
</template>
<el-tag type="success" v-if="scope.row.isDataT === 1">符合</el-tag>
<el-tag type="danger" v-if="scope.row.isDataT === 2">不符合</el-tag>
<el-tag type="warning" v-if="scope.row.isDataT === 4">/</el-tag>
<el-tag type="info" v-if="scope.row.isDataA === 5">-</el-tag>
</el-tooltip>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script lang="tsx" setup>
import { computed } from 'vue'
import { CheckData } from '@/api/check/interface'
import {defineProps} from 'vue';
import {CheckData} from "@/api/check/interface";
const {tableData, currentScriptTypeName} = defineProps<{
tableData: CheckData.CheckResult[],
currentScriptTypeName: string
}>();
const { tableData, currentScriptTypeName } = defineProps<{
tableData: CheckData.CheckResult[]
currentScriptTypeName: string
}>()
const outerUnit = computed(() => {
return tableData.length > 0 ? tableData[0].unit : ''
return tableData.length > 0 ? tableData[0].unit : '';
})
const innerUnitA = computed(() => {
return tableData.length > 0 ? tableData[0].unitA : ''
return tableData.length > 0 ? tableData[0].unitA : '';
})
const innerUnitB = computed(() => {
return tableData.length > 0 ? tableData[0].unitB : ''
return tableData.length > 0 ? tableData[0].unitB : '';
})
const innerUnitC = computed(() => {
return tableData.length > 0 ? tableData[0].unitC : ''
return tableData.length > 0 ? tableData[0].unitC : '';
})
const innerUnitT = computed(() => {
return tableData.length > 0 ? tableData[0].unitT : ''
return tableData.length > 0 ? tableData[0].unitT : '';
})
const phaseA = computed(() => {
return tableData.length <= 0 || tableData[0].dataA == null || tableData[0].dataA == '/' ? 0 : 1
return tableData.length <= 0 || tableData[0].dataA == null || tableData[0].dataA == "/" ? 0 : 1
})
const phaseB = computed(() => {
return tableData.length <= 0 || tableData[0].dataB == null || tableData[0].dataB == '/' ? 0 : 1
return tableData.length <= 0 || tableData[0].dataB == null || tableData[0].dataB == "/" ? 0 : 1
})
const phaseC = computed(() => {
return tableData.length <= 0 || tableData[0].dataC == null || tableData[0].dataC == '/' ? 0 : 1
return tableData.length <= 0 || tableData[0].dataC == null || tableData[0].dataC == "/" ? 0 : 1
})
const phaseT = computed(() => {
return tableData.length <= 0 || tableData[0].dataT == null || tableData[0].dataT == '/' ? 0 : 1
return tableData.length <= 0 || tableData[0].dataT == null || tableData[0].dataT == "/" ? 0 : 1
})
const tableHeader = computed(() => {
if (phaseT.value === 1) {
let index = currentScriptTypeName.indexOf('=')
return currentScriptTypeName.substring(0, index)
}
return currentScriptTypeName
if (phaseT.value === 1) {
let index = currentScriptTypeName.indexOf('=');
return currentScriptTypeName.substring(0, index);
}
return currentScriptTypeName
})
// const maxErrorStr = computed((data) => {
@@ -149,36 +143,38 @@ const tableHeader = computed(() => {
// }
// return result;
// })
</script>
<style scoped>
.form-grid {
display: flex;
flex-direction: row; /* 横向排列 */
flex-wrap: wrap; /* 允许换行 */
display: flex;
flex-direction: row; /* 横向排列 */
flex-wrap: wrap; /* 允许换行 */
}
.form-grid .el-form-item {
flex: 1 1 30%; /* 控件宽度 */
margin-right: 20px; /* 控件间距 */
flex: 1 1 30%; /* 控件宽度 */
margin-right: 20px; /* 控件间距 */
}
.form-grid .el-form-item:last-child {
margin-right: 0; /* 最后一个控件不需要右边距 */
margin-right: 0; /* 最后一个控件不需要右边距 */
}
.dialog-footer {
display: flex;
justify-content: flex-start;
margin-bottom: 10px; /* 调整这里的值以增加或减少间距 */
display: flex;
justify-content: flex-start;
margin-bottom: 10px; /* 调整这里的值以增加或减少间距 */
}
.el-tabs {
margin-bottom: 20px; /* 添加底部边距 */
margin-bottom: 20px; /* 添加底部边距 */
}
.el-table th,
.el-table td {
text-align: center; /* 所有单元格文字居中 */
.el-table th, .el-table td {
text-align: center; /* 所有单元格文字居中 */
}
</style>

View File

@@ -1,516 +0,0 @@
<template>
<!-- <el-dialog title="设备通道配对" v-model="dialogVisible" v-bind="dialogBig">
<div
class="flow-container"
style="overflow: hidden; position: relative"
:style="{ height: dialogHeight + 'px' }"
>
<VueFlow
:nodes="nodes"
:edges="edges"
:connection-radius="30"
:nodes-draggable="false"
:dragging="false"
:zoom-on-scroll="false"
:pan-on-drag="false"
:disable-zoom-pan-on-connect="true"
:prevent-scrolling="true"
:fit-view="true"
:min-zoom="1"
:max-zoom="1"
:elements-selectable="false"
auto-connect
@connect="handleConnect"
@connect-start="handleConnectStart"
@connect-end="handleConnectEnd"
@pane-ready="onPaneReady"
v-on:pane-mouse-move="false"
></VueFlow>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleNext">下一步</el-button>
</div>
</template>
</el-dialog> -->
<!-- devIdList.value
pqStandardDevList.value
planIdKey.value -->
<!-- 手动检测-勾选检测项弹窗 -->
<SelectTestItemPopup ref="selectTestItemPopupRef" @openTestDialog="openTestDialog"></SelectTestItemPopup>
<CompareTestPopup
ref="testPopup"
:key="compareTestKey"
v-if="CompareTestVisible"
:devIdList="devIdList"
:pqStandardDevList="pqStandardDevList"
:planIdKey="planIdKey"
></CompareTestPopup>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { dialogBig } from '@/utils/elementBind'
import { Platform, Flag } from '@element-plus/icons-vue'
import { Device } from '@/api/device/interface/device'
import { StandardDevice } from '@/api/device/interface/standardDevice'
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
import CompareTestPopup from './compareTestPopup.vue'
import { ElMessage } from 'element-plus'
import CustomEdge from './RemoveableEdge.vue' // 导入自定义连接线组件
import { jwtUtil } from '@/utils/jwtUtil'
import { useCheckStore } from '@/stores/modules/check'
import { Plan } from '@/api/plan/interface'
import { fa } from 'element-plus/es/locale'
const checkStore = useCheckStore()
const dialogVisible = ref(false)
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
const testPopup = ref()
const dialogTitle = ref('手动检测')
const compareTestKey = ref(0)
// 计算对话框高度
const dialogHeight = ref(600)
const CompareTestVisible = ref(false)
// 初始化 VueFlow注册自定义连线类型
const { edges, setViewport } = useVueFlow({
edgeTypes: {
default: CustomEdge
}
})
// 初始化时锁定画布位置
const onPaneReady = () => {
setViewport({ x: 0, y: 0, zoom: 1 })
}
// 提取公共的label渲染函数
const createLabel = (text: string, type: string) => {
return h(
'div',
{
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
fontSize: '15px',
textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
h(Platform, {
style: {
width: '20px',
marginBottom: '4px',
color: '#526ade'
}
}),
h('div', null, '设备名称:' + text),
h('div', null, '设备类型:' + type)
]
) as any
}
const createLabel3 = (text: string) => {
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
fontSize: '15px',
textAlign: 'center',
border: '1px solid #ccc',
borderRadius: '8px',
padding: '8px',
backgroundColor: '#f9f9f9'
}
},
[
h(Flag, {
style: {
width: '20px',
marginRight: '4px',
color: '#526ade'
}
}),
text
]
) as any
}
const handleConnectStart = (params: any) => {
onPaneReady()
}
const handleConnectEnd = (params: any) => {
onPaneReady()
}
const handleConnect = (params: any) => {
console.log('连接信息:', params)
const sourceNode = nodes.value.find(node => node.id === params.source)
const targetNode = nodes.value.find(node => node.id === params.target)
// 连接规则验证
const isValidConnection = sourceNode?.type === 'input' && targetNode?.type === 'output'
if (!isValidConnection) {
removeEdge(params)
ElMessage.warning('只能从被检通道连接到标准通道')
return
}
// 过滤掉当前连接,检查是否还有重复的
const existingEdges = edges.value.filter(edge => edge.source === params.source || edge.target === params.target)
// 如果同源或同目标的连接超过1个说明有重复
if (existingEdges.length > 1) {
const duplicateSource = existingEdges.filter(edge => edge.source === params.source).length > 1
const duplicateTarget = existingEdges.filter(edge => edge.target === params.target).length > 1
if (duplicateSource) {
removeEdge(params)
ElMessage.warning('该被检通道已经连接,不能重复连接')
return
}
if (duplicateTarget) {
removeEdge(params)
ElMessage.warning('该标准通道已经连接,不能重复连接')
return
}
}
}
// 删除不合法连接
const removeEdge = (params: any) => {
const edgeIndex = edges.value.findIndex(edge => edge.source === params.source && edge.target === params.target)
if (edgeIndex !== -1) {
edges.value.splice(edgeIndex, 1)
}
}
const nodes = ref([])
const planId = ref('')
const devIds = ref<string[]>()
const standardDevIds = ref<string[]>()
const devIdList = ref<Device.ResPqDev[]>([])
const pqStandardDevList = ref<StandardDevice.ResPqStandardDevice[]>([])
const planIdKey = ref<string>('')
const deviceMonitor = ref<Map<string, any[]>>();
const planIsOnlyWave = ref(false)
const open = async (
device: Device.ResPqDev[],
standardDev: StandardDevice.ResPqStandardDevice[],
fatherPlanId: string,
DeviceMonitoringMap: Map<string, any[]>,
checkType: string,
isOnlyWave:boolean
) => {
planIsOnlyWave.value = isOnlyWave
CompareTestVisible.value = false
devIdList.value = device
pqStandardDevList.value = standardDev
planIdKey.value = fatherPlanId
deviceMonitor.value = DeviceMonitoringMap
if(checkType == "一键检测"){
openTestDialog()
}else{
selectTestItemPopupRef.value?.open()
}
// edges.value = []
// devIds.value = device.map(d => d.id)
// standardDevIds.value = standardDev.map(d => d.id)
// planId.value = fatherPlanId
// nodes.value = createNodes(device, standardDev)
// edges.value = []
// devIds.value = device.map(d => d.id)
// standardDevIds.value = standardDev.map(d => d.id)
// planId.value = fatherPlanId
// nodes.value = createNodes(device, standardDev)
// dialogVisible.value = true
// onPaneReady()
}
const handleNext = async () => {
if (edges.value.length === 0) {
ElMessage.warning('请先完成通道配对')
return
}
// const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_');
let chnNumList: string[] = []
await edges.value.forEach(edge => {
const match = edge.source.split('-')
if (match) {
chnNumList.push(match[2])
}
})
await checkStore.setChnNum(chnNumList)
CompareTestVisible.value = false
dialogVisible.value = false
selectTestItemPopupRef.value?.open()
}
const openTestDialog = async () => {
compareTestKey.value++ // 每次调用时更新key
CompareTestVisible.value = true
// 转换连接信息只保留设备ID和通道号
const connections = edges.value.reduce(
(map, edge) => {
// 从source中提取设备ID和通道号: 被检通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const sourceKey = edge.source.replace('被检通道-', '').replace('-', '_')
// 从target中提取设备ID和通道号: 标准通道-{deviceId}-{channelNum} => {deviceId}-{channelNum}
const targetValue = edge.target.replace('标准通道-', '').replace('-', '_')
map[sourceKey] = targetValue
return map
},
{} as Record<string, string>
)
generateChannelMapping()
setTimeout(() => {
testPopup.value?.open(
dialogTitle.value,
channelMapping.value,
planId.value,
jwtUtil.getLoginName(),
devIds.value,
standardDevIds.value,
connections,
deviceMonitor.value ,
planIsOnlyWave.value
)
}, 100)
}
// 转换 edges.value 为 channelMapping 格式
const channelMapping = ref<Record<string, Record<string, string>>>({})
// 生成映射关系的方法
const generateChannelMapping = () => {
const mapping: Record<string, Record<string, string>> = {}
edges.value.forEach(edge => {
// 解析 source 节点信息(被检通道)
const sourceParts = edge.source.split('-')
const sourceDeviceId = sourceParts[1]
const sourceChannel = sourceParts[2]
// 解析 target 节点信息(标准通道)
const targetParts = edge.target.split('-')
const targetDeviceId = targetParts[1]
const targetChannel = targetParts[2]
// 查找对应的节点以获取显示名称
const sourceDeviceNode = nodes.value.find(node => node.id === sourceDeviceId)
const targetDeviceNode = nodes.value.find(node => node.id === targetDeviceId)
if (sourceDeviceNode && targetDeviceNode) {
// 提取设备显示文本
const sourceDeviceText = sourceDeviceNode.data.label.children[1].children
const targetDeviceText = targetDeviceNode.data.label.children[1].children
// 构造键名 - 现在以标准设备为键
const targetKey = `${targetDeviceText}`.replace('设备名称:', '')
const sourceValue = `${sourceDeviceText}通道${sourceChannel}`.replace('设备名称:', '')
// 初始化对象
if (!mapping[targetKey]) {
mapping[targetKey] = {}
}
// 添加映射关系 - 标准设备通道 -> 被检设备信息
mapping[targetKey][`通道${targetChannel}`] = sourceValue
}
})
channelMapping.value = mapping
}
const createNodes = (device: Device.ResPqDev[], standardDev: StandardDevice.ResPqStandardDevice[]) => {
const channelCounts: Record<string, number> = {}
device.forEach(device => {
channelCounts[device.id] = device.devChns || 0
})
const inspectionDevices = device.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const channelCounts2: Record<string, number> = {}
standardDev.forEach(dev => {
const channelList = dev.inspectChannel ? dev.inspectChannel.split(',') : []
channelCounts2[dev.id] = channelList.length
})
const standardDevices = standardDev.map(d => ({
id: d.id,
name: d.name,
type: 'normal',
deviceType: d.devType
}))
const newNodes: any[] = []
const deviceChannelGroups: { deviceId: string; centerY: number }[] = []
const standardChannelGroups: any[] = []
const deviceWidth = 0
const inputChannelX = 200
const outputChannelX = 800
const standardWidth = 950
const yPosition = ref(25)
const yPosition2 = ref(25)
// 添加被检通道
Object.entries(channelCounts).forEach(([deviceId, count]) => {
for (let i = 1; i <= count; i++) {
const channelId = `被检通道-${deviceId}-${i}`
newNodes.push({
id: channelId,
type: 'input',
data: { label: createLabel3(`被检通道${i}`) },
position: { x: inputChannelX, y: yPosition.value },
sourcePosition: 'right',
style: { width: '150px', border: 'none', boxShadow: 'none' }
})
// 计算设备节点Y坐标居中显示
if (i == 1 && count == 1) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 25
})
} else if (i == 2 && count == 2) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 50
})
} else if (i == 3 && count == 3) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 75
})
} else if (i == 4 && count == 4) {
deviceChannelGroups.push({
deviceId,
centerY: yPosition.value - 100
})
}
yPosition.value += 50
}
yPosition.value += 50
})
// 添加标准通道
Object.entries(channelCounts2).forEach(([deviceId, count]) => {
for (let i = 1; i <= count; i++) {
const channelId = `标准通道-${deviceId}-${i}`
newNodes.push({
id: channelId,
type: 'output',
data: { label: createLabel3(`标准通道${i}`) },
position: { x: outputChannelX, y: yPosition2.value },
targetPosition: 'left',
style: { width: '150px', border: 'none', boxShadow: 'none' }
})
// 计算设备节点Y坐标居中显示
if (i == 1 && count == 1) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 25
})
} else if (i == 2 && count == 2) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 50
})
} else if (i == 3 && count == 3) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 100
})
} else if (i == 4 && count == 4) {
standardChannelGroups.push({
deviceId,
centerY: yPosition2.value - 100
})
}
yPosition2.value += 50
}
yPosition2.value += 50
})
// 添加被检设备
deviceChannelGroups.forEach(({ deviceId, centerY }) => {
const device = inspectionDevices.find(d => d.id === deviceId)
if (device) {
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType) },
position: { x: deviceWidth, y: centerY },
class: 'no-handle-node',
style: { width: '200px', border: 'none', boxShadow: 'none' }
})
}
})
// 添加标准设备
standardChannelGroups.forEach(({ deviceId, centerY }) => {
const device = standardDevices.find(d => d.id === deviceId)
if (device) {
newNodes.push({
id: device.id,
data: { label: createLabel(device.name, device.deviceType) },
position: { x: standardWidth, y: centerY },
class: 'no-handle-node',
style: { width: '200px', border: 'none', boxShadow: 'none' }
})
}
})
//页面高度取决于设备通道
dialogHeight.value = Math.max(yPosition.value, yPosition2.value)
return newNodes
}
defineExpose({ open })
</script>
<style>
.flow-container {
width: 100%;
overflow: hidden;
}
.vue-flow__node.no-handle-node .vue-flow__handle {
display: none !important;
}
</style>

View File

@@ -2,12 +2,16 @@
<div class="dialog" v-bind="dialogBig">
<div class="dialog-content">
<div class="right-title">
<!-- <div>系数校准表</div> -->
<div>{{ outputDsc }}</div>
<div>
<span style=" font-size: 18px;font-weight: 600;">
设备已合格 <span style="color: #91cc75">{{ qualified }}</span> / <span style="color: green">{{ total }}</span>
</span>
<!-- <el-button type="primary" loading
v-if="activeIndex > 0 && activeIndex < activeTotalNum">通道系数已校准3台/共3台</el-button>
<el-button type="primary" :disabled="true" v-if="activeIndex === activeTotalNum">通道系数已校准3台/共3台</el-button> -->
</div>
</div>
<div class="container">
@@ -86,21 +90,6 @@
</template>
<script lang="tsx" setup name="FactorTest">
/**
* 系数校准组件
*
* 功能概述:
* 1. 对电力设备进行系数校准,确保测量精度
* 2. 包含4个校准步骤大/小电压电流系数的下装和校准
* 3. 支持多设备并行校准,实时显示校准数据和结果
* 4. 校准完成后自动统计合格设备数量并通知父组件
*
* 校准流程:
* 第1步大电压/电流系数下装 - 向设备下发大幅值的系数参数
* 第2步小电压/电流系数下装 - 向设备下发小幅值的系数参数
* 第3步大电压/电流校准 - 使用大幅值进行校准测试
* 第4步小电压/电流校准 - 使用小幅值进行校准测试
*/
import {type Device} from '@/api/device/interface/device';
import {Failed} from '@element-plus/icons-vue'
import {onBeforeMount, type Ref, ref, toRef, watch} from 'vue'
@@ -112,235 +101,188 @@ import type {Plan} from '@/api/plan/interface';
import {useCheckStore} from "@/stores/modules/check";
import {useUserStore} from "@/stores/modules/user";
// ==================== 状态管理 ====================
const checkStore = useCheckStore()
const userStore = useUserStore()
// ==================== 界面控制变量 ====================
const activeIndex = ref(0) // 当前活跃的表格数据索引
const activeTotalNum = ref(4) // 总步骤数
const qualified = ref(0) // 已合格的设备数量
const outputDsc = ref('电压误差为±0.1Un% 电流误差为±0.5%') // 输出描述信息
const total = ref(0) // 设备总数
const dialogVisible = ref(false) // 弹窗显示状态
const active = ref(0) // 当前激活的校准步骤0-5
const editableTabsValue = ref('0') // 当前选中的标签页
// ==================== 定时器管理 ====================
let timer1: NodeJS.Timeout | null = null // 定时器1
let timer2: NodeJS.Timeout | null = null // 定时器2
// ==================== 设备信息管理 ====================
const name = ref<string[]>([]) // 系数校准所选设备名称数组
const channel = ref<number[]>([]) // 系数校准所选设备通道数量数组
const devIdArray = ref<string[]>([]) // 系数校准所选设备ID数组
const select_Plan = ref<Plan.ReqPlan>() // 选中的检测计划
const planId = ref('') // 计划ID
const CurV = ref<number>() // 额定电压值
// ==================== 状态控制变量 ====================
const isButtonDisabled = ref(false) // 按钮禁用状态
const errorStates = ref(new Array(name.value.length).fill(false)) // 各设备错误状态数组
// ==================== 加载状态管理 ====================
const big_V_loadingStates = ref(false) // 大电压大电流下装加载状态
const small_V_loadingStates = ref(false) // 小电压小电流下装加载状态
const big_V_loadingStates2 = ref(false) // 大电压大电流校准加载状态
const small_V_loadingStates2 = ref(false) // 小电压小电流校准加载状态
// ==================== 源输出参数显示 ====================
const big_V_Download = ref('') // 大电压下装时的电压输出显示
const big_I_Download = ref('') // 大电压下装时的电流输出显示
const small_V_Download = ref('') // 小电压下装时的电压输出显示
const small_I_Download = ref('') // 小电压下装时的电流输出显示
const big_V_Adjust = ref('') // 大电压校准时的电压输出显示
const big_I_Adjust = ref('') // 大电压校准时的电流输出显示
const small_V_Adjust = ref('') // 小电压校准时的电压输出显示
const small_I_Adjust = ref('') // 小电压校准时的电流输出显示
// ==================== 组件Props定义 ====================
const activeIndex = ref(0)
const activeTotalNum = ref(4)
const qualified = ref(0)
const outputDsc = ref('电压误差为±0.1Un% 电流误差为±0.5%')
const total = ref(0)
const dialogVisible = ref(false)
const active = ref(0)
let timer1: NodeJS.Timeout | null = null; // 声明并初始化 timer1
let timer2: NodeJS.Timeout | null = null; // 同样声明并初始化 timer2
const name = ref<string[]>([])//系数校准所选设备名字数组
const channel = ref<number[]>([])//系数校准所选设备通道数组
const devIdArray = ref<string[]>([])//系数校准所选设备ID数组
const select_Plan = ref<Plan.ReqPlan>()
const planId = ref('')
const isButtonDisabled = ref(false);
const CurV = ref<number>()//额定电压
// 在 setup 函数中
const errorStates = ref(new Array(name.value.length).fill(false));
//const loadingStates = ref(new Array(name.value.length).fill(false)); // 初始化 loading 状态
const big_V_loadingStates = ref(false); // 初始化 大电压大电流下装loading 状态
const small_V_loadingStates = ref(false); // 初始化 小电压小电流下装loading 状态
const big_V_loadingStates2 = ref(false); // 初始化 大电压大电流校准loading 状态
const small_V_loadingStates2 = ref(false); // 初始化 小电压小电流校准loading 状态
const editableTabsValue = ref('0')
const big_V_Download = ref('')
const big_I_Download = ref('')
const small_V_Download = ref('')
const small_I_Download = ref('')
const big_V_Adjust = ref('')
const big_I_Adjust = ref('')
const small_V_Adjust = ref('')
const small_I_Adjust = ref('')
const props = defineProps({
testStatus: {
type: String,
default: 'wait' // 从父组件接收的测试状态
default: 'wait'
},
webMsgSend: {
type: Object,
default: () => ({}) // 从父组件接收的WebSocket消息
default: () => ({})
},
})
// ==================== 响应式Props引用 ====================
const userStore = useUserStore()
const testStatus = toRef(props, 'testStatus');
const tableDataMap = new Map<number, Ref<ChannelsTest.CoefficientVO[]>>([]);
const currentStepStatus = ref<'error' | 'finish' | 'wait' | 'success' | 'process'>('finish');
const webMsgSend = toRef(props, 'webMsgSend');
// ==================== 数据管理 ====================
const tableDataMap = new Map<number, Ref<ChannelsTest.CoefficientVO[]>>([]); // 存储每个设备的表格数据映射
const currentStepStatus = ref<'error' | 'finish' | 'wait' | 'success' | 'process'>('finish'); // 当前步骤状态
// ==================== 组件生命周期 ====================
onBeforeMount(() => {
initData() // 组件挂载前初始化数据
// 初始化
initData()
})
/**
* 初始化组件数据
* 从store中获取设备信息初始化表格数据和状态变量
*/
const initData = () => {
// 获取设备基础信息
CurV.value = checkStore.devices[0]?.devVolt || 57.74; // 获取额定电压默认57.74V
isButtonDisabled.value = false; // 恢复按钮可用状态
select_Plan.value = checkStore.plan // 获取检测计划
planId.value = checkStore.devices[0]?.planId || ''; // 获取计划ID
// 提取设备信息数组
devIdArray.value = checkStore.devices.map(item => item.deviceId); // 设备ID数组
name.value = checkStore.devices.map(item => item.deviceName); // 设备名称数组
channel.value = checkStore.devices.map(item => item.chnNum); // 设备通道数量数组
// 设置界面状态
dialogVisible.value = true; // 显示弹窗
total.value = name.value.length; // 设置设备总数
CurV.value = checkStore.devices[0]?.devVolt || 57.74;
isButtonDisabled.value = false; // 恢复按钮
select_Plan.value = checkStore.plan
planId.value = checkStore.devices[0]?.planId || '';
devIdArray.value = checkStore.devices.map(item => item.deviceId);
name.value = checkStore.devices.map(item => item.deviceName)
channel.value = checkStore.devices.map(item => item.chnNum)
dialogVisible.value = true;
total.value = name.value.length
// 初始化错误状态数组,所有设备初始状态为无错误
// 初始化 loadingStates 为 false
// loadingStates.value = new Array(selection.length).fill(false);
errorStates.value = new Array(checkStore.devices.length).fill(false);
// 为每个设备初始化表格数据
for (let i = 0; i < channel.value.length; i++) {
const currentTableData = initializeTableData(dataTemplates, i);
tableDataMap.set(i, currentTableData); // 将表格数据存储到Map中以设备索引为key
tableDataMap.set(i, currentTableData)
}
//console.log('tableDataMap',tableDataMap);
}
// ==================== 状态监听器 ====================
/**
* 监听父组件传入的测试状态变化
* 当父组件通知开始测试时,启动系数校准流程
*/
watch(testStatus, function (newValue, oldValue) {
if (newValue === 'start') {
// 父组件通知开始系数校准操作
handleSubmit()
}
// 开始系数校准操作
handleSubmit()
}
})
/**
* 监听WebSocket消息处理系数校准过程中的各种响应
* 主要处理:
* 1. 错误代码的统一处理
* 2. 系数校准流程的各个步骤响应
* 3. 表格数据的实时更新
*/
watch(webMsgSend, function (newValue, oldValue) {
console.log('webMsgSend---code', newValue.code)
console.log('webMsgSend---requestId', newValue.requestId)
// 只有在非等待状态下才处理WebSocket消息
if (testStatus.value !== 'waiting') {
// ==================== 通用错误代码处理 ====================
if (newValue.code == 10520) {
// 报文解析异常
ElMessageBox.alert('报文解析异常!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10521) {
// 程控源参数有误
ElMessageBox.alert('程控源參数有误!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10522) {
// 测试项解析有误
ElMessageBox.alert('测试项解析有误!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10523) {
// 源连接失败
ElMessageBox.alert('源连接失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10524) {
// 获取源控制权失败
ElMessageBox.alert('获取源控制权失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10525) {
// 重置源失败
ElMessageBox.alert('重置源失败!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10527) {
// 源未进行初始化
ElMessageBox.alert('源未进行初始化!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10528) {
// 目标源有误(用户已控制其他源)
ElMessageBox.alert('目标源有误(该用户已控制其他源,在关闭前无法操作新的源)', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10529) {
// 源状态有误,无法响应报文
ElMessageBox.alert('源状态有误,无法响应报文(例如源处于输出状态,无法响应初始化报文)', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10550) {
// 设备连接异常
ElMessageBox.alert(`${newValue.data}设备连接异常!`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10551) {
// 设备触发报告异常
ElMessageBox.alert(`${newValue.data}设备触发报告异常!`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10552) {
// 存在已经初始化步骤,执行自动关闭
} else if (newValue.code == 10552) { //todo 10552之后还会发送消息吗
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == -1) { //todo 10552之后还会发送消息吗
ElMessageBox.alert('源未知异常', '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else {
// ==================== 特定业务消息处理 ====================
console.log('显示东西code', newValue.code)
console.log('显示东西requestId', newValue.requestId)
switch (newValue.requestId) {
// 处理源通讯校验相关消息
case 'yjc_ytxjy':
switch (newValue.operateCode) {
case'INIT_GATHER':
if (newValue.code == -1) {
// 源未知异常
ElMessageBox.alert('源未知异常', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10523) {
// 源连接失败
ElMessageBox.alert('源连接失败', '源连接失败', {
confirmButtonText: '确定',
type: 'error',
@@ -349,19 +291,16 @@ watch(webMsgSend, function (newValue, oldValue) {
}
}
break;
// 处理相序校验相关消息
case 'YJC_xujy':
switch (newValue.operateCode) {
case 'OPER_GATHER':
if (newValue.code == 10552) {
// 存在已经初始化步骤,执行自动关闭
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10520) {
// 解析报文异常
ElMessageBox.alert('解析报文异常,执行自动关闭,请重新发起检测', '解析报文异常', {
confirmButtonText: '确定',
type: 'error',
@@ -371,7 +310,6 @@ watch(webMsgSend, function (newValue, oldValue) {
break;
case 'DATA_REQUEST$02':
if (newValue.code == 25003) {
// 相序校验未通过
ElMessageBox.alert('相序校验未通过,执行自动关闭,请重新发起检测', '相序校验未通过', {
confirmButtonText: '确定',
type: 'error',
@@ -381,48 +319,42 @@ watch(webMsgSend, function (newValue, oldValue) {
break;
}
break;
// 处理设备通讯校验相关消息
case 'yjc_sbtxjy':
switch (newValue.operateCode) {
case 'INIT_GATHER$01':
if (newValue.code == 10550) {
// 设备连接异常
ElMessageBox.alert('设备连接异常', '设备连接异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10551) {
// 设备触发报告异常
ElMessageBox.alert('设备触发报告异常', '设备触发报告异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10552) {
// 存在已经初始化步骤,执行自动关闭
ElMessageBox.alert('存在已经初始化步骤,执行自动关闭,请重新发起检测', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
} else if (newValue.code == 10520) {
// 解析报文异常
ElMessageBox.alert('解析报文异常,执行自动关闭,请重新发起检测', '解析报文异常', {
confirmButtonText: '确定',
type: 'error',
})
TableInit();
}
break;
}
break;
// 处理协议校验相关消息
case 'yjc_xyjy':
switch (newValue.operateCode) {
case 'VERIFY_MAPPING$01':
if (newValue.code == 25002) {
// 脚本与ICD校验失败
if (newValue.code == 10200) {
let data = JSON.parse(newValue.data)
ElMessageBox.alert(`脚本与icd校验失败icd名称${data['icdType']} -> 校验项:${data['dataType']}`, '初始化失败', {
confirmButtonText: '确定',
@@ -431,7 +363,6 @@ watch(webMsgSend, function (newValue, oldValue) {
TableInit();
}
if (newValue.code == 10500) {
// 装置中未找到该ICD
ElMessageBox.alert(`装置中未找到该icd!`, '初始化失败', {
confirmButtonText: '确定',
type: 'error',
@@ -441,162 +372,136 @@ watch(webMsgSend, function (newValue, oldValue) {
break;
}
break;
// ★★★ 处理系数校准核心业务消息 ★★★
case 'Coefficient_Check':
console.log("Coefficient_Checkactive", active.value);
// ==================== 第1阶段大电压/电流系数下装 ====================
switch (newValue.operateCode) {
case 'big_start':
// 大电压/电流系数下装开始,显示源输出参数
case 'big_start'://大电压,电流下装
big_V_Download.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
big_I_Download.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'big_end':
// 大电压/电流系数下装完成,进入下一阶段
active.value++; // 步骤进度+1
tableLoading('small', '系数下装') // 开始小电压/电流下装的加载动画
case 'big_end'://大电压,电流下装
active.value++;
tableLoading('small', '系数下装')
break;
}
// ==================== 第2阶段小电压/电流系数下装 ====================
switch (newValue.operateCode) {
case 'small_start':
// 小电压/电流系数下装开始,显示源输出参数
case 'small_start'://小电压,电流下装
small_V_Download.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
small_I_Download.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'small_end':
// 小电压/电流系数下装完成,进入校准阶段
active.value++; // 步骤进度+1
tableLoading('big', '系数校准') // 开始大电压/电流校准的加载动画
case 'small_end'://小电压,电流下装
active.value++;
tableLoading('big', '系数校准')
break;
}
// ==================== 第3阶段大电压/电流系数校准 ====================
switch (newValue.operateCode) {
case 'big_comp_start':
// 大电压/电流系数校准开始,显示源输出参数
case 'big_comp_start'://大电压,电流校准
big_V_Adjust.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
big_I_Adjust.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'big_comp_end':
// 大电压/电流系数校准完成,进入最后阶段
active.value++; // 步骤进度+1
tableLoading('small', '系数校准') // 开始小电压/电流校准的加载动画
case 'big_comp_end'://大电压,电流校准
active.value++;
tableLoading('small', '系数校准')
break;
}
// ==================== 第4阶段小电压/电流系数校准(最终阶段)====================
switch (newValue.operateCode) {
case 'small_comp_start':
// 小电压/电流系数校准开始,显示源输出参数
case 'small_comp_start'://小电压,电流校准
small_V_Adjust.value = 'Ua=Ub=Uc=' + newValue.data.devVolt + 'V';
small_I_Adjust.value = 'Ia=Ib=Ic=' + newValue.data.devCurr + 'A';
break;
case 'small_comp_end':
// ★★★ 小电压/电流系数校准完成 - 整个系数校准流程结束 ★★★
active.value++; // 步骤进度+1
active.value++; // 再+1跳转到完成状态
case 'small_comp_end'://小电压,电流校准
active.value++;
active.value++;
// 统计所有设备的校准结果
for (let i = 0; i < name.value.length; i++) {
const currentDataRef = tableDataMap.get(i);
if (currentDataRef) {
const currentData = currentDataRef.value;
// 检查当前设备的校准数据是否合格
// 检查当前数据中有无不合格字段
const hasError = checkForErrors(currentData);
if (!hasError) {
qualified.value++; // 合格设备计数+1
if (hasError) {
} else {
qualified.value++;
}
updateErrorState(i, hasError); // 更新设备错误状态显示
updateErrorState(i, hasError);
}
}
isButtonDisabled.value = false; // 恢复按钮可用状态
// ★★★ 通知父组件系数校准成功完成,触发自动步骤切换 ★★★
//editableTabsValue.value = (tabNumber.value).toString();//显示下一个tab
isButtonDisabled.value = false; // 恢复按钮
emit('update:testStatus', 'success')
break;
}
// ==================== 表格数据实时更新 ====================
switch (newValue.operateCode) {
case 'DATA_CHNFACTOR$02':
// 接收并更新表格中的系数校准数据
case 'DATA_CHNFACTOR$02'://表格
// 输出 key 为 0 的数组中的第一条 ChannelsTest.CoefficientVO 对象
console.log('表格', name.value)
// 遍历所有设备,找到匹配的表格项并更新数据
for (let i = 0; i < name.value.length; i++) {
const targetArrayRef = tableDataMap.get(i);
if (targetArrayRef) {
const targetArray = targetArrayRef.value;
if (targetArray.length > 0) {
// 根据监测点号、类型、描述和设备名称找到对应的表格行
const firstCoefficientVO = targetArray.find(item =>
item.monitorNum === newValue.data.monitorNum &&
item.type === newValue.data.type &&
item.desc === newValue.data.desc &&
item.devName === newValue.data.devName);
if (firstCoefficientVO) {
// 更新A相电压数据和误差
const firstCoefficientVO = targetArray.find(item => item.monitorNum === newValue.data.monitorNum &&
item.type === newValue.data.type &&
item.desc === newValue.data.desc &&
item.devName === newValue.data.devName);
if (firstCoefficientVO) { // 检查 firstCoefficientVO 是否存在
firstCoefficientVO.aVuData = parseFloat(newValue.data.aVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.aVuXi)) && isFinite(newValue.data.aVuXi)) {
firstCoefficientVO.aVuXi = (parseFloat(newValue.data.aVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.aVuXi = newValue.data.aVuXi; // 可能是"不合格"等文本
firstCoefficientVO.aVuXi = newValue.data.aVuXi;
}
// 更新B相电压数据和误差
firstCoefficientVO.bVuData = parseFloat(newValue.data.bVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.bVuXi)) && isFinite(newValue.data.bVuXi)) {
firstCoefficientVO.bVuXi = (parseFloat(newValue.data.bVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.bVuXi = newValue.data.bVuXi; // 可能是"不合格"等文本
firstCoefficientVO.bVuXi = newValue.data.bVuXi;
}
// 更新C相电压数据和误差
firstCoefficientVO.cVuData = parseFloat(newValue.data.cVuData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.cVuXi)) && isFinite(newValue.data.cVuXi)) {
firstCoefficientVO.cVuXi = (parseFloat(newValue.data.cVuXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.cVuXi = newValue.data.cVuXi; // 可能是"不合格"等文本
firstCoefficientVO.cVuXi = newValue.data.cVuXi;
}
// 更新A相电流数据和误差
firstCoefficientVO.aIeData = parseFloat(newValue.data.aIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.aIeXi)) && isFinite(newValue.data.aIeXi)) {
firstCoefficientVO.aIeXi = (parseFloat(newValue.data.aIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.aIeXi = newValue.data.aIeXi; // 可能是"不合格"等文本
firstCoefficientVO.aIeXi = newValue.data.aIeXi;
}
// 更新B相电流数据和误差
firstCoefficientVO.bIeData = parseFloat(newValue.data.bIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.bIeXi)) && isFinite(newValue.data.bIeXi)) {
firstCoefficientVO.bIeXi = (parseFloat(newValue.data.bIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.bIeXi = newValue.data.bIeXi; // 可能是"不合格"等文本
firstCoefficientVO.bIeXi = newValue.data.bIeXi;
}
// 更新C相电流数据和误差
firstCoefficientVO.cIeData = parseFloat(newValue.data.cIeData).toFixed(4);
if (!isNaN(parseFloat(newValue.data.cIeXi)) && isFinite(newValue.data.cIeXi)) {
firstCoefficientVO.cIeXi = (parseFloat(newValue.data.cIeXi) / 10000).toFixed(4);
} else {
firstCoefficientVO.cIeXi = newValue.data.cIeXi; // 可能是"不合格"等文本
firstCoefficientVO.cIeXi = newValue.data.cIeXi;
}
// 更新三相电压和电流的原始值
firstCoefficientVO.aV = newValue.data.aV;
firstCoefficientVO.bV = newValue.data.bV;
firstCoefficientVO.cV = newValue.data.cV;
firstCoefficientVO.aI = newValue.data.aI;
firstCoefficientVO.bI = newValue.data.bI;
firstCoefficientVO.cI = newValue.data.cI;
console.log(newValue.data.devName + '对象:', firstCoefficientVO);
activeIndex.value++; // 更新活跃索引
activeIndex.value++;
} else {
console.log('未找到匹配的' + newValue.data.devName + '对象');
}
@@ -610,11 +515,9 @@ watch(webMsgSend, function (newValue, oldValue) {
break;
}
break;
// 处理Socket连接超时
case 'socket_timeout':
switch (newValue.operateCode) {
case 'VOLTAGE':
// 电压连接超时
ElMessageBox.alert('连接超时!', '连接超时', {
confirmButtonText: '确定',
type: 'error',
@@ -623,11 +526,9 @@ watch(webMsgSend, function (newValue, oldValue) {
break;
}
break;
// 处理连接失败
case 'connect':
switch (newValue.operateCode) {
case "Source":
// 源服务端连接失败
ElMessageBox.alert('源服务端连接失败', '源服务端连接失败', {
confirmButtonText: '确定',
type: 'error',
@@ -635,7 +536,6 @@ watch(webMsgSend, function (newValue, oldValue) {
TableInit();
break;
case "Dev":
// 设备服务端连接失败
ElMessageBox.alert('设备服务端连接失败', '设备服务端连接失败', {
confirmButtonText: '确定',
type: 'error',
@@ -643,97 +543,57 @@ watch(webMsgSend, function (newValue, oldValue) {
TableInit();
break;
}
break;
}
}
}
})
// ==================== 错误处理函数 ====================
/**
* 系数校准出错时的初始化处理
* 通知父组件检测失败,重置相关状态
*/
//出错系数检测初始化
const TableInit = () => {
console.log("出错系数检测",active.value);
// 通知父组件系数校准失败
emit('update:testStatus', 'error')
emit('update:testStatus', 'test_init_fail')
// isButtonDisabled.value = false; // 恢复按钮
// for (let i = 0; i < channel.value.length; i++) {
// const currentTableData = initializeTableData(dataTemplates, i);
// tableDataMap.set(i, currentTableData)
// }
// activeIndex.value = 0
// qualified.value = 0
// active.value = 0
}
// ==================== 界面动画控制 ====================
/**
* 控制表格中特定类型和描述的行显示加载动画
* @param type 类型:'big' | 'small'
* @param desc 描述:'系数下装' | '系数校准'
*/
//按行图标转动
const tableLoading = (type: string, desc: string) => {
console.log('转动',channel.value)
// 遍历所有设备
for (let i = 0; i < channel.value.length; i++) {
const targetArrayRef = tableDataMap.get(i);
if (targetArrayRef) {
const targetArray = targetArrayRef.value;
if (targetArray.length > 0) {
// 遍历当前设备的所有通道
for (let j = 0; j < channel.value[i]; j++) {
// 找到对应类型和描述的表格行
const firstCoefficientVO = targetArray.find(item =>
item.monitorNum === (j + 1).toString() &&
item.type === type &&
item.desc === desc);
const firstCoefficientVO = targetArray.find(item => item.monitorNum === (j + 1).toString() &&
item.type === type &&
item.desc === desc);
if (firstCoefficientVO) {
firstCoefficientVO.loading = true; // 开启加载动画
firstCoefficientVO.loading = true;
}
}
}
} else {
}else{
console.log('不转了')
}
}
}
// ==================== 表格数据模板 ====================
/**
* 系数校准表格的数据模板
* 每个设备的每个通道都会根据这4个模板生成对应的表格行
*
* 模板说明:
* 1. 监测点1 - 大幅值系数下装
* 2. 监测点2 - 小幅值系数下装
* 3. 监测点3 - 大幅值系数校准
* 4. 监测点4 - 小幅值系数校准
*/
const dataTemplates: ChannelsTest.CoefficientVO[] = [
{
monitorNum: '1', // 监测点编号
desc: '系数下装', // 操作描述
type: 'big', // 幅值类型:大幅值
aVuData: '—', // A相电压测量值
aVuXi: '—', // A相电压误差
bVuData: '—', // B相电压测量值
bVuXi: '—', // B相电压误差
cVuData: '—', // C相电压测量值
cVuXi: '—', // C相电压误差
aIeData: '—', // A相电流测量值
aIeXi: '—', // A相电流误差
bIeData: '—', // B相电流测量值
bIeXi: '—', // B相电流误差
cIeData: '—', // C相电流测量值
cIeXi: '—', // C相电流误差
loading: false, // 加载状态
devName: '', // 设备名称(动态填充)
aV: '—', // A相电压原始值
bV: '—', // B相电压原始值
cV: '—', // C相电压原始值
aI: '—', // A相电流原始值
bI: '—', // B相电流原始值
cI: '—', // C相电流原始值
},
{
monitorNum: '2', // 监测点编号
desc: '系数下装', // 操作描述
type: 'small', // 幅值类型:小幅值
monitorNum: '1',
desc: '系数下装',
type: 'big',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
@@ -756,9 +616,9 @@ const dataTemplates: ChannelsTest.CoefficientVO[] = [
cI: '—',
},
{
monitorNum: '3', // 监测点编号
desc: '系数校准', // 操作描述
type: 'big', // 幅值类型:大幅值
monitorNum: '2',
desc: '系数下装',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
@@ -781,9 +641,9 @@ const dataTemplates: ChannelsTest.CoefficientVO[] = [
cI: '—',
},
{
monitorNum: '4', // 监测点编号
desc: '系数校准', // 操作描述
type: 'small', // 幅值类型:小幅值
monitorNum: '3',
desc: '系数校准',
type: 'big',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
@@ -805,51 +665,64 @@ const dataTemplates: ChannelsTest.CoefficientVO[] = [
bI: '—',
cI: '—',
},
{
monitorNum: '4',
desc: '系数校准',
type: 'small',
aVuData: '—',
aVuXi: '—',
bVuData: '—',
bVuXi: '—',
cVuData: '—',
cVuXi: '—',
aIeData: '—',
aIeXi: '—',
bIeData: '—',
bIeXi: '—',
cIeData: '—',
cIeXi: '—',
loading: false,
devName: '',
aV: '—',
bV: '—',
cV: '—',
aI: '—',
bI: '—',
cI: '—',
},
];
// ==================== 工具函数 ====================
/**
* 更新设备错误状态显示
* @param index 设备索引
* @param hasError 是否有错误
*/
// 更新错误状态的方法
const updateErrorState = (index: number, hasError: boolean) => {
errorStates.value[index] = hasError;
};
/**
* 获取指定设备的表格数据
* @param index 设备索引
* @returns 返回对应设备的表格数据数组
*/
// const emit = defineEmits<{
// (e: 'submitClicked', callback: (resolve: (value: boolean) => void) => void): void;
// }>();
const emit = defineEmits(['update:testStatus']);
const getTableDataForChannel = (index: number): any[] => {
const data = tableDataMap.get(index);
return data ? data.value : [];
}
// ==================== 父子组件通信 ====================
const emit = defineEmits(['update:testStatus']);
// ==================== 界面状态监听 ====================
/**
* 监听活跃索引变化,更新界面描述信息
*/
watch(activeIndex, function (newValue, oldValue) {
if (activeIndex.value === 1) {
outputDsc.value = "电压误差为±0.1Un% 电流误差为±0.5%";
// 当前源输出为Ua=Ub=Uc=57.74V Ia=Ib=Ic=1A"
}
})
// ==================== 业务逻辑函数 ====================
/**
* 检查设备校准数据是否存在错误
* @param data 设备的校准数据数组
* @returns 返回是否存在不合格项
*/
// 示例的 checkForErrors 函数,根据实际需求进行调整
const checkForErrors = (data: ChannelsTest.CoefficientVO[]): boolean => {
// 检查各相电压和电流的误差值是否为"不合格"
// 这里假设不合格字段的标准是 status 为 '不合格' 或 isValid 为 false
return data.some(item =>
item.aVuXi === '不合格' ||
item.bVuXi === '不合格' ||
@@ -860,35 +733,43 @@ const checkForErrors = (data: ChannelsTest.CoefficientVO[]): boolean => {
);
};
/**
* 处理系数校准开始事件
* 由父组件通过状态监听触发
*/
const handleSubmit = async () => {
isButtonDisabled.value = true; // 禁用按钮,防止重复提交
tableLoading('big', '系数下装') // 开启大幅值系数下装的加载动画
active.value++; // 步骤进度+1进入第一个校准阶段
// 创建一个 Promise 来等待父组件的回调
// const response = await new Promise<boolean>((resolve) => {
// emit('submitClicked', resolve);
// });
// if (!response) {
// return;
// }()
isButtonDisabled.value = true; // 禁用按钮
tableLoading('big', '系数下装')
/* await getCoefficientCheck({
userPageId: "cdf",
devIds: devIdArray.value,
planId: planId.value,
errorSysId: select_Plan.value?.errorSysId,
scriptId: select_Plan.value?.scriptId,
operateType: '0', // '0'为预检测、1为正式检测
userId:userStore.userInfo.id
})*/
active.value++;
console.log('开始检测active.value', active.value)
};
/**
* 为指定设备初始化表格数据
* @param templates 数据模板数组
* @param index 设备索引
* @returns 返回初始化后的表格数据引用
*/
// 提取初始化并填充 currentTableData 的函数
const initializeTableData = (templates: ChannelsTest.CoefficientVO[], index: number): Ref<ChannelsTest.CoefficientVO[]> => {
const currentTableData = ref<ChannelsTest.CoefficientVO[]>([]);
// 为当前设备的每个通道生成表格数据
for (let j = 0; j < channel.value[index]; j++) {
templates.forEach((template) => {
// 使用解构赋值复制模板,排除不需要的属性
// 使用解构赋值排除 id 和 MonitorIdx 属性
const {devName, monitorNum: __, ...rest} = template;
currentTableData.value.push({
monitorNum: (j + 1).toString(), // 监测点号从1开始
devName: name.value[index], // 设置设备名称
...rest, // 其他模板属性
monitorNum: (j + 1).toString(),
devName: name.value[index],
...rest,
});
});
}
@@ -938,6 +819,9 @@ const initializeTableData = (templates: ChannelsTest.CoefficientVO[], index: num
height: 470px;
}
/* .el-icon svg {
color: #ff7171;
} */
.icon-style {
color: #ff7171;

View File

@@ -0,0 +1,202 @@
<template>
<!-- 基础信息弹出框 -->
<el-dialog :model-value="visible" title="被检监测点匹配" v-bind="dialogSmall" @close="handleCancel" width="500" draggable>
<div>
<el-form :model="formData" ref='formRuleRef' :rules='rules' change-event="selectChange">
<el-form-item label="监测点名称:" :label-width="100">
<el-tree-select v-model="value1" :data="sourcesList" style="width: 240px" />
</el-form-item>
<el-form-item label="监测点名称:" :label-width="100">
<el-tree-select v-model="value2" :data="sourcesList" style="width: 240px" />
</el-form-item>
<el-form-item label="监测点名称:" :label-width="100">
<el-tree-select v-model="value3" :data="sourcesList" style="width: 240px" />
</el-form-item>
<el-form-item label="监测点名称:" :label-width="100">
<el-tree-select v-model="value4" :data="sourcesList" style="width: 240px" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import{ElMessage, FormInstance,FormItemRule}from'element-plus'
import { defineProps, defineEmits, reactive,watch,ref, Ref } from 'vue';
import { dialogSmall} from '@/utils/elementBind'
import {dictPattern,dictTestState,dictReportState,dictResult,testPlanDataList,sourceDataList,deviceDataList,testSoureDataList,testScriptDataList,testErrSystDataList,planData,testFatherPlanList} from '@/api/plan/planData'
const selectChange = (val: any) => {
console.log(val)
}
const value1 = ref()
const value2 = ref()
const value3 = ref()
const value4 = ref()
const sourcesList = [
{
value: '1',
label: '高精度设备-PQV520-1',
disabled: false,
children: [
{
value: '1-1',
label: '监测点1',
disabled: false,
},
{
value: '1-2',
label: '监测点2',
disabled: false,
},
],
},
{
value: '2',
label: '高精度设备-PQV520-2',
disabled: false,
children: [
{
value: '2-1',
label: '监测点1',
disabled: false,
},
{
value: '2-2',
label: '监测点2',
disabled: false,
},
],
},
{
value: '3',
label: '高精度设备-PQV520-3',
disabled: false,
children: [
{
value: '3-1',
label: '监测点1',
disabled: false,
},
{
value: '3-2',
label: '监测点2',
disabled: false,
},
],
},
{
value: '4',
label: '高精度设备-PQV520-4',
disabled: false,
children: [
{
value: '4-1',
label: '监测点1',
disabled: false,
},
{
value: '4-2',
label: '监测点2',
disabled: false,
},
],
},
]
const props = defineProps<{
visible: boolean;
}>();
const fatherPlanList = [
{ label: '/', value: 'type0' },
{ label: '检测计划1', value: 'type1' },
{ label: '检测计划2', value: 'type2' },
{ label: '检测计划3', value: 'type3' },
{ label: '检测计划4', value: 'type4' },
];
const sourceList = [
{ label: '分钟统计数据最大值', value: 'type0' },
{ label: '分钟统计数据最大值', value: 'type1' },
{ label: '分钟统计数据CP95值', value: 'type2' },
];
const scriptList = [
{ label: '/', value: 'type0' },
{ label: '国网入网检测脚本(单影响量-模拟式)', value: 'type1' },
{ label: '国网入网检测脚本Q/GDW 10650.4 - 2021) 数字式', value: 'type1' },
];
const errorList = [
{ label: 'Q/GDW 1650.2- 2016', value: 'type0' },
{ label: 'Q/GDW 10650.2 - 2021', value: 'type1' },
];
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'submit', data: any): void;
}>();
// 定义规则
const formRuleRef = ref<FormInstance>()
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
name: [{ required: true, message: '检测计划名称必填!', trigger: 'blur' }],
source_Id: [{ required: true, message: '检测源必选!', trigger: 'blur' }],
dataSource_Id: [{ required: true, message: '数据源必选!', trigger: 'blur' }],
script_Id: [{ required: true, message: '检测脚本必选!', trigger: 'blur' }],
error_Sys_Id: [{ required: true, message: '误差体系必选!', trigger: 'blur' }],
// name: [{ required: true, message: '检测计划名称必填!', trigger: 'blur' }],
// father_Plan_Id: [{ required: true, message: '参照标准名称必填!', trigger: 'change' }],
});
const handleCancel = () => {
//重置表单内容
//取消表单校验状态
formRuleRef.value && formRuleRef.value.resetFields()
emit('update:visible', false); // 关闭对话框
};
const handleSubmit = () => {
try {
formRuleRef.value?.validate((valid: boolean) => {
if (valid)
{
// 将表单数据转为json,发送到后端
let confirmFormData = JSON.parse(JSON.stringify(props.formData));
//console.log(confirmFormData)
emit('submit', props.formData); // 提交表单数据
emit('update:visible', false); // 提交后关闭对话框
}
else
{
ElMessage.error('请填选必填项!')
}
})
} catch (error) {
console.error('验证过程中发生错误', error)
}
};
// 当 props.visible 改变时,更新 formData
watch(() => props.visible, (newVal) => {
if (!newVal) {
// 这里可以重置表单数据,如果需要的话
}
});
</script>
<style scoped>
</style>

View File

@@ -11,7 +11,8 @@
<div class="test-dialog">
<div class="dialog-left">
<el-steps direction="vertical" :active="activeIndex" finish-status="success">
<el-steps direction="vertical" :active="activeIndex" :process-status="currentStepStatus"
finish-status="success">
<el-step :status="step1" title="源通讯校验"/>
<el-step :status="step2" title="设备通讯校验"/>
<el-step :status="step3" title="协议校验"/>
@@ -66,19 +67,9 @@
</template>
<script lang="tsx" setup name="preTest">
/**
* 预检测组件
* 负责电力设备检测中的预检测阶段包含4个步骤
* 1. 源通讯校验 - 验证源端通讯是否正常
* 2. 设备通讯校验 - 检查设备IP、端口、识别码、密钥
* 3. 协议校验 - 进行ICD报告触发测试
* 4. 相序校验 - 判断装置接线是否正确
*/
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, toRef, watch } from 'vue'
import {ElMessage, ElMessageBox} from "element-plus";
import {defineExpose, toRef} from 'vue';
// ==================== 日志数据存储 ====================
// 各步骤的日志数据,用于在右侧折叠面板中显示实时日志
const step1InitLog = ref([
{
type: 'info',
@@ -86,6 +77,13 @@ const step1InitLog = ref([
},
])
const step1Log = ref([
{
type: 'info',
log: '源通讯校验成功',
},
])
const step2InitLog = ref([
{
type: 'info',
@@ -93,6 +91,25 @@ const step2InitLog = ref([
},
])
const step2Log = ref([
{
type: 'info',
log: '被检设备240001通讯校验成功',
},
{
type: 'info',
log: '被检设备240002通讯校验成功',
},
{
type: 'info',
log: '被检设备240003通讯校验成功',
},
{
type: 'info',
log: '被检设备240004通讯校验成功',
},
])
const step3InitLog = ref([
{
type: 'info',
@@ -100,6 +117,26 @@ const step3InitLog = ref([
},
])
const step3Log = ref([
{
type: 'info',
log: '被检设备240001协议校验成功',
},
{
type: 'info',
log: '被检设备240002协议校验成功',
},
{
type: 'info',
log: '被检设备240003协议校验成功',
},
{
type: 'info',
log: '被检设备240004协议校验成功',
},
])
const step4InitLog = ref([
{
type: 'info',
@@ -107,21 +144,35 @@ const step4InitLog = ref([
},
])
// ==================== 界面状态控制 ====================
const collapseActiveName = ref('1') // 当前展开的折叠面板
const activeIndex = ref(0) // 当前激活的步骤索引(用于步骤条高亮)
const activeTotalNum = ref(5) // 总步骤数
const step4Log = ref([
{
type: 'info',
log: '被检设备240001相序校验成功',
},
{
type: 'info',
log: '被检设备240002相序校验成功',
},
{
type: 'info',
log: '被检设备240003相序校验成功',
},
{
type: 'info',
log: '被检设备240004相序校验成功',
},
])
// ==================== 步骤状态管理 ====================
// 各步骤的执行状态wait/process/success/error
const step1 = ref('wait') // 源通讯校验状态
const step2 = ref('wait') // 设备通讯校验状态
const step3 = ref('wait') // 协议校验状态
const step4 = ref('wait') // 相序校验状态
const step5 = ref('wait') // 整体检测结果状态
const collapseActiveName = ref('1')
const activeIndex = ref(0)
const activeTotalNum = ref(5)
const step1 = ref('wait')
const step2 = ref('wait')
const step3 = ref('wait')
const step4 = ref('wait')
const step5 = ref('wait')
// ==================== 预检测项目配置 ====================
// 定义预检测包含的检测项目顶部tabs显示用只读
//定义与预检测配置数组
const detectionOptions = ref([
{
id: 0,
@@ -142,67 +193,45 @@ const detectionOptions = ref([
id: 3,
name: "相序校验",//判断装置的接线是否正确
selected: true,
}
},
// {
// id: 4,
// name: "守时校验",//判断装置24小时内的守时误差是否小于1s
// selected: true,
// },
// {
// id: 5,
// name: "通道系数校准",//通过私有协议与装置进行通讯,校准三相电压电流的通道系数
// selected: true,
// },
// {
// id: 6,
// name: "实时数据比对",
// },
// {
// id: 7,
// name: "录波数据比对",
// },
]);
const currentStepStatus = ref<'error' | 'finish' | 'wait' | 'success' | 'process'>('finish');
// ==================== 组件Props定义 ====================
const props = defineProps({
testStatus: {
type: String,
default: 'wait' // 从父组件接收的测试状态
default: 'wait'
},
webMsgSend: {
type: Object,
default: () => ({}) // 从父组件接收的WebSocket消息
default: () => ({})
}
})
// ==================== 响应式Props引用 ====================
const testStatus = toRef(props, 'testStatus');
const webMsgSend = toRef(props, 'webMsgSend');
const ts = ref(''); // 内部测试状态,用于向父组件同步
const ts = ref('');
// ==================== 错误处理函数 ====================
/**
* 处理致命错误 - 会终止整个检测流程
* @param stepRef 当前步骤状态引用
* @param logRef 当前步骤日志引用
* @param message 错误消息
*/
function handleFatalError(stepRef: any, logRef: any, message: string) {
stepRef.value = 'error';
ts.value = 'error';
step5.value = 'error';
logRef.value.push({
type: 'error',
log: message
});
}
/**
* 处理警告错误 - 只标记当前步骤失败,检测可继续
* @param stepRef 当前步骤状态引用
* @param logRef 当前步骤日志引用
* @param message 错误消息
*/
function handleWarningError(stepRef: any, logRef: any, message: string) {
stepRef.value = 'error';
logRef.value.push({
type: 'error',
log: message
});
}
// ==================== WebSocket消息处理 ====================
/**
* 监听WebSocket消息根据不同的requestId和operateCode处理各种检测状态
* 主要消息类型:
* - yjc_ytxjy: 源通讯校验
* - yjc_sbtxjy: 设备通讯校验
* - yjc_xyjy: 协议校验
* - YJC_xujy: 相序校验
*/
watch(webMsgSend, function (newValue, oldValue) {
if (testStatus.value !== 'waiting') {
switch (newValue.requestId) {
@@ -225,16 +254,33 @@ watch(webMsgSend, function (newValue, oldValue) {
}];
} else if (newValue.code == 10552) {
ElMessage.error(newValue.code)
handleFatalError(step1, step1InitLog, '重复的初始化操作!')
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10523) {
handleFatalError(step1, step1InitLog, '源连接失败!')
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
step1InitLog.value = [{
type: 'error',
log: '源连接失败!',
}];
} else if (newValue.code == -1) {
handleFatalError(step1, step1InitLog, '源未知异常!')
step1.value = 'error'
ts.value = 'error'
step5.value = 'error'
step1InitLog.value = [{
type: 'error',
log: '源未知异常!',
}];
}
break;
}
break;
case 'yjc_sbtxjy':
switch (newValue.operateCode) {
case 'INIT_GATHER$01':
if (newValue.code == 10200) {
@@ -250,11 +296,30 @@ watch(webMsgSend, function (newValue, oldValue) {
log: '正在进行设备通讯校验.....',
}];
} else if (newValue.code == 10550) {
handleWarningError(step2, step2InitLog, newValue.data + '设备连接异常!')
step2InitLog.value.push({
type: 'error',
log: newValue.data + '设备连接异常!',
})
step2.value = 'error'
// ts.value = 'error'
// step5.value = 'error'
} else if (newValue.code == 10551) {
handleFatalError(step2, step2InitLog, newValue.data + '设备触发报告异常!')
step2InitLog.value.push({
type: 'error',
log: newValue.data + '设备触发报告异常!',
})
step2.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10552) {
handleFatalError(step2, step2InitLog, '存在已经初始化步骤,执行自动关闭,请重新发起检测!')
//ElMessage.error("存在已经初始化步骤,已经自动关闭,请重新发起检测!")
step2InitLog.value = [{
type: 'wait',
log: '存在已经初始化步骤,执行自动关闭,请重新发起检测!',
}];
step2.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 25001) {
activeIndex.value = 2
step2.value = 'success'
@@ -262,6 +327,7 @@ watch(webMsgSend, function (newValue, oldValue) {
}
break;
}
break;
case 'yjc_xyjy':
switch (newValue.operateCode) {
@@ -279,11 +345,30 @@ watch(webMsgSend, function (newValue, oldValue) {
log: '正在进行通讯协议校验.....',
}];
} else if (newValue.code == 10550) {
handleWarningError(step3, step3InitLog, newValue.data + '设备连接异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备连接异常!',
})
step3.value = 'error'
// ts.value = 'error'
// step5.value = 'error'
} else if (newValue.code == 10551) {
handleFatalError(step3, step3InitLog, newValue.data + '设备触发报告异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备触发报告异常!',
})
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10552) {
handleFatalError(step3, step3InitLog, '存在已经初始化步骤,执行自动关闭,请重新发起检测!')
step3.value = 'error'
//ElMessage.error("存在已经初始化步骤,已经自动关闭,请重新发起检测!")
step3InitLog.value = [{
type: 'wait',
log: '存在已经初始化步骤,执行自动关闭,请重新发起检测!',
}];
ts.value = 'error'
step5.value = 'error'
}
break;
case 'INIT_GATHER$02':
@@ -300,11 +385,30 @@ watch(webMsgSend, function (newValue, oldValue) {
log: '正在进行通讯协议校验.....',
}];
} else if (newValue.code == 10550) {
handleWarningError(step3, step3InitLog, newValue.data + '设备连接异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备连接异常!',
})
step3.value = 'error'
// ts.value = 'error'
// step5.value = 'error'
} else if (newValue.code == 10551) {
handleFatalError(step3, step3InitLog, newValue.data + '设备触发报告异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备触发报告异常!',
})
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10552) {
handleFatalError(step3, step3InitLog, '存在已经初始化步骤,执行自动关闭,请重新发起检测!')
step3.value = 'error'
//ElMessage.error("存在已经初始化步骤,已经自动关闭,请重新发起检测!")
step3InitLog.value = [{
type: 'wait',
log: '存在已经初始化步骤,执行自动关闭,请重新发起检测!',
}];
ts.value = 'error'
step5.value = 'error'
}
break;
case 'INIT_GATHER$03':
@@ -317,11 +421,30 @@ watch(webMsgSend, function (newValue, oldValue) {
} else if (newValue.code == 10201) {
step3.value = 'process'
} else if (newValue.code == 10550) {
handleWarningError(step3, step3InitLog, newValue.data + '设备连接异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备连接异常!',
})
step3.value = 'error'
// ts.value = 'error'
// step5.value = 'error'
} else if (newValue.code == 10551) {
handleFatalError(step3, step3InitLog, newValue.data + '设备触发报告异常!')
step3InitLog.value.push({
type: 'error',
log: newValue.data + '设备触发报告异常!',
})
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10552) {
handleFatalError(step3, step3InitLog, '存在已经初始化步骤,执行自动关闭,请重新发起检测!')
//ElMessage.error("当前步骤已经初始化,执行自动关闭,请重新发起检测!")
step3.value = 'error'
step3InitLog.value = [{
type: 'wait',
log: '存在已经初始化步骤,执行自动关闭,请重新发起检测!',
}];
ts.value = 'error'
step5.value = 'error'
}
break;
case 'VERIFY_MAPPING$01':
@@ -329,11 +452,23 @@ watch(webMsgSend, function (newValue, oldValue) {
activeIndex.value = 3
step3.value = 'success'
step4.value = 'process'
} else if (newValue.code == 25002) {
} else if (newValue.code == 10200) {
let data = JSON.parse(newValue.data)
handleFatalError(step3, step3InitLog, `脚本与icd检验失败! icd名称${data['icdType']} -> 校验项:${data['dataType']}`)
step3InitLog.value.push({
type: 'error',
log: `脚本与icd检验失败! icd名称${data['icdType']} -> 校验项:${data['dataType']}`,
})
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10500) {
handleFatalError(step3, step3InitLog, '装置中未找到该icd')
step3InitLog.value.push({
type: 'error',
log: `装置中未找到该icd`,
})
step3.value = 'error'
ts.value = 'error'
step5.value = 'error'
}
break;
}
@@ -349,36 +484,61 @@ watch(webMsgSend, function (newValue, oldValue) {
} else if (newValue.code == 10201) {
step4.value = 'process'
step4InitLog.value.push({
step4InitLog.value = [{
type: 'wait',
log: '源参数下发中.....',
});
}];
} else if (newValue.code == 10552) {
ElMessage.error("存在已经初始化步骤,已经自动关闭,请重新发起检测!")
handleFatalError(step4, step4InitLog, '存在已经初始化步骤,执行自动关闭,请重新发起检测!')
step4.value = 'error'
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 10520) {
handleFatalError(step4, step4InitLog, '解析报文异常')
step4.value = 'error'
step4InitLog.value.push({
type: 'error',
log: '解析报文异常',
})
ts.value = 'error'
step5.value = 'error'
}else if (newValue.code == -1) {
step4.value = 'error'
step4InitLog.value = [{
type: 'error',
log: '源未知异常!',
}];
ts.value = 'error'
step5.value = 'error'
}
break;
case 'DATA_REQUEST$02':
if (newValue.code == 10200) {
let type = 'info'
if (newValue.data.includes('不合格')) {
type = 'error'
}
newValue.data.split('<br/>')
step4InitLog.value.push({
type: type,
log: newValue.data,
})
} else if (newValue.code == 10201) {
step4.value = 'process'
step4InitLog.value.push({
step4InitLog.value = [{
type: 'wait',
log: '获取数据相序校验数据!',
});
}];
} else if (newValue.code == 25003) {
handleFatalError(step4, step4InitLog, '相序校验未通过!')
step4.value = 'error'
step4InitLog.value.push({
type: 'error',
log: '相序校验未通过!',
})
ts.value = 'error'
step5.value = 'error'
} else if (newValue.code == 25001) {
step4.value = 'success'
step5.value = 'success'
@@ -392,85 +552,71 @@ watch(webMsgSend, function (newValue, oldValue) {
console.log("@@@@", ts.value)
break
}
break;
case 'quit':
break;
case 'connect':
switch (newValue.operateCode) {
case "Source":
handleFatalError(step1, step1InitLog, '源服务端连接失败!')
step1.value = 'error'
step1InitLog.value = [{
type: 'error',
log: '源服务端连接失败!',
}];
ts.value = 'error'
step5.value = 'error'
break;
case "Dev":
handleFatalError(step2, step2InitLog, '设备服务端连接失败!')
step2.value = 'error'
step2InitLog.value = [{
type: 'error',
log: '设备服务端连接失败!',
}];
ts.value = 'error'
step5.value = 'error'
break;
}
break;
case 'unknown_operate':
break;
case 'error_flow_end':
ElMessageBox.alert(`当前流程存在异常结束`, '检测失败', {
ElMessageBox.alert(`设备连接异常,请检查设备连接情况`, '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
// 根据当前步骤选择对应的日志记录
const currentStepLog = activeIndex.value === 0 ? step1InitLog :
activeIndex.value === 1 ? step2InitLog :
activeIndex.value === 2 ? step3InitLog : step4InitLog
const currentStep = activeIndex.value === 0 ? step1 :
activeIndex.value === 1 ? step2 :
activeIndex.value === 2 ? step3 : step4
handleFatalError(currentStep, currentStepLog, '设备连接异常,检测终止!')
ts.value = 'error'
step5.value = 'error'
break;
case 'socket_timeout':
ElMessageBox.alert(`设备连接异常,请检查设备连接情况!`, '检测失败', {
confirmButtonText: '确定',
type: 'error',
})
// 根据当前步骤选择对应的日志记录
const timeoutStepLog = activeIndex.value === 0 ? step1InitLog :
activeIndex.value === 1 ? step2InitLog :
activeIndex.value === 2 ? step3InitLog : step4InitLog
const timeoutStep = activeIndex.value === 0 ? step1 :
activeIndex.value === 1 ? step2 :
activeIndex.value === 2 ? step3 : step4
handleFatalError(timeoutStep, timeoutStepLog, 'Socket连接超时检测终止')
ts.value = 'error'
step5.value = 'error'
break;
case 'server_error':
ElMessageBox.alert('服务端主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
// 根据当前步骤选择对应的日志记录
const serverStepLog = activeIndex.value === 0 ? step1InitLog :
activeIndex.value === 1 ? step2InitLog :
activeIndex.value === 2 ? step3InitLog : step4InitLog
const serverStep = activeIndex.value === 0 ? step1 :
activeIndex.value === 1 ? step2 :
activeIndex.value === 2 ? step3 : step4
handleFatalError(serverStep, serverStepLog, '服务端主动关闭连接!')
ts.value = 'error'
step5.value = 'error'
break;
case 'device_error':
ElMessageBox.alert('设备主动关闭连接!', '初始化失败', {
confirmButtonText: '确定',
type: 'error',
})
// 根据当前步骤选择对应的日志记录
const deviceStepLog = activeIndex.value === 0 ? step1InitLog :
activeIndex.value === 1 ? step2InitLog :
activeIndex.value === 2 ? step3InitLog : step4InitLog
const deviceStep = activeIndex.value === 0 ? step1 :
activeIndex.value === 1 ? step2 :
activeIndex.value === 2 ? step3 : step4
handleFatalError(deviceStep, deviceStepLog, '设备主动关闭连接!')
ts.value = 'error'
step5.value = 'error'
break;
}
}
})
// ==================== 界面联动控制 ====================
/**
* 监听当前激活步骤,自动展开对应的日志折叠面板
*/
watch(activeIndex, function (newValue, oldValue) {
if (newValue <= activeTotalNum.value - 2) {
collapseActiveName.value = (newValue + 1).toString()
@@ -479,10 +625,7 @@ watch(activeIndex, function (newValue, oldValue) {
}
})
/**
* 监听父组件传入的测试状态变化
* 处理测试开始和重置逻辑
*/
//监听goods_sn的变化
watch(testStatus, function (newValue, oldValue) {
ts.value = props.testStatus;
if (ts.value === 'start') {
@@ -503,23 +646,15 @@ watch(testStatus, function (newValue, oldValue) {
}
})
// ==================== 父子组件通信 ====================
const emit = defineEmits(['update:testStatus']);
/**
* 监听内部测试状态变化,同步给父组件
* 实现双向数据绑定
*/
//监听sn
watch(ts, function (newValue, oldValue) {
//修改父组件
emit('update:testStatus', ts.value)
})
// ==================== 对外暴露方法 ====================
/**
* 初始化参数方法
* 由父组件调用,用于重置所有步骤状态和日志
*/
// 定义一个初始化参数的方法
function initializeParameters() {
activeIndex.value = 0
step1.value = 'process'
@@ -556,7 +691,6 @@ function initializeParameters() {
}
// 暴露方法给父组件使用
defineExpose({
initializeParameters,
});
@@ -569,6 +703,9 @@ defineExpose({
flex-direction: row;
/* 横向排列 */
margin-top: 20px;
/* .dialog-left{
margin-right: 20px;
} */
}
.dialog-left {
@@ -576,6 +713,19 @@ defineExpose({
margin-left: 20px;
}
/* .dialog-left :deep(.test-head-steps){
height: 80px;
/* margin-bottom: 10px;
}
*/
/* .dialog-left :deep(.el-step__title) {
font-size: 18px !important; /* 设置标题字体大小
} */
/* .dialog-left :deep(.el-step__icon-inner) {
font-size: 24px !important;
} */
.dialog-right {
margin-left: 20px;

View File

@@ -1,292 +0,0 @@
<template>
<el-dialog title="实时数据详情" v-model='dialogVisible' @close="handleClose" v-bind="dialogBig">
<el-tabs v-model="activeTab" type="card">
<el-tab-pane
v-for="(device, deviceName, index) in testDataStructure"
:key="deviceName"
:name="`channel${index + 1}`">
<template #label>
<span>
{{ deviceName }}
<el-icon v-if="tabStatus[deviceName]" style="color: red; margin-left: 5px;">
<CircleClose />
</el-icon>
<el-icon v-else style="color: green; margin-left: 5px;">
<CircleCheck />
</el-icon>
</span>
</template>
<div class="table-toolbar">
<el-form-item label="标准设备通道号" prop="createId">
<el-select
v-model="selectedChannels[deviceName]"
placeholder="选择通道"
style="width: 150px;"
@change="() => handleDutChannelChange(deviceName)">
<el-option
v-for="channel in device.channelDataList"
:key="channel.stdDevNum"
:label="`通道${channel.stdDevNum}`"
:value="`通道${channel.stdDevNum}`">
</el-option>
</el-select>
<span style="margin-left: 20px; font-size: 14px; color: var(--el-color-primary);">
标准设备{{ deviceName }}-{{ selectedChannels[deviceName] }} ---> 被检设备{{ formatDutChannelLabel(getMappedDutChannel(deviceName, selectedChannels[deviceName])) }}
</span>
</el-form-item>
<el-button type="primary" @click="exportData">导出数据</el-button>
</div>
<el-table
:data="tableDataMap[deviceName]"
:header-cell-style="{ textAlign: 'center',backgroundColor: 'var(--el-color-primary)',color: '#fff' } "
:cell-style="{ textAlign: 'center' }"
style="width: 100%"
:style="{ height: '400px',maxHeight: '400px',overflow:'hidden'}">
<el-table-column :label="`${deviceName}-${selectedChannels[deviceName] || '通道1'}`">
<el-table-column prop="timeStdDev" label="数据时标" width="200"/>
<el-table-column prop="uaStdDev" label="A相(V)"/>
<el-table-column prop="ubStdDev" label="B相(V)"/>
<el-table-column prop="ucStdDev" label="C相(V)"/>
</el-table-column>
<el-table-column :label="formatDutChannelLabel(getMappedDutChannel(deviceName, selectedChannels[deviceName]))">
<el-table-column prop="timeDev" label="数据时标" width="200"/>
<el-table-column prop="uaDev" label="A相(V)"/>
<el-table-column prop="ubDev" label="B相(V)"/>
<el-table-column prop="ucDev" label="C相(V)"/>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script setup lang='tsx' name='realTimeDataAlign'>
import { dialogBig } from "@/utils/elementBind";
import { PropType, ref, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { CircleCheck, CircleClose } from '@element-plus/icons-vue';
import {exportAlignData} from "@/api/socket/socket";
import {useDownload} from "@/hooks/useDownload";
const dialogVisible = ref(false);
const activeTab = ref('channel1');
// 在 script setup 中定义接口
interface ChannelData {
stdDevNum: string;
devInfo: string;
dataList: {
timeDev: string | null;
uaDev: number | null;
ubDev: number | null;
ucDev: number | null;
timeStdDev: string | null;
uaStdDev: number | null;
ubStdDev: number | null;
ucStdDev: number | null;
}[];
}
interface DeviceData {
stdDevName: string;
channelDataList: ChannelData[];
}
// 修改 testDataStructure 的类型声明
const testDataStructure = ref<Record<string, DeviceData>>({});
// 每个设备选中的通道
const selectedChannels = ref<Record<string, string>>({});
// 通道映射关系:标准设备通道 -> 被检设备通道
const channelMapping = ref<Record<string, Record<string, string>>>({});
// 每个设备的表格数据
const tableDataMap = ref<Record<string, any[]>>({});
// 每个tab的状态true表示有不完整数据false表示数据完整
const tabStatus = ref<Record<string, boolean>>({});
// 检查设备数据是否有不完整的行包含null的行
const hasIncompleteData = (deviceName: string) => {
const tableData = tableDataMap.value[deviceName];
if (!tableData || tableData.length === 0) return false;
// 检查每一行是否有缺失数据包含null的字段
return tableData.some(row => {
return row.uaDev === '/' || row.ubDev === '/' || row.ucDev === '/' ||
row.uaStdDev === '/' || row.ubStdDev === '/' || row.ucStdDev === '/';
});
};
// 获取映射的被检设备通道
const getMappedDutChannel = (deviceName: string, stdChannel: string) => {
// 添加安全检查
if (!channelMapping.value[deviceName]) return '';
return channelMapping.value[deviceName][stdChannel] || '';
};
// 格式化被检设备通道标签,将设备名称和通道号用"-"连接
const formatDutChannelLabel = (dutChannel: string) => {
// 如果是"被检设备X通道Y"格式,则转换为"被检设备X-通道Y"
if (!dutChannel) return '未映射';
return dutChannel.replace(/(.+)(通道\d+)/, '$1-$2');
};
// 处理标准设备通道切换
const handleDutChannelChange = (deviceName: string) => {
// 更新指定设备的表格数据但不改变tab图标状态
updateTableData(deviceName);
};
// 根据 testDataStructure 生成表格数据
const generateTableData = (deviceName: string, stdChannel: string) => {
const deviceData = testDataStructure.value[deviceName];
if (!deviceData) return [];
// 根据实际通道编号查找对应的数据
const channelData = deviceData.channelDataList.find(channel =>
`通道${channel.stdDevNum}` === stdChannel
);
if (!channelData) return [];
// 生成表格数据
return channelData.dataList.map(dataItem => {
return {
timeDev: dataItem.timeDev !== null ? dataItem.timeDev : '/',
uaDev: dataItem.uaDev !== null ? dataItem.uaDev : '/',
ubDev: dataItem.ubDev !== null ? dataItem.ubDev : '/',
ucDev: dataItem.ucDev !== null ? dataItem.ucDev : '/',
timeStdDev: dataItem.timeStdDev !== null ? dataItem.timeStdDev : '/',
uaStdDev: dataItem.uaStdDev !== null ? dataItem.uaStdDev : '/',
ubStdDev: dataItem.ubStdDev !== null ? dataItem.ubStdDev : '/',
ucStdDev: dataItem.ucStdDev !== null ? dataItem.ucStdDev : '/'
};
});
};
// 更新指定设备的表格数据
const updateTableData = (deviceName: string) => {
const selectedChannel = selectedChannels.value[deviceName];
if (selectedChannel) {
const tableData = generateTableData(deviceName, selectedChannel);
tableDataMap.value[deviceName] = tableData;
}
};
// 初始化所有设备的数据和状态
const initAllTableData = () => {
Object.keys(testDataStructure.value).forEach(deviceName => {
// 确保设备有数据
if (!testDataStructure.value[deviceName] ||
!testDataStructure.value[deviceName].channelDataList ||
testDataStructure.value[deviceName].channelDataList.length === 0) {
return;
}
// 默认选择第一个可用通道
const firstChannel = testDataStructure.value[deviceName].channelDataList[0];
selectedChannels.value[deviceName] = `通道${firstChannel.stdDevNum}`;
// 生成表格数据
updateTableData(deviceName);
// 初始化tab状态只在初始化时设置一次
if (tabStatus.value[deviceName] === undefined) {
tabStatus.value[deviceName] = hasIncompleteData(deviceName);
}
});
};
const open = async (mapping : Record<string, Record<string, string>>,data : any) => {
let parsedData = data;
// 如果 data 是字符串,先解析为对象
if (typeof data === 'string') {
try {
parsedData = JSON.parse(data);
} catch (error) {
console.error('数据解析失败:', error);
ElMessage.error('数据格式错误');
return;
}
}
// 转换数据格式以匹配组件期望的格式
const convertedData: Record<string, DeviceData> = {};
// 假设传入的数据是一个数组,需要转换为以设备名为键的对象
if (Array.isArray(parsedData)) {
parsedData.forEach((deviceItem: any) => {
const deviceName = deviceItem.stdDevName;
convertedData[deviceName] = {
stdDevName: deviceName,
channelDataList: deviceItem.channelDataList?.map((channel: any) => ({
stdDevNum: channel.stdDevNum,
devInfo: channel.devInfo,
dataList: channel.dataList?.map((dataItem: any) => ({
timeDev: dataItem.timeDev,
uaDev: dataItem.uaDev,
ubDev: dataItem.ubDev,
ucDev: dataItem.ucDev,
timeStdDev: dataItem.timeStdDev,
uaStdDev: dataItem.uaStdDev,
ubStdDev: dataItem.ubStdDev,
ucStdDev: dataItem.ucStdDev
})) || []
})) || []
};
});
} else if (parsedData && typeof parsedData === 'object') {
// 如果已经是期望的格式,直接使用
Object.assign(convertedData, parsedData);
}
testDataStructure.value = convertedData;
channelMapping.value = mapping;
dialogVisible.value = true;
// 使用 nextTick 确保 DOM 更新后再初始化数据
await nextTick();
// 初始化数据和状态
initAllTableData();
// 设置默认激活的 tab
activeTab.value = 'channel1';
};
// 导出数据
const exportData =async () => {
useDownload(exportAlignData, '原始数据', null, false, '.xlsx')
ElMessage.success('数据导出成功');
// 这里可以添加实际的数据导出逻辑
};
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false;
// 清空数据
testDataStructure.value = {};
tableDataMap.value = {};
selectedChannels.value = {};
tabStatus.value = {};
};
defineExpose({ open });
</script>
<style scoped lang="scss">
.table-toolbar {
display: flex;
justify-content: space-between;
}
:deep(.el-dialog__body) {
padding: 10px 20px 20px 20px;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<el-dialog title="报告生成" :model-value='visible' @close="handleCancel" width="832px" draggable>
<div class="report-dialog">
<div class="report-title form-two">
<el-form-item label="检测脚本" label-width="100px">
<el-input v-model='testScriptName' :disabled="true"/>
</el-form-item>
<el-form-item label="误差体系" label-width="100px">
<el-input v-model='errorSysName' :disabled="true"/>
</el-form-item>
<el-form-item label="数据原则" label-width="100px">
<el-input v-model='dataRule' :disabled="true"/>
</el-form-item>
<el-form-item label="报告模板" label-width="100px">
<el-input v-model='reportTemplate' :disabled="true"/>
</el-form-item>
</div>
<div class="report-content">
<div>
<el-tabs type="border-card">
<el-tab-pane label="报告生成进度">
<div class="form-grid">
<div class="tabs-title ">
<el-button type="primary" :icon="Download" >报告下载</el-button>
<span style=" font-size: 18px;font-weight: 600;">
已生成 <span style="color: #91cc75">2</span> / <span style="color: green">3</span>
</span>
</div>
<div class="table-main">
<el-table :data="reportData" :header-cell-style="{ textAlign: 'center' } " :cell-style="{ textAlign: 'center' }" style="width: 100%" border class="custom-table">
<el-table-column type="selection" width="55" />
<el-table-column prop="id" width="70" label="序号" />
<el-table-column prop="deviceName" width="150" label="设备名称" />
<el-table-column label="生成进度">
<template #default="scope">
<el-progress :color="customColors" :percentage="scope.row.processValue" />
</template>
</el-table-column>
<el-table-column prop="action" label="操作" width="100">
<template #default="scope">
<el-button type='primary' link :icon='Download' :disabled="scope.row.processValue < 100">下载</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup lang='ts'>
import IPAddress from '@/components/IpAddress/index.vue'
import { dialogBig } from '@/utils/elementBind'
import { type Device } from '@/api/device/interface/device'
import { ElMessage, type FormItemRule } from 'element-plus'
import { addPqDev, updatePqDev } from '@/api/device/device'
import { computed, reactive, type Ref, ref } from 'vue'
import { useDictStore } from '@/stores/modules/dict'
import { CirclePlus, Delete, Download,View } from '@element-plus/icons-vue'
const reportTemplate = ref('国网检测模板V1.0');
const testScriptName = ref('Q/GDW 10650.4-2021 模拟式');
const errorSysName = ref('Q/GDW 10650.2-2021');
const dataRule = ref('所有值');
const scriptSwitch = ref(true);
const currentScriptDsc = ref('电压准确度检测频率42.5Hz Ua=46.192V 0° Ub=46.192V -120° Uc=46.192V 120° Ia=1A 0° Ib=1A -120° Ic=1A 120°');
const reportData = ref([
{ id: '1', deviceName: '240001', processValue: '100' , action:'查看' },
{ id: '2', deviceName: '240002', processValue: '100' , action:'查看' },
{ id: '3', deviceName: '240003', processValue: '10', action:'查看' },
])
const customColors = [
{ color: "red", percentage: 0 },
{ color: "red", percentage: 10 },
{ color: "red", percentage: 20 },
{ color: "red", percentage: 30 }, //红
{ color: "red", percentage: 40 },
{ color: "#e6a23c", percentage: 50 },
{ color: "#e6a23c", percentage: 60 },
{ color: "#e6a23c", percentage: 70 }, //黄
{ color: "#e6a23c", percentage: 80 }, //1989fa
{ color: "#e6a23c", percentage: 90 }, //1989fa
{ color: "#5cb87a", percentage: 100 }, //绿
];
const handleNodeClick = (data) => {
console.log(data);
};
const MonIsShow = ref(false)
const DevIsShow = ref(false)
const IsPasswordShow = ref(false)
const dictStore = useDictStore()
// 定义弹出组件元信息
const dialogFormRef = ref()
const disabledDate = (time: Date) => {
return time.getTime() > Date.now()
}
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'submit', data: any): void;
}>();
const handleCancel = () => {
emit('update:visible', false); // 关闭对话框
};
</script>
<style scoped>
.report-dialog{
display: flex;
flex-direction: column;
}
.report-title{
margin-left: 15px;
/* display: flex; */
/* flex-direction: row;
margin-top: 10px; */
}
.report-content{
display: flex;
flex-direction: column;
}
.tabs-title{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.el-tabs__content{
padding-top: 5px !important;
}
</style>

View File

@@ -1,267 +0,0 @@
<template>
<el-dialog
v-model="dialogVisible"
title="报告生成"
destroy-on-close
width="750"
draggable
:close-on-click-modal="false"
@close="handleClose"
>
<el-tabs v-if="dialogVisible" v-model="activeName" @tab-click="handleTabClick">
<el-tab-pane
v-for="(result, index) in resultData"
:key="result.monitorId"
:label="`测量回路${result.monitorNum}`"
:name="index"
>
<el-row :gutter="20" style="margin-top: 10px">
<el-col :span="8">
<div
style="
background-color: #f9fafb;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
border-radius: 5px;
"
>
<el-text style="margin-right: 10px">总测试次数</el-text>
<el-text size="large" type="primary" tag="b">
{{ result.totalNum }}
</el-text>
</div>
</el-col>
<el-col :span="8">
<div
style="
background-color: #f9fafb;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
border-radius: 5px;
"
>
<el-text style="margin-right: 10px">符合次数</el-text>
<el-text size="large" type="success" tag="b">{{ result.qualifiedNum }}</el-text>
</div>
</el-col>
<el-col :span="8">
<div
style="
background-color: #f9fafb;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
border-radius: 5px;
"
>
<el-text style="margin-right: 10px">不符合次数</el-text>
<el-text size="large" type="danger" tag="b">{{ result.unQualifiedNum }}</el-text>
</div>
</el-col>
</el-row>
<el-descriptions :column="1" style="margin-top: 20px">
<el-descriptions-item label-align="right">
<template #label>
<el-text type="info">测试标准</el-text>
</template>
<el-text>{{ result.errorSysName }}</el-text>
</el-descriptions-item>
<el-descriptions-item label-align="right">
<template #label>
<el-text type="info">检测结论</el-text>
</template>
<el-tag disable-transitions v-if="result.checkResult === 1" type="success">符合</el-tag>
<el-tag disable-transitions v-else-if="result.checkResult === 2" type="danger">不符合</el-tag>
<el-tag disable-transitions v-else-if="result.checkResult === 4" type="danger">无法比较</el-tag>
</el-descriptions-item>
<el-descriptions-item label-align="right">
<template #label>
<el-text type="info">结论来源</el-text>
</template>
<el-text>{{ result.whichTime }}次检测的{{ result.resultOrigin }}</el-text>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button type="primary" size="small" @click="handleChooseClick">重新选择</el-button>
<el-button type="primary" @click="handleConfirmGenerate">确认生成</el-button>
</template>
<!-- 选择检测数据源弹框-->
<el-dialog
v-model="dialogSourceVisible"
append-to-body
title="选择检测数据源"
destroy-on-close
width="400"
:close-on-click-modal="false"
>
<el-form ref="formRef" :rules="rules" :model="submitSourceData" label-width="120px" label-position="top">
<el-form-item label="选择次数:" prop="whichTime">
<el-select v-model="submitSourceData.whichTime" placeholder="请选择次数" @change="handleTimeChange">
<el-option
v-for="time in whichTimeData"
:key="time"
:label="`第${time}次`"
:value="time"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据源和检测结论:" prop="resultType">
<el-select
v-model="submitSourceData.resultType"
placeholder="请选择数据源和检测结论"
clearable
@change="handleSourceChange"
>
<template #label="{ label }">
<div style="display: flex; align-items: center; justify-content: space-between">
<el-text>{{ label }}</el-text>
<el-tag disable-transitions v-if="submitSourceData.checkResult === 1" type="success">
符合
</el-tag>
<el-tag disable-transitions v-if="submitSourceData.checkResult === 2" type="danger">
不符合
</el-tag>
<el-tag disable-transitions v-if="submitSourceData.checkResult === 4" type="info">
无法比较
</el-tag>
</div>
</template>
<el-option
v-for="item in sourceData"
:key="item.dataSourceCode"
:label="item.dataSourceName"
:value="item.dataSourceCode"
>
<div style="display: flex; align-items: center; justify-content: space-between">
<el-text>{{ item.dataSourceName }}</el-text>
<el-tag v-if="item.checkResult === 1" type="success">符合</el-tag>
<el-tag v-if="item.checkResult === 2" type="danger">不符合</el-tag>
</div>
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogSourceVisible = false">取消</el-button>
<el-button type="primary" @click="handleSureChoose">确认</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts" name="reportPopup">
import { getMonitorDataSourceResult, getMonitorResult, updateMonitorResult } from '@/api/result/result'
import { type MonitorResult } from '@/api/result/interface'
import { generateDevReport } from '@/api/plan/plan'
import { useCheckStore } from '@/stores/modules/check'
import { ElMessage } from 'element-plus'
const dialogVisible = ref(false)
const dialogSourceVisible = ref(false)
const devData = ref<any>()
const activeName = ref<number>(0)
const checkStore = useCheckStore()
// 定义 emit 事件
const emit = defineEmits<{
(e: 'reportGenerated'): void
}>()
const resultData = ref<MonitorResult[]>([])
const resultSourceData = ref<any>({})
const whichTimeData = ref<any>([])
const sourceData = ref<any>([])
const formRef = ref()
const submitSourceData = reactive({
monitorId: '',
whichTime: '',
resultType: '',
checkResult: -1
})
const rules = {
whichTime: [{ required: true, message: '请选择次数', trigger: 'change' }],
resultType: [{ required: true, message: '请选择数据源和检测结论', trigger: 'change' }]
}
const handleClose = () => {
activeName.value = 0
}
const open = (data: any) => {
devData.value = data
getResultData()
}
const getResultData = async () => {
const res = await getMonitorResult(devData.value.id)
if (res.data && Array.isArray(res.data)) {
resultData.value = res.data
}
dialogVisible.value = true
}
const handleTabClick = (tab: any) => {
activeName.value = tab.name
}
const handleChooseClick = async () => {
const currentResult = resultData.value[activeName.value]
if (currentResult) {
submitSourceData.monitorId = currentResult.monitorId
submitSourceData.whichTime = currentResult.whichTime
submitSourceData.resultType = currentResult.resultType
submitSourceData.checkResult = currentResult.checkResult
const res = await getMonitorDataSourceResult(currentResult.monitorId)
if (res.data) {
resultSourceData.value = res.data
// 选择第几次
whichTimeData.value = Object.keys(resultSourceData.value)
sourceData.value = resultSourceData.value[currentResult.whichTime]
}
}
dialogSourceVisible.value = true
}
const handleTimeChange = (value: any) => {
sourceData.value = resultSourceData.value[value]
submitSourceData.resultType = ''
submitSourceData.checkResult = -1
}
const handleSourceChange = (value: any) => {
submitSourceData.checkResult = resultSourceData.value[submitSourceData.whichTime].find(
(item: any) => item.dataSourceCode === value
).checkResult
}
const handleSureChoose = () => {
formRef.value.validate().then(async () => {
await updateMonitorResult(submitSourceData)
await getResultData()
dialogSourceVisible.value = false
})
}
// 处理确认生成报告
const handleConfirmGenerate = async () => {
try {
await generateDevReport({
planId: checkStore.plan.id,
devIdList: [devData.value.id],
scriptId: checkStore.plan.scriptId,
planCode: checkStore.plan.code + '',
pageNum: 1,
pageSize: 999
})
ElMessage.success({ message: `报告生成成功!` })
dialogVisible.value = false
emit('reportGenerated') // 触发事件通知父组件
} catch (error) {
ElMessage.error('报告生成失败')
console.error('报告生成错误:', error)
}
}
defineExpose({
open
})
</script>
<style scoped lang="scss"></style>

View File

@@ -1,111 +1,113 @@
<template>
<el-dialog title="检测结果" :model-value="visible" @close="handleCancel" v-bind="dialogBig" width="895px">
<div class="result-dialog">
<div class="result-title">
<el-row>
<el-form-item label="检测脚本">
<el-input v-model="testScriptName" :disabled="true" />
</el-form-item>
<el-form-item label="误差体系">
<el-input v-model="errorSysName" :disabled="true" />
</el-form-item>
<el-form-item label="数据原则">
<el-input v-model="dataRule" :disabled="true" />
</el-form-item>
</el-row>
</div>
<div class="result-content">
<el-table
:data="resultData"
stripe
max-height="350"
:header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }"
style="width: 100%"
border
v-on:cell-click="handleClick"
>
<el-table-column prop="deviceName" label="被检设备" />
<el-table-column prop="result_1" label="通道1" />
<el-table-column prop="result_2" label="通道2" />
<el-table-column prop="result_3" label="通道3" />
<el-table-column prop="result_4" label="通道4" />
</el-table>
</div>
<div class="result-footer">你可以停留在本页查看数据或返回首页进行复检报告生成和归档</div>
</div>
<DataCheckPopup
:visible="DataCheckDialogVisible"
<el-dialog title="检测结果" :model-value="visible" @close="handleCancel" v-bind="dialogBig" width="895px">
<div class="result-dialog">
<div class="result-title">
<el-row>
<el-form-item label="检测脚本" >
<el-input v-model='testScriptName' :disabled="true"/>
</el-form-item>
<el-form-item label="误差体系" >
<el-input v-model='errorSysName' :disabled="true"/>
</el-form-item>
<el-form-item label="数据原则" >
<el-input v-model='dataRule' :disabled="true"/>
</el-form-item>
</el-row>
</div>
<div class="result-content">
<el-table :data="resultData" stripe max-height="350" :header-cell-style="{ textAlign: 'center' } " :cell-style="{ textAlign: 'center' }" style="width: 100%" border v-on:cell-click="handleClick">
<el-table-column prop="deviceName" label="被检设备" />
<el-table-column prop="result_1" label="通道1" />
<el-table-column prop="result_2" label="通道2" />
<el-table-column prop="result_3" label="通道3" />
<el-table-column prop="result_4" label="通道4" />
</el-table>
</div>
<div class="result-footer">
你可以停留在本页查看数据或返回首页进行复检报告生成和归档
</div>
</div>
<DataCheckPopup
:visible="DataCheckDialogVisible"
@update:visible="DataCheckDialogVisible = $event"
></DataCheckPopup>
></DataCheckPopup>
</el-dialog>
</template>
<script lang="ts" setup name="testPopup">
import{ElMessage, ElSelectV2, FormInstance,FormItemRule}from'element-plus'
import { defineProps, defineEmits, reactive,watch,ref, Ref } from 'vue';
import { dialogBig,dialogMiddle} from '@/utils/elementBind'
//import IndicatorTypeDialog from "@/views/machine/errorSystem/components/IndicatorTypeDialog.vue"; // 导入子组件
import {CirclePlus, Delete, EditPen,FolderOpened,CopyDocument,Edit, Picture, UploadFilled, SuccessFilled,VideoPlay,Right,Refresh,Close} from '@element-plus/icons-vue'
import { useDictStore } from '@/stores/modules/dict'
import preTest from './preTest.vue'
import timeTest from './timeTest.vue'
import channelsTest from './channelsTest.vue'
import DataCheckPopup from './dataCheckPopup.vue';
import { log } from 'console';
<script lang="ts" setup name="testPopup">
import { ref } from 'vue'
import { dialogBig } from '@/utils/elementBind'
//import IndicatorTypeDialog from "@/views/machine/errorSystem/components/IndicatorTypeDialog.vue"; // 导入子组件
import DataCheckPopup from './dataCheckPopup.vue'
const props = defineProps<{
visible: boolean;
}>();
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'submit', data: any): void;
}>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'submit', data: any): void
}>()
const testScriptName = ref('Q/GDW 10650.4-2021 模拟式')
const errorSysName = ref('Q/GDW 10650.2-2021')
const dataRule = ref('所有值')
const DataCheckDialogVisible = ref(false)
const testScriptName = ref('Q/GDW 10650.4-2021 模拟式');
const errorSysName = ref('Q/GDW 10650.2-2021');
const dataRule = ref('所有值');
const DataCheckDialogVisible = ref(false);
const resultData = ref([
{
deviceName: '被检设备1',
result_1: '合格',
result_2: '合格',
result_3: '合格',
result_4: '合格'
},
{
deviceName: '被检设备2',
result_1: '合格',
result_2: '合格',
result_3: '—',
result_4: '—'
},
{
deviceName: '被检设备3',
result_1: '不合格',
result_2: '合格',
result_3: '—',
result_4: '—'
}
])
{
deviceName: "被检设备1",
result_1: "合格",
result_2: "合格",
result_3: "合格",
result_4: "合格",
},
{
deviceName: "被检设备2",
result_1: "合格",
result_2: "合格",
result_3: "—",
result_4: "—",
},
{
deviceName: "被检设备3",
result_1: "不合格",
result_2: "合格",
result_3: "—",
result_4: "—",
},
]);
const handleClick = (row: any) => {
const handleClick = (row:any) => {
console.log(111)
DataCheckDialogVisible.value = true
}
DataCheckDialogVisible.value = true;
};
const handleCancel = () => {
emit('update:visible', false); // 关闭对话框
};
const handleCancel = () => {
emit('update:visible', false) // 关闭对话框
}
</script>
<style scoped>
.result-dialog {
</script>
<style scoped>
.result-dialog{
display: flex;
flex-direction: column;
}
.result-title {
.result-title{
/* display: flex;
flex-direction: row; */
}
.result-footer {
}
.result-footer{
text-align: right;
margin-top: 10px;
}

View File

@@ -8,7 +8,7 @@
<el-form-item v-if="checkStore.plan.timeCheck===1" prop="timeTest" :label-width="100">
<el-checkbox v-model="formContent.timeTest" label="守时检测"/>
</el-form-item>
<el-form-item v-if="channelsTestShow" prop="channelsTest" :label-width="100">
<el-form-item v-if="AppSceneStore.currentScene === '1'" prop="channelsTest" :label-width="100">
<el-checkbox v-model="formContent.channelsTest" label="系数校准"/>
</el-form-item>
<el-form-item prop="test" :label-width="100">
@@ -28,36 +28,25 @@
<script setup lang='tsx' name='selectTestItemPopup'>
import {dialogSmall} from "@/utils/elementBind";
import {reactive, ref} from "vue";
import {ref} from "vue";
import {useCheckStore} from "@/stores/modules/check";
import type {CheckData} from "@/api/check/interface";
import {ElMessageBox} from "element-plus";
import {useAppSceneStore,useModeStore} from "@/stores/modules/mode";
import {useAppSceneStore} from "@/stores/modules/mode";
const AppSceneStore = useAppSceneStore()
const emit = defineEmits(['openTestDialog'])
const checkStore = useCheckStore();
const modeStore = useModeStore()
const dialogFormRef = ref()
const channelsTestShow = ref(false)
const dialogVisible = ref(false)
const formContent = reactive<CheckData.SelectTestItem>({preTest: true, timeTest: false, channelsTest: false, test: false})
const open = async () => {
const open = async () => {
resetFormContent()
checkStore.setSelectTestItems(formContent)
dialogVisible.value = true
if(modeStore.currentMode === '比对式'){
channelsTestShow.value = false
}else{
if(AppSceneStore.currentScene === '1'){
channelsTestShow.value = true
}else{
channelsTestShow.value = false
}
}
}
// 清空表单内容
@@ -66,7 +55,7 @@ const resetFormContent = () => {
Object.assign(formContent, {preTest: !hasResult, channelsTest: false, timeTest: false, test: hasResult})
}
const handleStart = async () => {
const handleStart = () => {
let count = 0
for (let key in formContent) {
if (formContent[key]) {
@@ -100,9 +89,6 @@ const handleStart = async () => {
}
checkStore.setCheckType(0)
checkStore.setSelectTestItems({...formContent})
handleClose()
emit('openTestDialog',checkStore.selectTestItems.test)

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

@@ -1,319 +1,233 @@
<template>
<div class="plan_tree">
<div class="search_view">
<el-input
placeholder="请输入计划名称"
clearable
v-model="searchForm.planName"
show-word-limit
maxlength="32"
></el-input>
<el-tooltip content="检测计划列表" placement="top">
<Menu
style="width: 26px; height: 26px; margin-left: 8px; cursor: pointer; color: var(--el-color-primary)"
@click.stop="detail()"
/>
</el-tooltip>
</div>
<div class="tree_container">
<el-tree
:data="data"
ref="treeRef"
:filter-node-method="filterNode"
:props="defaultProps"
node-key="id"
class="filter-tree"
:highlight-current="true"
default-expand-all
:default-checked-keys="defaultChecked"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node" style="display: flex; align-items: center">
<!-- 父节点图标 -->
<Platform
v-if="!data.pid"
style="width: 18px; height: 18px; margin-right: 8px"
:style="{
color: node.label == '未检' ? '#fac858' : node.label == '检测中' ? '#ee6666' : '#91cc75'
}"
/>
<!-- 节点名称 -->
<span>{{ node.label }}</span>
<!-- 子节点右侧图标 + tooltip -->
<el-tooltip
v-if="
node.label != '未检' &&
node.label != '检测中' &&
node.label != '检测完成' &&
hasChildrenInPlanTable(node.data)
"
placement="top"
:manual="true"
content="子计划信息"
>
<List
@click.stop="childDetail(node.data)"
style="
width: 16px;
height: 16px;
margin-left: 8px;
cursor: pointer;
color: var(--el-color-primary);
"
/>
</el-tooltip>
</span>
</template>
</el-tree>
</div>
<div class='plan_tree'>
<div class='search_view'>
<el-input
placeholder='请输入计划名称'
clearable
v-model='searchForm.planName'
show-word-limit
maxlength="32"
></el-input>
<el-tooltip content="检测计划列表" placement="top">
<Menu style='width: 26px;height: 26px; margin-left: 8px;cursor: pointer;color:var(--el-color-primary)'
@click.stop='detail()' />
</el-tooltip>
</div>
<SourceOpen ref="openSourceView" :width="width" :height="height + 175"></SourceOpen>
<div class='tree_container'>
<el-tree
:data='data'
ref='treeRef'
:filter-node-method='filterNode'
:props='defaultProps'
node-key='id'
class="filter-tree"
:highlight-current="true"
default-expand-all
:default-checked-keys='defaultChecked'
@node-click='handleNodeClick'
>
<template #default='{ node, data }'>
<span class='custom-tree-node' style='display: flex;align-items: center;'>
<Platform v-if='!data.pid' style='width:18px;height: 18px;margin-right:8px;'
:style="{color:node.label=='未检'?'#fac858':node.label=='检测中'?'#ee6666':'#91cc75'}" />
<span>{{ node.label }}</span>
<!-- <Menu v-if="data.pid" @click.stop="detail(data)" style="width: 12px;margin-left: 8px;"/> -->
</span>
</template>
</el-tree>
</div>
</div>
</template>
<script lang="ts" setup>
import { type Plan } from '@/api/plan/interface'
import { List, Menu, Platform } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref, watch } from 'vue'
<script lang='ts' setup>
import { type Plan } from '@/api/plan/interface';
import { Menu, Platform, CircleCheck,Loading } from '@element-plus/icons-vue'
import { nextTick, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router'
import { useCheckStore } from '@/stores/modules/check'
import { ElTooltip } from 'element-plus'
import SourceOpen from '@/views/plan/planList/components/childrenPlan.vue'
import { getPlanList } from '@/api/plan/plan.ts'
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
import { useDictStore } from '@/stores/modules/dict'
import {useCheckStore} from "@/stores/modules/check";
import { ElTooltip } from 'element-plus';
const openSourceView = ref()
const router = useRouter()
const checkStore = useCheckStore()
const filterText = ref('')
const treeRef = ref()
const data: any = ref([])
const modeStore = useModeStore()
const dictStore = useDictStore()
const defaultProps = {
children: 'children',
label: 'name',
pid: 'pid'
children: 'children',
label: 'name',
pid: 'pid',
}
const searchForm = ref({
planName: ''
planName: '',
})
const defaultChecked = ref<string[]>([]) // 明确类型为 number[]
const tree = ref(false) //确保左侧树高凉只执行一次
const defaultChecked = ref<string[]>([]) // 明确类型为 number[]
const tree = ref(false)//确保左侧树高凉只执行一次
const getTreeData = (val: any) => {
defaultChecked.value = []
// 遍历 val 的每个 children过滤掉 pid !== '0'
data.value = val
for (let item of data.value) {
if (item.children.length > 0) {
let node = item.children[0]
defaultChecked.value.push(node.id)
checkStore.setPlan(node)
// 高亮显示第一个节点
// 使用 nextTick 确保在 DOM 更新后调用 setCurrentKey
nextTick(() => {
treeRef.value?.setCurrentKey(node.id)
idd.value = node.id
})
// 找到第一个符合条件的 children 后跳出循环
break
}
defaultChecked.value = [];
data.value = val;
for (let item of data.value) {
if (item.children.length > 0) {
let node = item.children[0];
defaultChecked.value.push(node.id);
checkStore.setPlan(node);
// 高亮显示第一个节点
// 使用 nextTick 确保在 DOM 更新后调用 setCurrentKey
nextTick(() => {
treeRef.value?.setCurrentKey(node.id);
idd.value = node.id;
});
// 找到第一个符合条件的 children 后跳出循环
break;
}
}
}
//点击表格后左侧树刷新,高亮显示对应节点
const clickTableToTree = (val: any, id: any) => {
defaultChecked.value = []
data.value = val
let node = ref('')
if (data.value.length > 0) {
for (let i = 0; i < data.value.length; i++) {
for (let j = 0; j < data.value[i].children.length; j++) {
if (data.value[i].children[j].id == id) {
node.value = data.value[i].children[j].id
break
}
const clickTableToTree = (val: any,id:any) => {
defaultChecked.value = []
data.value = val
let node = ref('')
if (data.value.length > 0) {
for (let i = 0; i < data.value.length; i++){
for (let j = 0; j < data.value[i].children.length; j++) {
if (data.value[i].children[j].id == id) {
node.value = data.value[i].children[j].id
break;
}
}
// 使用 nextTick 确保在 DOM 更新后调用 setCurrentKey
nextTick(() => {
treeRef.value?.setCurrentKey(node.value)
idd.value = node.value
})
}
// 使用 nextTick 确保在 DOM 更新后调用 setCurrentKey
nextTick(() => {
treeRef.value?.setCurrentKey(node.value);
idd.value = node.value
});
}
}
const { updateSelectedTreeNode, width, height, planTable } = defineProps<{
updateSelectedTreeNode: Function
width: number
height: number
planTable?: Array<[]>
}>()
const {updateSelectedTreeNode} = defineProps<{
updateSelectedTreeNode:Function;
}>();
watch(
() => searchForm.value.planName,
val => {
treeRef.value!.filter(val)
},
{
deep: true
}
() => searchForm.value.planName,
(val) => {
treeRef.value!.filter(val)
},
{
deep: true,
},
)
const hasChildrenInPlanTable = (nodeData: Plan.ResPlan) => {
try {
// 在 planTable 中查找对应的节点数据
const foundItem = tableData.value.find((item: any) => item.id === nodeData.id)
// 检查是否有 children 且 children 数组不为空
return foundItem && Array.isArray(foundItem.children) && foundItem.children.length > 0
} catch (error) {
console.error('检查子节点时出错:', error)
return false
}
}
const idd = ref('')
const handleNodeClick = (data: Plan.ResPlan) => {
if (data.name === '未检' || data.name === '检测中' || data.name === '检测完成') {
// 如果是父节点,不执行任何操作
//console.log('父节点不执行任何操作');
// 设置当前高亮节点
nextTick(() => {
treeRef.value?.setCurrentKey(idd.value)
})
return
}
// 如果是父节点,不执行任何操作
//console.log('父节点不执行任何操作');
// 设置当前高亮节点
nextTick(() => {
treeRef.value?.setCurrentKey(idd.value);
});
return;
}
idd.value = data.id
idd.value = data.id
checkStore.setPlan(data)
updateSelectedTreeNode(data.id)
checkStore.setPlan(data);
updateSelectedTreeNode(data.id)
}
const filterNode = (value: string, data: any) => {
if (!value) return true
return data.name.includes(value)
if (!value) return true
return data.name.includes(value)
}
// 点击详情
const detail = () => {
router.push('/plan/planList')
router.push('/plan/planList')
}
const childDetail = (data: Plan.ResPlan) => {
const filteredPlans = tableData.value.filter(item => item.id === data.id)
// 确保有匹配项再访问 [0]
if (filteredPlans.length > 0 && openSourceView.value) {
openSourceView.value.open('检测计划详情', filteredPlans[0])
}
}
function buildTree(flatList: any[]): any[] {
const map = new Map()
const tree: any[] = []
// First, create a map of all items by id for fast lookup
flatList.forEach(item => {
map.set(item.id, { ...item, children: [] })
})
// Then, assign each item to its parent's children array
flatList.forEach(item => {
if (item.fatherPlanId && map.has(item.fatherPlanId)) {
map.get(item.fatherPlanId).children.push(map.get(item.id))
} else if (item.fatherPlanId === '0') {
// Items with fatherPlanId '0' are root nodes
tree.push(map.get(item.id))
}
})
return tree
}
const tableData = ref<any[]>([])
onMounted(async () => {
if (modeStore.currentMode != '比对式') return
const patternId = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id
const result = await getPlanList({ patternId: patternId })
tableData.value = buildTree(result.data as any[])
onMounted(() => {
// console.log()
})
defineExpose({ getTreeData ,clickTableToTree})
defineExpose({ getTreeData, clickTableToTree })
</script>
<style lang="scss" scoped>
<style lang='scss' scoped>
.plan_tree {
height: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #fff;
.search_view {
width: 100%;
height: auto;
display: flex;
flex-direction: column;
background-color: #fff;
.search_view {
width: 100%;
height: auto;
display: flex;
justify-content: space-between;
padding: 0 5px;
box-sizing: border-box;
align-items: center;
.el-input {
margin-top: 6px;
}
}
justify-content: space-between;
padding: 0 5px;
box-sizing: border-box;
align-items: center;
.el-input {
width: 100%;
margin: 0 10px 10px 0;
margin-top: 6px;
}
}
.tree_container {
height: 100%;
width: 100%;
flex: 1;
overflow-y: auto;
overflow-x: auto;
.el-input {
width: 100%;
margin: 0 10px 10px 0;
}
.el-tree {
// height: 100%;
width: auto;
}
.tree_container {
height: 100%;
width: 100%;
flex: 1;
overflow-y: auto;
overflow-x: auto;
.el-tree {
// height: 100%;
width: auto;
}
}
}
.filter-tree {
// border: 1px solid #dcdfe6;
min-width: 100%;
height: 97%;
display: inline-block;
overflow: auto;
margin-top: 12px;
// border: 1px solid #dcdfe6;
min-width: 100%;
height: 97%;
display: inline-block;
overflow: auto;
margin-top: 12px;
}
//.filter-tree span {
// font-size: 16px;
// display:block;
// overflow:hidden;
// word-break:keep-all;
// white-space:nowrap;
// text-overflow:ellipsis;
// word-break:keep-all;
// white-space:nowrap;
// text-overflow:ellipsis;
// padding-right: 12px;
//}
.leftBox {
// float: left;
// width: 20%;
height: 100%;
width: 100%;
// float: left;
// width: 20%;
height: 100%;
width: 100%;
}
.left {
height: calc(100% - 45px);
overflow: auto;
height: calc(100% - 45px);
overflow: auto;
}
/* 设置滚动条宽度 */
:deep(.bodyTwo ::-webkit-scrollbar) {
width: 3px !important;
height: 6px !important;
}
width: 3px !important;
height: 6px !important;
}
</style>

View File

@@ -1,39 +1,39 @@
<template>
<el-dialog title='填写实验室环境' v-model='dialogVisible' @close='handleClose' v-bind='dialogSmall'>
<el-dialog title="填写实验室环境" v-model='dialogVisible' @close="handleClose" v-bind="dialogSmall" >
<div>
<el-form ref='dialogFormRef' :model='formContent' :rules='rules'>
<el-form-item label='温度(℃)' prop='temperature' :label-width='110'>
<el-input v-model='formContent.temperature' placeholder='请输入温度' maxlength='32' show-word-limit />
</el-form-item>
<el-form-item label='相对湿度(%)' prop='humidity' :label-width='110'>
<el-input v-model='formContent.humidity' placeholder='请输入湿度' maxlength='32' show-word-limit />
</el-form-item>
<el-form ref="dialogFormRef" :model="formContent" :rules='rules' >
<el-form-item label="温度(℃)" prop="temperature" :label-width="110">
<el-input v-model="formContent.temperature" placeholder="请输入温度" maxlength="32" show-word-limit/>
</el-form-item>
<el-form-item label="相对湿度(%)" prop="humidity" :label-width="110">
<el-input v-model="formContent.humidity" placeholder="请输入湿度" maxlength="32" show-word-limit/>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class='dialog-footer'>
<el-button @click='handleClose'>取消</el-button>
<el-button type='primary' @click='handleStart'>确定</el-button>
<span class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleStart">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang='tsx' name='selectTestItemPopup'>
import { dialogSmall } from '@/utils/elementBind'
import { reactive, Ref, ref } from 'vue'
import type { Device } from '@/api/device/interface/device.ts'
import { ElMessageBox, FormItemRule } from 'element-plus'
import { useCheckStore } from '@/stores/modules/check'
import {dialogSmall} from "@/utils/elementBind";
import {reactive, Ref, ref} from "vue";
import type {Device} from "@/api/device/interface/device.ts";
import {ElMessageBox, FormItemRule} from "element-plus";
import {useCheckStore} from "@/stores/modules/check";
const emit = defineEmits(['openTestDialog2'])
const dialogFormRef = ref()
const dialogVisible = ref(false)
const formContent = reactive<Device.ResTH>({ temperature: 0, humidity: 0 })
const checkStore = useCheckStore()
const formContent = reactive<Device.ResTH>({temperature:0,humidity:0})
const checkStore = useCheckStore();
const open = async () => {
resetFormContent()
dialogVisible.value = true
@@ -41,39 +41,35 @@ const open = async () => {
// 清空表单内容
const resetFormContent = () => {
Object.assign(formContent, { temperature: '22', humidity: '50' })
Object.assign(formContent,{temperature:'',humidity:''})
}
//定义校验规则
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
temperature: [{ required: true, message: '温度必填!', trigger: 'blur' },
// 指定正则,此处是数字正则
{
pattern: /^(?:(?:-50)|-?[1-4][0-9]|-?[0-9]|[1-4][0-9]|50)(\.[0-9]+)?$/,
//定义校验规则
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
temperature: [{ required: true, message: '温度必填!', trigger: 'blur' },
// 指定正则,此处是数字正则
{ pattern: /^(?:(?:-50)|-?[1-4][0-9]|-?[0-9]|[1-4][0-9]|50)(\.[0-9]+)?$/,
message: '温度必须为 -50 到 50 之间的合法数字',
trigger: 'blur',
trigger: 'blur'
}],
humidity: [{ required: true, message: '湿度必填!', trigger: 'blur' },
{
pattern: /^(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?|0?\.\d+)$/,
message: '湿度必须为 0 到 100 之间的合法数字',
trigger: 'blur',
},
],
})
humidity: [{ required: true, message: '湿度必填!', trigger: 'blur' },
{ pattern: /^(?:100(?:\.0+)?|\d{1,2}(?:\.\d+)?|0?\.\d+)$/ , message: '湿度必须为 0 到 100 之间的合法数字', trigger: 'blur' },
],
})
const handleStart = () => {
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
checkStore.setTemperature(formContent.temperature)
checkStore.setHumidity(formContent.humidity)
emit('openTestDialog2')
handleClose()
}
try {
dialogFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
checkStore.setTemperature(formContent.temperature)
checkStore.setHumidity(formContent.humidity)
emit('openTestDialog2')
handleClose()
}
})
} catch (err) {
console.error('验证过程中出现错误', err)
@@ -89,11 +85,11 @@ const handleClose = () => {
dialogFormRef.value?.resetFields()
}
defineExpose({ open })
defineExpose({open})
</script>
<style scoped lang='scss'>
<style scoped lang="scss">
</style>

View File

@@ -70,14 +70,14 @@ const modeList = [
const handelOpen = async (isActive: any) => {
await authStore.setShowMenu();
return;
// if (isActive) {
// router.push({ path: "/static" });
// } else {
// ElMessage({
// message: "当前模式未配置",
// type: "warning",
// });
// }
if (isActive) {
router.push({ path: "/static" });
} else {
ElMessage({
message: "当前模式未配置",
type: "warning",
});
}
};
const handleSelect = (key: string, keyPath: string[]) => {
//console.log(key, keyPath);

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,6 @@ import { useAuthStore } from "@/stores/modules/auth";
import { useModeStore, useAppSceneStore } from "@/stores/modules/mode"; // 引入模式 store
import { ref } from "vue";
import {getCurrentScene} from "@/api/user/login";
const authStore = useAuthStore();
const modeStore = useModeStore(); // 使用模式 store
const AppSceneStore = useAppSceneStore();
@@ -79,7 +78,7 @@ const modeList = [
code: "比对式",
subName: "启用比对式检测计划",
img: new URL('/src/assets/images/dashboard/3.svg', import.meta.url).href,
isActive: true,
isActive: false,
},
];
const handelOpen = async (item: any) => {
@@ -88,7 +87,6 @@ const handelOpen = async (item: any) => {
// AppSceneStore.setCurrentMode(scene+'');//0省级平台1设备出厂2研发自测
AppSceneStore.setCurrentMode(scene+'');//0省级平台1设备出厂2研发自测
await authStore.setShowMenu();
await authStore.getAuthMenuList();
return;
// if (isActive) {
// router.push({ path: "/static" });

View File

@@ -1,23 +1,33 @@
<template>
<div class="table-box">
<ProTable ref="proTable" :columns="columns" :request-api="getTableList">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<el-button type="primary" :icon="DataAnalysis">分析</el-button>
<el-button type="primary" :icon="Upload" @click="handleExport">导出csv</el-button>
</template>
</ProTable>
</div>
<div class='table-box'>
<ProTable
ref='proTable'
:columns='columns'
:request-api='getTableList'
>
<!-- 表格 header 按钮 -->
<template #tableHeader>
<el-button type='primary' :icon='DataAnalysis'>分析</el-button>
<el-button type='primary' :icon='Upload' @click='handleExport'>导出csv</el-button>
</template>
</ProTable>
</div>
</template>
<script setup lang="tsx" name="useProTable">
<script setup lang='tsx' name='useProTable'>
// 根据实际路径调整
import TimeControl from '@/components/TimeControl/index.vue'
import {type AuditLog} from '@/api/system/log/interface/log.ts'
import ProTable from '@/components/ProTable/index.vue'
import { DataAnalysis, Upload } from '@element-plus/icons-vue'
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
import { reactive, ref } from 'vue'
import { exportCsv, getAuditLog } from '@/api/system/log'
import { useDownload } from '@/hooks/useDownload'
import {DataAnalysis, Upload} from '@element-plus/icons-vue'
import type {ColumnProps, ProTableInstance} from '@/components/ProTable/interface'
import {reactive, ref} from 'vue'
import {getAuditLog, exportCsv} from '@/api/system/log/index.ts'
import {useDownload} from "@/hooks/useDownload";
import {exportPqDev} from "@/api/device/device";
// defineOptions({
// name: 'log'
@@ -29,92 +39,94 @@ const endDate = ref('')
const proTable = ref<ProTableInstance>()
const getTableList = async (params: any) => {
let newParams = JSON.parse(JSON.stringify(params))
newParams.searchEndTime = endDate.value
newParams.searchBeginTime = startDate.value
return getAuditLog(newParams)
let newParams = JSON.parse(JSON.stringify(params))
newParams.searchEndTime = endDate.value
newParams.searchBeginTime = startDate.value
return getAuditLog(newParams)
}
// 表格配置项
const columns = reactive<ColumnProps[]>([
{ type: 'selection', fixed: 'left', width: 70 },
{ type: 'index', fixed: 'left', width: 70, label: '序号' },
{
prop: 'userName',
label: '操作用户',
search: { el: 'input' },
minWidth: 100
const columns = reactive<ColumnProps<AuditLog.ReqAuditLogParams>[]>([
{type: 'selection', fixed: 'left', width: 70},
{type: 'index', fixed: 'left', width: 70, label: '序号'},
{
prop: 'userName',
label: '操作用户',
search: {el: 'input'},
minWidth: 100,
},
{
prop: 'ip',
label: 'IP',
minWidth: 120,
},
{
prop: 'logTime',
label: '记录时间',
minWidth: 180,
search: {
render: () => {
return (
<div class='flx-flex-start'>
<TimeControl
include={['日', '周', '月', '自定义']}
default={'月'}
onUpdate-dates={handleDateChange}
/>
</div>
)
},
},
{
prop: 'ip',
label: 'IP',
minWidth: 120
},
{
prop: 'logTime',
label: '记录时间',
minWidth: 180,
search: {
render: () => {
return (
<div class="flx-flex-start">
<TimeControl
include={['日', '周', '月', '自定义']}
default={'月'}
onUpdate-dates={handleDateChange}
/>
</div>
)
}
}
},
{
label: '事件描述',
minWidth: 450,
render(scope) {
return `${scope.row.userName}${scope.row.logTime}执行了【${scope.row.operateType}${scope.row.operate}操作,结果为${scope.row.result}`
}
},
{
prop: 'result',
label: '事件结果',
minWidth: 120
},
{
prop: 'warn',
label: '告警标志',
minWidth: 100,
render: scope => {
return (
<el-tag type={scope.row.warn == 1 ? 'danger' : 'success'}>
{scope.row.warn == 1 ? '已告警' : '未告警'}
</el-tag>
)
}
},
{
prop: 'operateType',
label: '日志类型',
width: 100
},
{
label: '事件描述',
minWidth: 450,
render: (scope) => {
return (scope.row.userName + '在' + scope.row.logTime + '执行了' + scope.row.operateType + scope.row.operate + '操作,结果为' + scope.row.result + '。')
}
},
{
prop: 'result',
label: '事件结果',
minWidth: 120,
},
{
prop: 'warn',
label: '告警标志',
minWidth: 100,
render: scope => {
return (
<el-tag type={scope.row.warn == 1 ? 'danger' : 'success'}>{scope.row.warn == 1 ? '已告警' : '未告警'}</el-tag>
)
},
},
{
prop: 'operateType',
label: '日志类型',
width: 100,
},
])
// 处理日期变化的回调函数
const handleDateChange = (startDateTemp: string, endDateTemp: string) => {
startDate.value = startDateTemp
endDate.value = endDateTemp
startDate.value = startDateTemp
endDate.value = endDateTemp
}
const handleExport = () => {
// 获取当前的搜索参数
let searchParam = proTable.value?.searchParam || {}
// 获取当前的搜索参数
let searchParam = proTable.value?.searchParam || {}
// 将开始时间和结束时间添加到搜索参数中
searchParam.searchBeginTime = startDate.value
searchParam.searchEndTime = endDate.value
// 将开始时间和结束时间添加到搜索参数中
searchParam.searchBeginTime = startDate.value
searchParam.searchEndTime = endDate.value
useDownload(exportCsv, '日志列表', searchParam, false, '.csv')
useDownload(exportCsv, '日志列表', searchParam, false, '.csv')
}
</script>
<style scoped></style>
<style scoped>
</style>

View File

@@ -1,57 +1,76 @@
<template>
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" size="large">
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="用户名">
<template #prefix>
<el-icon class="el-input__icon">
<user />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="密码"
show-password
autocomplete="new-password"
>
<template #prefix>
<el-icon class="el-input__icon">
<lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="checked">
<el-checkbox v-model="loginForm.checked">记住我</el-checkbox>
</el-form-item>
</el-form>
<div class="login-btn">
<el-button :icon="UserFilled" round size="large" type="primary" :loading="loading" @click="login(loginFormRef)">
登录
</el-button>
<el-button :icon="CircleClose" round size="large" @click="resetForm(loginFormRef)">重置</el-button>
</div>
<el-form
ref='loginFormRef'
:model='loginForm'
:rules='loginRules'
size='large'
>
<el-form-item prop='username'>
<el-input v-model='loginForm.username' placeholder='用户名'>
<template #prefix>
<el-icon class='el-input__icon'>
<user/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop='password'>
<el-input
v-model='loginForm.password'
type='password'
placeholder='密码'
show-password
autocomplete='new-password'
>
<template #prefix>
<el-icon class='el-input__icon'>
<lock/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop='checked'>
<el-checkbox v-model="loginForm.checked">记住我</el-checkbox>
</el-form-item>
</el-form>
<div class='login-btn'>
<el-button
:icon='UserFilled'
round
size='large'
type='primary'
:loading='loading'
@click='login(loginFormRef)'
>
登录
</el-button>
<el-button
:icon='CircleClose'
round
size='large'
@click='resetForm(loginFormRef)'
>
重置
</el-button>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { HOME_URL } from '@/config'
import { getTimeState } from '@/utils'
import { type Dict } from '@/api/interface'
import { type Login } from '@/api/user/interface/user'
import type { ElForm } from 'element-plus'
import { ElNotification } from 'element-plus'
import { getDictList, getPublicKey, loginApi } from '@/api/user/login'
import { useUserStore } from '@/stores/modules/user'
import { useTabsStore } from '@/stores/modules/tabs'
import { useKeepAliveStore } from '@/stores/modules/keepAlive'
import { initDynamicRouter } from '@/routers/modules/dynamicRouter'
import { CircleClose, UserFilled } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/modules/auth'
import { useDictStore } from '@/stores/modules/dict'
<script setup lang='ts'>
import {useRouter} from 'vue-router'
import {HOME_URL} from '@/config'
import {getTimeState} from '@/utils'
import {type Dict} from '@/api/interface'
import {type Login} from '@/api/user/interface/user'
import type {ElForm} from 'element-plus'
import {ElNotification} from 'element-plus'
import {getDictList, getPublicKey, loginApi} from '@/api/user/login'
import {useUserStore} from '@/stores/modules/user'
import {useTabsStore} from '@/stores/modules/tabs'
import {useKeepAliveStore} from '@/stores/modules/keepAlive'
import {initDynamicRouter} from '@/routers/modules/dynamicRouter'
import {CircleClose, UserFilled} from '@element-plus/icons-vue'
import {useAuthStore} from '@/stores/modules/auth'
import {useDictStore} from "@/stores/modules/dict";
import forge from 'node-forge'
const authStore = useAuthStore()
@@ -61,97 +80,97 @@ const tabsStore = useTabsStore()
const keepAliveStore = useKeepAliveStore()
const dictStore = useDictStore()
let publicKey: any = null
let publicKey: any = null;
type FormInstance = InstanceType<typeof ElForm>
type FormInstance = InstanceType<typeof ElForm>;
const loginFormRef = ref<FormInstance>()
const loginRules = reactive({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
username: [{required: true, message: '请输入用户名', trigger: 'blur'}],
password: [{required: true, message: '请输入密码', trigger: 'blur'}],
})
const loading = ref(false)
const loginForm = reactive<Login.ReqLoginForm>({
username: '',
password: '',
checked: false
username: '',
password: '',
checked: false,
})
// login
const login = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate(async valid => {
if (!valid) return
loading.value = true
try {
let { data: publicKeyBase64 }: { data: string } = await getPublicKey(loginForm.username)
//将base64格式的公钥转换为Forge可以使用的格式
const publicKeyDer = forge.util.decode64(publicKeyBase64)
publicKey = forge.pki.publicKeyFromPem(
forge.pki.publicKeyToPem(forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyDer)))
)
if (!formEl) return
formEl.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
let {data: publicKeyBase64}: { data: string } = await getPublicKey(loginForm.username)
//将base64格式的公钥转换为Forge可以使用的格式
const publicKeyDer = forge.util.decode64(publicKeyBase64);
publicKey = forge.pki.publicKeyFromPem(forge.pki.publicKeyToPem(forge.pki.publicKeyFromAsn1(forge.asn1.fromDer(publicKeyDer))));
// 1.执行登录接口
const { data } = await loginApi({
username: forge.util.encode64(loginForm.username),
password: encryptPassword(loginForm.password)
})
userStore.setAccessToken(data.accessToken)
userStore.setRefreshToken(data.refreshToken)
userStore.setUserInfo(data.userInfo)
if (loginForm.checked) {
userStore.setExp(Date.now() + 1000 * 60 * 60 * 24 * 30)
}
const response = await getDictList()
const dictData = response.data as unknown as Dict[]
await dictStore.initDictData(dictData)
// 2.添加动态路由
await initDynamicRouter()
// 1.执行登录接口
const {data} = await loginApi({
username: forge.util.encode64(loginForm.username),
password: encryptPassword(loginForm.password),
})
userStore.setAccessToken(data.accessToken)
userStore.setRefreshToken(data.refreshToken)
userStore.setUserInfo(data.userInfo)
if(loginForm.checked){
userStore.setExp(Date.now() + 1000 * 60 * 60 * 24 * 30)
}
const response = await getDictList()
const dictData = response.data as unknown as Dict[]
await dictStore.initDictData(dictData)
// 2.添加动态路由
await initDynamicRouter()
// 3.清空 tabs、keepAlive 数据
tabsStore.setTabs([])
keepAliveStore.setKeepAliveName([])
// 3.清空 tabs、keepAlive 数据
tabsStore.setTabs([])
keepAliveStore.setKeepAliveName([])
// 4.跳转到首页
router.push(HOME_URL)
// 5.登录默认不显示菜单和导航栏
authStore.resetAuthStore()
ElNotification({
title: getTimeState(),
message: '登录成功',
type: 'success',
duration: 3000
})
} finally {
loading.value = false
}
})
// 4.跳转到首页
router.push(HOME_URL)
// 5.登录默认不显示菜单和导航栏
authStore.resetAuthStore()
ElNotification({
title: getTimeState(),
message: '登录成功',
type: 'success',
duration: 3000,
})
} finally {
loading.value = false
}
})
}
// resetForm
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
if (!formEl) return
formEl.resetFields()
}
onMounted(() => {
// 监听 enter 事件(调用登录)
document.onkeydown = (e: KeyboardEvent) => {
e = (window.event as KeyboardEvent) || e
if (e.code === 'Enter' || e.code === 'enter' || e.code === 'NumpadEnter') {
if (loading.value) return
login(loginFormRef.value)
}
// 监听 enter 事件(调用登录)
document.onkeydown = (e: KeyboardEvent) => {
e = (window.event as KeyboardEvent) || e
if (e.code === 'Enter' || e.code === 'enter' || e.code === 'NumpadEnter') {
if (loading.value) return
login(loginFormRef.value)
}
}
})
const encryptPassword = (password: string) => {
const encrypted = publicKey.encrypt(password, 'RSAES-PKCS1-V1_5')
// 将加密后的数据转换为base64格式以便传输
return forge.util.encode64(encrypted)
const encrypted = publicKey.encrypt(password, 'RSAES-PKCS1-V1_5');
// 将加密后的数据转换为base64格式以便传输
return forge.util.encode64(encrypted);
}
</script>
<style scoped lang="scss">
@use '../index.scss';
<style scoped lang='scss'>
@import "../index.scss";
</style>

View File

@@ -1,28 +1,28 @@
<template>
<div class="login-container flx-center">
<div class="login-box">
<!-- <SwitchDark class="dark" /> -->
<div class="login-left">
<img class="login-left-img" src="@/assets/images/login_left.png" alt="login" />
</div>
<div class="login-form">
<div class="login-logo">
<!-- <img class="login-icon" src="@/assets/images/logo.svg" alt="" /> -->
<img class="login-icon" src="@/assets/images/cn_pms9100_logo.png" alt="" />
<h2 class="logo-text">{{ title }}</h2>
</div>
<LoginForm />
</div>
<div class="login-container flx-center">
<div class="login-box">
<!-- <SwitchDark class="dark" /> -->
<div class="login-left">
<img class="login-left-img" src="@/assets/images/login_left.png" alt="login" />
</div>
<div class="login-form">
<div class="login-logo">
<!-- <img class="login-icon" src="@/assets/images/logo.svg" alt="" /> -->
<img class="login-icon" src="@/assets/images/cn_pms9100_logo.png" alt="" />
<h2 class="logo-text">{{ title }}</h2>
</div>
<LoginForm />
</div>
</div>
</div>
</template>
<script setup lang="ts" name="login">
import LoginForm from './components/LoginForm.vue'
import LoginForm from "./components/LoginForm.vue";
const title = import.meta.env.VITE_GLOB_APP_TITLE;
const title = import.meta.env.VITE_GLOB_APP_TITLE
</script>
<style scoped lang="scss">
@use './index.scss';
@import "./index.scss";
</style>

View File

@@ -6,7 +6,7 @@
</div>
<div class="data-check-content">
<div class="content-tree">
<MachineTree ref="treeRef" :treeData="treeData" @setTab="setTab" />
<Tree ref="treeRef" :treeData="treeData" @setTab="setTab" />
</div>
<div class="content-right-Tabs" style="height: calc(100vh - 315px); width: 100px">
@@ -204,22 +204,32 @@
</template>
<script setup lang="ts">
import { nextTick, onMounted, type PropType, ref, watch } from 'vue'
import MachineTree from './machineTree.vue'
import { type PropType, ref, nextTick, onMounted, watch } from 'vue'
import Tree from './tree.vue'
import Commun from '@/views/machine/testScript/components//communication.vue'
import { ElMessage } from 'element-plus'
import { type CascaderOption, ElMessage } from 'element-plus'
import { getTreeData } from '@/api/check/test'
import { VideoPause, VideoPlay, View } from '@element-plus/icons-vue'
import {
CirclePlus,
Delete,
Check,
CopyDocument,
View,
EditPen,
VideoPlay,
VideoPause,
Loading
} from '@element-plus/icons-vue'
import type { TestScript } from '@/api/device/interface/testScript'
import TestProjectPopup from '@/views/machine/testScript/components/testProjectPopup.vue'
import { CheckData } from '@/api/check/interface'
import { dlsDetails } from '@/api/device/testScript'
import { dlsDetails, deleteDtls, updateDtls, addScriptDtls, checkDataList } from '@/api/device/testScript'
import { useDictStore } from '@/stores/modules/dict'
import { useHandleData } from '@/hooks/useHandleData'
import { scriptDtlsCheckDataList } from '@/api/device/testScript/index'
import ViewRow from '@/views/machine/testScript/components/viewRow.vue'
import { closeSimulateTest, startSimulateTest } from '@/api/device/controlSource/index.ts'
import { startSimulateTest, closeSimulateTest } from '@/api/device/controlSource/index.ts'
import { controlSource } from '@/api/device/interface/controlSource'
import { JwtUtil } from '@/utils/jwtUtil'
interface TabOption {
label?: string
name?: string
@@ -454,7 +464,7 @@ const startLoading = async () => {
emit('update:pauseDisabled', true)
ElMessage.success({ message: '启动中...', duration: 6000 })
// 启动加载逻辑
controlContent.value.userPageId = JwtUtil.getLoginName()
controlContent.value.userPageId = 'cdf'
controlContent.value.scriptId = props.formControl.scriptId
controlContent.value.scriptIndex = childActiveIndex.value
controlContent.value.sourceId = props.formControl.sourceId
@@ -466,7 +476,7 @@ const startLoading = async () => {
// 定义 startLoading 方法
const stopLoading = async () => {
// 启动加载逻辑
controlContent.value.userPageId = JwtUtil.getLoginName()
controlContent.value.userPageId = 'cdf'
controlContent.value.scriptId = props.formControl.scriptId
controlContent.value.scriptIndex = childActiveIndex.value
controlContent.value.sourceId = props.formControl.sourceId

View File

@@ -94,6 +94,7 @@ function findTargetCodes(data: any[], targetCodes: string[]) {
// for (let item of data) {
// // scriptTypeCode
// if (item.scriptTypeCode !=null && targetCodes.includes(item.scriptTypeCode)) {
// console.log("🚀 ~ findTargetCodes ~ targetCodes.includes(item.scriptTypeCode):",item.scriptTypeCode, targetCodes.includes(item.scriptTypeCode))
// result.push(item.scriptTypeCode)
// return result
// }

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