feat(data-tools): 新增入库类型选择功能并优化数据工具界面

- 在补数任务面板中添加入库类型单选按钮组,支持 MySQL 和 InfluxDB
- 更新 AddData 接口定义,添加 StorageType 相关类型和选项接口
- 修改补数 API 请求逻辑,根据入库类型动态调整接口路径前缀
- 重构台账设备表单,统一使用装置网络参数作为 MAC 和 NDID 的单一数据源
- 优化台账线路表单,仅当存在 ID 时才设置 lineId 字段,避免空值传递
- 添加入库类型列表获取接口和相关数据处理逻辑
- 更新台账字典代码常量,新增终端型号字典码
- 优化台账树节点添加逻辑,增加前置条件验证和禁用原因提示
- 添加 InfluxDB 配置文件到额外资源目录
- 更新稳定数据分析视图,优化台账树数据结构处理和样式布局
- 完善 API 调试契约检查,确保设备和线路数据映射正确性
- 优化趋势查询性能,禁用全局加载状态提升用户体验
This commit is contained in:
2026-05-20 08:32:24 +08:00
parent 6755476969
commit f1eaabae0e
40 changed files with 1556 additions and 183 deletions

View File

@@ -6,6 +6,7 @@ type AddDataRequestMethod = 'get' | 'post'
const ADD_DATA_ROUTE_PATHS = ['/addData', '/api/addData'] as const
const ADD_DATA_BASE_URL = String(import.meta.env.VITE_API_URL || '').trim()
const ADD_DATA_INFLUX_STORAGE_TYPE = 'INFLUXDB'
const resolveDevProxyTarget = () => {
const proxyConfig = import.meta.env.VITE_PROXY
@@ -81,16 +82,25 @@ const requestAddData = async <T>(
throw lastError
}
const resolveTaskPathPrefix = (storageType?: AddData.StorageType) => {
// MySQL 与 InfluxDB 任务接口相互独立,创建和状态轮询必须使用同一入库类型前缀。
return storageType === ADD_DATA_INFLUX_STORAGE_TYPE ? '/influx/task' : '/task'
}
export const getAddDataStorageTypeList = () => {
return requestAddData<AddData.StorageTypeItem[]>('get', '/storage-type/list')
}
export const getAddDataPreview = (params: AddData.TaskRequestParams) => {
return requestAddData<AddData.PreviewResponse>('post', '/task/preview', params)
}
export const createAddDataTask = (params: AddData.TaskRequestParams) => {
return requestAddData<AddData.CreateTaskResponse>('post', '/task/create', params)
export const createAddDataTask = (params: AddData.TaskRequestParams, storageType?: AddData.StorageType) => {
return requestAddData<AddData.CreateTaskResponse>('post', `${resolveTaskPathPrefix(storageType)}/create`, params)
}
export const getAddDataTaskStatus = (taskId: string | number) => {
return requestAddData<AddData.TaskStatusResponse>('get', `/task/status/${taskId}`)
export const getAddDataTaskStatus = (taskId: string | number, storageType?: AddData.StorageType) => {
return requestAddData<AddData.TaskStatusResponse>('get', `${resolveTaskPathPrefix(storageType)}/status/${taskId}`)
}
export const getAddDataTemplateList = () => {

View File

@@ -1,6 +1,7 @@
export namespace AddData {
export type LineMode = 'single' | 'multiple'
export type IntervalMinutes = 1 | 3 | 5 | 10
export type StorageType = 'MYSQL' | 'INFLUXDB' | (string & {})
export type TaskStatus = 'WAITING' | 'RUNNING' | 'SUCCESS' | 'FAILED' | (string & {})
export interface TaskRequestParams {
@@ -12,12 +13,18 @@ export namespace AddData {
export interface TaskFormModel {
lineMode: LineMode
storageType: StorageType
lineIds: string[]
startTime: string
endTime: string
intervalMinutes: IntervalMinutes
}
export interface StorageTypeItem {
code?: StorageType
name?: string
}
export interface PreviewTableStat {
tableName?: string
timePointCount?: number | string
@@ -106,4 +113,9 @@ export namespace AddData {
cp95ValueRule: string
decimalScaleText: string
}
export interface StorageTypeOption {
code: StorageType
name: string
}
}

View File

@@ -11,12 +11,23 @@ const apiSource = fs.readFileSync(apiFile, 'utf8')
const interfaceSource = fs.readFileSync(interfaceFile, 'utf8')
const expectations = [
[
'equipment payload uses network param as single source for mac and ndid',
/const networkParam = resolveOptionalText\(params\.mac\)/.test(apiSource) &&
/ndid:\s*networkParam/.test(apiSource) &&
/mac:\s*networkParam/.test(apiSource)
],
['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 resolves lineId only from existing line', /const lineId = resolveOptionalText\(params\.id\s*\|\|\s*params\.line_id\)/],
['line payload omits empty lineId field', /if \(lineId\) \{\s*payload\.lineId = lineId\s*\}/],
[
'new line form does not generate lineId before backend save',
/line_id:\s*''/.test(fs.readFileSync(path.join(currentDir, '..', '..', '..', 'views', 'tools', 'addLedger', 'utils', 'ledgerData.ts'), 'utf8'))
],
['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/],
@@ -26,7 +37,10 @@ const expectations = [
['delete response type is boolean', /requestAddLedger<boolean>\('delete',\s*'\/node'/]
]
const failures = expectations.filter(([, pattern]) => !pattern.test(`${apiSource}\n${interfaceSource}`))
const source = `${apiSource}\n${interfaceSource}`
const failures = expectations.filter(([, expectation]) =>
typeof expectation === 'boolean' ? !expectation : !expectation.test(source)
)
if (failures.length) {
console.error('addLedger API_DEBUG contract check failed:')

View File

@@ -27,41 +27,54 @@ const toAddLedgerProjectPayload = (params: AddLedger.ProjectForm) => ({
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 toAddLedgerEquipmentPayload = (params: AddLedger.EquipmentForm) => {
// 后端仍接收 mac/ndid 两个字段,前端统一用“装置网络参数”作为唯一来源。
const networkParam = resolveOptionalText(params.mac)
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
})
return {
id: resolveOptionalText(params.id),
projectId: resolveOptionalText(params.projectId || params.parentId),
name: params.name,
ndid: networkParam,
mac: networkParam,
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) => {
const lineId = resolveOptionalText(params.id || params.line_id)
const payload = {
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
} as Record<string, unknown>
if (lineId) {
payload.lineId = lineId
}
return payload
}
const resolveDevProxyTarget = () => {
const proxyConfig = import.meta.env.VITE_PROXY