feat:点位字典页面

master
黄伟杰 1 month ago
parent 59433a0bcc
commit cc9449226a

@ -253,5 +253,6 @@ export enum DICT_TYPE {
IOT_MODBUS_FRAME_FORMAT = 'iot_modbus_frame_format', // IoT Modbus 帧格式
IOT_PROTOCOL = 'iot_protocol',
IOT_DEVICE_DATA_TYPE = 'iot_device_data_type',
PRIMARY_FLAG = 'primary_flag'
PRIMARY_FLAG = 'primary_flag',
IOT_ALARM_REGISTRATION = 'alarm_registration'
}

@ -1,49 +1,83 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item :label="t('DataCollection.Device.attributeCode')" prop="attributeCode">
<el-input
v-model="formData.attributeCode" :placeholder="t('DataCollection.Device.placeholderAttributeCode')"
@input="handleAttributeCodeInput" :disabled="formType === 'update'" />
v-model="formData.attributeCode"
:placeholder="t('DataCollection.Device.placeholderAttributeCode')"
:disabled="formType === 'update'"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeName')" prop="attributeName">
<el-input v-model="formData.attributeName" :placeholder="t('DataCollection.Device.placeholderAttributeName')" />
<el-input
v-model="formData.attributeName"
:placeholder="t('DataCollection.Device.placeholderAttributeName')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeType')" prop="attributeType">
<el-select
v-model="formData.attributeType" clearable filterable
:placeholder="t('DataCollection.Device.placeholderAttributeType')" @change="handleAttributeTypeChange">
v-model="formData.attributeType"
clearable
filterable
:placeholder="t('DataCollection.Device.placeholderAttributeType')"
@change="handleAttributeTypeChange"
>
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.dataType')" prop="dataType">
<el-select
v-model="formData.dataType" :placeholder="t('DataCollection.Device.placeholderDataType')"
:disabled="formType === 'update'">
v-model="formData.dataType"
:placeholder="t('DataCollection.Device.placeholderDataType')"
:disabled="formType === 'update'"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)" :key="dict.value"
:label="dict.label" :value="dict.value" />
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.address')" prop="address">
<el-input v-model="formData.address" :placeholder="t('DataCollection.Device.placeholderAddress')" />
<el-input
v-model="formData.address"
:placeholder="t('DataCollection.Device.placeholderAddress')"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.dataUnit')" prop="dataUnit">
<el-select
v-model="formData.dataUnit" clearable
:placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
v-model="formData.dataUnit"
clearable
:placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')"
class="w-1/1"
>
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio">
<el-input-number
v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00"
:decision="2" :step="0.01" class="!w-full" :disabled="!ratioEnabled" />
v-model="formData.ratio"
:placeholder="t('DataCollection.Device.placeholderRatio')"
:min="0.0"
:decision="2"
:step="0.01"
class="!w-full"
:disabled="!ratioEnabled"
/>
</el-form-item>
<el-form-item :label="t('DataCollection.Device.remark')" prop="remark">
<el-input
v-model="formData.remark" :placeholder="t('DataCollection.Device.placeholderRemark')"
type="textarea" />
v-model="formData.remark"
:placeholder="t('DataCollection.Device.placeholderRemark')"
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
@ -74,7 +108,10 @@ const loadTypeList = async () => {
return
}
try {
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({ pageNo: 1, pageSize: 10 })
const data = await DeviceAttributeTypeApi.getDeviceAttributeTypePage({
pageNo: 1,
pageSize: 10
})
typeList.value = data?.list ?? []
} catch {
typeList.value = []
@ -122,22 +159,18 @@ watch(
}
)
const handleAttributeCodeInput = (val: string) => {
const sanitized = (val || '').replace(/[^A-Za-z0-9_]/g, '')
formData.value.attributeCode = sanitized.replace(/^[0-9]+/, '')
}
const handleAttributeTypeChange = (val: number | string) => {
const matched = typeList.value.find(
(item) => item.id === val || String(item.id) === String(val)
)
const matched = typeList.value.find((item) => item.id === val || String(item.id) === String(val))
formData.value.typeName = matched?.name
}
const formRules = reactive({
attributeCode: [
{ required: true, message: t('DataCollection.Device.attributeValidatorCodeRequired'), trigger: 'blur' },
{
required: true,
message: t('DataCollection.Device.attributeValidatorCodeRequired'),
trigger: 'blur'
},
{
validator: (_rule: any, value: string, callback: any) => {
if (!value) {
@ -158,10 +191,18 @@ const formRules = reactive({
}
],
attributeName: [
{ required: true, message: t('DataCollection.Device.attributeValidatorNameRequired'), trigger: 'blur' }
{
required: true,
message: t('DataCollection.Device.attributeValidatorNameRequired'),
trigger: 'blur'
}
],
dataType: [
{ required: true, message: t('DataCollection.DeviceModel.validatorDataTypeRequired'), trigger: 'change' }
{
required: true,
message: t('DataCollection.DeviceModel.validatorDataTypeRequired'),
trigger: 'change'
}
],
remark: [
{
@ -227,7 +268,7 @@ const open = async (type: string, id?: number, deviceId: number) => {
try {
formData.value = await DeviceApi.getDeviceAttribute(id)
if (!(formData.value as any)?.deviceId) {
; (formData.value as any).deviceId = deviceId
;(formData.value as any).deviceId = deviceId
}
const currentType = (formData.value as any)?.attributeType
@ -240,8 +281,8 @@ const open = async (type: string, id?: number, deviceId: number) => {
item.code === currentType
)
if (matched) {
; (formData.value as any).attributeType = matched.id
; (formData.value as any).typeName = matched.name
;(formData.value as any).attributeType = matched.id
;(formData.value as any).typeName = matched.name
}
}
} finally {

@ -47,9 +47,6 @@
{{ getNodeTypeLabel(data.nodeType) }}
</span>
</div>
<template v-if="data.type === 'device'">
<span class="device-mgmt__treeNodeMeta">{{ data.code }}</span>
</template>
<div class="device-mgmt__treeNodeActions">
<el-button
v-if="canCreateChild(data)"
@ -133,7 +130,7 @@
<el-card shadow="never" class="device-mgmt__panel device-mgmt__header">
<div class="device-mgmt__headerMain">
<div class="device-mgmt__titleRow">
<el-button link type="primary" @click="backToList"></el-button>
<el-button :icon="Back" @click="backToList"></el-button>
<span
class="device-mgmt__dot device-mgmt__dot--lg"
:class="
@ -312,35 +309,39 @@
<el-card shadow="never" class="device-mgmt__panel" v-loading="historyLoading">
<div class="device-mgmt__filters">
<div class="device-mgmt__filter">
<div class="device-mgmt__filterLabel">参数选择</div>
<el-select v-model="historyMetricKey" class="!w-160px" clearable>
<el-option
v-for="m in historyMetricOptions"
:key="m.id"
:label="m.attributeName"
:value="m.id"
<div class="device-mgmt__filter">
<div class="device-mgmt__filterLabel">参数选择</div>
<el-select v-model="historyMetricKey" class="!w-160px" clearable>
<el-option
v-for="m in historyMetricOptions"
:key="m.id"
:label="m.attributeName"
:value="m.id"
/>
</el-select>
</div>
<div class="device-mgmt__filter">
<div class="device-mgmt__filterLabel">时间范围</div>
<el-date-picker
v-model="historyTimeRange"
type="datetimerange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled-date="historyDisabledDate"
@change="handleHistoryRangeChange"
class="!w-320px"
/>
</el-select>
</div>
<div class="device-mgmt__filter">
<div class="device-mgmt__filterLabel">时间范围</div>
<el-date-picker
v-model="historyTimeRange"
type="datetimerange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
class="!w-320px"
/>
<el-button type="primary" @click="loadHistoryData"></el-button>
</div>
</div>
<el-button type="primary" @click="loadHistoryData"></el-button>
<div class="device-mgmt__filterActions">
<el-radio-group v-model="historyViewType" class="ml-16px">
<el-radio-button label="chart">曲线</el-radio-button>
<el-radio-button label="list">列表</el-radio-button>
</el-radio-group>
<el-button class="ml-16px">导出</el-button>
</div>
</div>
@ -403,13 +404,7 @@
: 'info'
"
>
{{
String(row.alarmLevel) === '1'
? '高级'
: String(row.alarmLevel) === '2'
? '中级'
: '低级'
}}
{{ getAlarmLevelLabel(row.alarmLevel) }}
</el-tag>
</template>
</el-table-column>
@ -449,7 +444,8 @@ import { useRoute } from 'vue-router'
import NodeForm from './NodeForm.vue'
import DeviceForm from '../DeviceForm.vue'
import { formatDate } from '@/utils/formatTime'
import { OfficeBuilding, Monitor, Refresh } from '@element-plus/icons-vue'
import { getIntDictOptions } from '@/utils/dict'
import { OfficeBuilding, Monitor, Refresh, Back } from '@element-plus/icons-vue'
defineOptions({ name: 'IoTDeviceManagement' })
type TreeNodeType = 'company' | 'line' | 'device'
@ -661,6 +657,13 @@ const getOperatingStatusLabel = (status?: string | number) => {
return String(status)
}
const alarmLevelOptions = computed(() => getIntDictOptions('alarm_registration'))
const getAlarmLevelLabel = (value?: string | number) => {
const item = alarmLevelOptions.value.find((option) => String(option.value) === String(value))
return item?.label || String(value ?? '-')
}
const loadNodeDeviceList = async () => {
if (!selectedNode.value) return
nodeDeviceLoading.value = true
@ -837,10 +840,57 @@ watch([() => activeTab.value, () => selectedDeviceDetail.value.id], ([tab, devic
const historyMetricOptions = ref<any[]>([])
const historyMetricKey = ref<string | number>('')
const historyTimeRange = ref<string[]>([])
const historyViewType = ref<'chart' | 'list'>('chart')
const historyList = ref<any[]>([])
const historyLoading = ref(false)
const HISTORY_MAX_RANGE_MS = 8 * 60 * 60 * 1000
const formatDateTimeText = (date: Date) => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const buildDefaultHistoryRange = () => {
const end = new Date()
const start = new Date(end.getTime() - 2 * 60 * 60 * 1000)
return [formatDateTimeText(start), formatDateTimeText(end)]
}
const historyTimeRange = ref<string[]>(buildDefaultHistoryRange())
const toTimeStamp = (value?: string) => {
if (!value) return 0
const ts = new Date(value).getTime()
return Number.isNaN(ts) ? 0 : ts
}
const historyDisabledDate = (time: Date) => {
const endOfDay = new Date(time)
endOfDay.setHours(23, 59, 59, 999)
return endOfDay.getTime() > Date.now()
}
const handleHistoryRangeChange = (range?: string[]) => {
if (!range || range.length !== 2) return
const startTime = toTimeStamp(range[0])
const endTime = Math.min(toTimeStamp(range[1]), Date.now())
if (!startTime || !endTime) return
if (endTime - startTime > HISTORY_MAX_RANGE_MS) {
const fixedStart = formatDateTimeText(new Date(endTime - HISTORY_MAX_RANGE_MS))
const fixedEnd = formatDateTimeText(new Date(endTime))
historyTimeRange.value = [fixedStart, fixedEnd]
message.warning('时间范围最多 8 小时,已自动调整')
return
}
if (endTime !== toTimeStamp(range[1])) {
historyTimeRange.value = [range[0], formatDateTimeText(new Date(endTime))]
}
}
const parseHistoryTime = (timeText?: string) => {
if (!timeText) return { display: '-', timestamp: 0 }
@ -888,6 +938,7 @@ const loadHistoryData = async () => {
modelId: Number(historyMetricKey.value)
}
if (historyTimeRange.value && historyTimeRange.value.length === 2) {
handleHistoryRangeChange(historyTimeRange.value)
params.collectionStartTime = historyTimeRange.value[0]
params.collectionEndTime = historyTimeRange.value[1]
}
@ -986,6 +1037,9 @@ watch([() => activeTab.value, () => selectedDeviceDetail.value.id], ([tab, devic
</script>
<style scoped lang="scss">
:deep(.el-tree-node__content) {
margin: 2px 0;
}
.device-mgmt {
display: flex;
gap: 16px;

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save