main
kkk-ops 1 month ago
commit 59552f1b3c

@ -150,6 +150,11 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/devicePointList` }) 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 })
},
// ==================== 子表(设备属性) ==================== // ==================== 子表(设备属性) ====================
// 获得设备属性分页 // 获得设备属性分页

@ -176,7 +176,7 @@ export const RecipeConfigApi = {
}, },
getRecipePointDetailPage: async (params: any) => { 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[] }) => { updateRecipeDeviceAttribute: async (data: { recipeId: string | number; ids: number[] }) => {
return await request.put({ url: `/iot/recipe-device-attribute/update`, data }) return await request.put({ url: `/iot/recipe-device-attribute/update`, data })

@ -2657,6 +2657,7 @@ export default {
readDialogSubmitButtonText: 'Read', readDialogSubmitButtonText: 'Read',
readDialogCancelButton: 'Cancel', readDialogCancelButton: 'Cancel',
readDeviceConfirmMessage: 'Do you want to read device data?', readDeviceConfirmMessage: 'Do you want to read device data?',
readDialogOverwriteTip: 'Reading again will overwrite existing device data and manual parameters',
detailTabDeviceDataLabel: 'Device Data', detailTabDeviceDataLabel: 'Device Data',
detailTabManualLabel: 'Manual Parameters', detailTabManualLabel: 'Manual Parameters',
detailDevicePointNameColumn: 'Point Name', detailDevicePointNameColumn: 'Point Name',
@ -3591,11 +3592,13 @@ export default {
placeholderUrl: 'Please enter endpoint URL', placeholderUrl: 'Please enter endpoint URL',
placeholderUsername: 'Please enter username', placeholderUsername: 'Please enter username',
placeholderPassword: 'Please enter password', placeholderPassword: 'Please enter password',
placeholderTopic: 'Please enter subscription topic',
model: 'Device Model', model: 'Device Model',
url: 'Endpoint URL', url: 'Endpoint URL',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',
topic: 'Subscription Topic',
settingDialogTitle: 'Device Settings', settingDialogTitle: 'Device Settings',

@ -2240,6 +2240,7 @@ export default {
readDialogSubmitButtonText: '读 取', readDialogSubmitButtonText: '读 取',
readDialogCancelButton: '取 消', readDialogCancelButton: '取 消',
readDeviceConfirmMessage: '是否读取设备数据?', readDeviceConfirmMessage: '是否读取设备数据?',
readDialogOverwriteTip: '再次读取会覆盖已有设备数据和手动录入参数',
detailTabDeviceDataLabel: '设备数据', detailTabDeviceDataLabel: '设备数据',
detailTabManualLabel: '手动录入参数', detailTabManualLabel: '手动录入参数',
detailDevicePointNameColumn: '点位名称', detailDevicePointNameColumn: '点位名称',
@ -3415,7 +3416,7 @@ export default {
protocol: '采集协议', protocol: '采集协议',
status: '连接状态', status: '连接状态',
sampleCycle: '采集周期(s)', sampleCycle: '采集周期(s)',
isEnable: '是否启用', isEnable: '启用',
collectionTime: '采集时间', collectionTime: '采集时间',
operate: '操作', operate: '操作',
@ -3432,11 +3433,13 @@ export default {
placeholderUrl: '请输入端点URL', placeholderUrl: '请输入端点URL',
placeholderUsername: '请输入用户名', placeholderUsername: '请输入用户名',
placeholderPassword: '请输入密码', placeholderPassword: '请输入密码',
placeholderTopic: '请输入订阅主题',
model: '设备模型', model: '设备模型',
url: '端点URL', url: '端点URL',
username: '用户名', username: '用户名',
password: '密码', password: '密码',
topic: '订阅主题',
settingDialogTitle: '设备设置', settingDialogTitle: '设备设置',

@ -323,9 +323,15 @@ const getList = async () => {
pageSize, pageSize,
recipeId: props.recipeId recipeId: props.recipeId
}) })
list.value = (data.list ?? []).map(normalizeDetail) const rawList = Array.isArray((data as any)?.list)
total.value = data.total ? (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() unitList.value = await ProductUnitApi.getProductUnitSimpleList()
} finally { } finally {
loading.value = false loading.value = false

@ -663,7 +663,7 @@ const buildTransferLabel = (item: any) => {
const loadConfigCandidatesByDevice = async (deviceId: number) => { const loadConfigCandidatesByDevice = async (deviceId: number) => {
const data = await DeviceApi.getDeviceAttributeList(deviceId) const data = await DeviceApi.getDeviceAttributeList(deviceId)
const list = (data?.list ?? data?.data ?? []) as any[] const list = (data ?? []) as any[]
const deviceItems = (list ?? []) const deviceItems = (list ?? [])
.map((item: any) => { .map((item: any) => {
const key = normalizeKey(item?.attributeId ?? item?.id) const key = normalizeKey(item?.attributeId ?? item?.id)
@ -674,12 +674,7 @@ const loadConfigCandidatesByDevice = async (deviceId: number) => {
} as TransferItem } as TransferItem
}) })
.filter((it: any) => it && typeof it.key === 'number') as TransferItem[] .filter((it: any) => it && typeof it.key === 'number') as TransferItem[]
const merged = new Map<number, TransferItem>() configCandidates.value = deviceItems
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())
} }
const openConfigDialog = async (recipeId: number | string) => { const openConfigDialog = async (recipeId: number | string) => {

@ -1,6 +1,14 @@
<template> <template>
<Dialog v-model="visible" :title="title" width="920px"> <Dialog v-model="visible" :title="title" width="920px">
<div class="formula-library-read-content"> <div class="formula-library-read-content">
<el-alert
v-if="hasExistingRecords"
type="warning"
:closable="false"
:title="t('RecipeManagement.RecipeLibrary.readDialogOverwriteTip')"
show-icon
class="mb-15px"
/>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<!-- <el-table-column type="index" :label="t('RecipeManagement.RecipeLibrary.tableIndexColumn')" align="center" width="70" /> --> <!-- <el-table-column type="index" :label="t('RecipeManagement.RecipeLibrary.tableIndexColumn')" align="center" width="70" /> -->
<el-table-column <el-table-column
@ -94,6 +102,7 @@ type PageResult<T> = { list: T[]; total: number }
const visible = ref(false) const visible = ref(false)
const title = ref('') const title = ref('')
const loading = ref(false) const loading = ref(false)
const hasExistingRecords = ref(false)
const recipeId = ref<string | number | undefined>(undefined) const recipeId = ref<string | number | undefined>(undefined)
const list = ref<RecipePointVO[]>([]) const list = ref<RecipePointVO[]>([])
@ -144,11 +153,13 @@ const open = async (options: {
recipeId: string | number recipeId: string | number
recipeName?: string recipeName?: string
data?: Array<RecipePointVO> data?: Array<RecipePointVO>
id : undefined | number id: number | undefined
hasExistingRecords?: boolean
}) => { }) => {
visible.value = true visible.value = true
formulaLibraryId.value = options.id formulaLibraryId.value = options.id
recipeId.value = options.recipeId recipeId.value = options.recipeId
hasExistingRecords.value = !!options.hasExistingRecords
const baseTitle = t('RecipeManagement.RecipeLibrary.readDialogTitle') const baseTitle = t('RecipeManagement.RecipeLibrary.readDialogTitle')
title.value = options.recipeName ? `${baseTitle}-${options.recipeName}` : baseTitle title.value = options.recipeName ? `${baseTitle}-${options.recipeName}` : baseTitle
queryParams.pageNo = 1 queryParams.pageNo = 1

@ -224,6 +224,7 @@ import { RecipePointApi } from '@/api/iot/recipePoint'
import FormulaLibraryDetailTabs from './components/FormulaLibraryDetailTabs.vue' import FormulaLibraryDetailTabs from './components/FormulaLibraryDetailTabs.vue'
import FormulaLibraryReadDialog from './components/FormulaLibraryReadDialog.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<T = any> = { label: string; value: T } type SelectOption<T = any> = { label: string; value: T }
@ -398,11 +399,37 @@ const openDialog = async (type: 'create' | 'update', row?: RecipePlanDetailVO) =
const readDialogRef = ref<InstanceType<typeof FormulaLibraryReadDialog>>() const readDialogRef = ref<InstanceType<typeof FormulaLibraryReadDialog>>()
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 handleRead = async (row: RecipePlanDetailVO) => {
const recipeId = row?.recipeId const recipeId = row?.recipeId
const id = row?.id const id = row?.id
if (!recipeId) return if (!recipeId) return
const recipeName = row?.recipeName const recipeName = row?.recipeName
const { hasDeviceRecords, hasManualRecords } = await checkRecipeRecords(recipeId)
const data = await RecipePointApi.getRecipePointList(Number(recipeId)) const data = await RecipePointApi.getRecipePointList(Number(recipeId))
if (!data?.length) { if (!data?.length) {
await message.confirm(t('RecipeManagement.RecipeLibrary.readDeviceConfirmMessage')) await message.confirm(t('RecipeManagement.RecipeLibrary.readDeviceConfirmMessage'))
@ -412,7 +439,13 @@ const handleRead = async (row: RecipePlanDetailVO) => {
return return
} }
} }
await readDialogRef.value?.open({ recipeId, recipeName, data, id }) await readDialogRef.value?.open({
recipeId,
recipeName,
data,
id,
hasExistingRecords: hasDeviceRecords || hasManualRecords
})
} }
const submitDialog = async () => { const submitDialog = async () => {

@ -79,12 +79,6 @@
<el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.sampleCycle')" prop="sampleCycle">
<el-input
v-model="formData.sampleCycle"
:placeholder="t('DataCollection.Device.placeholderSampleCycle')"
/>
</el-form-item>
<!-- <el-form-item label="备注" prop="remark"> <!-- <el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" /> <el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item> --> </el-form-item> -->
@ -102,12 +96,6 @@
</template> </template>
<template v-else-if="formType === 'update'"> <template v-else-if="formType === 'update'">
<el-form-item :label="t('DataCollection.Device.sampleCycle')" prop="sampleCycle">
<el-input
v-model="formData.sampleCycle"
:placeholder="t('DataCollection.Device.placeholderSampleCycle')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.isEnable')" prop="isEnable"> <el-form-item :label="t('DataCollection.Device.isEnable')" prop="isEnable">
<el-radio-group v-model="formData.isEnable"> <el-radio-group v-model="formData.isEnable">
<el-radio <el-radio
@ -122,23 +110,10 @@
</template> </template>
<template v-else> <template v-else>
<el-form-item :label="t('DataCollection.Device.url')" prop="url"> <el-form-item :label="t('DataCollection.Device.topic')" prop="topic">
<el-input
v-model="formData.url"
:placeholder="t('DataCollection.Device.placeholderUrl')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.username')" prop="username">
<el-input
v-model="formData.username"
:placeholder="t('DataCollection.Device.placeholderUsername')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.password')" prop="password">
<el-input <el-input
v-model="formData.password" v-model="formData.topic"
:placeholder="t('DataCollection.Device.placeholderPassword')" :placeholder="t('DataCollection.Device.placeholderTopic')"
show-password
/> />
</el-form-item> </el-form-item>
</template> </template>
@ -187,20 +162,19 @@ const formData = ref({
url: undefined, url: undefined,
username: undefined, username: undefined,
password: undefined, password: undefined,
topic: undefined,
}) })
const formRules = reactive({ const formRules = reactive({
create: { create: {
deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }], deviceCode: [{ required: true, message: '设备编码不能为空', trigger: 'blur' }],
deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }], deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
sampleCycle: [{ required: true, message: '采集周期不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }] isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
}, },
update: { update: {
sampleCycle: [{ required: true, message: '采集周期不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }] isEnable: [{ required: true, message: '是否启用不能为空', trigger: 'blur' }]
}, },
setting: { setting: {
url: [{ required: true, message: '端点URL不能为空', trigger: 'blur' }] topic: [{ required: true, message: '订阅主题不能为空', trigger: 'blur' }]
} }
}) })
@ -236,7 +210,7 @@ const submitForm = async () => {
// //
formLoading.value = true formLoading.value = true
try { 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') { if (formType.value === 'create') {
const data: Partial<DeviceVO> = { deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable } const data: Partial<DeviceVO> = { deviceCode, deviceName, deviceModelId, sampleCycle, remark, isEnable }
@ -247,7 +221,7 @@ const submitForm = async () => {
await DeviceApi.updateDevice(data) await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} else { } 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) await DeviceApi.updateDevice(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }

@ -101,8 +101,6 @@
<dict-tag :type="DICT_TYPE.IOT_GATEWAY_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.IOT_GATEWAY_STATUS" :value="scope.row.status" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('DataCollection.Device.sampleCycle')" align="center" prop="sampleCycle" width="120px" />
<!-- <el-table-column label="读主题" align="center" prop="readTopic" /> <!-- <el-table-column label="读主题" align="center" prop="readTopic" />
<el-table-column label="写主题" align="center" prop="writeTopic" /> <el-table-column label="写主题" align="center" prop="writeTopic" />
<el-table-column label="网关id" align="center" prop="gatewayId" /> --> <el-table-column label="网关id" align="center" prop="gatewayId" /> -->
@ -125,7 +123,10 @@
/> --> /> -->
<el-table-column :label="t('DataCollection.Device.isEnable')" align="center" prop="isEnable" width="120px"> <el-table-column :label="t('DataCollection.Device.isEnable')" align="center" prop="isEnable" width="120px">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.isEnable" /> <el-switch
:model-value="isDeviceEnabled(scope.row)"
@change="(val) => handleDeviceEnableChange(scope.row, val)"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -162,11 +163,6 @@
> >
{{ t('action.edit') }} {{ t('action.edit') }}
</el-button> </el-button>
<el-button
link :type="isRowConnected(scope.row) ? 'warning' : 'success'"
:loading="!!connectLoadingMap[scope.row.id]" @click.stop="handleToggleConnect(scope.row)">
{{ isRowConnected(scope.row) ? t('DataCollection.Device.disconnect') : t('DataCollection.Device.connect') }}
</el-button>
<el-button <el-button
link link
type="danger" type="danger"
@ -651,6 +647,34 @@ const getOperatingStatusType = (value: string | number | undefined) => {
return 'info' 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<number[]>([]) const selectedIds = ref<number[]>([])
const handleSelectionChange = (rows: any[]) => { const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? [] selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined) ?? []

Loading…
Cancel
Save