From 8f5642d0b5ea0e03928a33af7823d4a8b5972bcd Mon Sep 17 00:00:00 2001 From: caozehui <2427765068@qq.com> Date: Tue, 16 Jun 2026 19:26:47 +0800 Subject: [PATCH] =?UTF-8?q?icd=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/device/icd/index.ts | 44 +- frontend/src/api/device/interface/icd.ts | 62 +- frontend/src/api/index.ts | 4 + .../devType/components/devTypePopup.vue | 6 +- frontend/src/views/machine/devType/index.vue | 19 +- .../views/machine/icd/components/icdForm.ts | 41 + .../views/machine/icd/components/icdPopup.vue | 764 ++++++++++++------ frontend/src/views/machine/icd/index.vue | 229 +++--- 8 files changed, 757 insertions(+), 412 deletions(-) create mode 100644 frontend/src/views/machine/icd/components/icdForm.ts diff --git a/frontend/src/api/device/icd/index.ts b/frontend/src/api/device/icd/index.ts index ffe4a26..e143c6f 100644 --- a/frontend/src/api/device/icd/index.ts +++ b/frontend/src/api/device/icd/index.ts @@ -4,34 +4,34 @@ import http from '@/api' /** * @name ICD管理模块 */ - - - -//获取ICD分页 export const getICDList = (params: ICD.ReqICDParams) => { - return http.post(`/icd/list`,params) + return http.post(`/icd/list`, params) } - -//获取ICD -export const getICDAllList = (params: ICD.ResICD) => { - return http.get(`/icd/listAll`,params) +export const getICDAllList = () => { + return http.get(`/icd/listAll`) } -//添加ICD -export const addICD = (params: ICD.ResICD) => { +export const getStandardICDList = () => { + return http.get(`/icd/standard-list`) +} + +export const getICDById = (id: string) => { + return http.get(`/icd/getById`, { id }) +} + +export const addICD = (params: FormData) => { return http.upload(`/icd/add`, params) - } - - //编辑ICD - export const updateICD = (params: ICD.ResICD) => { +} + +export const updateICD = (params: FormData) => { return http.upload(`/icd/update`, params) - } - - //删除ICD - export const deleteICD = (params: string[]) => { +} + +export const deleteICD = (params: string[]) => { return http.post(`/icd/delete`, params) - } - - +} +export const exportICD = (id: string) => { + return http.downloadGetWithHeaders(`/icd/export/${id}`) +} diff --git a/frontend/src/api/device/interface/icd.ts b/frontend/src/api/device/interface/icd.ts index 6905ebd..d700535 100644 --- a/frontend/src/api/device/interface/icd.ts +++ b/frontend/src/api/device/interface/icd.ts @@ -1,38 +1,36 @@ import type { ReqPage, ResPage } from '@/api/interface' -// ICD模块 export namespace ICD { + export interface ReqICDParams extends ReqPage { + name?: string + type?: number + } - /** - * ICD表格分页查询参数 - */ - export interface ReqICDParams extends ReqPage { - id: string; // 装置序号id 必填 - devType?: string; // 设备名称 - createTime?: string; //创建时间 - } + export interface StandardOption { + id: string + name: string + type?: number + } - /** - * ICD新增、修改、根据id查询返回的对象 - */ - export interface ResICD { - id: string; //icdID - name: string;//icd名称 - path: string;//icd存储地址 - state: number; - createBy?: string| null; //创建用户 - createTime?: string| null; //创建时间 - updateBy?: string| null; //更新用户 - updateTime?: string| null; //更新时间 - angle: number; // 是否支持电压相角、电流相角指标 - usePhaseIndex: number; // 角型接线时是否使用相别的指标来进行检测 - mappingFile: string | null;//映射文件 - } + export interface ResICD { + id: string + name: string + state: number + createBy?: string | null + createTime?: string | null + updateBy?: string | null + updateTime?: string | null + angle: number + usePhaseIndex: number + jsonStr: string + xmlStr: string + result: number + msg: string | null + type: number + referenceIcdId: string | null + referenceIcdName?: string | null + hasIcdFile?: boolean | null + } - /** - * ICD表格查询分页返回的对象; - */ - export interface ResICDPage extends ResPage { - - } -} \ No newline at end of file + export interface ResICDPage extends ResPage {} +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index bebe843..d1fd7b9 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -214,6 +214,10 @@ class RequestHttp { return this.service.post(url, params, { ..._object, responseType: 'blob' }) } + downloadGetWithHeaders(url: string, _object = {}): Promise> { + return this.service.get(url, { ..._object, responseType: 'blob' }) + } + upload(url: string, params?: object, _object = {}): Promise { return this.service.post(url, params, { ..._object, diff --git a/frontend/src/views/machine/devType/components/devTypePopup.vue b/frontend/src/views/machine/devType/components/devTypePopup.vue index 9211038..64350dc 100644 --- a/frontend/src/views/machine/devType/components/devTypePopup.vue +++ b/frontend/src/views/machine/devType/components/devTypePopup.vue @@ -99,7 +99,7 @@ // 定义弹出组件元信息 const dialogFormRef = ref() const scene = ref('') - const icdOptions = ref([]) + const icdOptions = ref([]) function useMetaInfo() { const dialogVisible = ref(false) const titleType = ref('add') @@ -209,7 +209,7 @@ const close = () => { } // 打开弹窗,可能是新增,也可能是编辑 - const open = async (sign: string, data: DevType.ResPqDevType,icd: ICD.ResICD[],currentScene: string) => { + const open = async (sign: string, data: DevType.ResPqDevType,icd: ICD.StandardOption[],currentScene: string) => { // 重置表单 dialogFormRef.value?.resetFields() titleType.value = sign @@ -230,4 +230,4 @@ const close = () => { refreshTable: (() => Promise) | undefined; }>() - \ No newline at end of file + diff --git a/frontend/src/views/machine/devType/index.vue b/frontend/src/views/machine/devType/index.vue index 06694e9..a55cbe9 100644 --- a/frontend/src/views/machine/devType/index.vue +++ b/frontend/src/views/machine/devType/index.vue @@ -41,8 +41,8 @@ getDevTypeList, deleteDevType, } from '@/api/device/devType/index.ts' - import { onBeforeMount, reactive, ref } from 'vue' - import { useModeStore, useAppSceneStore } from '@/stores/modules/mode' +import { onBeforeMount, reactive, ref } from 'vue' +import { useModeStore, useAppSceneStore } from '@/stores/modules/mode' import { getICDAllList } from '@/api/device/icd' import type { ICD } from '@/api/device/interface/icd' @@ -55,7 +55,7 @@ import type { ICD } from '@/api/device/interface/icd' // ProTable 实例 const proTable = ref() const devTypePopup = ref() - const icdOptions = ref([]) + const icdOptions = ref([]) const getTableList = async (params: any) => { let newParams = JSON.parse(JSON.stringify(params)) @@ -156,19 +156,12 @@ import type { ICD } from '@/api/device/interface/icd' onBeforeMount(async () => { - const response = await getICDAllList({ - id: '', - name: '', - path: '', - state: 1 - }) + const response = await getICDAllList() icdOptions.value = (response.data as ICD.ResICD[]).map(item => ({ id: item.id, - name: item.name, - path: item.path, - state: item.state, + name: item.name })) }) - \ No newline at end of file + diff --git a/frontend/src/views/machine/icd/components/icdForm.ts b/frontend/src/views/machine/icd/components/icdForm.ts new file mode 100644 index 0000000..e295b41 --- /dev/null +++ b/frontend/src/views/machine/icd/components/icdForm.ts @@ -0,0 +1,41 @@ +export const ICD_TYPE_OPTIONS = [ + { value: 1, label: "标准ICD", disabled: false }, + { value: 2, label: "非标准ICD", disabled: false }, + { value: 3, label: "上游标准ICD", disabled: true }, + { value: 4, label: "上游非标准ICD", disabled: true } +] as const; + +export const ICD_REFERENCE_REQUIRED_TYPES = [2, 4] as const; + +export const extractIcdName = (fileName: string) => + fileName.toLowerCase().endsWith(".icd") ? fileName.slice(0, -4) : fileName; + +export const formatIcdTypeLabel = (type: number | null | undefined) => + ICD_TYPE_OPTIONS.find(item => item.value === type)?.label ?? "--"; + +export const isValidJsonText = (value: string) => { + try { + JSON.parse(value); + return true; + } catch { + return false; + } +}; + +export const isValidXmlText = (value: string) => { + const parsed = new DOMParser().parseFromString(value, "application/xml"); + return parsed.getElementsByTagName("parsererror").length === 0; +}; + +export const normalizeIcdSubmitPayload = (form: T) => ({ + ...form, + msg: form.result === 1 ? null : form.msg, + referenceIcdId: ICD_REFERENCE_REQUIRED_TYPES.includes(form.type as (typeof ICD_REFERENCE_REQUIRED_TYPES)[number]) + ? form.referenceIcdId + : null +}); diff --git a/frontend/src/views/machine/icd/components/icdPopup.vue b/frontend/src/views/machine/icd/components/icdPopup.vue index 55de2ef..6f8870c 100644 --- a/frontend/src/views/machine/icd/components/icdPopup.vue +++ b/frontend/src/views/machine/icd/components/icdPopup.vue @@ -1,252 +1,546 @@ \ No newline at end of file + +const dialogFormRef = ref() +const dialogVisible = ref(false) +const titleType = ref<'add' | 'edit'>('add') +const standardIcdOptions = ref([]) +const selectedIcdFile = ref(null) +const icdFileList = ref([]) +const originalName = ref('') +const MAX_ICD_FILE_SIZE = 5 * 1024 * 1024 + +const createDefaultForm = (): ICD.ResICD => ({ + id: '', + name: '', + state: 1, + angle: 0, + usePhaseIndex: 0, + jsonStr: '', + xmlStr: '', + result: 1, + msg: null, + type: 1, + referenceIcdId: null, + referenceIcdName: null, + hasIcdFile: false +}) + +const formContent = ref(createDefaultForm()) + +const dialogTitle = computed(() => (titleType.value === 'add' ? '新增ICD' : '编辑ICD')) + +const shouldShowReferenceIcd = computed(() => + ICD_REFERENCE_REQUIRED_TYPES.includes(formContent.value.type as (typeof ICD_REFERENCE_REQUIRED_TYPES)[number]) +) + +const visibleStandardOptions = computed(() => { + return standardIcdOptions.value.filter(item => item.id !== formContent.value.id) +}) + +const currentReferenceName = computed(() => { + if (!formContent.value.referenceIcdId) { + return '' + } + return visibleStandardOptions.value.find(item => item.id === formContent.value.referenceIcdId)?.name ?? '' +}) + +const displayIcdFileName = computed(() => { + if (selectedIcdFile.value?.name) { + return selectedIcdFile.value.name + } + if (titleType.value === 'edit' && formContent.value.hasIcdFile && formContent.value.name) { + return `${formContent.value.name}.icd` + } + return '' +}) + +watch( + () => formContent.value.result, + value => { + if (value === 1) { + formContent.value.msg = null + dialogFormRef.value?.clearValidate(['msg']) + } + } +) + +watch( + () => formContent.value.type, + value => { + if (!ICD_REFERENCE_REQUIRED_TYPES.includes(value as (typeof ICD_REFERENCE_REQUIRED_TYPES)[number])) { + formContent.value.referenceIcdId = null + dialogFormRef.value?.clearValidate(['referenceIcdId']) + } + } +) + +const rules: Ref>> = ref({ + type: [{ required: true, message: '请选择ICD类型', trigger: 'change' }], + result: [{ required: true, message: '请选择结论', trigger: 'change' }], + msg: [ + { + validator: (_rule, value, callback) => { + if (formContent.value.result === 0 && !String(value ?? '').trim()) { + callback(new Error('当结论为否时,描述必填')) + return + } + callback() + }, + trigger: 'blur' + } + ], + referenceIcdId: [ + { + validator: (_rule, value, callback) => { + if (shouldShowReferenceIcd.value && !String(value ?? '').trim()) { + callback(new Error('非标准ICD必须选择标准ICD')) + return + } + callback() + }, + trigger: 'change' + } + ], + jsonStr: [ + { required: true, message: 'JSON内容必填', trigger: 'blur' }, + { + validator: (_rule, value, callback) => { + if (!isValidJsonText(String(value ?? ''))) { + callback(new Error('JSON格式不正确')) + return + } + callback() + }, + trigger: 'blur' + } + ], + xmlStr: [ + { required: true, message: 'XML内容必填', trigger: 'blur' }, + { + validator: (_rule, value, callback) => { + if (!isValidXmlText(String(value ?? ''))) { + callback(new Error('XML格式不正确')) + return + } + callback() + }, + trigger: 'blur' + } + ] +}) + +const resetForm = () => { + formContent.value = createDefaultForm() + standardIcdOptions.value = [] + selectedIcdFile.value = null + icdFileList.value = [] + originalName.value = '' +} + +const close = () => { + dialogVisible.value = false + dialogFormRef.value?.resetFields() + resetForm() +} + +const handleIcdFileChange = (file: UploadFile) => { + const rawFile = file.raw as File | undefined + if (!rawFile) { + return + } + if (!file.name.toLowerCase().endsWith('.icd')) { + ElMessage.error('仅支持上传 .icd 文件') + icdFileList.value = [] + selectedIcdFile.value = null + formContent.value.name = titleType.value === 'edit' ? originalName.value : '' + return + } + if (rawFile.size > MAX_ICD_FILE_SIZE) { + ElMessage.error('仅支持上传 5MB 以内的 .icd 文件') + icdFileList.value = [] + selectedIcdFile.value = null + formContent.value.name = titleType.value === 'edit' ? originalName.value : '' + return + } + selectedIcdFile.value = rawFile + icdFileList.value = [{ name: file.name }] + formContent.value.name = extractIcdName(file.name) +} + +const handleIcdFileRemove = () => { + selectedIcdFile.value = null + icdFileList.value = [] + formContent.value.name = titleType.value === 'edit' ? originalName.value : '' +} + +const ensureIcdFile = () => { + const hasCurrentFile = titleType.value === 'edit' && !!formContent.value.hasIcdFile + if (!selectedIcdFile.value && !hasCurrentFile) { + ElMessage.warning('请上传 .icd 原始文件') + return false + } + return true +} + +const save = async () => { + if (!ensureIcdFile()) { + return + } + await dialogFormRef.value?.validate() + + const normalized = normalizeIcdSubmitPayload({ + ...formContent.value, + msg: formContent.value.msg ?? null, + referenceIcdId: formContent.value.referenceIcdId ?? null + }) + + const payload = new FormData() + if (normalized.id) { + payload.append('id', normalized.id) + } + payload.append('angle', String(normalized.angle ?? 0)) + payload.append('usePhaseIndex', String(normalized.usePhaseIndex ?? 0)) + payload.append('jsonStr', normalized.jsonStr) + payload.append('xmlStr', normalized.xmlStr) + payload.append('result', String(normalized.result)) + payload.append('msg', normalized.msg ?? '') + payload.append('type', String(normalized.type)) + payload.append('referenceIcdId', normalized.referenceIcdId ?? '') + if (selectedIcdFile.value) { + payload.append('icdFile', selectedIcdFile.value, selectedIcdFile.value.name) + } + + if (normalized.id) { + await updateICD(payload) + } else { + await addICD(payload) + } + + ElMessage.success(`${dialogTitle.value}成功`) + close() + await props.refreshTable?.() +} + +const open = async (sign: 'add' | 'edit', data: Partial = {}) => { + titleType.value = sign + dialogVisible.value = true + resetForm() + await nextTick() + dialogFormRef.value?.clearValidate() + + const standardPromise = getStandardICDList() + const detailPromise = data.id ? getICDById(data.id) : Promise.resolve(null) + const [standardResult, detailResult] = await Promise.all([standardPromise, detailPromise]) + + standardIcdOptions.value = standardResult.data || [] + if (detailResult?.data) { + formContent.value = { + ...createDefaultForm(), + ...detailResult.data, + msg: detailResult.data.msg ?? null, + referenceIcdId: detailResult.data.referenceIcdId ?? null, + referenceIcdName: detailResult.data.referenceIcdName ?? null, + hasIcdFile: Boolean(detailResult.data.hasIcdFile) + } + originalName.value = formContent.value.name + } + + await nextTick() + dialogFormRef.value?.clearValidate() +} + +defineExpose({ open }) + + + diff --git a/frontend/src/views/machine/icd/index.vue b/frontend/src/views/machine/icd/index.vue index 288d7c6..85de20e 100644 --- a/frontend/src/views/machine/icd/index.vue +++ b/frontend/src/views/machine/icd/index.vue @@ -1,122 +1,137 @@ - - - \ No newline at end of file +} + +const handleExport = async (row: ICD.ResICD) => { + await useDownloadWithServerFileName(() => exportICD(row.id), row.name, {}, false, '.zip') +} +