Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1202f64bfc | ||
|
|
ce1738daf0 | ||
|
|
a41d824ca3 | ||
|
|
ac5a8450e8 | ||
|
|
01e817a5d6 | ||
|
|
01bf07fc42 | ||
|
|
633e914c9a | ||
|
|
37e69e7bda | ||
|
|
19fb90432a | ||
|
|
4a3c81a792 | ||
|
|
12d3073241 | ||
|
|
72838462ad | ||
|
|
327addf625 |
@@ -33,9 +33,9 @@ mybatis-plus:
|
||||
#驼峰命名
|
||||
map-underscore-to-camel-case: true
|
||||
#配置sql日志输出
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
#关闭日志输出
|
||||
# log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
global-config:
|
||||
db-config:
|
||||
#指定主键生成策略
|
||||
@@ -115,10 +115,4 @@ activate:
|
||||
private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
|
||||
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
|
||||
|
||||
freq-converter:
|
||||
schedule-period: 200 #定时器运行间隔
|
||||
tolerant: 1 #耐受状态
|
||||
dt: 200 #延迟时间ms
|
||||
direction: 0 #0为横向1为纵向
|
||||
allow-error-duration: 6 #暂态持续时间允许最大误差ms
|
||||
allow-error-residual-voltage: 2.0 #暂态幅值允许最多误差%
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo43
Normal file
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo43
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo44
Normal file
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo44
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo73_tmp
Normal file
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo73_tmp
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
42428
|
||||
95428
|
||||
|
||||
BIN
build/extraResources/mysql/data/binlog.000023
Normal file
BIN
build/extraResources/mysql/data/binlog.000023
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000024
Normal file
BIN
build/extraResources/mysql/data/binlog.000024
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000025
Normal file
BIN
build/extraResources/mysql/data/binlog.000025
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000026
Normal file
BIN
build/extraResources/mysql/data/binlog.000026
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000027
Normal file
BIN
build/extraResources/mysql/data/binlog.000027
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000028
Normal file
BIN
build/extraResources/mysql/data/binlog.000028
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000029
Normal file
BIN
build/extraResources/mysql/data/binlog.000029
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000030
Normal file
BIN
build/extraResources/mysql/data/binlog.000030
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000031
Normal file
BIN
build/extraResources/mysql/data/binlog.000031
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000032
Normal file
BIN
build/extraResources/mysql/data/binlog.000032
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000033
Normal file
BIN
build/extraResources/mysql/data/binlog.000033
Normal file
Binary file not shown.
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000037
Normal file
BIN
build/extraResources/mysql/data/binlog.000037
Normal file
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000038
Normal file
BIN
build/extraResources/mysql/data/binlog.000038
Normal file
Binary file not shown.
@@ -1,3 +1,16 @@
|
||||
.\binlog.000023
|
||||
.\binlog.000024
|
||||
.\binlog.000025
|
||||
.\binlog.000026
|
||||
.\binlog.000027
|
||||
.\binlog.000028
|
||||
.\binlog.000029
|
||||
.\binlog.000030
|
||||
.\binlog.000031
|
||||
.\binlog.000032
|
||||
.\binlog.000033
|
||||
.\binlog.000034
|
||||
.\binlog.000035
|
||||
.\binlog.000036
|
||||
.\binlog.000037
|
||||
.\binlog.000038
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,7 @@ function createTray() {
|
||||
tray = new Tray(iconPath);
|
||||
}
|
||||
|
||||
tray.setToolTip('变频器暂降耐受实验平台');
|
||||
tray.setToolTip('NPQS-9100自动检测平台');
|
||||
console.log('[Tray] Tray created successfully');
|
||||
|
||||
// 创建托盘菜单
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# title
|
||||
VITE_GLOB_APP_TITLE=变频器暂降耐受实验平台
|
||||
VITE_GLOB_APP_TITLE=NPQS-9100自动检测平台
|
||||
|
||||
# 本地运行端口号
|
||||
VITE_PORT=18091
|
||||
|
||||
@@ -19,7 +19,7 @@ VITE_API_URL=/api
|
||||
|
||||
# 开发环境跨域代理,支持配置多个
|
||||
|
||||
VITE_PROXY=[["/api","http://127.0.0.1:18092/"]]
|
||||
VITE_PROXY=[["/api","http://127.0.0.1:18093/"]]
|
||||
#VITE_PROXY=[["/api","http://192.168.1.124:18092/"]]
|
||||
#VITE_PROXY=[["/api","http://192.168.2.125:18092/"]]
|
||||
# VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文
|
||||
|
||||
@@ -23,6 +23,6 @@ VITE_PWA=true
|
||||
|
||||
# 线上环境接口地址
|
||||
#VITE_API_URL="/api" # 打包时用
|
||||
VITE_API_URL="http://127.0.0.1:18092/"
|
||||
VITE_API_URL="http://127.0.0.1:18093/"
|
||||
# 开启激活验证
|
||||
VITE_ACTIVATE_OPEN=false
|
||||
@@ -1,10 +1,6 @@
|
||||
import type {Device} from '@/api/device/interface/device'
|
||||
import http from '@/api'
|
||||
|
||||
export const getPqDevListAll = () => {
|
||||
return http.get<Device.ResPqDev[]>(`/pqDev/listAll`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 被检设备管理模块
|
||||
*/
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type {FreqConverter} from '@/api/device/interface/freqConverter'
|
||||
import http from '@/api'
|
||||
|
||||
/**
|
||||
* @name 变频器管理模块
|
||||
*/
|
||||
|
||||
//获取设备类型
|
||||
export const getFreqConverterList = (params: FreqConverter.ReqFreqConverterParams) => {
|
||||
return http.post(`/freqConverter/list`, params)
|
||||
}
|
||||
|
||||
//添加设备类型
|
||||
export const addFreqConverter = (params: FreqConverter.ResFreqConverter) => {
|
||||
return http.post(`/freqConverter/add`, params)
|
||||
}
|
||||
|
||||
//编辑设备类型
|
||||
export const updateFreqConverter = (params: FreqConverter.ResFreqConverter) => {
|
||||
return http.post(`/freqConverter/update`, params)
|
||||
}
|
||||
|
||||
//删除设备类型
|
||||
export const deleteFreqConverter = (params: string[]) => {
|
||||
return http.post(`/freqConverter/delete`, params)
|
||||
}
|
||||
|
||||
export const getFreqConverterResult = (params: { converterId?: string }) => {
|
||||
return http.get(`/freqConverter/result?converterId=${params.converterId || ''}`)
|
||||
}
|
||||
|
||||
export const startFreqConverterDetect = (params: {
|
||||
converterId?: string;
|
||||
monitorId?: string;
|
||||
userId?: string | null;
|
||||
reset?: boolean;
|
||||
}) => {
|
||||
return http.get(`/prepare/startFreqConverter`, params, {loading: false})
|
||||
}
|
||||
|
||||
export const stopFreqConverterDetect = (params: {
|
||||
userId?: string | null;
|
||||
}) => {
|
||||
return http.get(`/prepare/stopFreqConverter`, params, {loading: false})
|
||||
}
|
||||
|
||||
export const getFreqConverterSCurve = (params: FreqConverter.ReqFreqConverterSCurveParams) => {
|
||||
return http.get(`/freqConverter/scurve?converterId=${params.converterId || ''}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import type {ReqPage, ResPage} from '@/api/interface'
|
||||
|
||||
// 变频器模块
|
||||
export namespace FreqConverter {
|
||||
|
||||
/**
|
||||
* 变频器数据表格分页查询参数
|
||||
*/
|
||||
export interface ReqFreqConverterParams extends ReqPage {
|
||||
name?: string; // 名称
|
||||
}
|
||||
|
||||
/**
|
||||
* 变频器新增、修改、根据id查询返回的对象
|
||||
*/
|
||||
export interface ResFreqConverter {
|
||||
id?: string; //变频器ID
|
||||
name: string;//变频器名称
|
||||
portName: string; //串口名称
|
||||
slaveAddress: number; //从机地址
|
||||
baudRate: number; //波特率
|
||||
parity: string; //奇偶校验类型
|
||||
dataBits: number; //数据位
|
||||
stopBits: number; //停止位
|
||||
timeoutMs: number; //超时时间(毫秒)
|
||||
suffix?: number; //数据表后缀
|
||||
state?: number;
|
||||
testStatus?: number; //测试状态 0:未测试 1:测试完成
|
||||
createBy?: string | null; //创建用户
|
||||
createTime?: string | null; //创建时间
|
||||
updateBy?: string | null; //更新用户
|
||||
updateTime?: string | null; //更新时间
|
||||
}
|
||||
|
||||
/**
|
||||
* 变频器表格查询分页返回的对象;
|
||||
*/
|
||||
export interface ResFreqConverterPage extends ResPage<ResFreqConverter> {
|
||||
|
||||
}
|
||||
|
||||
export interface ReqFreqConverterSCurveParams {
|
||||
converterId?: string;
|
||||
}
|
||||
|
||||
export interface ResTolerantPoint {
|
||||
durationMs?: number | null;
|
||||
residualVoltage?: number | null;
|
||||
tolerant?: number | null;
|
||||
}
|
||||
}
|
||||
@@ -69,5 +69,30 @@ export namespace Plan {
|
||||
maxTime: number;
|
||||
}
|
||||
|
||||
export interface PlanStatisticsItem {
|
||||
itemId: string;
|
||||
itemName: string;
|
||||
unqualifiedCount: number;
|
||||
}
|
||||
|
||||
export interface PlanStatistics {
|
||||
planId: string;
|
||||
planName: string;
|
||||
totalCheckCount: number;
|
||||
checkedDeviceCount: number;
|
||||
uncheckedDeviceCount: number;
|
||||
firstQualifiedDeviceCount: number;
|
||||
secondQualifiedDeviceCount: number;
|
||||
thirdOrMoreQualifiedDeviceCount: number;
|
||||
qualifiedDeviceCount: number;
|
||||
unqualifiedDeviceCount: number;
|
||||
unqualifiedItemCount: number;
|
||||
firstPassRate: number;
|
||||
secondPassRate: number;
|
||||
thirdOrMorePassRate: number;
|
||||
unqualifiedRate: number;
|
||||
itemDistributions: PlanStatisticsItem[];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -94,6 +94,10 @@ export const staticsAnalyse = (params: { id: string[] }) => {
|
||||
return http.download('/adPlan/analyse', params)
|
||||
}
|
||||
|
||||
export const getPlanStatistics = (params: { planId: string; manufacturer?: string; devType?: string }) => {
|
||||
return http.post<Plan.PlanStatistics>(`/adPlan/statistics`, params)
|
||||
}
|
||||
|
||||
//根据计划id分页查询被检设
|
||||
export const getDevListByPlanId = (params: any) => {
|
||||
return http.post(`/adPlan/listDevByPlanId`, params)
|
||||
|
||||
@@ -130,7 +130,6 @@ const initChart = () => {
|
||||
chart.resize()
|
||||
}, 0)
|
||||
}
|
||||
const getChartInstance = () => chart
|
||||
const handlerBar = (options: any) => {
|
||||
if (Array.isArray(options.series)) {
|
||||
options.series.forEach((item: any) => {
|
||||
@@ -254,7 +253,7 @@ onMounted(() => {
|
||||
initChart()
|
||||
resizeObserver.observe(chartRef.value!)
|
||||
})
|
||||
defineExpose({ initChart, getChartInstance })
|
||||
defineExpose({ initChart })
|
||||
onBeforeUnmount(() => {
|
||||
resizeObserver.unobserve(chartRef.value!)
|
||||
chart?.dispose()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// ? 全局默认配置项
|
||||
|
||||
// 首页地址(默认)
|
||||
export const HOME_URL: string = "/machine/freqConverter";
|
||||
export const HOME_URL: string = "/home/index";
|
||||
|
||||
// export const HOME_URL: string = "/machine/controlSource";
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { HOME_URL } from '@/config'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||
import { useTabsStore } from '@/stores/modules/tabs'
|
||||
@@ -75,8 +74,8 @@ const handelOpen = async (item: string, key: string) => {
|
||||
await initDynamicRouter()
|
||||
|
||||
// 只有当目标路径与当前路径不同时才跳转
|
||||
if (router.currentRoute.value.path !== HOME_URL) {
|
||||
await router.push({ path: HOME_URL })
|
||||
if (router.currentRoute.value.path !== '/home/index') {
|
||||
await router.push({ path: '/home/index' })
|
||||
} else {
|
||||
// 如果已在目标页面,手动触发组件更新
|
||||
window.location.reload() // 或者采用其他方式刷新数据
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
<el-icon><Edit /></el-icon>
|
||||
{{ t('header.changePassword') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="changeMode" v-if="authStore.showMenuFlag">
|
||||
<el-icon><Switch /></el-icon>
|
||||
{{ t('header.changeMode') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openDialog('versionRegisterRef')">
|
||||
<el-icon><SetUp /></el-icon>
|
||||
{{ t('header.versionRegister') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown trigger="hover" placement="left-start" v-if="userStore.userInfo.loginName == 'root'">
|
||||
<div class="custom-dropdown-trigger">
|
||||
<el-icon><Tools /></el-icon>
|
||||
@@ -54,6 +62,8 @@
|
||||
<InfoDialog ref="infoRef"></InfoDialog>
|
||||
<!-- passwordDialog -->
|
||||
<PasswordDialog ref="passwordRef"></PasswordDialog>
|
||||
<!-- versionRegisterDialog -->
|
||||
<VersionDialog ref="versionRegisterRef"></VersionDialog>
|
||||
<!-- ThemeDialog -->
|
||||
<ThemeDialog ref="themeRef"></ThemeDialog>
|
||||
</template>
|
||||
@@ -67,7 +77,9 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import InfoDialog from './InfoDialog.vue'
|
||||
import PasswordDialog from './PasswordDialog.vue'
|
||||
import ThemeDialog from './ThemeDialog.vue'
|
||||
import { Avatar, Sunny, Tools } from '@element-plus/icons-vue'
|
||||
import VersionDialog from '@/views/system/versionRegister/index.vue'
|
||||
import { Avatar, Sunny, Switch, Tools } from '@element-plus/icons-vue'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
import { useAppSceneStore } from '@/stores/modules/mode'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -78,6 +90,7 @@ const dictStore = useDictStore()
|
||||
const username = computed(() => userStore.userInfo.name)
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 初始化 i18n
|
||||
const { t } = useI18n() // 使用 t 方法替代 $t
|
||||
@@ -98,10 +111,12 @@ const logout = () => {
|
||||
// 打开修改密码和个人信息弹窗
|
||||
const infoRef = ref<InstanceType<typeof InfoDialog> | null>(null)
|
||||
const passwordRef = ref<InstanceType<typeof PasswordDialog> | null>(null)
|
||||
const versionRegisterRef = ref<InstanceType<typeof VersionDialog> | null>(null)
|
||||
const themeRef = ref<InstanceType<typeof ThemeDialog> | null>(null)
|
||||
const openDialog = (ref: string) => {
|
||||
if (ref == 'infoRef') infoRef.value?.openDialog()
|
||||
if (ref == 'passwordRef') passwordRef.value?.openDialog()
|
||||
if (ref == 'versionRegisterRef') versionRegisterRef.value?.openDialog()
|
||||
if (ref == 'themeRef') themeRef.value?.openDialog()
|
||||
}
|
||||
|
||||
@@ -115,6 +130,10 @@ const changeScene = async (value: string) => {
|
||||
}
|
||||
|
||||
//模式切换
|
||||
const changeMode = async () => {
|
||||
authStore.changeModel()
|
||||
await router.push('/home/index')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -29,7 +29,6 @@ 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 { HOME_URL } from '@/config'
|
||||
import { TabPaneName, TabsPaneContext } from 'element-plus'
|
||||
import MoreButton from './components/MoreButton.vue'
|
||||
|
||||
@@ -74,13 +73,13 @@ watch(
|
||||
// 初始化需要固定的 tabs
|
||||
const initTabs = () => {
|
||||
authStore.flatMenuListGet.forEach(item => {
|
||||
if (item.path === HOME_URL && !item.meta.isHide && !item.meta.isFull) {
|
||||
if (item.meta.isAffix && !item.meta.isHide && !item.meta.isFull) {
|
||||
const tabsParams = {
|
||||
icon: item.meta.icon,
|
||||
title: item.meta.title,
|
||||
path: item.path,
|
||||
name: item.name,
|
||||
close: false,
|
||||
close: !item.meta.isAffix,
|
||||
isKeepAlive: item.meta.isKeepAlive,
|
||||
unshift: true
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { type AuthState } from '@/stores/interface'
|
||||
import { getAuthButtonListApi, getAuthMenuListApi } from '@/api/user/login'
|
||||
import { getAllBreadcrumbList, getFlatMenuList, getShowMenuList } from '@/utils'
|
||||
import { AUTH_STORE_KEY } from '@/stores/constant'
|
||||
import { HOME_URL } from '@/config'
|
||||
import { useModeStore } from '@/stores/modules/mode'
|
||||
import { getLicense } from '@/api/activate'
|
||||
import type { Activate } from '@/api/activate/interface'
|
||||
@@ -53,7 +52,7 @@ export const useAuthStore = defineStore(AUTH_STORE_KEY, {
|
||||
? filterMenuByExcludedNames(menuData, ['testSource', 'testScript', 'controlSource'])
|
||||
: filterMenuByExcludedNames(menuData, ['standardDevice'])
|
||||
|
||||
this.authMenuList = normalizeHomeAffix(filteredMenu)
|
||||
this.authMenuList = filteredMenu
|
||||
},
|
||||
// Set RouteName
|
||||
async setRouteName(name: string) {
|
||||
@@ -113,22 +112,3 @@ function filterMenuByExcludedNames(menuList: any[], excludedNames: string[]): an
|
||||
return !excludedNames.includes(menu.name)
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeHomeAffix(menuList: any[]): any[] {
|
||||
return menuList.map(menu => {
|
||||
const nextMenu = { ...menu }
|
||||
|
||||
if (nextMenu.meta) {
|
||||
nextMenu.meta = {
|
||||
...nextMenu.meta,
|
||||
isAffix: nextMenu.path === HOME_URL
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(nextMenu.children) && nextMenu.children.length > 0) {
|
||||
nextMenu.children = normalizeHomeAffix(nextMenu.children)
|
||||
}
|
||||
|
||||
return nextMenu
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// src/stores/modules/mode.ts
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const DEFAULT_MODE = '模拟式'
|
||||
|
||||
export const useModeStore = defineStore('mode', {
|
||||
state: () => ({
|
||||
currentMode: localStorage.getItem('currentMode') || DEFAULT_MODE
|
||||
currentMode: localStorage.getItem('currentMode') || ('' as string)
|
||||
}),
|
||||
actions: {
|
||||
setCurrentMode(modeName: string) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import piniaPersistConfig from '@/stores/helper/persist'
|
||||
import { USER_STORE_KEY } from '@/stores/constant'
|
||||
import { logoutApi } from '@/api/user/login'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { DEFAULT_MODE, useAppSceneStore, useModeStore } from '@/stores/modules/mode'
|
||||
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode'
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
|
||||
export const useUserStore = defineStore(USER_STORE_KEY, {
|
||||
@@ -48,7 +48,7 @@ export const useUserStore = defineStore(USER_STORE_KEY, {
|
||||
this.setUserInfo({ id: '', name: '', loginName: '' })
|
||||
this.setIsRefreshToken(false)
|
||||
dictStore.setDictData([])
|
||||
modeStore.setCurrentMode(DEFAULT_MODE)
|
||||
modeStore.setCurrentMode('')
|
||||
appSceneStore.setCurrentMode('')
|
||||
await authStore.resetAuthStore()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<template #operation='scope'>
|
||||
<el-button v-auth.role="'edit'" type='primary' link :icon='EditPen' @click="openDrawer('edit', scope.row)" :disabled="scope.row.code == 'root'">编辑</el-button>
|
||||
<el-button v-auth.role="'delete'" type='primary' link :icon='Delete' @click='deleteAccount(scope.row)' :disabled="scope.row.code == 'root'">删除</el-button>
|
||||
<el-button v-auth.role="'SetPermissions'" type='primary' link :icon='Share' @click="openDrawer('设置权限', scope.row)">设置权限</el-button>
|
||||
<el-button v-auth.role="'SetPermissions'" type='primary' link :icon='Share' @click="openDrawer('设置权限', scope.row)" :disabled="scope.row.code == 'root'">设置权限</el-button>
|
||||
</template>
|
||||
|
||||
</ProTable>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
type="primary"
|
||||
icon="Clock"
|
||||
@click="handleTest('手动检测')"
|
||||
v-if="form.activeTabs === 0 && modeStore.currentMode == '模拟式'"
|
||||
v-if="form.activeTabs === 0 && modeStore.currentMode != '比对式'"
|
||||
>
|
||||
手动检测
|
||||
</el-button>
|
||||
@@ -483,7 +483,7 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
|
||||
sortable: true,
|
||||
isShow: checkStateShow,
|
||||
render: scope => {
|
||||
return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : '检测完成'
|
||||
return scope.row.checkState === 0 ? '未检' : scope.row.checkState === 1 ? '检测中' : scope.row.checkState === 2 ? '检测完成':'归档'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -494,9 +494,11 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
|
||||
render: scope => {
|
||||
if (scope.row.checkResult === 0) {
|
||||
return <el-tag type="danger">不符合</el-tag>
|
||||
} else if (scope.row.checkResult === 0) {
|
||||
return '不符合'
|
||||
} else if (scope.row.checkResult === 1) {
|
||||
return '符合'
|
||||
} else if (scope.row.checkResult === 2) {
|
||||
}else if(scope.row.checkResult === 2) {
|
||||
return '未检'
|
||||
}
|
||||
return ''
|
||||
@@ -539,7 +541,6 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
|
||||
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
|
||||
])
|
||||
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
|
||||
let qualifiedCount = 0 //合格数量
|
||||
|
||||
|
||||
//比对单个报告生成
|
||||
@@ -575,8 +576,6 @@ const handleSelectionChange = (selection: any[]) => {
|
||||
} else {
|
||||
testType = 'reTest'
|
||||
}
|
||||
qualifiedCount=selection.filter(item => item.checkResult == 1).length
|
||||
|
||||
let devices: CheckData.Device[] = selection.map((item: any) => {
|
||||
return {
|
||||
deviceId: item.id,
|
||||
@@ -599,6 +598,19 @@ const handleSelectionChange = (selection: any[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isUncheckedDevice = (device: Device.ResPqDev) => Number(device.checkState) === 0 || Number(device.checkResult) === 2
|
||||
|
||||
const hasCheckedSelectedDevice = () => channelsSelection.value.some(device => !isUncheckedDevice(device))
|
||||
|
||||
const hasUncheckedSelectedDevice = () => channelsSelection.value.some(device => isUncheckedDevice(device))
|
||||
|
||||
const hasCheckedUnqualifiedSelectedDevice = () =>
|
||||
channelsSelection.value.some(device => !isUncheckedDevice(device) && Number(device.checkResult) === 0)
|
||||
|
||||
const shouldShowRecheckModeDialog = () => hasCheckedSelectedDevice()
|
||||
|
||||
const canUseUnqualifiedItemRecheck = () => hasCheckedUnqualifiedSelectedDevice() && !hasUncheckedSelectedDevice()
|
||||
|
||||
//查询
|
||||
const handleSearch = () => {
|
||||
proTable.value?.getTableList()
|
||||
@@ -923,12 +935,12 @@ const handleTest = async (val: string) => {
|
||||
dialogTitle.value = val
|
||||
if (val === '手动检测') {
|
||||
checkStore.setShowDetailType(2)
|
||||
if (testType === 'reTest') {
|
||||
if (shouldShowRecheckModeDialog()) {
|
||||
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '不合格项复检',
|
||||
cancelButtonText: '全部复检',
|
||||
showConfirmButton:qualifiedCount<=0,
|
||||
showConfirmButton: canUseUnqualifiedItemRecheck(),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
@@ -963,11 +975,12 @@ const handleTest = async (val: string) => {
|
||||
checkStore.setCheckType(1)
|
||||
checkStore.initSelectTestItems()
|
||||
// 一键检测
|
||||
if (testType === 'reTest' && modeStore.currentMode != '比对式') {
|
||||
if (shouldShowRecheckModeDialog() && modeStore.currentMode != '比对式') {
|
||||
ElMessageBox.confirm('请选择复检检测方式', '设备复检', {
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '不合格项复检',
|
||||
cancelButtonText: '全部复检',
|
||||
showConfirmButton: canUseUnqualifiedItemRecheck(),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
@@ -1087,7 +1100,7 @@ const openDrawer = async (title: string, row: any) => {
|
||||
|
||||
if (title === '检测数据查询') {
|
||||
checkStore.setShowDetailType(0)
|
||||
if (modeStore.currentMode == '模拟式') {
|
||||
if (modeStore.currentMode == '模拟式'||modeStore.currentMode == '数字式') {
|
||||
dataCheckPopupRef.value?.open(row.id, '-1', null)
|
||||
} else if (modeStore.currentMode == '比对式') {
|
||||
dataCheckSingleChannelSingleTestPopupRef.value?.open(row, null, row.id, 2)
|
||||
@@ -1095,7 +1108,7 @@ const openDrawer = async (title: string, row: any) => {
|
||||
}
|
||||
if (title === '误差体系更换') {
|
||||
checkStore.setShowDetailType(1)
|
||||
if (modeStore.currentMode == '模拟式') {
|
||||
if (modeStore.currentMode == '模拟式'||modeStore.currentMode == '数字式') {
|
||||
dataCheckPopupRef.value?.open(row.id, '-1', null)
|
||||
} else if (modeStore.currentMode == '比对式') {
|
||||
dataCheckSingleChannelSingleTestPopupRef.value?.open(row, null, row.id, 2)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
@node-click="handleNodeClick"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node" style="display: flex; align-items: center;">
|
||||
<span class="custom-tree-node">
|
||||
<!-- 父节点图标 -->
|
||||
<Platform
|
||||
v-if="!data.pid"
|
||||
@@ -39,7 +39,14 @@
|
||||
}"
|
||||
/>
|
||||
<!-- 节点名称 -->
|
||||
<span>{{ node.label }}</span>
|
||||
<span class="node-label">{{ node.label }}</span>
|
||||
<span class="node-actions">
|
||||
<PieChart
|
||||
v-if="isCompletedPlanNode(node.data)"
|
||||
class="node-action-icon"
|
||||
@click.stop="openStatistics(node.data)"
|
||||
style="margin-right: 8px"
|
||||
/>
|
||||
<!-- 子节点右侧图标 + tooltip -->
|
||||
<el-tooltip
|
||||
v-if="
|
||||
@@ -52,37 +59,32 @@
|
||||
:manual="true"
|
||||
content="子计划信息"
|
||||
>
|
||||
<List
|
||||
@click.stop="childDetail(node.data)"
|
||||
style="
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
"
|
||||
/>
|
||||
<List class="node-action-icon" @click.stop="childDetail(node.data)" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
<SourceOpen ref="openSourceView" :width="width" :height="height + 175"></SourceOpen>
|
||||
<PlanStatisticsPopup ref="planStatisticsPopupRef" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { type Plan } from '@/api/plan/interface'
|
||||
import { List, Menu, Platform } from '@element-plus/icons-vue'
|
||||
import { List, Menu, PieChart, Platform } 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 PlanStatisticsPopup from '@/views/plan/planList/components/planStatisticsPopup.vue'
|
||||
import { getPlanList } from '@/api/plan/plan.ts'
|
||||
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
|
||||
const openSourceView = ref()
|
||||
const planStatisticsPopupRef = ref<InstanceType<typeof PlanStatisticsPopup> | null>(null)
|
||||
const router = useRouter()
|
||||
const checkStore = useCheckStore()
|
||||
const filterText = ref('')
|
||||
@@ -211,6 +213,14 @@ const childDetail = (data: Plan.ResPlan) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isCompletedPlanNode = (data: Partial<Plan.ResPlan>) => {
|
||||
return [1, 2].includes(Number(data.testState))
|
||||
}
|
||||
|
||||
const openStatistics = (data: Partial<Plan.ResPlan>) => {
|
||||
planStatisticsPopupRef.value?.open(data)
|
||||
}
|
||||
|
||||
function buildTree(flatList: any[]): any[] {
|
||||
const map = new Map()
|
||||
const tree: any[] = []
|
||||
@@ -293,6 +303,40 @@ defineExpose({ getTreeData, clickTableToTree })
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__content) {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.node-action-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
//.filter-tree span {
|
||||
// font-size: 16px;
|
||||
// display:block;
|
||||
|
||||
@@ -120,7 +120,7 @@ const login = (formEl: FormInstance | undefined) => {
|
||||
await tabsStore.setTabs([])
|
||||
await keepAliveStore.setKeepAliveName([])
|
||||
// 登录默认不显示菜单和导航栏
|
||||
await authStore.setShowMenu()
|
||||
await authStore.resetAuthStore()
|
||||
// 跳转到首页
|
||||
await router.push(HOME_URL)
|
||||
} finally {
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
<template>
|
||||
<el-card class="section-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="section-header">
|
||||
<span>通道配对</span>
|
||||
<span class="section-tip">选择设备后默认连接到第一个通道,如需调整可删除后重新配对</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-item">
|
||||
<span class="toolbar-label">设备</span>
|
||||
<el-select
|
||||
v-model="selectedDeviceId"
|
||||
class="device-select"
|
||||
filterable
|
||||
:loading="loading"
|
||||
placeholder="请选择设备"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in deviceOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="selectedDevice" ref="flowContainerRef" class="flow-container" :style="{height: `${flowHeight}px`}">
|
||||
<VueFlow
|
||||
:nodes="nodes"
|
||||
:edges="edges"
|
||||
:edge-types="edgeTypes"
|
||||
:nodes-draggable="false"
|
||||
:elements-selectable="true"
|
||||
:zoom-on-scroll="false"
|
||||
:pan-on-drag="false"
|
||||
:zoom-on-double-click="false"
|
||||
:prevent-scrolling="true"
|
||||
:min-zoom="0.35"
|
||||
:max-zoom="1"
|
||||
fit-view-on-init
|
||||
@connect="handleConnect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-empty v-else description="请选择设备后进行通道配对" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {computed, h, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
|
||||
import {Position, VueFlow, useVueFlow, type Connection, type Edge, type Node} from '@vue-flow/core'
|
||||
import {getPqDevListAll} from '@/api/device/device'
|
||||
import {type Device} from '@/api/device/interface/device'
|
||||
import {type FreqConverter} from '@/api/device/interface/freqConverter'
|
||||
import FreqConverterDetectEdge from '@/views/machine/freqConverter/components/freqConverterDetectEdge.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
freqConverter: FreqConverter.ResFreqConverter | null;
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const deviceOptions = ref<Device.ResPqDev[]>([])
|
||||
const selectedDeviceId = ref('')
|
||||
const nodes = ref<Node[]>([])
|
||||
const edges = ref<Edge[]>([])
|
||||
const flowContainerRef = ref<HTMLElement | null>(null)
|
||||
const selectedTargetChannelId = ref('')
|
||||
const {fitView} = useVueFlow()
|
||||
const flowHeight = 340
|
||||
let flowContainerResizeObserver: ResizeObserver | null = null
|
||||
const edgeTypes = {
|
||||
deletable: FreqConverterDetectEdge
|
||||
}
|
||||
|
||||
const selectedDevice = computed(() => {
|
||||
return deviceOptions.value.find(item => item.id === selectedDeviceId.value) || null
|
||||
})
|
||||
|
||||
const createDeviceLabel = (title: string, lines: string[]) => {
|
||||
return h(
|
||||
'div',
|
||||
{class: 'device-node-label'},
|
||||
[
|
||||
h('div', {class: 'device-node-title'}, title),
|
||||
...lines.map(line => h('div', {class: 'device-node-line'}, line))
|
||||
]
|
||||
) as any
|
||||
}
|
||||
|
||||
const createChannelLabel = (title: string) => {
|
||||
return h(
|
||||
'div',
|
||||
{class: 'channel-node-label'},
|
||||
h('span', {class: 'channel-node-text'}, title)
|
||||
) as any
|
||||
}
|
||||
|
||||
const createConnectionEdge = (source: string, target: string): Edge => ({
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target,
|
||||
type: 'deletable',
|
||||
selectable: true,
|
||||
focusable: true,
|
||||
data: {
|
||||
onDelete: removeConnectionById
|
||||
},
|
||||
style: {
|
||||
stroke: 'var(--el-color-primary)'
|
||||
}
|
||||
})
|
||||
|
||||
const getDeviceChannels = (device: Device.ResPqDev | null) => {
|
||||
if (!device) {
|
||||
return [] as string[]
|
||||
}
|
||||
|
||||
const channelCount = Math.max(Number(device.devChns) || 0, 0)
|
||||
return Array.from({length: channelCount}, (_, index) => `${index + 1}`)
|
||||
}
|
||||
|
||||
const buildNodes = (device: Device.ResPqDev) => {
|
||||
const channelList = getDeviceChannels(device)
|
||||
const flowNodes: Node[] = []
|
||||
const channelCount = Math.max(channelList.length, 1)
|
||||
const flowCanvasHeight = flowHeight
|
||||
const gapY = 42
|
||||
const channelHeight = 20
|
||||
const cardHeight = 120
|
||||
const cardWidth = 210
|
||||
const layoutCenterY = flowCanvasHeight / 2
|
||||
const channelGroupHeight = channelHeight + (channelCount - 1) * gapY
|
||||
const startY = layoutCenterY - channelGroupHeight / 2
|
||||
const cardY = layoutCenterY - cardHeight / 2
|
||||
|
||||
flowNodes.push({
|
||||
id: `freq-converter-${props.freqConverter?.id || 'default'}`,
|
||||
type: 'output',
|
||||
data: {
|
||||
label: createDeviceLabel('变频器', [
|
||||
`名称:${props.freqConverter?.name || '-'}`,
|
||||
`串口:${props.freqConverter?.portName || '-'}`,
|
||||
`从机地址:${props.freqConverter?.slaveAddress ?? '-'}`,
|
||||
`波特率:${props.freqConverter?.baudRate ?? '-'}`
|
||||
])
|
||||
},
|
||||
position: {x: 84, y: cardY},
|
||||
sourcePosition: Position.Right,
|
||||
draggable: false,
|
||||
selectable: false,
|
||||
class: 'freq-converter-node',
|
||||
style: {width: `${cardWidth}px`, height: `${cardHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
|
||||
})
|
||||
|
||||
flowNodes.push({
|
||||
id: `device-card-${device.id}`,
|
||||
data: {
|
||||
label: createDeviceLabel('设备', [
|
||||
`名称:${device.name || '-'}`,
|
||||
`类型:${device.devType || '-'}`,
|
||||
`IP:${device.ip || '-'}`,
|
||||
`通道数:${channelList.length}`
|
||||
])
|
||||
},
|
||||
position: {x: 654, y: cardY},
|
||||
draggable: false,
|
||||
selectable: false,
|
||||
class: 'no-handle-node',
|
||||
style: {width: `${cardWidth}px`, height: `${cardHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
|
||||
})
|
||||
|
||||
channelList.forEach((channel, index) => {
|
||||
const y = startY + index * gapY
|
||||
|
||||
flowNodes.push({
|
||||
id: `device-channel-${device.id}-${channel}`,
|
||||
type: 'input',
|
||||
data: {label: createChannelLabel(`通道${channel}`)},
|
||||
position: {x: 584, y},
|
||||
targetPosition: Position.Left,
|
||||
draggable: false,
|
||||
selectable: false,
|
||||
class: 'channel-node',
|
||||
style: {width: '68px', height: `${channelHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
|
||||
})
|
||||
})
|
||||
|
||||
return flowNodes
|
||||
}
|
||||
|
||||
const fitFlowView = async () => {
|
||||
if (!nodes.value.length) {
|
||||
return
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
await fitView({
|
||||
padding: 0.18,
|
||||
duration: 0
|
||||
})
|
||||
}
|
||||
|
||||
const observeFlowContainer = () => {
|
||||
flowContainerResizeObserver?.disconnect()
|
||||
flowContainerResizeObserver = null
|
||||
|
||||
if (!flowContainerRef.value || typeof ResizeObserver === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
flowContainerResizeObserver = new ResizeObserver(() => {
|
||||
void fitFlowView()
|
||||
})
|
||||
flowContainerResizeObserver.observe(flowContainerRef.value)
|
||||
}
|
||||
|
||||
const getExpectedSourceId = () => `freq-converter-${props.freqConverter?.id || 'default'}`
|
||||
|
||||
const getFirstChannelId = (device: Device.ResPqDev | null) => {
|
||||
if (!device) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const [firstChannel] = getDeviceChannels(device)
|
||||
return firstChannel ? `device-channel-${device.id}-${firstChannel}` : ''
|
||||
}
|
||||
|
||||
const syncEdges = () => {
|
||||
const source = getExpectedSourceId()
|
||||
edges.value = selectedTargetChannelId.value ? [createConnectionEdge(source, selectedTargetChannelId.value)] : []
|
||||
}
|
||||
|
||||
const rebuildNodes = async () => {
|
||||
const currentDevice = selectedDevice.value
|
||||
nodes.value = currentDevice ? buildNodes(currentDevice) : []
|
||||
|
||||
if (currentDevice && selectedTargetChannelId.value && !selectedTargetChannelId.value.startsWith(`device-channel-${currentDevice.id}-`)) {
|
||||
selectedTargetChannelId.value = getFirstChannelId(currentDevice)
|
||||
}
|
||||
|
||||
syncEdges()
|
||||
await nextTick()
|
||||
observeFlowContainer()
|
||||
await fitFlowView()
|
||||
}
|
||||
|
||||
const clearConnections = () => {
|
||||
selectedTargetChannelId.value = ''
|
||||
edges.value = []
|
||||
}
|
||||
|
||||
const removeConnectionById = (edgeId: string) => {
|
||||
if (edges.value.some(item => item.id === edgeId)) {
|
||||
selectedTargetChannelId.value = ''
|
||||
}
|
||||
edges.value = edges.value.filter(item => item.id !== edgeId)
|
||||
}
|
||||
|
||||
const handleConnect = (params: Connection) => {
|
||||
if (!params.source || !params.target) {
|
||||
return
|
||||
}
|
||||
|
||||
const expectedSource = getExpectedSourceId()
|
||||
const connectionNodeIds = [params.source, params.target]
|
||||
const hasFreqConverterNode = connectionNodeIds.includes(expectedSource)
|
||||
const deviceChannelId = connectionNodeIds.find(item => item.startsWith('device-channel-'))
|
||||
const sourceNode = nodes.value.find(item => item.id === expectedSource)
|
||||
const targetNode = nodes.value.find(item => item.id === deviceChannelId)
|
||||
|
||||
if (!hasFreqConverterNode || !deviceChannelId || sourceNode?.type !== 'output' || targetNode?.type !== 'input') {
|
||||
ElMessage.warning('只能从左侧变频器连线到右侧设备通道')
|
||||
return
|
||||
}
|
||||
|
||||
if (edges.value.length > 0) {
|
||||
ElMessage.warning('变频器只能选择一个设备通道,如需重选请先删除配对')
|
||||
return
|
||||
}
|
||||
|
||||
selectedTargetChannelId.value = deviceChannelId
|
||||
syncEdges()
|
||||
}
|
||||
|
||||
const loadDeviceOptions = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await getPqDevListAll()
|
||||
const records = Array.isArray(result?.data) ? result.data : []
|
||||
deviceOptions.value = records
|
||||
if (records.length) {
|
||||
selectedDeviceId.value = records[0].id
|
||||
return
|
||||
}
|
||||
|
||||
selectedDeviceId.value = ''
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getChannelMapping = () => {
|
||||
const edge = edges.value[0]
|
||||
if (!edge) {
|
||||
return null
|
||||
}
|
||||
|
||||
const targetParts = edge.target.split('-')
|
||||
|
||||
return {
|
||||
deviceId: selectedDevice.value?.id || '',
|
||||
deviceName: selectedDevice.value?.name || '',
|
||||
deviceChannel: targetParts[targetParts.length - 1],
|
||||
freqConverterId: props.freqConverter?.id || '',
|
||||
freqConverterName: props.freqConverter?.name || ''
|
||||
}
|
||||
}
|
||||
|
||||
watch(selectedDeviceId, async () => {
|
||||
selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
|
||||
await rebuildNodes()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.freqConverter?.id,
|
||||
async () => {
|
||||
selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
|
||||
await rebuildNodes()
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDeviceOptions()
|
||||
selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
|
||||
await rebuildNodes()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
flowContainerResizeObserver?.disconnect()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
getSelectedDevice: () => selectedDevice.value,
|
||||
hasValidConnection: () => edges.value.length === 1,
|
||||
getChannelMapping,
|
||||
clearConnections,
|
||||
resetSelection: () => {
|
||||
const firstDevice = deviceOptions.value[0] || null
|
||||
selectedDeviceId.value = firstDevice?.id || ''
|
||||
selectedTargetChannelId.value = getFirstChannelId(firstDevice)
|
||||
void rebuildNodes()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section-card {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
|
||||
:deep(.section-card .el-card__header) {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
:deep(.section-card .el-card__body) {
|
||||
padding: 10px 14px 14px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.section-tip {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.toolbar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar-label {
|
||||
color: var(--el-text-color-regular);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-select {
|
||||
width: 280px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.flow-container {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node) {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
background: var(--el-bg-color-page);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node.no-handle-node .vue-flow__handle) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__handle) {
|
||||
width: 0;
|
||||
height: 0;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__handle::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-primary);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node.channel-node) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node.channel-node .vue-flow__handle) {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node.freq-converter-node .vue-flow__handle) {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
|
||||
:deep(.device-node-label) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 10px;
|
||||
background: var(--el-fill-color-light);
|
||||
padding: 10px 12px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
:deep(.device-node-title) {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.device-node-line) {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:deep(.channel-node-label) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
padding: 0 0 0 10px;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-primary);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.channel-node-text) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.section-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.toolbar-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.device-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,100 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {Delete} from '@element-plus/icons-vue'
|
||||
import {BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow} from '@vue-flow/core'
|
||||
import {computed} from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
sourcePosition: string;
|
||||
targetPosition: string;
|
||||
markerEnd?: string;
|
||||
style?: Record<string, any>;
|
||||
selected?: boolean;
|
||||
data?: {
|
||||
onDelete?: (id: string) => void;
|
||||
};
|
||||
}>()
|
||||
|
||||
const {removeEdges} = useVueFlow()
|
||||
|
||||
const path = computed(() => getBezierPath(props))
|
||||
const edgeStyle = computed(() => ({
|
||||
...props.style,
|
||||
strokeWidth: props.selected ? 3 : 2,
|
||||
strokeLinecap: 'round',
|
||||
stroke: props.selected ? 'var(--el-color-danger)' : props.style?.stroke || 'var(--el-color-primary)'
|
||||
}))
|
||||
|
||||
const handleDelete = () => {
|
||||
if (props.data?.onDelete) {
|
||||
props.data.onDelete(props.id)
|
||||
return
|
||||
}
|
||||
|
||||
removeEdges(props.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseEdge :id="id" :path="path[0]" :marker-end="markerEnd" :style="edgeStyle" />
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
v-if="selected"
|
||||
class="edge-delete-trigger nodrag nopan"
|
||||
:style="{
|
||||
transform: `translate(-50%, -50%) translate(${path[1]}px, ${path[2]}px)`
|
||||
}"
|
||||
>
|
||||
<el-popconfirm
|
||||
title="是否删除当前连线?"
|
||||
confirm-button-text="是"
|
||||
cancel-button-text="否"
|
||||
width="180"
|
||||
@confirm="handleDelete"
|
||||
>
|
||||
<template #reference>
|
||||
<button class="delete-button" type="button" aria-label="删除连线">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.edge-delete-trigger {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 50%;
|
||||
background: var(--el-bg-color);
|
||||
color: var(--el-color-danger);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.14);
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
border-color: var(--el-color-danger);
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user