详情页面

This commit is contained in:
Flow 2025-05-30 15:26:21 +08:00
parent 67dce31b56
commit d6f433bd84
4 changed files with 246 additions and 19 deletions

View File

@ -0,0 +1,181 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
label-width="100px"
:disabled="true"
>
<el-form-item label="设备ID">
<span>{{ formData.deviceKey }}</span>
</el-form-item>
<el-form-item label="设备名称">
<span>{{ formData.deviceName }}</span>
</el-form-item>
<el-form-item label="所属机构">
<span>{{ getOrgName(formData.productId) }}</span>
</el-form-item>
<el-form-item label="设备类型">
<span>{{ getDeviceTypeName(formData.deviceType) }}</span>
</el-form-item>
<el-form-item label="设备位置">
<span>{{ formData.address }}</span>
</el-form-item>
<el-form-item label="设备状态">
<span>{{ getDeviceStateName(formData.state) }}</span>
</el-form-item>
<el-form-item label="设备描述">
<span>{{ formData.description || '-' }}</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as DeviceApi from '@/api/iot/device/device'
import * as ProductApi from '@/api/iot/product/product'
import { DeviceStateEnum } from '@/api/iot/device/device'
defineOptions({ name: 'DeviceDetailsForm' })
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) //
const formData = ref<DeviceApi.DeviceVO>({
id: 0,
deviceKey: '',
deviceName: '',
productId: 0,
productKey: '',
deviceType: 0,
nickname: '',
gatewayId: 0,
state: DeviceStateEnum.INACTIVE,
onlineTime: new Date(),
offlineTime: new Date(),
activeTime: new Date(),
createTime: new Date(),
ip: '',
firmwareVersion: '',
deviceSecret: '',
mqttClientId: '',
mqttUsername: '',
mqttPassword: '',
authType: '',
latitude: 0,
longitude: 0,
areaId: 0,
address: '',
serialNumber: '',
config: '',
description: '',
groupIds: []
})
const productList = ref<ProductApi.ProductVO[]>([]) //
//
const getOrgName = (productId: number) => {
const product = productList.value.find(item => item.id === productId)
return product?.name || '-'
}
//
const getDeviceTypeName = (type: number) => {
const dict = getIntDictOptions(DICT_TYPE.IOT_DEVICE_TYPE).find(item => item.value === type)
return dict?.label || '-'
}
//
const getDeviceStateName = (state: number) => {
const dict = getIntDictOptions(DICT_TYPE.IOT_DEVICE_STATE).find(item => item.value === state)
return dict?.label || '-'
}
/** 打开弹窗 */
const open = async (id: number) => {
dialogVisible.value = true
dialogTitle.value = '设备详情'
resetForm()
//
if (id) {
formLoading.value = true
try {
// API
const mockDeviceData = {
id: id,
deviceKey: 'Device' + id,
deviceName: '测试设备' + id,
deviceType: 1,
state: DeviceStateEnum.INACTIVE,
address: '北京市/海淀区/中关村',
description: '这是一个测试设备的描述信息',
productId: 1,
productKey: 'product_key_' + id,
nickname: '设备昵称' + id,
gatewayId: 1,
onlineTime: new Date(),
offlineTime: new Date(),
activeTime: new Date(),
createTime: new Date(),
ip: '192.168.1.1',
firmwareVersion: '1.0.0',
deviceSecret: 'secret_' + id,
mqttClientId: 'client_' + id,
mqttUsername: 'username_' + id,
mqttPassword: 'password_' + id,
authType: 'password',
latitude: 39.123456,
longitude: 117.123456,
areaId: 1,
serialNumber: 'SN' + id,
config: '{}',
groupIds: []
}
formData.value = mockDeviceData
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: 0,
deviceKey: '',
deviceName: '',
productId: 0,
productKey: '',
deviceType: 0,
nickname: '',
gatewayId: 0,
state: DeviceStateEnum.INACTIVE,
onlineTime: new Date(),
offlineTime: new Date(),
activeTime: new Date(),
createTime: new Date(),
ip: '',
firmwareVersion: '',
deviceSecret: '',
mqttClientId: '',
mqttUsername: '',
mqttPassword: '',
authType: '',
latitude: 0,
longitude: 0,
areaId: 0,
address: '',
serialNumber: '',
config: '',
description: '',
groupIds: []
}
}
</script>

View File

@ -6,10 +6,14 @@
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
:disabled="isDetail"
> >
<el-form-item label="设备ID" prop="deviceId"> <el-form-item label="设备ID" prop="deviceId">
<el-input v-model="formData.deviceId" placeholder="请输入设备ID" /> <el-input v-model="formData.deviceId" placeholder="请输入设备ID" />
</el-form-item> </el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="formData.deviceName" placeholder="请输入设备名称" />
</el-form-item>
<el-form-item label="所属机构" prop="orgId"> <el-form-item label="所属机构" prop="orgId">
<el-select v-model="formData.orgId" placeholder="请选择所属机构" @change="handleProductChange"> <el-select v-model="formData.orgId" placeholder="请选择所属机构" @change="handleProductChange">
<el-option <el-option
@ -67,8 +71,8 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button type="primary" @click="submitForm"> </el-button> <el-button v-if="!isDetail" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="dialogVisible = false">{{ isDetail ? '关 闭' : '取 消' }}</el-button>
</template> </template>
</Dialog> </Dialog>
</template> </template>
@ -97,9 +101,10 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const isDetail = ref(false) //
const formData = ref<DeviceApi.DeviceVO>({ const formData = ref<DeviceApi.DeviceVO>({
id: undefined, id: undefined,
deviceKey: undefined, deviceId: undefined,
deviceName: undefined, deviceName: undefined,
productId: undefined, productId: undefined,
productKey: undefined, productKey: undefined,
@ -122,14 +127,16 @@ const formData = ref<DeviceApi.DeviceVO>({
longitude: undefined, longitude: undefined,
areaId: undefined, areaId: undefined,
address: undefined, address: undefined,
detailAddress: undefined,
serialNumber: undefined, serialNumber: undefined,
config: undefined, config: undefined,
description: undefined,
groupIds: [] groupIds: []
}) })
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }], deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }],
orgId: [{ required: true, message: '所属机构不能为空', trigger: 'blur' }], deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'blur' }], deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'blur' }],
address: [{ required: true, message: '设备位置不能为空', trigger: 'blur' }], address: [{ required: true, message: '设备位置不能为空', trigger: 'blur' }],
}) })
@ -145,12 +152,46 @@ const open = async (type: string, id?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
isDetail.value = type === 'details' //
resetForm() resetForm()
// //
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await DeviceApi.getDevice(id) // API
const mockDeviceData = {
id: id,
deviceId: 'Device' + id,
deviceName: '测试设备' + id,
deviceType: 1,
state: DeviceStateEnum.INACTIVE,
address: '',
detailAddress: '测试地址123号',
description: '这是一个测试设备的描述信息',
productId: 1,
productKey: 'product_key_' + id,
nickname: '设备昵称' + id,
gatewayId: 1,
onlineTime: new Date(),
offlineTime: new Date(),
activeTime: new Date(),
createTime: new Date(),
ip: '192.168.1.1',
firmwareVersion: '1.0.0',
deviceSecret: 'secret_' + id,
mqttClientId: 'client_' + id,
mqttUsername: 'username_' + id,
mqttPassword: 'password_' + id,
authType: 'password',
latitude: 39.123456,
longitude: 117.123456,
areaId: 1,
serialNumber: 'SN' + id,
config: '{}',
groupIds: []
}
formData.value = mockDeviceData
// //
if (formData.value.address) { if (formData.value.address) {
const addressParts = formData.value.address.split('/') const addressParts = formData.value.address.split('/')
@ -176,10 +217,6 @@ const open = async (type: string, id?: number) => {
formLoading.value = false formLoading.value = false
} }
} }
//
productList.value = await ProductApi.getSimpleProductList()
//
groupList.value = await DeviceGroupApi.getSimpleDeviceGroupList()
} }
defineExpose({ open }) // open defineExpose({ open }) // open
@ -213,7 +250,7 @@ const submitForm = async () => {
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
deviceKey: undefined, deviceId: undefined,
deviceName: undefined, deviceName: undefined,
productId: undefined, productId: undefined,
productKey: undefined, productKey: undefined,
@ -236,8 +273,10 @@ const resetForm = () => {
longitude: undefined, longitude: undefined,
areaId: undefined, areaId: undefined,
address: undefined, address: undefined,
detailAddress: undefined,
serialNumber: undefined, serialNumber: undefined,
config: undefined, config: undefined,
description: undefined,
groupIds: [] groupIds: []
} }
selectedOptions.value = [] selectedOptions.value = []

View File

@ -29,7 +29,7 @@
</p> </p>
<p class="detail-item"> <p class="detail-item">
<span class="label">位置</span> <span class="label">位置</span>
<span class="value">{{ deviceInfo.location }}</span> <span class="value">{{ deviceInfo.address }}</span>
</p> </p>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
@ -82,7 +82,7 @@ const props = defineProps({
id: '', id: '',
name: '', name: '',
type: '', type: '',
location: '', address: '',
status: 'normal', status: 'normal',
statusText: '正常' statusText: '正常'
}) })

View File

@ -76,6 +76,7 @@
<!-- 设备表单 --> <!-- 设备表单 -->
<DeviceForm ref="deviceFormRef" @success="handleQuery" /> <DeviceForm ref="deviceFormRef" @success="handleQuery" />
<DetailsForm ref="detailsFormRef" @success="handleQuery" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -85,6 +86,7 @@ import { getIntDictOptions } from '@/utils/dict'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import DeviceCard from '../devices/devices_cards.vue' import DeviceCard from '../devices/devices_cards.vue'
import DeviceForm from './DevFrom.vue' import DeviceForm from './DevFrom.vue'
import DetailsForm from './DetailsForm.vue'
// //
interface QueryParams { interface QueryParams {
@ -105,7 +107,7 @@ const deviceList = ref([
id: 'Blood001', id: 'Blood001',
name: '血压仪', name: '血压仪',
type: '血压仪', type: '血压仪',
location: '天津市/西青区/精武镇', address: '天津市/市辖区/西青区/精武镇',
status: 'normal', status: 'normal',
statusText: '在线' statusText: '在线'
}, },
@ -113,7 +115,7 @@ const deviceList = ref([
id: 'Glucose001', id: 'Glucose001',
name: '血糖仪', name: '血糖仪',
type: '血糖仪', type: '血糖仪',
location: '河北省/廊坊市/安次区', address: '河北省/廊坊市/安次区',
status: 'warning', status: 'warning',
statusText: '待激活' statusText: '待激活'
}, },
@ -121,7 +123,7 @@ const deviceList = ref([
id: 'Oxygen001', id: 'Oxygen001',
name: '血氧仪', name: '血氧仪',
type: '血氧仪', type: '血氧仪',
location: '四川省/绵阳市/涪城区', address: '四川省/绵阳市/涪城区',
status: 'error', status: 'error',
statusText: '离线' statusText: '离线'
} }
@ -131,6 +133,8 @@ const deviceList = ref([
const queryFormRef = ref() const queryFormRef = ref()
// //
const deviceFormRef = ref() const deviceFormRef = ref()
//
const detailsFormRef = ref()
// //
const handleQuery = () => { const handleQuery = () => {
@ -145,14 +149,17 @@ const resetQuery = () => {
} }
// //
const openForm = (type: string, id?: number) => { const openForm = (type: string) => {
deviceFormRef.value?.open(type, id) deviceFormRef.value?.open(type)
} }
// //
const handleDeviceAction = (action: any) => { const handleDeviceAction = (action: any) => {
if (action.type === 'detail') { if (action.action === 'details') {
openForm('update', action.device.id) //
detailsFormRef.value?.open(action.deviceId)
} else {
console.log('设备操作:', action)
} }
} }