feat(tools): 新增台账管理功能模块

- 添加 addLedger API 接口定义和实现
- 创建工程配置表单组件 (EngineeringForm)
- 创建设备配置表单组件 (EquipmentForm)
- 创建项目和测点表单组件 (ProjectForm, LineForm)
- 实现台账树形结构面板 (LedgerTreePanel)
- 添加台账数据验证契约检查脚本
- 集成字典选项和动态表单验证功能
- 实现台账节点增删改查完整流程
- 优化 Echarts 图表组件分组渲染性能
This commit is contained in:
2026-05-09 07:53:32 +08:00
parent a1e1fb124a
commit cd54bb676c
14 changed files with 2005 additions and 26 deletions

View File

@@ -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')

View 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)
}

View 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
}
}