feat(tools): 新增台账管理功能模块
- 添加 addLedger API 接口定义和实现 - 创建工程配置表单组件 (EngineeringForm) - 创建设备配置表单组件 (EquipmentForm) - 创建项目和测点表单组件 (ProjectForm, LineForm) - 实现台账树形结构面板 (LedgerTreePanel) - 添加台账数据验证契约检查脚本 - 集成字典选项和动态表单验证功能 - 实现台账节点增删改查完整流程 - 优化 Echarts 图表组件分组渲染性能
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const apiFile = path.join(currentDir, 'index.ts')
|
||||||
|
const interfaceFile = path.join(currentDir, 'interface', 'index.ts')
|
||||||
|
|
||||||
|
const apiSource = fs.readFileSync(apiFile, 'utf8')
|
||||||
|
const interfaceSource = fs.readFileSync(interfaceFile, 'utf8')
|
||||||
|
|
||||||
|
const expectations = [
|
||||||
|
['equipment payload maps devType', /devType:\s*params\.dev_type/],
|
||||||
|
['equipment payload maps devModel', /devModel:\s*params\.dev_model/],
|
||||||
|
['equipment payload maps devAccessMethod', /devAccessMethod:\s*params\.dev_access_method/],
|
||||||
|
['equipment payload maps nodeId', /nodeId:\s*params\.node_id/],
|
||||||
|
['equipment payload maps nodeProcess', /nodeProcess:\s*resolveOptionalNumber\(params\.node_process\)/],
|
||||||
|
['line payload maps lineId', /lineId:\s*resolveOptionalText\(params\.line_id\s*\|\|\s*params\.id\)/],
|
||||||
|
['line payload maps lineNo', /lineNo:\s*params\.line_no/],
|
||||||
|
['line payload maps volGrade', /volGrade:\s*params\.vol_grade/],
|
||||||
|
['line payload maps ctRatio', /ctRatio:\s*params\.ct_ratio/],
|
||||||
|
['line payload maps isGovern', /isGovern:\s*params\.is_govern/],
|
||||||
|
['tree node supports parentId', /parentId\?:\s*string/],
|
||||||
|
['tree node supports parentIds', /parentIds\?:\s*string/],
|
||||||
|
['delete response type is boolean', /requestAddLedger<boolean>\('delete',\s*'\/node'/]
|
||||||
|
]
|
||||||
|
|
||||||
|
const failures = expectations.filter(([, pattern]) => !pattern.test(`${apiSource}\n${interfaceSource}`))
|
||||||
|
|
||||||
|
if (failures.length) {
|
||||||
|
console.error('addLedger API_DEBUG contract check failed:')
|
||||||
|
for (const [name] of failures) {
|
||||||
|
console.error(`- ${name}`)
|
||||||
|
}
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('addLedger API_DEBUG contract check passed')
|
||||||
172
frontend/src/api/tools/addLedger/index.ts
Normal file
172
frontend/src/api/tools/addLedger/index.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import http from '@/api'
|
||||||
|
import type { ResultData } from '@/api/interface'
|
||||||
|
import type { AddLedger } from './interface'
|
||||||
|
|
||||||
|
type AddLedgerRequestMethod = 'get' | 'post' | 'delete'
|
||||||
|
|
||||||
|
const ADD_LEDGER_ROUTE_PATHS = ['/addLedger', '/api/addLedger'] as const
|
||||||
|
const ADD_LEDGER_BASE_URL = String(import.meta.env.VITE_API_URL || '').trim()
|
||||||
|
|
||||||
|
const resolveOptionalText = (value: unknown) => {
|
||||||
|
if (value === null || value === undefined) return undefined
|
||||||
|
const text = String(value).trim()
|
||||||
|
return text || undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveOptionalNumber = (value: unknown) => {
|
||||||
|
if (value === null || value === undefined || value === '') return undefined
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isFinite(parsed) ? parsed : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const toAddLedgerProjectPayload = (params: AddLedger.ProjectForm) => ({
|
||||||
|
id: resolveOptionalText(params.id),
|
||||||
|
engineeringId: resolveOptionalText(params.engineeringId || params.parentId),
|
||||||
|
name: params.name,
|
||||||
|
area: params.area,
|
||||||
|
description: params.description
|
||||||
|
})
|
||||||
|
|
||||||
|
const toAddLedgerEquipmentPayload = (params: AddLedger.EquipmentForm) => ({
|
||||||
|
id: resolveOptionalText(params.id),
|
||||||
|
projectId: resolveOptionalText(params.projectId || params.parentId),
|
||||||
|
name: params.name,
|
||||||
|
ndid: params.ndid,
|
||||||
|
mac: params.mac,
|
||||||
|
devType: params.dev_type,
|
||||||
|
devModel: params.dev_model,
|
||||||
|
devAccessMethod: params.dev_access_method,
|
||||||
|
nodeId: params.node_id,
|
||||||
|
nodeProcess: resolveOptionalNumber(params.node_process),
|
||||||
|
upgrade: params.upgrade
|
||||||
|
})
|
||||||
|
|
||||||
|
const toAddLedgerLinePayload = (params: AddLedger.LineForm) => ({
|
||||||
|
lineId: resolveOptionalText(params.line_id || params.id),
|
||||||
|
deviceId: resolveOptionalText(params.deviceId || params.parentId),
|
||||||
|
name: params.name,
|
||||||
|
lineNo: params.line_no,
|
||||||
|
conType: params.conType,
|
||||||
|
volGrade: params.vol_grade,
|
||||||
|
position: params.position,
|
||||||
|
ctRatio: params.ct_ratio,
|
||||||
|
ct2Ratio: params.ct2_ratio,
|
||||||
|
ptRatio: params.pt_ratio,
|
||||||
|
pt2Ratio: params.pt2_ratio,
|
||||||
|
shortCircuitCapacity: params.short_circuit_capacity,
|
||||||
|
basicCapacity: params.basic_capacity,
|
||||||
|
protocolCapacity: params.protocol_capacity,
|
||||||
|
devCapacity: params.dev_capacity,
|
||||||
|
monitorObj: params.monitor_obj,
|
||||||
|
isGovern: params.is_govern,
|
||||||
|
monitorUser: params.monitor_user,
|
||||||
|
isImportant: params.is_important
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolveDevProxyTarget = () => {
|
||||||
|
const proxyConfig = import.meta.env.VITE_PROXY
|
||||||
|
if (!Array.isArray(proxyConfig)) return ''
|
||||||
|
|
||||||
|
const matchedProxy = proxyConfig.find(item => Array.isArray(item) && item[0] === '/api')
|
||||||
|
if (!matchedProxy?.[1]) return ''
|
||||||
|
|
||||||
|
return String(matchedProxy[1]).replace(/\/+$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildAddLedgerRequestPaths = (path: string) => {
|
||||||
|
const requestPaths = new Set<string>()
|
||||||
|
const devProxyTarget = resolveDevProxyTarget()
|
||||||
|
|
||||||
|
for (const routePath of ADD_LEDGER_ROUTE_PATHS) {
|
||||||
|
if (ADD_LEDGER_BASE_URL === '/api' && routePath.startsWith('/api/')) {
|
||||||
|
if (devProxyTarget) {
|
||||||
|
requestPaths.add(`${devProxyTarget}${routePath}${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPaths.add(`${window.location.origin}${routePath}${path}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPaths.add(`${routePath}${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(requestPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFallbackableAddLedgerError = (error: unknown) => {
|
||||||
|
const responseCode = typeof error === 'object' && error !== null && 'code' in error ? String(error.code) : ''
|
||||||
|
const responseMessage = typeof error === 'object' && error !== null && 'message' in error ? String(error.message) : ''
|
||||||
|
const normalizedMessage = responseMessage.toLowerCase()
|
||||||
|
|
||||||
|
return (
|
||||||
|
responseCode === '404' ||
|
||||||
|
normalizedMessage.includes('unknown operate') ||
|
||||||
|
normalizedMessage.includes('not found') ||
|
||||||
|
normalizedMessage.includes('no handler found')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestAddLedger = async <T>(
|
||||||
|
method: AddLedgerRequestMethod,
|
||||||
|
path: string,
|
||||||
|
params?: object
|
||||||
|
): Promise<ResultData<T>> => {
|
||||||
|
let lastError: unknown
|
||||||
|
const requestPaths = buildAddLedgerRequestPaths(path)
|
||||||
|
|
||||||
|
for (let index = 0; index < requestPaths.length; index += 1) {
|
||||||
|
const requestPath = requestPaths[index]
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (method === 'get') {
|
||||||
|
return await http.get<T>(requestPath, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'delete') {
|
||||||
|
return await http.delete<T>(requestPath, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await http.post<T>(requestPath, params)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error
|
||||||
|
|
||||||
|
if (index === requestPaths.length - 1 || !isFallbackableAddLedgerError(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAddLedgerTree = () => {
|
||||||
|
return requestAddLedger<AddLedger.LedgerTreeNode[]>('get', '/tree')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAddLedgerDetail = (params: AddLedger.DetailParams) => {
|
||||||
|
return requestAddLedger<AddLedger.NodeDetail>('get', '/detail', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveAddLedgerEngineering = (params: AddLedger.EngineeringForm) => {
|
||||||
|
return requestAddLedger<AddLedger.EngineeringForm>('post', '/engineering/save', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveAddLedgerProject = (params: AddLedger.ProjectForm) => {
|
||||||
|
return requestAddLedger<AddLedger.ProjectForm>('post', '/project/save', toAddLedgerProjectPayload(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveAddLedgerEquipment = (params: AddLedger.EquipmentForm) => {
|
||||||
|
return requestAddLedger<AddLedger.EquipmentForm>('post', '/equipment/save', toAddLedgerEquipmentPayload(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveAddLedgerLine = (params: AddLedger.LineForm) => {
|
||||||
|
return requestAddLedger<AddLedger.LineForm>('post', '/line/save', toAddLedgerLinePayload(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAvailableLineNos = (params: AddLedger.AvailableLineNoParams) => {
|
||||||
|
return requestAddLedger<number[]>('get', '/line/availableLineNos', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteAddLedgerNode = (params: AddLedger.DeleteNodeParams) => {
|
||||||
|
return requestAddLedger<boolean>('delete', '/node', params)
|
||||||
|
}
|
||||||
110
frontend/src/api/tools/addLedger/interface/index.ts
Normal file
110
frontend/src/api/tools/addLedger/interface/index.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
export namespace AddLedger {
|
||||||
|
export type NodeLevel = 0 | 1 | 2 | 3
|
||||||
|
|
||||||
|
export interface LedgerTreeNode {
|
||||||
|
id?: string
|
||||||
|
Id?: string
|
||||||
|
pid?: string
|
||||||
|
Pid?: string
|
||||||
|
pids?: string
|
||||||
|
Pids?: string
|
||||||
|
parentId?: string
|
||||||
|
parentIds?: string
|
||||||
|
name?: string
|
||||||
|
Name?: string
|
||||||
|
level?: NodeLevel
|
||||||
|
Level?: NodeLevel
|
||||||
|
state?: number
|
||||||
|
State?: number
|
||||||
|
children?: LedgerTreeNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedTreeNode {
|
||||||
|
id: string
|
||||||
|
pid: string
|
||||||
|
pids: string
|
||||||
|
name: string
|
||||||
|
level: NodeLevel
|
||||||
|
children: NormalizedTreeNode[]
|
||||||
|
raw: LedgerTreeNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetailParams {
|
||||||
|
id: string
|
||||||
|
level: NodeLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EngineeringForm {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
province?: string
|
||||||
|
city?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectForm {
|
||||||
|
id?: string
|
||||||
|
engineeringId?: string
|
||||||
|
parentId?: string
|
||||||
|
name: string
|
||||||
|
area?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EquipmentForm {
|
||||||
|
id?: string
|
||||||
|
engineeringId?: string
|
||||||
|
projectId?: string
|
||||||
|
parentId?: string
|
||||||
|
name: string
|
||||||
|
ndid: string
|
||||||
|
mac: string
|
||||||
|
dev_type?: string
|
||||||
|
dev_model: string
|
||||||
|
dev_access_method?: string
|
||||||
|
node_id?: string
|
||||||
|
node_process?: string
|
||||||
|
upgrade?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LineForm {
|
||||||
|
id?: string
|
||||||
|
line_id?: string
|
||||||
|
deviceId?: string
|
||||||
|
parentId?: string
|
||||||
|
name: string
|
||||||
|
line_no?: number
|
||||||
|
conType?: number
|
||||||
|
vol_grade?: number
|
||||||
|
position?: string
|
||||||
|
ct_ratio?: number
|
||||||
|
ct2_ratio?: number
|
||||||
|
pt_ratio?: number
|
||||||
|
pt2_ratio?: number
|
||||||
|
short_circuit_capacity?: number
|
||||||
|
basic_capacity?: number
|
||||||
|
protocol_capacity?: number
|
||||||
|
dev_capacity?: number
|
||||||
|
monitor_obj?: string
|
||||||
|
is_govern?: number
|
||||||
|
monitor_user?: string
|
||||||
|
is_important?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeDetail = EngineeringForm | ProjectForm | EquipmentForm | LineForm
|
||||||
|
|
||||||
|
export interface AvailableLineNoParams {
|
||||||
|
deviceId: string
|
||||||
|
lineId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeleteNodeParams {
|
||||||
|
id: string
|
||||||
|
level: NodeLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectOption<T = string | number> {
|
||||||
|
label: string
|
||||||
|
value: T
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
let chart: echarts.ECharts | any = null
|
let chart: echarts.ECharts | any = null
|
||||||
let isPanPointerDown = false
|
let isPanPointerDown = false
|
||||||
|
let currentGroup: string | undefined
|
||||||
|
|
||||||
const getChartViewportRoot = () => chart?.getZr()?.painter?.getViewportRoot?.() as HTMLElement | undefined
|
const getChartViewportRoot = () => chart?.getZr()?.painter?.getViewportRoot?.() as HTMLElement | undefined
|
||||||
|
|
||||||
@@ -199,18 +200,18 @@ const resizeHandler = () => {
|
|||||||
chart.getZr().painter.getViewportRoot().style.display = ''
|
chart.getZr().painter.getViewportRoot().style.display = ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const initChart = () => {
|
const updateChartGroup = () => {
|
||||||
if (!props.isInterVal && !props.pieInterVal) {
|
if (!chart || props.group === currentGroup) return
|
||||||
unbindPanCursorEvents()
|
|
||||||
chart?.dispose()
|
currentGroup = props.group
|
||||||
}
|
chart.group = props.group || undefined
|
||||||
// chart?.dispose()
|
|
||||||
chart = echarts.init(chartRef.value as HTMLDivElement)
|
|
||||||
if (props.group) {
|
if (props.group) {
|
||||||
chart.group = props.group
|
|
||||||
echarts.connect(props.group)
|
echarts.connect(props.group)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildChartOptions = () => {
|
||||||
const options = {
|
const options = {
|
||||||
title: {
|
title: {
|
||||||
left: 'center',
|
left: 'center',
|
||||||
@@ -295,7 +296,10 @@ const initChart = () => {
|
|||||||
// console.log(options.series,"获取x轴");
|
// console.log(options.series,"获取x轴");
|
||||||
handlerBar(options)
|
handlerBar(options)
|
||||||
|
|
||||||
chart.setOption(options, true)
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindChartEvents = () => {
|
||||||
chart.off('datazoom')
|
chart.off('datazoom')
|
||||||
chart.on('datazoom', (params: any) => {
|
chart.on('datazoom', (params: any) => {
|
||||||
const zoomPayload = Array.isArray(params.batch) ? params.batch[0] : params
|
const zoomPayload = Array.isArray(params.batch) ? params.batch[0] : params
|
||||||
@@ -309,16 +313,51 @@ const initChart = () => {
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
bindPanCursorEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderChart = (isInitial = false) => {
|
||||||
|
if (!chartRef.value) return
|
||||||
|
|
||||||
|
if (!chart) {
|
||||||
|
chart = echarts.init(chartRef.value)
|
||||||
|
updateChartGroup()
|
||||||
|
bindChartEvents()
|
||||||
|
} else {
|
||||||
|
updateChartGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = buildChartOptions()
|
||||||
|
|
||||||
|
chart.setOption(options, {
|
||||||
|
notMerge: isInitial,
|
||||||
|
lazyUpdate: !isInitial
|
||||||
|
})
|
||||||
chart.dispatchAction({
|
chart.dispatchAction({
|
||||||
type: 'takeGlobalCursor',
|
type: 'takeGlobalCursor',
|
||||||
key: 'dataZoomSelect',
|
key: 'dataZoomSelect',
|
||||||
dataZoomSelectActive: props.options?.activeTool === 'box-zoom'
|
dataZoomSelectActive: props.options?.activeTool === 'box-zoom'
|
||||||
})
|
})
|
||||||
bindPanCursorEvents()
|
if (props.options?.activeTool !== 'pan' && props.options?.activeTool !== 'mark') {
|
||||||
|
resetChartCursor()
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
if (isInitial) {
|
||||||
chart.resize()
|
setTimeout(() => {
|
||||||
}, 0)
|
chart.resize()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (!props.isInterVal && !props.pieInterVal) {
|
||||||
|
unbindPanCursorEvents()
|
||||||
|
chart?.dispose()
|
||||||
|
chart = null
|
||||||
|
currentGroup = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChart(true)
|
||||||
}
|
}
|
||||||
const handlerBar = (options: any) => {
|
const handlerBar = (options: any) => {
|
||||||
if (Array.isArray(options.series)) {
|
if (Array.isArray(options.series)) {
|
||||||
@@ -450,9 +489,10 @@ onBeforeUnmount(() => {
|
|||||||
chart?.dispose()
|
chart?.dispose()
|
||||||
})
|
})
|
||||||
watch(
|
watch(
|
||||||
() => props.options,
|
() => [props.options, props.group],
|
||||||
() => {
|
() => {
|
||||||
initChart()
|
// 缩放按钮只更新坐标和缩放配置,复用实例可避免大数据趋势图反复销毁重建。
|
||||||
|
renderChart()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ export const staticRouter: RouteRecordRaw[] = [
|
|||||||
title: '模拟数据'
|
title: '模拟数据'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tools/addLedger',
|
||||||
|
name: 'toolAddLedger',
|
||||||
|
component: () => import('@/views/tools/addLedger/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '数据台账'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/403',
|
path: '/403',
|
||||||
name: '403',
|
name: '403',
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card ledger-form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<div>
|
||||||
|
<div class="section-title">工程配置</div>
|
||||||
|
<div class="section-description">维护工程基础信息,并从工程下继续新增项目。</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<el-button type="primary" :icon="CirclePlus" @click="emit('add-project')">新增项目</el-button>
|
||||||
|
<el-button type="primary" :icon="Check" :loading="saving" @click="emit('save')">保存工程</el-button>
|
||||||
|
<el-button type="danger" plain :icon="Delete" :disabled="!localForm.id" @click="emit('delete')">
|
||||||
|
删除工程
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="localForm" :rules="formRules" label-width="96px" class="ledger-form form-two">
|
||||||
|
<el-form-item label="工程名称" prop="name">
|
||||||
|
<el-input v-model="localForm.name" maxlength="80" clearable placeholder="请输入工程名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="省" prop="province">
|
||||||
|
<el-input v-model="localForm.province" maxlength="40" clearable placeholder="请输入省份" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="市" prop="city">
|
||||||
|
<el-input v-model="localForm.city" maxlength="40" clearable placeholder="请输入城市" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="localForm.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入工程描述"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { Check, CirclePlus, Delete } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'EngineeringForm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
form: AddLedger.EngineeringForm
|
||||||
|
saving: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:form': [form: AddLedger.EngineeringForm]
|
||||||
|
save: []
|
||||||
|
delete: []
|
||||||
|
'add-project': []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const syncingFromProp = ref(false)
|
||||||
|
const localForm = reactive<AddLedger.EngineeringForm>({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const syncLocalForm = (form: AddLedger.EngineeringForm) => {
|
||||||
|
localForm.id = form.id || ''
|
||||||
|
localForm.name = form.name || ''
|
||||||
|
localForm.province = form.province || ''
|
||||||
|
localForm.city = form.city || ''
|
||||||
|
localForm.description = form.description || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.form,
|
||||||
|
value => {
|
||||||
|
syncingFromProp.value = true
|
||||||
|
syncLocalForm(value)
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
localForm,
|
||||||
|
value => {
|
||||||
|
if (syncingFromProp.value) {
|
||||||
|
syncingFromProp.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:form', { ...value })
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const formRules: FormRules<AddLedger.EngineeringForm> = {
|
||||||
|
name: [{ required: true, message: '请输入工程名称', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = async () => {
|
||||||
|
const result = await formRef.value?.validate().catch(() => false)
|
||||||
|
return Boolean(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validateForm
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './ledgerForm.scss';
|
||||||
|
</style>
|
||||||
152
frontend/src/views/tools/addLedger/components/EquipmentForm.vue
Normal file
152
frontend/src/views/tools/addLedger/components/EquipmentForm.vue
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card ledger-form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<div>
|
||||||
|
<div class="section-title">设备配置</div>
|
||||||
|
<div class="section-description">维护装置编号、型号和前置节点信息,并从设备下继续新增测点。</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<el-button type="primary" :icon="CirclePlus" @click="emit('add-line')">新增测点</el-button>
|
||||||
|
<el-button type="primary" :icon="Check" :loading="saving" @click="emit('save')">保存设备</el-button>
|
||||||
|
<el-button type="danger" plain :icon="Delete" :disabled="!localForm.id" @click="emit('delete')">
|
||||||
|
删除设备
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="localForm" :rules="formRules" label-width="126px" class="ledger-form form-two">
|
||||||
|
<el-form-item label="装置名称" prop="name">
|
||||||
|
<el-input v-model="localForm.name" maxlength="80" clearable placeholder="请输入装置名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="网络设备 ID" prop="ndid">
|
||||||
|
<el-input v-model="localForm.ndid" maxlength="64" clearable placeholder="请输入网络设备 ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="装置 MAC 地址" prop="mac">
|
||||||
|
<el-input v-model="localForm.mac" maxlength="64" clearable placeholder="请输入装置 MAC 地址" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="装置类型" prop="dev_type">
|
||||||
|
<el-select v-model="localForm.dev_type" clearable placeholder="请选择装置类型">
|
||||||
|
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="装置型号" prop="dev_model">
|
||||||
|
<el-select v-model="localForm.dev_model" clearable placeholder="请选择装置型号">
|
||||||
|
<el-option v-for="item in deviceModelOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="接入方式" prop="dev_access_method">
|
||||||
|
<el-input v-model="localForm.dev_access_method" maxlength="40" clearable placeholder="请输入接入方式" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="前置服务器 IP" prop="node_id">
|
||||||
|
<el-input v-model="localForm.node_id" maxlength="64" clearable placeholder="请输入前置服务器 IP" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="前置进程号" prop="node_process">
|
||||||
|
<el-input v-model="localForm.node_process" maxlength="40" clearable placeholder="请输入前置进程号" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否支持升级" prop="upgrade">
|
||||||
|
<el-switch v-model="localForm.upgrade" :active-value="1" :inactive-value="0" active-text="支持" inactive-text="不支持" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { Check, CirclePlus, Delete } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'EquipmentForm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
form: AddLedger.EquipmentForm
|
||||||
|
saving: boolean
|
||||||
|
deviceTypeOptions: AddLedger.SelectOption[]
|
||||||
|
deviceModelOptions: AddLedger.SelectOption[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:form': [form: AddLedger.EquipmentForm]
|
||||||
|
save: []
|
||||||
|
delete: []
|
||||||
|
'add-line': []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const syncingFromProp = ref(false)
|
||||||
|
const localForm = reactive<AddLedger.EquipmentForm>({
|
||||||
|
id: '',
|
||||||
|
engineeringId: '',
|
||||||
|
projectId: '',
|
||||||
|
parentId: '',
|
||||||
|
name: '',
|
||||||
|
ndid: '',
|
||||||
|
mac: '',
|
||||||
|
dev_type: '',
|
||||||
|
dev_model: '',
|
||||||
|
dev_access_method: '',
|
||||||
|
node_id: '',
|
||||||
|
node_process: '',
|
||||||
|
upgrade: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const syncLocalForm = (form: AddLedger.EquipmentForm) => {
|
||||||
|
localForm.id = form.id || ''
|
||||||
|
localForm.engineeringId = form.engineeringId || ''
|
||||||
|
localForm.projectId = form.projectId || ''
|
||||||
|
localForm.parentId = form.parentId || ''
|
||||||
|
localForm.name = form.name || ''
|
||||||
|
localForm.ndid = form.ndid || ''
|
||||||
|
localForm.mac = form.mac || ''
|
||||||
|
localForm.dev_type = form.dev_type || ''
|
||||||
|
localForm.dev_model = form.dev_model || ''
|
||||||
|
localForm.dev_access_method = form.dev_access_method || ''
|
||||||
|
localForm.node_id = form.node_id || ''
|
||||||
|
localForm.node_process = form.node_process || ''
|
||||||
|
localForm.upgrade = form.upgrade ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.form,
|
||||||
|
value => {
|
||||||
|
syncingFromProp.value = true
|
||||||
|
syncLocalForm(value)
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
localForm,
|
||||||
|
value => {
|
||||||
|
if (syncingFromProp.value) {
|
||||||
|
syncingFromProp.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:form', { ...value })
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const formRules: FormRules<AddLedger.EquipmentForm> = {
|
||||||
|
name: [{ required: true, message: '请输入装置名称', trigger: 'blur' }],
|
||||||
|
ndid: [{ required: true, message: '请输入网络设备 ID', trigger: 'blur' }],
|
||||||
|
mac: [{ required: true, message: '请输入装置 MAC 地址', trigger: 'blur' }],
|
||||||
|
dev_model: [{ required: true, message: '请选择装置型号', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = async () => {
|
||||||
|
const result = await formRef.value?.validate().catch(() => false)
|
||||||
|
return Boolean(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validateForm
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './ledgerForm.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card ledger-tree-card">
|
||||||
|
<div class="ledger-tree-header">
|
||||||
|
<div class="section-title">台账树</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="emit('add-engineering')">新增工程</el-button>
|
||||||
|
<el-button type="primary" plain :icon="Refresh" :loading="loading" @click="emit('refresh')">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-input v-model="keyword" class="tree-search" clearable placeholder="搜索工程、项目、设备、监测点" />
|
||||||
|
|
||||||
|
<el-scrollbar class="tree-scrollbar">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="treeData"
|
||||||
|
node-key="id"
|
||||||
|
highlight-current
|
||||||
|
default-expand-all
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
:props="{ label: 'name', children: 'children' }"
|
||||||
|
@node-click="handleNodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<div class="ledger-tree-node">
|
||||||
|
<el-tag size="small" effect="plain" :type="resolveTagType(data.level)">
|
||||||
|
{{ resolveLevelName(data.level) }}
|
||||||
|
</el-tag>
|
||||||
|
<span class="node-name">{{ data.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-scrollbar>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, ref, watch } from 'vue'
|
||||||
|
import { Plus, Refresh } from '@element-plus/icons-vue'
|
||||||
|
import type { TreeInstance } from 'element-plus'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'LedgerTreePanel'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
treeData: AddLedger.NormalizedTreeNode[]
|
||||||
|
selectedId: string
|
||||||
|
loading: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [node: AddLedger.NormalizedTreeNode]
|
||||||
|
refresh: []
|
||||||
|
'add-engineering': []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const treeRef = ref<TreeInstance>()
|
||||||
|
const keyword = ref('')
|
||||||
|
const levelNames: Record<AddLedger.NodeLevel, string> = {
|
||||||
|
0: '工程',
|
||||||
|
1: '项目',
|
||||||
|
2: '设备',
|
||||||
|
3: '测点'
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveTagType = (value: unknown) => {
|
||||||
|
const level = normalizeLevel(value)
|
||||||
|
const tagTypes: Record<AddLedger.NodeLevel, 'primary' | 'success' | 'warning' | 'info'> = {
|
||||||
|
0: 'primary',
|
||||||
|
1: 'success',
|
||||||
|
2: 'warning',
|
||||||
|
3: 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagTypes[level]
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLevel = (value: unknown): AddLedger.NodeLevel => {
|
||||||
|
const level = Number(value)
|
||||||
|
if (level === 0 || level === 1 || level === 2 || level === 3) {
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveLevelName = (value: unknown) => {
|
||||||
|
return levelNames[normalizeLevel(value)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: unknown) => {
|
||||||
|
if (!value) return true
|
||||||
|
const node = data as Partial<AddLedger.NormalizedTreeNode>
|
||||||
|
return String(node.name || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(value.trim().toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNodeClick = (node: AddLedger.NormalizedTreeNode) => {
|
||||||
|
emit('select', node)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(keyword, value => {
|
||||||
|
treeRef.value?.filter(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selectedId,
|
||||||
|
async value => {
|
||||||
|
await nextTick()
|
||||||
|
treeRef.value?.setCurrentKey(value || undefined)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.ledger-tree-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledger-tree-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions :deep(.el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-search {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-scrollbar {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledger-tree-node {
|
||||||
|
display: inline-flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
254
frontend/src/views/tools/addLedger/components/LineForm.vue
Normal file
254
frontend/src/views/tools/addLedger/components/LineForm.vue
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card ledger-form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<div>
|
||||||
|
<div class="section-title">监测点配置</div>
|
||||||
|
<div class="section-description">维护线路号、接线方式、电压等级和 CT/PT 参数。</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<el-button type="primary" :icon="Check" :loading="saving" @click="emit('save')">保存测点</el-button>
|
||||||
|
<el-button type="danger" plain :icon="Delete" :disabled="!lineId" @click="emit('delete')">删除测点</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="localForm" :rules="formRules" label-width="146px" class="ledger-form form-two">
|
||||||
|
<el-form-item label="监测点名" prop="name">
|
||||||
|
<el-input v-model="localForm.name" maxlength="80" clearable placeholder="请输入监测点名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="设备线路" prop="line_no">
|
||||||
|
<el-select v-model="localForm.line_no" clearable placeholder="请选择设备线路">
|
||||||
|
<el-option v-for="item in lineNoOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="接线方式" prop="conType">
|
||||||
|
<el-select v-model="localForm.conType" clearable placeholder="请选择接线方式">
|
||||||
|
<el-option v-for="item in conTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="电压等级" prop="vol_grade">
|
||||||
|
<el-select v-model="localForm.vol_grade" clearable placeholder="请选择电压等级">
|
||||||
|
<el-option v-for="item in voltageOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="安装位置" prop="position">
|
||||||
|
<el-input v-model="localForm.position" maxlength="80" clearable placeholder="请输入安装位置" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="CT 一次额定值" prop="ct_ratio">
|
||||||
|
<el-input-number v-model="localForm.ct_ratio" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="CT 二次额定值" prop="ct2_ratio">
|
||||||
|
<el-input-number v-model="localForm.ct2_ratio" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="PT 一次额定值" prop="pt_ratio">
|
||||||
|
<el-input-number v-model="localForm.pt_ratio" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="PT 二次额定值" prop="pt2_ratio">
|
||||||
|
<el-input-number v-model="localForm.pt2_ratio" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="最小短路容量(MVA)" prop="short_circuit_capacity">
|
||||||
|
<el-input-number
|
||||||
|
v-model="localForm.short_circuit_capacity"
|
||||||
|
:min="0"
|
||||||
|
:precision="3"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="基准短路容量(MVA)" prop="basic_capacity">
|
||||||
|
<el-input-number v-model="localForm.basic_capacity" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户协议容量(MVA)" prop="protocol_capacity">
|
||||||
|
<el-input-number v-model="localForm.protocol_capacity" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="供电设备容量(MVA)" prop="dev_capacity">
|
||||||
|
<el-input-number v-model="localForm.dev_capacity" :min="0" :precision="3" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="监测对象类型" prop="monitor_obj">
|
||||||
|
<el-input v-model="localForm.monitor_obj" maxlength="80" clearable placeholder="请输入监测对象类型" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否治理" prop="is_govern">
|
||||||
|
<el-switch v-model="localForm.is_govern" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="敏感用户" prop="monitor_user">
|
||||||
|
<el-input v-model="localForm.monitor_user" maxlength="80" clearable placeholder="请输入敏感用户 ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="主要监测点" prop="is_important">
|
||||||
|
<el-switch
|
||||||
|
v-model="localForm.is_important"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
active-text="是"
|
||||||
|
inactive-text="否"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
|
import { Check, Delete } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'LineForm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
form: AddLedger.LineForm
|
||||||
|
saving: boolean
|
||||||
|
lineNoOptions: AddLedger.SelectOption<number>[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:form': [form: AddLedger.LineForm]
|
||||||
|
save: []
|
||||||
|
delete: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const syncingFromProp = ref(false)
|
||||||
|
const localForm = reactive<AddLedger.LineForm>({
|
||||||
|
id: '',
|
||||||
|
line_id: '',
|
||||||
|
deviceId: '',
|
||||||
|
parentId: '',
|
||||||
|
name: '',
|
||||||
|
line_no: undefined,
|
||||||
|
conType: undefined,
|
||||||
|
vol_grade: undefined,
|
||||||
|
position: '',
|
||||||
|
ct_ratio: undefined,
|
||||||
|
ct2_ratio: undefined,
|
||||||
|
pt_ratio: undefined,
|
||||||
|
pt2_ratio: undefined,
|
||||||
|
short_circuit_capacity: undefined,
|
||||||
|
basic_capacity: undefined,
|
||||||
|
protocol_capacity: undefined,
|
||||||
|
dev_capacity: undefined,
|
||||||
|
monitor_obj: '',
|
||||||
|
is_govern: 0,
|
||||||
|
monitor_user: '',
|
||||||
|
is_important: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const conTypeOptions: AddLedger.SelectOption<number>[] = [
|
||||||
|
{ label: '星型接线', value: 0 },
|
||||||
|
{ label: '角型接线', value: 1 },
|
||||||
|
{ label: 'V 型接线', value: 2 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const voltageOptions: AddLedger.SelectOption<number>[] = [
|
||||||
|
{ label: '0.38kV', value: 0.38 },
|
||||||
|
{ label: '10kV', value: 10 },
|
||||||
|
{ label: '35kV', value: 35 },
|
||||||
|
{ label: '110kV', value: 110 },
|
||||||
|
{ label: '220kV', value: 220 },
|
||||||
|
{ label: '500kV', value: 500 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const lineId = computed(() => localForm.line_id || localForm.id || '')
|
||||||
|
|
||||||
|
const syncLocalForm = (form: AddLedger.LineForm) => {
|
||||||
|
localForm.id = form.id || ''
|
||||||
|
localForm.line_id = form.line_id || ''
|
||||||
|
localForm.deviceId = form.deviceId || ''
|
||||||
|
localForm.parentId = form.parentId || ''
|
||||||
|
localForm.name = form.name || ''
|
||||||
|
localForm.line_no = form.line_no
|
||||||
|
localForm.conType = form.conType
|
||||||
|
localForm.vol_grade = form.vol_grade
|
||||||
|
localForm.position = form.position || ''
|
||||||
|
localForm.ct_ratio = form.ct_ratio
|
||||||
|
localForm.ct2_ratio = form.ct2_ratio
|
||||||
|
localForm.pt_ratio = form.pt_ratio
|
||||||
|
localForm.pt2_ratio = form.pt2_ratio
|
||||||
|
localForm.short_circuit_capacity = form.short_circuit_capacity
|
||||||
|
localForm.basic_capacity = form.basic_capacity
|
||||||
|
localForm.protocol_capacity = form.protocol_capacity
|
||||||
|
localForm.dev_capacity = form.dev_capacity
|
||||||
|
localForm.monitor_obj = form.monitor_obj || ''
|
||||||
|
localForm.is_govern = form.is_govern ?? 0
|
||||||
|
localForm.monitor_user = form.monitor_user || ''
|
||||||
|
localForm.is_important = form.is_important ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.form,
|
||||||
|
value => {
|
||||||
|
syncingFromProp.value = true
|
||||||
|
syncLocalForm(value)
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
localForm,
|
||||||
|
value => {
|
||||||
|
if (syncingFromProp.value) {
|
||||||
|
syncingFromProp.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:form', { ...value })
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const requiredNumberRule = (message: string) => ({
|
||||||
|
validator: (_rule: unknown, value: number | undefined, callback: (error?: Error) => void) => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
callback(new Error(message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: 'change'
|
||||||
|
})
|
||||||
|
|
||||||
|
const nonNegativeRule = (message: string) => ({
|
||||||
|
validator: (_rule: unknown, value: number | undefined, callback: (error?: Error) => void) => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(value) < 0) {
|
||||||
|
callback(new Error(message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: 'change'
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRules: FormRules<AddLedger.LineForm> = {
|
||||||
|
name: [{ required: true, message: '请输入监测点名', trigger: 'blur' }],
|
||||||
|
line_no: [requiredNumberRule('请选择设备线路')],
|
||||||
|
conType: [requiredNumberRule('请选择接线方式')],
|
||||||
|
vol_grade: [requiredNumberRule('请选择电压等级')],
|
||||||
|
ct_ratio: [requiredNumberRule('请输入 CT 一次额定值'), nonNegativeRule('CT 一次额定值不能小于 0')],
|
||||||
|
ct2_ratio: [requiredNumberRule('请输入 CT 二次额定值'), nonNegativeRule('CT 二次额定值不能小于 0')],
|
||||||
|
pt_ratio: [requiredNumberRule('请输入 PT 一次额定值'), nonNegativeRule('PT 一次额定值不能小于 0')],
|
||||||
|
pt2_ratio: [requiredNumberRule('请输入 PT 二次额定值'), nonNegativeRule('PT 二次额定值不能小于 0')],
|
||||||
|
short_circuit_capacity: [nonNegativeRule('最小短路容量不能小于 0')],
|
||||||
|
basic_capacity: [nonNegativeRule('基准短路容量不能小于 0')],
|
||||||
|
protocol_capacity: [nonNegativeRule('用户协议容量不能小于 0')],
|
||||||
|
dev_capacity: [nonNegativeRule('供电设备容量不能小于 0')]
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = async () => {
|
||||||
|
const result = await formRef.value?.validate().catch(() => false)
|
||||||
|
return Boolean(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validateForm
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './ledgerForm.scss';
|
||||||
|
</style>
|
||||||
118
frontend/src/views/tools/addLedger/components/ProjectForm.vue
Normal file
118
frontend/src/views/tools/addLedger/components/ProjectForm.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<section class="card ledger-form-card">
|
||||||
|
<div class="form-header">
|
||||||
|
<div>
|
||||||
|
<div class="section-title">项目配置</div>
|
||||||
|
<div class="section-description">维护项目相对位置和说明,并从项目下继续新增设备。</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<el-button type="primary" :icon="CirclePlus" @click="emit('add-equipment')">新增设备</el-button>
|
||||||
|
<el-button type="primary" :icon="Check" :loading="saving" @click="emit('save')">保存项目</el-button>
|
||||||
|
<el-button type="danger" plain :icon="Delete" :disabled="!localForm.id" @click="emit('delete')">
|
||||||
|
删除项目
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form ref="formRef" :model="localForm" :rules="formRules" label-width="96px" class="ledger-form form-two">
|
||||||
|
<el-form-item label="项目名称" prop="name">
|
||||||
|
<el-input v-model="localForm.name" maxlength="80" clearable placeholder="请输入项目名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="相对位置" prop="area">
|
||||||
|
<el-input v-model="localForm.area" maxlength="80" clearable placeholder="请输入相对位置" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="localForm.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入项目描述"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { Check, CirclePlus, Delete } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ProjectForm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
form: AddLedger.ProjectForm
|
||||||
|
saving: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:form': [form: AddLedger.ProjectForm]
|
||||||
|
save: []
|
||||||
|
delete: []
|
||||||
|
'add-equipment': []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const syncingFromProp = ref(false)
|
||||||
|
const localForm = reactive<AddLedger.ProjectForm>({
|
||||||
|
id: '',
|
||||||
|
engineeringId: '',
|
||||||
|
parentId: '',
|
||||||
|
name: '',
|
||||||
|
area: '',
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const syncLocalForm = (form: AddLedger.ProjectForm) => {
|
||||||
|
localForm.id = form.id || ''
|
||||||
|
localForm.engineeringId = form.engineeringId || ''
|
||||||
|
localForm.parentId = form.parentId || ''
|
||||||
|
localForm.name = form.name || ''
|
||||||
|
localForm.area = form.area || ''
|
||||||
|
localForm.description = form.description || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.form,
|
||||||
|
value => {
|
||||||
|
syncingFromProp.value = true
|
||||||
|
syncLocalForm(value)
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
localForm,
|
||||||
|
value => {
|
||||||
|
if (syncingFromProp.value) {
|
||||||
|
syncingFromProp.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:form', { ...value })
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const formRules: FormRules<AddLedger.ProjectForm> = {
|
||||||
|
name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = async () => {
|
||||||
|
const result = await formRef.value?.validate().catch(() => false)
|
||||||
|
return Boolean(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validateForm
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './ledgerForm.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
.ledger-form-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-description {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions :deep(.el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledger-form {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledger-form :deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ledger-form :deep(.el-textarea) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.ledger-form.form-two :deep(.el-form-item) {
|
||||||
|
width: 98%;
|
||||||
|
}
|
||||||
|
}
|
||||||
718
frontend/src/views/tools/addLedger/index.vue
Normal file
718
frontend/src/views/tools/addLedger/index.vue
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
<template>
|
||||||
|
<div class="table-box add-ledger-page">
|
||||||
|
<div class="add-ledger-layout">
|
||||||
|
<LedgerTreePanel
|
||||||
|
:tree-data="treeData"
|
||||||
|
:selected-id="selectedNode?.id || ''"
|
||||||
|
:loading="loading.tree"
|
||||||
|
@select="handleSelectNode"
|
||||||
|
@refresh="loadTree"
|
||||||
|
@add-engineering="handleAddEngineering"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section class="add-ledger-main">
|
||||||
|
<el-alert
|
||||||
|
v-if="loading.detail"
|
||||||
|
class="detail-alert"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
title="正在加载节点详情"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EngineeringForm
|
||||||
|
v-if="activeLevel === 0"
|
||||||
|
ref="engineeringFormRef"
|
||||||
|
v-model:form="engineeringForm"
|
||||||
|
:saving="loading.saving"
|
||||||
|
@save="handleSaveEngineering"
|
||||||
|
@delete="handleDeleteNode"
|
||||||
|
@add-project="handleAddProject"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProjectForm
|
||||||
|
v-else-if="activeLevel === 1"
|
||||||
|
ref="projectFormRef"
|
||||||
|
v-model:form="projectForm"
|
||||||
|
:saving="loading.saving"
|
||||||
|
@save="handleSaveProject"
|
||||||
|
@delete="handleDeleteNode"
|
||||||
|
@add-equipment="handleAddEquipment"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EquipmentForm
|
||||||
|
v-else-if="activeLevel === 2"
|
||||||
|
ref="equipmentFormRef"
|
||||||
|
v-model:form="equipmentForm"
|
||||||
|
:saving="loading.saving"
|
||||||
|
:device-type-options="deviceTypeOptions"
|
||||||
|
:device-model-options="deviceModelOptions"
|
||||||
|
@save="handleSaveEquipment"
|
||||||
|
@delete="handleDeleteNode"
|
||||||
|
@add-line="handleAddLine"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LineForm
|
||||||
|
v-else-if="activeLevel === 3"
|
||||||
|
ref="lineFormRef"
|
||||||
|
v-model:form="lineForm"
|
||||||
|
:saving="loading.saving"
|
||||||
|
:line-no-options="lineNoOptions"
|
||||||
|
@save="handleSaveLine"
|
||||||
|
@delete="handleDeleteNode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-else class="card add-ledger-empty">
|
||||||
|
<div class="empty-title">请选择台账节点</div>
|
||||||
|
<div class="empty-text">从左侧台账树选择工程、项目、设备或监测点,或先新增一个工程。</div>
|
||||||
|
<el-button type="primary" :icon="CirclePlus" @click="handleAddEngineering">新增工程</el-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { CirclePlus } from '@element-plus/icons-vue'
|
||||||
|
import {
|
||||||
|
deleteAddLedgerNode,
|
||||||
|
getAddLedgerDetail,
|
||||||
|
getAddLedgerTree,
|
||||||
|
getAvailableLineNos,
|
||||||
|
saveAddLedgerEngineering,
|
||||||
|
saveAddLedgerEquipment,
|
||||||
|
saveAddLedgerLine,
|
||||||
|
saveAddLedgerProject
|
||||||
|
} from '@/api/tools/addLedger'
|
||||||
|
import type { AddLedger } from '@/api/tools/addLedger/interface'
|
||||||
|
import { useDictStore } from '@/stores/modules/dict'
|
||||||
|
import LedgerTreePanel from './components/LedgerTreePanel.vue'
|
||||||
|
import EngineeringForm from './components/EngineeringForm.vue'
|
||||||
|
import ProjectForm from './components/ProjectForm.vue'
|
||||||
|
import EquipmentForm from './components/EquipmentForm.vue'
|
||||||
|
import LineForm from './components/LineForm.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'AddLedgerView'
|
||||||
|
})
|
||||||
|
|
||||||
|
type FormExpose = {
|
||||||
|
validateForm: () => Promise<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
const treeData = ref<AddLedger.NormalizedTreeNode[]>([])
|
||||||
|
const selectedNode = ref<AddLedger.NormalizedTreeNode | null>(null)
|
||||||
|
const activeLevel = ref<AddLedger.NodeLevel | null>(null)
|
||||||
|
const lineNoOptions = ref<AddLedger.SelectOption<number>[]>([])
|
||||||
|
const loading = reactive({
|
||||||
|
tree: false,
|
||||||
|
detail: false,
|
||||||
|
saving: false,
|
||||||
|
lineNos: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const engineeringFormRef = ref<FormExpose>()
|
||||||
|
const projectFormRef = ref<FormExpose>()
|
||||||
|
const equipmentFormRef = ref<FormExpose>()
|
||||||
|
const lineFormRef = ref<FormExpose>()
|
||||||
|
|
||||||
|
const engineeringForm = ref<AddLedger.EngineeringForm>(createEmptyEngineeringForm())
|
||||||
|
const projectForm = ref<AddLedger.ProjectForm>(createEmptyProjectForm())
|
||||||
|
const equipmentForm = ref<AddLedger.EquipmentForm>(createEmptyEquipmentForm())
|
||||||
|
const lineForm = ref<AddLedger.LineForm>(createEmptyLineForm())
|
||||||
|
|
||||||
|
const fallbackDeviceTypeOptions: AddLedger.SelectOption[] = [
|
||||||
|
{ label: '直连设备', value: 'direct_device' },
|
||||||
|
{ label: '网关', value: 'gateway' },
|
||||||
|
{ label: '装置', value: 'device' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const fallbackDeviceModelOptions: AddLedger.SelectOption[] = [
|
||||||
|
{ label: 'PQS588', value: 'pqs588' },
|
||||||
|
{ label: 'PQS680', value: 'pqs680' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const deviceTypeOptions = computed(() => resolveDictOptions('ledger_device_type', fallbackDeviceTypeOptions))
|
||||||
|
const deviceModelOptions = computed(() => resolveDictOptions('ledger_device_model', fallbackDeviceModelOptions))
|
||||||
|
|
||||||
|
function createEmptyEngineeringForm(): AddLedger.EngineeringForm {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyProjectForm(parentEngineeringId = ''): AddLedger.ProjectForm {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
engineeringId: parentEngineeringId,
|
||||||
|
parentId: parentEngineeringId,
|
||||||
|
name: '',
|
||||||
|
area: '',
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyEquipmentForm(parentProjectId = '', parentEngineeringId = ''): AddLedger.EquipmentForm {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
engineeringId: parentEngineeringId,
|
||||||
|
projectId: parentProjectId,
|
||||||
|
parentId: parentProjectId,
|
||||||
|
name: '',
|
||||||
|
ndid: '',
|
||||||
|
mac: '',
|
||||||
|
dev_type: '',
|
||||||
|
dev_model: '',
|
||||||
|
dev_access_method: '',
|
||||||
|
node_id: '',
|
||||||
|
node_process: '',
|
||||||
|
upgrade: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyLineForm(parentDeviceId = ''): AddLedger.LineForm {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
line_id: generateGuidText(),
|
||||||
|
deviceId: parentDeviceId,
|
||||||
|
parentId: parentDeviceId,
|
||||||
|
name: '',
|
||||||
|
line_no: undefined,
|
||||||
|
conType: undefined,
|
||||||
|
vol_grade: undefined,
|
||||||
|
position: '',
|
||||||
|
ct_ratio: undefined,
|
||||||
|
ct2_ratio: undefined,
|
||||||
|
pt_ratio: undefined,
|
||||||
|
pt2_ratio: undefined,
|
||||||
|
short_circuit_capacity: undefined,
|
||||||
|
basic_capacity: undefined,
|
||||||
|
protocol_capacity: undefined,
|
||||||
|
dev_capacity: undefined,
|
||||||
|
monitor_obj: '',
|
||||||
|
is_govern: 0,
|
||||||
|
monitor_user: '',
|
||||||
|
is_important: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveDictOptions = (code: string, fallback: AddLedger.SelectOption[]) => {
|
||||||
|
const dictData = dictStore.getDictData(code)
|
||||||
|
if (!dictData.length) return fallback
|
||||||
|
|
||||||
|
return dictData.map(item => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveString = (data: Record<string, unknown>, ...keys: string[]) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = data[key]
|
||||||
|
if (value === null || value === undefined) continue
|
||||||
|
const text = String(value).trim()
|
||||||
|
if (text) return text
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveNumber = (data: Record<string, unknown>, ...keys: string[]) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = data[key]
|
||||||
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
const parsed = Number(value)
|
||||||
|
if (Number.isFinite(parsed)) return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLevel = (value: unknown): AddLedger.NodeLevel => {
|
||||||
|
const level = Number(value)
|
||||||
|
if (level === 0 || level === 1 || level === 2 || level === 3) {
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeTreeNode = (node: AddLedger.LedgerTreeNode): AddLedger.NormalizedTreeNode => {
|
||||||
|
const rawNode = node as Record<string, unknown>
|
||||||
|
const id = resolveString(rawNode, 'id', 'Id')
|
||||||
|
const children = Array.isArray(node.children) ? node.children.map(normalizeTreeNode).filter(item => item.id) : []
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
pid: resolveString(rawNode, 'parentId', 'pid', 'Pid'),
|
||||||
|
pids: resolveString(rawNode, 'parentIds', 'pids', 'Pids'),
|
||||||
|
name: resolveString(rawNode, 'name', 'Name') || id || '--',
|
||||||
|
level: normalizeLevel(rawNode.level ?? rawNode.Level),
|
||||||
|
children,
|
||||||
|
raw: node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findNodePath = (
|
||||||
|
nodes: AddLedger.NormalizedTreeNode[],
|
||||||
|
id: string,
|
||||||
|
path: AddLedger.NormalizedTreeNode[] = []
|
||||||
|
): AddLedger.NormalizedTreeNode[] => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
const nextPath = [...path, node]
|
||||||
|
if (node.id === id) return nextPath
|
||||||
|
|
||||||
|
const matchedPath = findNodePath(node.children, id, nextPath)
|
||||||
|
if (matchedPath.length) return matchedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrentPath = () => {
|
||||||
|
if (!selectedNode.value?.id) return []
|
||||||
|
return findNodePath(treeData.value, selectedNode.value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveContext = () => {
|
||||||
|
const path = getCurrentPath()
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineeringId: path.find(item => item.level === 0)?.id || '',
|
||||||
|
projectId: path.find(item => item.level === 1)?.id || '',
|
||||||
|
deviceId: path.find(item => item.level === 2)?.id || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGuidText() {
|
||||||
|
return window.crypto.randomUUID().replace(/-/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeEngineeringDetail = (
|
||||||
|
detail: AddLedger.NodeDetail | null,
|
||||||
|
node?: AddLedger.NormalizedTreeNode
|
||||||
|
): AddLedger.EngineeringForm => {
|
||||||
|
const data = (detail || {}) as Record<string, unknown>
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: resolveString(data, 'id', 'engineeringId') || node?.id || '',
|
||||||
|
name: resolveString(data, 'name') || node?.name || '',
|
||||||
|
province: resolveString(data, 'province'),
|
||||||
|
city: resolveString(data, 'city'),
|
||||||
|
description: resolveString(data, 'description')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeProjectDetail = (
|
||||||
|
detail: AddLedger.NodeDetail | null,
|
||||||
|
node?: AddLedger.NormalizedTreeNode
|
||||||
|
): AddLedger.ProjectForm => {
|
||||||
|
const data = (detail || {}) as Record<string, unknown>
|
||||||
|
const context = resolveContext()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: resolveString(data, 'id', 'projectId') || node?.id || '',
|
||||||
|
engineeringId: resolveString(data, 'engineeringId', 'associated_engineering') || context.engineeringId,
|
||||||
|
parentId: context.engineeringId,
|
||||||
|
name: resolveString(data, 'name') || node?.name || '',
|
||||||
|
area: resolveString(data, 'area'),
|
||||||
|
description: resolveString(data, 'description')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeEquipmentDetail = (
|
||||||
|
detail: AddLedger.NodeDetail | null,
|
||||||
|
node?: AddLedger.NormalizedTreeNode
|
||||||
|
): AddLedger.EquipmentForm => {
|
||||||
|
const data = (detail || {}) as Record<string, unknown>
|
||||||
|
const context = resolveContext()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: resolveString(data, 'id', 'equipmentId') || node?.id || '',
|
||||||
|
engineeringId: resolveString(data, 'engineeringId', 'associated_engineering') || context.engineeringId,
|
||||||
|
projectId: resolveString(data, 'projectId', 'associated_project') || context.projectId,
|
||||||
|
parentId: context.projectId,
|
||||||
|
name: resolveString(data, 'name') || node?.name || '',
|
||||||
|
ndid: resolveString(data, 'ndid'),
|
||||||
|
mac: resolveString(data, 'mac'),
|
||||||
|
dev_type: resolveString(data, 'dev_type', 'devType'),
|
||||||
|
dev_model: resolveString(data, 'dev_model', 'devModel'),
|
||||||
|
dev_access_method: resolveString(data, 'dev_access_method', 'devAccessMethod'),
|
||||||
|
node_id: resolveString(data, 'node_id', 'nodeId'),
|
||||||
|
node_process: resolveString(data, 'node_process', 'nodeProcess'),
|
||||||
|
upgrade: resolveNumber(data, 'upgrade') ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLineDetail = (detail: AddLedger.NodeDetail | null, node?: AddLedger.NormalizedTreeNode): AddLedger.LineForm => {
|
||||||
|
const data = (detail || {}) as Record<string, unknown>
|
||||||
|
const context = resolveContext()
|
||||||
|
const lineId = resolveString(data, 'line_id', 'lineId', 'id') || node?.id || generateGuidText()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: resolveString(data, 'id') || lineId,
|
||||||
|
line_id: lineId,
|
||||||
|
deviceId: resolveString(data, 'deviceId', 'device_id') || context.deviceId,
|
||||||
|
parentId: context.deviceId,
|
||||||
|
name: resolveString(data, 'name') || node?.name || '',
|
||||||
|
line_no: resolveNumber(data, 'line_no', 'lineNo'),
|
||||||
|
conType: resolveNumber(data, 'conType'),
|
||||||
|
vol_grade: resolveNumber(data, 'vol_grade', 'volGrade'),
|
||||||
|
position: resolveString(data, 'position'),
|
||||||
|
ct_ratio: resolveNumber(data, 'ct_ratio', 'ctRatio'),
|
||||||
|
ct2_ratio: resolveNumber(data, 'ct2_ratio', 'ct2Ratio'),
|
||||||
|
pt_ratio: resolveNumber(data, 'pt_ratio', 'ptRatio'),
|
||||||
|
pt2_ratio: resolveNumber(data, 'pt2_ratio', 'pt2Ratio'),
|
||||||
|
short_circuit_capacity: resolveNumber(data, 'short_circuit_capacity', 'shortCircuitCapacity'),
|
||||||
|
basic_capacity: resolveNumber(data, 'basic_capacity', 'basicCapacity'),
|
||||||
|
protocol_capacity: resolveNumber(data, 'protocol_capacity', 'protocolCapacity'),
|
||||||
|
dev_capacity: resolveNumber(data, 'dev_capacity', 'devCapacity'),
|
||||||
|
monitor_obj: resolveString(data, 'monitor_obj', 'monitorObj'),
|
||||||
|
is_govern: resolveNumber(data, 'is_govern', 'isGovern') ?? 0,
|
||||||
|
monitor_user: resolveString(data, 'monitor_user', 'monitorUser'),
|
||||||
|
is_important: resolveNumber(data, 'is_important', 'isImportant') ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildLineNoOptions = (lineNos: number[], currentLineNo?: number) => {
|
||||||
|
const values = new Set(lineNos.filter(item => Number.isInteger(item) && item >= 1 && item <= 20))
|
||||||
|
if (currentLineNo && currentLineNo >= 1 && currentLineNo <= 20) {
|
||||||
|
values.add(currentLineNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(values)
|
||||||
|
.sort((left, right) => left - right)
|
||||||
|
.map(item => ({ label: `${item} 号线路`, value: item }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadAvailableLineNoOptions = async (deviceId: string, lineId = '', currentLineNo?: number) => {
|
||||||
|
if (!deviceId) {
|
||||||
|
lineNoOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.lineNos = true
|
||||||
|
try {
|
||||||
|
const response = await getAvailableLineNos({ deviceId, lineId })
|
||||||
|
const availableNos = Array.isArray(response.data) ? response.data.map(item => Number(item)) : []
|
||||||
|
lineNoOptions.value = buildLineNoOptions(availableNos, currentLineNo)
|
||||||
|
} finally {
|
||||||
|
loading.lineNos = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadTree = async () => {
|
||||||
|
loading.tree = true
|
||||||
|
try {
|
||||||
|
const response = await getAddLedgerTree()
|
||||||
|
const nextTree = Array.isArray(response.data) ? response.data.map(normalizeTreeNode).filter(item => item.id) : []
|
||||||
|
treeData.value = nextTree
|
||||||
|
|
||||||
|
if (selectedNode.value?.id) {
|
||||||
|
const nextPath = findNodePath(nextTree, selectedNode.value.id)
|
||||||
|
selectedNode.value = nextPath[nextPath.length - 1] || null
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.tree = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadNodeDetail = async (node: AddLedger.NormalizedTreeNode) => {
|
||||||
|
selectedNode.value = node
|
||||||
|
activeLevel.value = node.level
|
||||||
|
loading.detail = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getAddLedgerDetail({ id: node.id, level: node.level })
|
||||||
|
const detail = response.data || null
|
||||||
|
|
||||||
|
if (node.level === 0) {
|
||||||
|
engineeringForm.value = normalizeEngineeringDetail(detail, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.level === 1) {
|
||||||
|
projectForm.value = normalizeProjectDetail(detail, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.level === 2) {
|
||||||
|
equipmentForm.value = normalizeEquipmentDetail(detail, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lineForm.value = normalizeLineDetail(detail, node)
|
||||||
|
await loadAvailableLineNoOptions(lineForm.value.deviceId || '', lineForm.value.line_id || '', lineForm.value.line_no)
|
||||||
|
} finally {
|
||||||
|
loading.detail = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectNode = (node: AddLedger.NormalizedTreeNode) => {
|
||||||
|
void loadNodeDetail(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddEngineering = () => {
|
||||||
|
selectedNode.value = null
|
||||||
|
activeLevel.value = 0
|
||||||
|
engineeringForm.value = createEmptyEngineeringForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddProject = () => {
|
||||||
|
const context = resolveContext()
|
||||||
|
const engineeringId = activeLevel.value === 0 ? engineeringForm.value.id || selectedNode.value?.id || '' : context.engineeringId
|
||||||
|
|
||||||
|
if (!engineeringId) {
|
||||||
|
ElMessage.warning('请先选择或保存父级工程')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedNode.value = null
|
||||||
|
activeLevel.value = 1
|
||||||
|
projectForm.value = createEmptyProjectForm(engineeringId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddEquipment = () => {
|
||||||
|
const context = resolveContext()
|
||||||
|
const projectId = activeLevel.value === 1 ? projectForm.value.id || selectedNode.value?.id || '' : context.projectId
|
||||||
|
const engineeringId = projectForm.value.engineeringId || context.engineeringId
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
ElMessage.warning('请先选择或保存父级项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedNode.value = null
|
||||||
|
activeLevel.value = 2
|
||||||
|
equipmentForm.value = createEmptyEquipmentForm(projectId, engineeringId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddLine = async () => {
|
||||||
|
const context = resolveContext()
|
||||||
|
const deviceId = activeLevel.value === 2 ? equipmentForm.value.id || selectedNode.value?.id || '' : context.deviceId
|
||||||
|
|
||||||
|
if (!deviceId) {
|
||||||
|
ElMessage.warning('请先选择或保存父级设备')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadAvailableLineNoOptions(deviceId)
|
||||||
|
if (!lineNoOptions.value.length) {
|
||||||
|
ElMessage.warning('当前设备 1-20 号线路已全部占用,无法继续新增测点')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedNode.value = null
|
||||||
|
activeLevel.value = 3
|
||||||
|
lineForm.value = createEmptyLineForm(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = async (formRef: FormExpose | undefined) => {
|
||||||
|
const isValid = await formRef?.validateForm()
|
||||||
|
return Boolean(isValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshAfterSave = async (id?: string) => {
|
||||||
|
await loadTree()
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
const path = findNodePath(treeData.value, id)
|
||||||
|
const node = path[path.length - 1]
|
||||||
|
if (node) {
|
||||||
|
await loadNodeDetail(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveEngineering = async () => {
|
||||||
|
if (!(await validateForm(engineeringFormRef.value))) return
|
||||||
|
|
||||||
|
loading.saving = true
|
||||||
|
try {
|
||||||
|
const response = await saveAddLedgerEngineering(engineeringForm.value)
|
||||||
|
const savedId = response.data?.id || engineeringForm.value.id
|
||||||
|
ElMessage.success('工程保存成功')
|
||||||
|
await refreshAfterSave(savedId)
|
||||||
|
} finally {
|
||||||
|
loading.saving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveProject = async () => {
|
||||||
|
if (!(await validateForm(projectFormRef.value))) return
|
||||||
|
|
||||||
|
if (!projectForm.value.parentId && !projectForm.value.engineeringId) {
|
||||||
|
ElMessage.warning('缺少父级工程,无法保存项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.saving = true
|
||||||
|
try {
|
||||||
|
const response = await saveAddLedgerProject(projectForm.value)
|
||||||
|
const savedId = response.data?.id || projectForm.value.id
|
||||||
|
ElMessage.success('项目保存成功')
|
||||||
|
await refreshAfterSave(savedId)
|
||||||
|
} finally {
|
||||||
|
loading.saving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveEquipment = async () => {
|
||||||
|
if (!(await validateForm(equipmentFormRef.value))) return
|
||||||
|
|
||||||
|
if (!equipmentForm.value.parentId && !equipmentForm.value.projectId) {
|
||||||
|
ElMessage.warning('缺少父级项目,无法保存设备')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.saving = true
|
||||||
|
try {
|
||||||
|
const response = await saveAddLedgerEquipment(equipmentForm.value)
|
||||||
|
const savedId = response.data?.id || equipmentForm.value.id
|
||||||
|
ElMessage.success('设备保存成功')
|
||||||
|
await refreshAfterSave(savedId)
|
||||||
|
} finally {
|
||||||
|
loading.saving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveLine = async () => {
|
||||||
|
if (!(await validateForm(lineFormRef.value))) return
|
||||||
|
|
||||||
|
if (!lineForm.value.parentId && !lineForm.value.deviceId) {
|
||||||
|
ElMessage.warning('缺少父级设备,无法保存测点')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lineForm.value.line_id) {
|
||||||
|
// 新增测点必须带 32 位 line_id,后端仍负责最终唯一性校验。
|
||||||
|
lineForm.value.line_id = generateGuidText()
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.saving = true
|
||||||
|
try {
|
||||||
|
const response = await saveAddLedgerLine(lineForm.value)
|
||||||
|
const savedId = response.data?.line_id || response.data?.id || lineForm.value.line_id || lineForm.value.id
|
||||||
|
ElMessage.success('测点保存成功')
|
||||||
|
await refreshAfterSave(savedId)
|
||||||
|
} finally {
|
||||||
|
loading.saving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveActiveNodeId = () => {
|
||||||
|
if (activeLevel.value === 0) return engineeringForm.value.id
|
||||||
|
if (activeLevel.value === 1) return projectForm.value.id
|
||||||
|
if (activeLevel.value === 2) return equipmentForm.value.id
|
||||||
|
if (activeLevel.value === 3) return lineForm.value.line_id || lineForm.value.id
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveDeleteMessage = () => {
|
||||||
|
if (activeLevel.value === 0) return '确认删除当前工程?删除后会同时删除所有下级项目、设备和监测点。'
|
||||||
|
if (activeLevel.value === 1) return '确认删除当前项目?删除后会同时删除所有下级设备和监测点。'
|
||||||
|
if (activeLevel.value === 2) return '确认删除当前设备?删除后会同时删除所有下级监测点。'
|
||||||
|
return '确认删除当前监测点?'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteNode = async () => {
|
||||||
|
if (activeLevel.value === null) return
|
||||||
|
|
||||||
|
const id = resolveActiveNodeId()
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.warning('当前节点尚未保存,无法删除')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(resolveDeleteMessage(), '删除确认', {
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '确认删除',
|
||||||
|
cancelButtonText: '取消'
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.saving = true
|
||||||
|
try {
|
||||||
|
await deleteAddLedgerNode({ id, level: activeLevel.value })
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
selectedNode.value = null
|
||||||
|
activeLevel.value = null
|
||||||
|
await loadTree()
|
||||||
|
} finally {
|
||||||
|
loading.saving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadTree()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.add-ledger-page {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-ledger-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-ledger-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-alert {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-ledger-empty {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 0;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.add-ledger-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: minmax(260px, 0.45fr) minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -23,6 +23,11 @@
|
|||||||
<div class="tool-name">addData</div>
|
<div class="tool-name">addData</div>
|
||||||
<div class="tool-text">进入 addData 页面壳子,后续在此扩展补录数据能力和业务交互。</div>
|
<div class="tool-text">进入 addData 页面壳子,后续在此扩展补录数据能力和业务交互。</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class="tool-item" type="button" @click="handleNavigate('/tools/addLedger')">
|
||||||
|
<div class="tool-name">数据台账</div>
|
||||||
|
<div class="tool-text">维护工程、项目、设备和监测点的四级台账配置关系。</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -197,6 +197,9 @@
|
|||||||
<el-form-item label="end">
|
<el-form-item label="end">
|
||||||
<el-input v-model="row.end" />
|
<el-input v-model="row.end" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="icdcout">
|
||||||
|
<el-input :model-value="row.icdcout" readonly />
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -241,8 +244,10 @@ interface SequenceConfigRow {
|
|||||||
parentDesc: string
|
parentDesc: string
|
||||||
desc: string
|
desc: string
|
||||||
name: string
|
name: string
|
||||||
|
baseflag: string
|
||||||
start: string
|
start: string
|
||||||
end: string
|
end: string
|
||||||
|
icdcout: string
|
||||||
startValueType: string
|
startValueType: string
|
||||||
endValueType: string
|
endValueType: string
|
||||||
}
|
}
|
||||||
@@ -317,8 +322,8 @@ const filteredSequenceRows = computed(() => {
|
|||||||
if (!keyword) return sequenceConfigRows.value
|
if (!keyword) return sequenceConfigRows.value
|
||||||
|
|
||||||
return sequenceConfigRows.value.filter(row =>
|
return sequenceConfigRows.value.filter(row =>
|
||||||
[row.topKey, row.topDesc, row.desc, row.parentDesc, row.name, row.pathText, row.start, row.end].some(value =>
|
[row.topKey, row.topDesc, row.desc, row.parentDesc, row.name, row.baseflag, row.pathText, row.start, row.end].some(
|
||||||
value.toLowerCase().includes(keyword)
|
value => value.toLowerCase().includes(keyword)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -368,12 +373,6 @@ const toDisplayText = (value: unknown, fallback: string) => {
|
|||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
const isZeroValue = (value: unknown) => {
|
|
||||||
if (typeof value === 'number') return value === 0
|
|
||||||
if (typeof value === 'string') return Number(value.trim()) === 0
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPathText = (path: JsonPath) => (path.length ? path.map(item => String(item)).join(' / ') : '$')
|
const getPathText = (path: JsonPath) => (path.length ? path.map(item => String(item)).join(' / ') : '$')
|
||||||
|
|
||||||
const getTopKey = (path: JsonPath) => (path.length ? String(path[0]) : '$')
|
const getTopKey = (path: JsonPath) => (path.length ? String(path[0]) : '$')
|
||||||
@@ -386,6 +385,13 @@ const resolveTopDesc = (source: unknown, path: JsonPath) => {
|
|||||||
return isRecord(topValue) ? toDisplayText(topValue.desc, '') : ''
|
return isRecord(topValue) ? toDisplayText(topValue.desc, '') : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isConfigurableBaseflag = (value: unknown) => {
|
||||||
|
if (typeof value === 'number') return value === 1 || value === 2
|
||||||
|
if (typeof value !== 'string') return false
|
||||||
|
|
||||||
|
return ['1', '2'].includes(value.trim())
|
||||||
|
}
|
||||||
|
|
||||||
const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc = '', rootSource = source): SequenceConfigRow[] => {
|
const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc = '', rootSource = source): SequenceConfigRow[] => {
|
||||||
if (Array.isArray(source)) {
|
if (Array.isArray(source)) {
|
||||||
return source.flatMap((item, index) => collectSequenceRows(item, [...path, index], parentDesc, rootSource))
|
return source.flatMap((item, index) => collectSequenceRows(item, [...path, index], parentDesc, rootSource))
|
||||||
@@ -402,7 +408,7 @@ const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc =
|
|||||||
const hasEnd = Object.prototype.hasOwnProperty.call(source, 'end')
|
const hasEnd = Object.prototype.hasOwnProperty.call(source, 'end')
|
||||||
|
|
||||||
if (!hasStart || !hasEnd) return childrenRows
|
if (!hasStart || !hasEnd) return childrenRows
|
||||||
if (isZeroValue(source.start) && isZeroValue(source.end)) return childrenRows
|
if (!isConfigurableBaseflag(source.baseflag)) return childrenRows
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -414,8 +420,10 @@ const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc =
|
|||||||
parentDesc: parentDesc || '未配置上层描述',
|
parentDesc: parentDesc || '未配置上层描述',
|
||||||
desc: toDisplayText(source.desc, '未命名序列'),
|
desc: toDisplayText(source.desc, '未命名序列'),
|
||||||
name: toDisplayText(source.name, '未配置 name'),
|
name: toDisplayText(source.name, '未配置 name'),
|
||||||
|
baseflag: toDisplayText(source.baseflag, ''),
|
||||||
start: source.start === undefined || source.start === null ? '' : String(source.start),
|
start: source.start === undefined || source.start === null ? '' : String(source.start),
|
||||||
end: source.end === undefined || source.end === null ? '' : String(source.end),
|
end: source.end === undefined || source.end === null ? '' : String(source.end),
|
||||||
|
icdcout: toDisplayText(source.icdcout, ''),
|
||||||
startValueType: typeof source.start,
|
startValueType: typeof source.start,
|
||||||
endValueType: typeof source.end
|
endValueType: typeof source.end
|
||||||
},
|
},
|
||||||
@@ -829,7 +837,7 @@ const confirmSequenceConfig = () => {
|
|||||||
|
|
||||||
.sequence-config-item {
|
.sequence-config-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) 300px;
|
grid-template-columns: minmax(0, 1fr) 420px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
border: 1px solid #dbe3f0;
|
border: 1px solid #dbe3f0;
|
||||||
@@ -872,7 +880,7 @@ const confirmSequenceConfig = () => {
|
|||||||
|
|
||||||
.sequence-config-form {
|
.sequence-config-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user