|
|
@@ -3,7 +3,7 @@
|
|
|
<div class="device-main">
|
|
|
<div class="device-media">
|
|
|
<button class="device-image-frame" type="button" @click="imagePreview(defaultPicUrl)">
|
|
|
- <el-image :src="defaultPicUrl" class="device-image" fit="cover">
|
|
|
+ <el-image :src="defaultPicUrl" class="device-image" fit="contain">
|
|
|
<template #error>
|
|
|
<div class="device-image__empty">
|
|
|
<Icon icon="ep:picture" />
|
|
|
@@ -39,7 +39,7 @@
|
|
|
<Icon :icon="field.icon" />
|
|
|
{{ field.label }}
|
|
|
</span>
|
|
|
- <strong>{{ field.value }}</strong>
|
|
|
+ <strong :title="field.value">{{ field.value }}</strong>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -103,6 +103,8 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, onMounted, ref } from 'vue'
|
|
|
import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
|
|
|
+import { DeviceAttrModelApi, type DeviceAttrModelData } from '@/api/pms/deviceattrmodel'
|
|
|
+import { IotYfClassifyApi } from '@/api/pms/yfclass'
|
|
|
import { createImageViewer } from '@/components/ImageViewer'
|
|
|
import { getAccessToken } from '@/utils/auth'
|
|
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
|
|
@@ -139,11 +141,18 @@ type DeviceDetail = Partial<
|
|
|
| 'totalMonth'
|
|
|
>
|
|
|
> & {
|
|
|
+ yfClass?: string
|
|
|
yfDeviceCode?: string
|
|
|
brandName?: string
|
|
|
assetClass?: number
|
|
|
assetClassName?: string
|
|
|
model?: string | number
|
|
|
+ carNo?: string
|
|
|
+ deviceNo?: string
|
|
|
+ useProject?: string
|
|
|
+ assetOwnership?: string
|
|
|
+ address?: string
|
|
|
+ remark?: string
|
|
|
responsibleNames?: string
|
|
|
devicePersons?: string
|
|
|
zzName?: string
|
|
|
@@ -163,13 +172,18 @@ type DeviceDetail = Partial<
|
|
|
monthAmount?: number | string
|
|
|
totalMonth?: number | string
|
|
|
currency?: string
|
|
|
- templateJson?: string
|
|
|
+ templateJson?: string | TemplateField[]
|
|
|
+ assetProperty?: string
|
|
|
+ [key: string]: unknown
|
|
|
}
|
|
|
|
|
|
interface TemplateField {
|
|
|
sort?: number
|
|
|
name?: string
|
|
|
- value?: string | number | null
|
|
|
+ code?: string
|
|
|
+ type?: string
|
|
|
+ defaultValue?: string
|
|
|
+ value?: string | number | Date | null
|
|
|
identifier?: string
|
|
|
}
|
|
|
|
|
|
@@ -180,6 +194,12 @@ interface MainInfoField {
|
|
|
icon: string
|
|
|
}
|
|
|
|
|
|
+interface EncodingTypeOption {
|
|
|
+ label: string
|
|
|
+ value: string
|
|
|
+ children?: EncodingTypeOption[]
|
|
|
+}
|
|
|
+
|
|
|
const defaultDevicePic =
|
|
|
import.meta.env.VITE_BASE_URL + '/admin-api/infra/file/29/get/IntegratedSolution.png'
|
|
|
|
|
|
@@ -190,6 +210,8 @@ const formLoading = ref(false)
|
|
|
const activeName = ref('info')
|
|
|
const defaultPicUrl = ref(defaultDevicePic)
|
|
|
const formData = ref<DeviceDetail>({})
|
|
|
+const encodingTypeOptions = ref<EncodingTypeOption[]>([])
|
|
|
+const templateFields = ref<TemplateField[]>([])
|
|
|
|
|
|
const deviceId = computed(() => {
|
|
|
const id = params.id ?? query.id
|
|
|
@@ -217,18 +239,87 @@ const responsibleNames = computed(() => {
|
|
|
return formData.value.responsibleNames || formData.value.devicePersons || '-'
|
|
|
})
|
|
|
|
|
|
-const templateFields = computed<TemplateField[]>(() => {
|
|
|
- if (!formData.value.templateJson) return []
|
|
|
+const yfClassLabel = computed(() => {
|
|
|
+ if (!formData.value.yfClass) return '-'
|
|
|
+
|
|
|
+ const codes = formData.value.yfClass.split(',').filter(Boolean)
|
|
|
+ const labels: string[] = []
|
|
|
+ let options = encodingTypeOptions.value
|
|
|
+
|
|
|
+ for (const code of codes) {
|
|
|
+ const option = options.find((item) => String(item.value) === code)
|
|
|
+ if (!option) return '-'
|
|
|
+ labels.push(option.label)
|
|
|
+ options = option.children || []
|
|
|
+ }
|
|
|
+
|
|
|
+ return labels.join(' / ') || '-'
|
|
|
+})
|
|
|
+
|
|
|
+const getEncodingTypeOptions = async () => {
|
|
|
+ const options = await IotYfClassifyApi.getChildrenList()
|
|
|
+ encodingTypeOptions.value = options || []
|
|
|
+}
|
|
|
+
|
|
|
+const parseTemplateFields = (source: unknown): TemplateField[] => {
|
|
|
+ let parsed = source
|
|
|
+
|
|
|
+ for (let index = 0; index < 2 && typeof parsed === 'string'; index += 1) {
|
|
|
+ if (!parsed.trim()) return []
|
|
|
+ try {
|
|
|
+ parsed = JSON.parse(parsed)
|
|
|
+ } catch {
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.isArray(parsed)) return parsed
|
|
|
+ if (!parsed || typeof parsed !== 'object') return []
|
|
|
+
|
|
|
+ const container = parsed as Record<string, unknown>
|
|
|
+ const fields = container.fields || container.list || container.data
|
|
|
+ return Array.isArray(fields) ? fields : []
|
|
|
+}
|
|
|
+
|
|
|
+const loadTemplateFields = async (detail: DeviceDetail) => {
|
|
|
+ const savedFields = parseTemplateFields(detail.templateJson)
|
|
|
+ if (!detail.assetClass) {
|
|
|
+ templateFields.value = savedFields
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
try {
|
|
|
- const fields = JSON.parse(formData.value.templateJson)
|
|
|
- return Array.isArray(fields) ? fields : []
|
|
|
+ const definitions = (await DeviceAttrModelApi.getDeviceAttrModelListByDeviceCategoryId(
|
|
|
+ detail.assetClass
|
|
|
+ )) as DeviceAttrModelData[]
|
|
|
+ const savedFieldMap = new Map(
|
|
|
+ savedFields.filter((field) => field.code).map((field) => [field.code, field])
|
|
|
+ )
|
|
|
+
|
|
|
+ const fieldsFromDefinitions: TemplateField[] = (definitions || []).map((definition) => {
|
|
|
+ const code = definition.code || ''
|
|
|
+ const savedField = savedFieldMap.get(code)
|
|
|
+ savedFieldMap.delete(code)
|
|
|
+ return {
|
|
|
+ ...definition,
|
|
|
+ ...savedField,
|
|
|
+ value: savedField?.value ?? detail[code] ?? definition.defaultValue ?? null
|
|
|
+ } as TemplateField
|
|
|
+ })
|
|
|
+
|
|
|
+ templateFields.value = [...fieldsFromDefinitions, ...savedFieldMap.values()]
|
|
|
} catch {
|
|
|
- return []
|
|
|
+ templateFields.value = savedFields
|
|
|
}
|
|
|
-})
|
|
|
+}
|
|
|
|
|
|
const mainInfoFields = computed<MainInfoField[]>(() => [
|
|
|
+ {
|
|
|
+ prop: 'yfClass',
|
|
|
+ label: t('iotDevice.yfClass'),
|
|
|
+ value: yfClassLabel.value,
|
|
|
+ icon: 'ep:connection'
|
|
|
+ },
|
|
|
{
|
|
|
prop: 'yfDeviceCode',
|
|
|
label: t('iotDevice.yfCode'),
|
|
|
@@ -267,7 +358,7 @@ const mainInfoFields = computed<MainInfoField[]>(() => [
|
|
|
},
|
|
|
{
|
|
|
prop: 'deviceStatus',
|
|
|
- label: '施工状态',
|
|
|
+ label: t('iotDevice.status'),
|
|
|
value: statusLabel.value,
|
|
|
icon: 'ep:operation'
|
|
|
},
|
|
|
@@ -283,11 +374,48 @@ const mainInfoFields = computed<MainInfoField[]>(() => [
|
|
|
value: formatValue(formData.value.model),
|
|
|
icon: 'ep:set-up'
|
|
|
},
|
|
|
+ {
|
|
|
+ prop: 'carNo',
|
|
|
+ label: '车牌号',
|
|
|
+ value: formatValue(formData.value.carNo),
|
|
|
+ icon: 'ep:van'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'deviceNo',
|
|
|
+ label: '设备号',
|
|
|
+ value: formatValue(formData.value.deviceNo),
|
|
|
+ icon: 'ep:document'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'useProject',
|
|
|
+ label: t('deviceForm.useProject'),
|
|
|
+ value: formatValue(formData.value.useProject),
|
|
|
+ icon: 'ep:briefcase'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'assetOwnership',
|
|
|
+ label: t('deviceForm.assetOwner'),
|
|
|
+ value: formatValue(formData.value.assetOwnership),
|
|
|
+ icon: 'ep:stamp'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'address',
|
|
|
+ label: '所在地点',
|
|
|
+ value: formatValue(formData.value.address),
|
|
|
+ icon: 'ep:location'
|
|
|
+ },
|
|
|
+
|
|
|
{
|
|
|
prop: 'responsibleNames',
|
|
|
label: t('devicePerson.rp'),
|
|
|
value: responsibleNames.value,
|
|
|
icon: 'ep:user'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'remark',
|
|
|
+ label: t('deviceForm.remark'),
|
|
|
+ value: formatValue(formData.value.remark),
|
|
|
+ icon: 'ep:chat-line-square'
|
|
|
}
|
|
|
])
|
|
|
|
|
|
@@ -299,6 +427,7 @@ const getDetail = async () => {
|
|
|
const detail = (await IotDeviceApi.getIotDevice(deviceId.value)) as DeviceDetail
|
|
|
formData.value = detail || {}
|
|
|
defaultPicUrl.value = detail?.picUrl || defaultDevicePic
|
|
|
+ await loadTemplateFields(formData.value)
|
|
|
} finally {
|
|
|
formLoading.value = false
|
|
|
}
|
|
|
@@ -318,6 +447,7 @@ const imagePreview = (imgUrl: string) => {
|
|
|
|
|
|
onMounted(() => {
|
|
|
getDetail()
|
|
|
+ getEncodingTypeOptions()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -339,7 +469,7 @@ onMounted(() => {
|
|
|
|
|
|
.device-main {
|
|
|
display: grid;
|
|
|
- grid-template-columns: 292px minmax(0, 1fr);
|
|
|
+ grid-template-columns: 264px minmax(0, 1fr);
|
|
|
min-height: 286px;
|
|
|
padding: 24px;
|
|
|
background: radial-gradient(circle at 18px 22px, rgb(44 123 229 / 12%), transparent 30%),
|
|
|
@@ -348,19 +478,19 @@ onMounted(() => {
|
|
|
|
|
|
.device-media {
|
|
|
display: flex;
|
|
|
- align-items: stretch;
|
|
|
+ align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.device-image-frame {
|
|
|
position: relative;
|
|
|
- width: 260px;
|
|
|
- height: 226px;
|
|
|
- padding: 8px;
|
|
|
+ width: 236px;
|
|
|
+ height: 236px;
|
|
|
+ padding: 12px;
|
|
|
overflow: hidden;
|
|
|
font: inherit;
|
|
|
cursor: zoom-in;
|
|
|
- background: var(--el-bg-color);
|
|
|
+ background: linear-gradient(145deg, #fff 0%, #f7faff 100%);
|
|
|
border: 1px solid rgb(44 123 229 / 14%);
|
|
|
border-radius: 12px;
|
|
|
box-shadow: 0 14px 32px rgb(39 83 135 / 15%);
|
|
|
@@ -381,6 +511,7 @@ onMounted(() => {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
+ background: transparent;
|
|
|
border-radius: 8px;
|
|
|
transition: transform 0.3s ease;
|
|
|
}
|
|
|
@@ -472,8 +603,8 @@ onMounted(() => {
|
|
|
|
|
|
.device-info-grid {
|
|
|
display: grid;
|
|
|
- grid-template-columns: repeat(5, minmax(132px, 1fr));
|
|
|
- gap: 0 22px;
|
|
|
+ grid-template-columns: repeat(6, minmax(120px, 1fr));
|
|
|
+ gap: 0 18px;
|
|
|
padding-top: 14px;
|
|
|
}
|
|
|
|
|
|
@@ -540,11 +671,11 @@ onMounted(() => {
|
|
|
|
|
|
@media (width <= 1399px) {
|
|
|
.device-main {
|
|
|
- grid-template-columns: 260px minmax(0, 1fr);
|
|
|
+ grid-template-columns: 232px minmax(0, 1fr);
|
|
|
}
|
|
|
|
|
|
.device-image-frame {
|
|
|
- width: 232px;
|
|
|
+ width: 208px;
|
|
|
height: 208px;
|
|
|
}
|
|
|
|