diff --git a/src/api/iot/device/index.ts b/src/api/iot/device/index.ts index 3e112e92..d28ebe7e 100644 --- a/src/api/iot/device/index.ts +++ b/src/api/iot/device/index.ts @@ -150,6 +150,11 @@ export const DeviceApi = { return await request.get({ url: `/iot/device/devicePointList` }) }, + updateDeviceEnabled: async (id: number | string, enabled: string) => { + const data = { id, enabled } + return await request.put({ url: `/iot/device/update-enabled`, data }) + }, + // ==================== 子表(设备属性) ==================== // 获得设备属性分页 diff --git a/src/api/iot/recipeConfig/index.ts b/src/api/iot/recipeConfig/index.ts index 06931a2d..6ab87269 100644 --- a/src/api/iot/recipeConfig/index.ts +++ b/src/api/iot/recipeConfig/index.ts @@ -176,7 +176,7 @@ export const RecipeConfigApi = { }, getRecipePointDetailPage: async (params: any) => { - return await request.get({ url: `/iot/recipe-device-attribute/page`, params }) + return await request.get({ url: `/iot/recipe-device-attribute/getList`, params }) }, updateRecipeDeviceAttribute: async (data: { recipeId: string | number; ids: number[] }) => { return await request.put({ url: `/iot/recipe-device-attribute/update`, data }) diff --git a/src/locales/en.ts b/src/locales/en.ts index e8e580aa..de9490ac 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -2657,6 +2657,7 @@ export default { readDialogSubmitButtonText: 'Read', readDialogCancelButton: 'Cancel', readDeviceConfirmMessage: 'Do you want to read device data?', + readDialogOverwriteTip: 'Reading again will overwrite existing device data and manual parameters', detailTabDeviceDataLabel: 'Device Data', detailTabManualLabel: 'Manual Parameters', detailDevicePointNameColumn: 'Point Name', @@ -3591,11 +3592,13 @@ export default { placeholderUrl: 'Please enter endpoint URL', placeholderUsername: 'Please enter username', placeholderPassword: 'Please enter password', + placeholderTopic: 'Please enter subscription topic', model: 'Device Model', url: 'Endpoint URL', username: 'Username', password: 'Password', + topic: 'Subscription Topic', settingDialogTitle: 'Device Settings', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 2bb971b6..32033b45 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -2240,6 +2240,7 @@ export default { readDialogSubmitButtonText: '读 取', readDialogCancelButton: '取 消', readDeviceConfirmMessage: '是否读取设备数据?', + readDialogOverwriteTip: '再次读取会覆盖已有设备数据和手动录入参数', detailTabDeviceDataLabel: '设备数据', detailTabManualLabel: '手动录入参数', detailDevicePointNameColumn: '点位名称', @@ -3415,7 +3416,7 @@ export default { protocol: '采集协议', status: '连接状态', sampleCycle: '采集周期(s)', - isEnable: '是否启用', + isEnable: '启用', collectionTime: '采集时间', operate: '操作', @@ -3432,11 +3433,13 @@ export default { placeholderUrl: '请输入端点URL', placeholderUsername: '请输入用户名', placeholderPassword: '请输入密码', + placeholderTopic: '请输入订阅主题', model: '设备模型', url: '端点URL', username: '用户名', password: '密码', + topic: '订阅主题', settingDialogTitle: '设备设置', diff --git a/src/views/erp/moldlist/index.vue b/src/views/erp/moldlist/index.vue index d9b88117..35ecc03a 100644 --- a/src/views/erp/moldlist/index.vue +++ b/src/views/erp/moldlist/index.vue @@ -13,7 +13,7 @@ v-loading="typeTreeLoading" :data="brandTreeData" node-key="id" highlight-curren - + { pageSize, recipeId: props.recipeId }) - list.value = (data.list ?? []).map(normalizeDetail) - total.value = data.total - // 产品单位 - unitList.value = await ProductUnitApi.getProductUnitSimpleList() + const rawList = Array.isArray((data as any)?.list) + ? (data as any).list + : Array.isArray((data as any)?.data) + ? (data as any).data + : Array.isArray(data) + ? data + : [] + list.value = rawList.map(normalizeDetail) + total.value = (data as any)?.total ?? rawList.length + unitList.value = await ProductUnitApi.getProductUnitSimpleList() } finally { loading.value = false } diff --git a/src/views/formula/formulaConfig/index.vue b/src/views/formula/formulaConfig/index.vue index a5a96ec3..61970a60 100644 --- a/src/views/formula/formulaConfig/index.vue +++ b/src/views/formula/formulaConfig/index.vue @@ -663,7 +663,7 @@ const buildTransferLabel = (item: any) => { const loadConfigCandidatesByDevice = async (deviceId: number) => { const data = await DeviceApi.getDeviceAttributeList(deviceId) - const list = (data?.list ?? data?.data ?? []) as any[] + const list = (data ?? []) as any[] const deviceItems = (list ?? []) .map((item: any) => { const key = normalizeKey(item?.attributeId ?? item?.id) @@ -674,12 +674,7 @@ const loadConfigCandidatesByDevice = async (deviceId: number) => { } as TransferItem }) .filter((it: any) => it && typeof it.key === 'number') as TransferItem[] - const merged = new Map() - for (const it of deviceItems) merged.set(it.key, it) - for (const it of configSelectedItems.value) { - if (!merged.has(it.key)) merged.set(it.key, it) - } - configCandidates.value = Array.from(merged.values()) + configCandidates.value = deviceItems } const openConfigDialog = async (recipeId: number | string) => { diff --git a/src/views/formula/formulaLibrary/components/FormulaLibraryReadDialog.vue b/src/views/formula/formulaLibrary/components/FormulaLibraryReadDialog.vue index 4eb9bbb5..855edfcc 100644 --- a/src/views/formula/formulaLibrary/components/FormulaLibraryReadDialog.vue +++ b/src/views/formula/formulaLibrary/components/FormulaLibraryReadDialog.vue @@ -1,6 +1,14 @@ + = { list: T[]; total: number } const visible = ref(false) const title = ref('') const loading = ref(false) +const hasExistingRecords = ref(false) const recipeId = ref(undefined) const list = ref([]) @@ -144,11 +153,13 @@ const open = async (options: { recipeId: string | number recipeName?: string data?: Array - id : undefined | number + id: number | undefined + hasExistingRecords?: boolean }) => { visible.value = true formulaLibraryId.value = options.id recipeId.value = options.recipeId + hasExistingRecords.value = !!options.hasExistingRecords const baseTitle = t('RecipeManagement.RecipeLibrary.readDialogTitle') title.value = options.recipeName ? `${baseTitle}-${options.recipeName}` : baseTitle queryParams.pageNo = 1 diff --git a/src/views/formula/formulaLibrary/index.vue b/src/views/formula/formulaLibrary/index.vue index 3bf3e535..45113b53 100644 --- a/src/views/formula/formulaLibrary/index.vue +++ b/src/views/formula/formulaLibrary/index.vue @@ -223,7 +223,8 @@ import { RecipeConfigApi } from '@/api/iot/recipeConfig' import { RecipePointApi } from '@/api/iot/recipePoint' import FormulaLibraryDetailTabs from './components/FormulaLibraryDetailTabs.vue' import FormulaLibraryReadDialog from './components/FormulaLibraryReadDialog.vue' -import { RecipeDeviceRecordApi} from '@/api/iot/recipeDeviceRecord' +import { RecipeDeviceRecordApi } from '@/api/iot/recipeDeviceRecord' +import { RecipePointRecordApi } from '@/api/iot/recipepointrecord' type SelectOption = { label: string; value: T } @@ -398,11 +399,37 @@ const openDialog = async (type: 'create' | 'update', row?: RecipePlanDetailVO) = const readDialogRef = ref>() +const checkRecipeRecords = async (recipeId: string | number) => { + const params = { + pageNo: 1, + pageSize: 1, + recipeId: String(recipeId) + } + try { + const [devicePage, manualPage] = await Promise.all([ + RecipeDeviceRecordApi.getRecipeDeviceRecordPage(params), + RecipePointRecordApi.getRecipePointRecordPage(params) + ]) + const deviceTotal = Number((devicePage as any)?.total ?? 0) + const manualTotal = Number((manualPage as any)?.total ?? 0) + return { + hasDeviceRecords: deviceTotal > 0, + hasManualRecords: manualTotal > 0 + } + } catch { + return { + hasDeviceRecords: false, + hasManualRecords: false + } + } +} + const handleRead = async (row: RecipePlanDetailVO) => { const recipeId = row?.recipeId const id = row?.id if (!recipeId) return const recipeName = row?.recipeName + const { hasDeviceRecords, hasManualRecords } = await checkRecipeRecords(recipeId) const data = await RecipePointApi.getRecipePointList(Number(recipeId)) if (!data?.length) { await message.confirm(t('RecipeManagement.RecipeLibrary.readDeviceConfirmMessage')) @@ -412,7 +439,13 @@ const handleRead = async (row: RecipePlanDetailVO) => { return } } - await readDialogRef.value?.open({ recipeId, recipeName, data, id }) + await readDialogRef.value?.open({ + recipeId, + recipeName, + data, + id, + hasExistingRecords: hasDeviceRecords || hasManualRecords + }) } const submitDialog = async () => { diff --git a/src/views/iot/device/DeviceForm.vue b/src/views/iot/device/DeviceForm.vue index 8565fd7d..f686420e 100644 --- a/src/views/iot/device/DeviceForm.vue +++ b/src/views/iot/device/DeviceForm.vue @@ -79,12 +79,6 @@ - - - @@ -102,12 +96,6 @@ - - - - - - - - - - + @@ -187,20 +162,19 @@ const formData = ref({ url: undefined, username: undefined, password: undefined, + topic: undefined, }) const formRules = reactive({ create: { deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }], deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }], - sampleCycle: [{ required: true, message: '采集周期不能为空', trigger: 'blur' }], isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }] }, update: { - sampleCycle: [{ required: true, message: '采集周期不能为空', trigger: 'blur' }], isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }] }, setting: { - url: [{ required: true, message: '端点URL不能为空', trigger: 'blur' }] + topic: [{ required: true, message: '订阅主题不能为空', trigger: 'blur' }] } }) @@ -236,7 +210,7 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, url, username, password } = formData.value as any + const { id, deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable, topic } = formData.value as any if (formType.value === 'create') { const data: Partial = { deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable } @@ -247,7 +221,7 @@ const submitForm = async () => { await DeviceApi.updateDevice(data) message.success(t('common.updateSuccess')) } else { - const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, url, username, password } + const data: any = { id, deviceCode, deviceName, deviceModelId, isEnable, topic } await DeviceApi.updateDevice(data) message.success(t('common.updateSuccess')) } diff --git a/src/views/iot/device/index.vue b/src/views/iot/device/index.vue index 5b90b359..f962ee68 100644 --- a/src/views/iot/device/index.vue +++ b/src/views/iot/device/index.vue @@ -101,8 +101,6 @@ - - @@ -125,7 +123,10 @@ /> --> - + handleDeviceEnableChange(scope.row, val)" + /> {{ t('action.edit') }} - - {{ isRowConnected(scope.row) ? t('DataCollection.Device.disconnect') : t('DataCollection.Device.connect') }} - { return 'info' } +const isDeviceEnabled = (row: DeviceVO) => { + const value = (row as any)?.isEnable + if (typeof value === 'boolean') return value + const text = String(value ?? '').toLowerCase().trim() + if (!text) return false + if (text === 'true' || text === '1' || text === 'yes') return true + return false +} + +const handleDeviceEnableChange = async (row: DeviceVO, value: boolean) => { + if (!row.id) return + const oldValue = (row as any).isEnable + ;(row as any).isEnable = value + try { + await DeviceApi.updateDeviceEnabled(row.id, value ? 'true' : 'false') + const name = (row as any).deviceName ?? (row as any).deviceCode ?? '' + const suffix = value ? '已启用' : '已停用' + if (name) { + message.success(`${name}${suffix}`) + } else { + message.success(suffix) + } + } catch { + ;(row as any).isEnable = oldValue + message.error(t('common.updateFail')) + } +} + const selectedIds = ref([]) const handleSelectionChange = (rows: any[]) => { selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []