From 1ac9339eb318fe27ba9b9d318cac3eb92cea4bc5 Mon Sep 17 00:00:00 2001 From: fhysy <1149505133@qq.com> Date: Thu, 21 Aug 2025 14:37:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=92=8C=E8=AE=BE=E5=A4=87=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=EF=BC=8C=E8=A7=A3=E5=86=B3=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E7=89=A9=E6=A8=A1=E5=9E=8B=E4=BF=AE=E6=94=B9=E4=B9=B1=E5=BA=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增设备详情页面组件和相关功能 - 实现设备信息加载和显示 - 添加运行状态、设备功能、日志管理等子页面 - 优化设备状态展示和操作 - 增加物模型查看功能 --- apps/web-antd/src/api/device/device/index.ts | 62 ++ .../web-antd/src/api/device/device/model.d.ts | 147 +++++ apps/web-antd/src/api/device/product/index.ts | 15 +- apps/web-antd/src/constants/dicts/device.ts | 12 +- apps/web-antd/src/router/access.ts | 15 +- apps/web-antd/src/store/device.ts | 52 ++ apps/web-antd/src/views/device/device/data.ts | 126 ++++ .../device/detail/components/BasicInfo.vue | 222 +++++++ .../detail/components/RunningStatus.vue | 571 ++++++++++++++++++ .../src/views/device/device/detail/index.vue | 274 +++++++++ .../src/views/device/device/device-drawer.vue | 141 +++++ .../src/views/device/device/index.vue | 212 +++++++ .../web-antd/src/views/device/product/data.ts | 9 +- .../detail/components/AccessSelector.vue | 26 +- .../detail/components/DeviceAccess.vue | 70 +-- .../product/detail/components/EventDrawer.vue | 62 +- .../detail/components/FunctionDrawer.vue | 84 +-- .../product/detail/components/Metadata.vue | 102 ++-- .../detail/components/MetadataTable.vue | 104 ++-- .../detail/components/PropertyDrawer.vue | 193 +++--- .../detail/components/PropertyGroupDrawer.vue | 45 +- .../product/detail/components/TSLViewer.vue | 16 +- .../src/views/device/product/detail/index.vue | 44 +- .../views/device/product/product-drawer.vue | 12 +- 24 files changed, 2144 insertions(+), 472 deletions(-) create mode 100644 apps/web-antd/src/api/device/device/index.ts create mode 100644 apps/web-antd/src/api/device/device/model.d.ts create mode 100644 apps/web-antd/src/store/device.ts create mode 100644 apps/web-antd/src/views/device/device/data.ts create mode 100644 apps/web-antd/src/views/device/device/detail/components/BasicInfo.vue create mode 100644 apps/web-antd/src/views/device/device/detail/components/RunningStatus.vue create mode 100644 apps/web-antd/src/views/device/device/detail/index.vue create mode 100644 apps/web-antd/src/views/device/device/device-drawer.vue create mode 100644 apps/web-antd/src/views/device/device/index.vue diff --git a/apps/web-antd/src/api/device/device/index.ts b/apps/web-antd/src/api/device/device/index.ts new file mode 100644 index 0000000..370739f --- /dev/null +++ b/apps/web-antd/src/api/device/device/index.ts @@ -0,0 +1,62 @@ +import type { DeviceForm, DeviceQuery, DeviceVO } from './model'; + +import type { ID, IDS, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +/** + * 查询设备列表 + * @param params + * @returns 设备列表 + */ +export function deviceList(params?: DeviceQuery) { + return requestClient.get>('/device/device/list', { + params, + }); +} + +/** + * 导出设备列表 + * @param params + * @returns 设备列表 + */ +export function deviceExport(params?: DeviceQuery) { + return commonExport('/device/device/export', params ?? {}); +} + +/** + * 查询设备详情 + * @param id id + * @returns 设备详情 + */ +export function deviceInfo(id: ID) { + return requestClient.get(`/device/device/${id}`); +} + +/** + * 新增设备 + * @param data + * @returns void + */ +export function deviceAdd(data: DeviceForm) { + return requestClient.postWithMsg('/device/device', data); +} + +/** + * 更新设备 + * @param data + * @returns void + */ +export function deviceUpdate(data: DeviceForm) { + return requestClient.putWithMsg('/device/device', data); +} + +/** + * 删除设备 + * @param id id + * @returns void + */ +export function deviceRemove(id: ID | IDS) { + return requestClient.deleteWithMsg(`/device/device/${id}`); +} diff --git a/apps/web-antd/src/api/device/device/model.d.ts b/apps/web-antd/src/api/device/device/model.d.ts new file mode 100644 index 0000000..2295820 --- /dev/null +++ b/apps/web-antd/src/api/device/device/model.d.ts @@ -0,0 +1,147 @@ +import type { BaseEntity, PageQuery } from '#/api/common'; + +export interface DeviceVO { + /** + * 编号 + */ + id: number | string; + + /** + * 设备名称 + */ + deviceName: string; + + /** + * 设备类型 + */ + deviceType: string; + + /** + * 描述 + */ + description: string; + + /** + * 产品id + */ + productId: number | string; + + /** + * 设备配置 + */ + deviceConf: string; + + /** + * 设备物模型 + */ + metadata: string; + + /** + * 设备状态 + */ + deviceState: string; + + /** + * 注册时间 + */ + registTime: string; + + /** + * 父设备id + */ + parentId: number | string; +} + +export interface DeviceForm extends BaseEntity { + /** + * 编号 + */ + id?: number | string; + + /** + * 设备名称 + */ + deviceName?: string; + + /** + * 设备类型 + */ + deviceType?: string; + + /** + * 描述 + */ + description?: string; + + /** + * 产品id + */ + productId?: number | string; + + /** + * 设备配置 + */ + deviceConf?: string; + + /** + * 设备物模型 + */ + metadata?: string; + + /** + * 设备状态 + */ + deviceState?: string; + + /** + * 注册时间 + */ + registTime?: string; + + /** + * 父设备id + */ + parentId?: number | string; +} + +export interface DeviceQuery extends PageQuery { + /** + * 设备名称 + */ + deviceName?: string; + + /** + * 设备类型 + */ + deviceType?: string; + + /** + * 产品id + */ + productId?: number | string; + + /** + * 设备物模型 + */ + metadata?: string; + + /** + * 设备状态 + */ + deviceState?: string; + + /** + * 注册时间 + */ + registTime?: string; + + /** + * 父设备id + */ + parentId?: number | string; + + /** + * 日期范围参数 + */ + params?: any; +} diff --git a/apps/web-antd/src/api/device/product/index.ts b/apps/web-antd/src/api/device/product/index.ts index 4765ea8..3f44a79 100644 --- a/apps/web-antd/src/api/device/product/index.ts +++ b/apps/web-antd/src/api/device/product/index.ts @@ -54,13 +54,21 @@ export function productUpdate(data: ProductForm) { /** * 更新设备产品(部分) - * @param data + * @param id data * @returns void */ export function productUpdateById(id: ID, data: ProductForm) { return requestClient.putWithMsg(`/device/product/${id}`, data); } +/** + * 推送产品物模型到设备(覆盖) + * @param id + * @returns void + */ +export function productPushMetadataById(id: ID) { + return requestClient.putWithMsg(`/device/product/pushMetadata/${id}`); +} /** * 更新设备产品状态 * @param data @@ -85,8 +93,5 @@ export function productRemove(id: ID | IDS) { * @returns 产品存储策略列表 */ export function getPoliciesList(params?: any) { - return requestClient.get>( - '/device/product/storage/policies', - { params }, - ); + return requestClient.get>('/device/product/storage/policies', { params }); } diff --git a/apps/web-antd/src/constants/dicts/device.ts b/apps/web-antd/src/constants/dicts/device.ts index 6618a91..8cdbade 100644 --- a/apps/web-antd/src/constants/dicts/device.ts +++ b/apps/web-antd/src/constants/dicts/device.ts @@ -50,7 +50,7 @@ export const dataTypeOptions = [ }, { value: 'date', - label: 'date(时间型)', + label: 'date(时间戳)', }, { value: 'enum', @@ -107,7 +107,13 @@ export const readWriteTypeOptions = [ ]; export const timeOptions = [ - { label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss' }, - { label: 'yyyy-MM-dd', value: 'yyyy-MM-dd' }, + { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' }, + { label: 'YYYY-MM-DD', value: 'YYYY-MM-DD' }, { label: 'HH:mm:ss', value: 'HH:mm:ss' }, ]; + +export const deviceStateOptions = [ + { label: '未激活', value: 'notActive', type: 'warning' }, + { label: '在线', value: 'online', type: 'success' }, + { label: '离线', value: 'offline', type: 'error' }, +]; diff --git a/apps/web-antd/src/router/access.ts b/apps/web-antd/src/router/access.ts index dab9a73..44c2766 100644 --- a/apps/web-antd/src/router/access.ts +++ b/apps/web-antd/src/router/access.ts @@ -37,6 +37,11 @@ const routeMetaMapping: Record> = { requireHomeRedirect: true, }, + '/device/device/detail/:id': { + activePath: '/device/device', + requireHomeRedirect: true, + }, + '/system/oss-config/index': { activePath: '/system/oss', requireHomeRedirect: true, @@ -64,10 +69,7 @@ const routeMetaMapping: Record> = { * @param parentPath 上级目录 * @returns vben路由 */ -function backMenuToVbenMenu( - menuList: Menu[], - parentPath = '', -): RouteRecordStringComponent[] { +function backMenuToVbenMenu(menuList: Menu[], parentPath = ''): RouteRecordStringComponent[] { const resultList: RouteRecordStringComponent[] = []; menuList.forEach((menu) => { // 根目录为菜单形式 @@ -100,10 +102,7 @@ function backMenuToVbenMenu( // 外链: http开头 & 组件为Layout || ParentView // 正则判断是否为http://或者https://开头 - if ( - /^https?:\/\//.test(menu.path) && - (menu.component === 'Layout' || menu.component === 'ParentView') - ) { + if (/^https?:\/\//.test(menu.path) && (menu.component === 'Layout' || menu.component === 'ParentView')) { menu.component = 'Link'; } diff --git a/apps/web-antd/src/store/device.ts b/apps/web-antd/src/store/device.ts new file mode 100644 index 0000000..1af9fe8 --- /dev/null +++ b/apps/web-antd/src/store/device.ts @@ -0,0 +1,52 @@ +import { defineStore } from 'pinia'; + +import { deviceInfo } from '#/api/device/device'; + +export const useDeviceStore = defineStore('device', { + state: () => ({ + currentDevice: {} as any, + tabActiveKey: 'BasicInfo', + deviceCount: 0, + }), + + getters: { + getCurrentDevice: (state) => state.currentDevice, + getTabActiveKey: (state) => state.tabActiveKey, + getDeviceCount: (state) => state.deviceCount, + }, + + actions: { + setCurrent(device: any) { + this.currentDevice = device; + }, + + setTabActiveKey(key: string) { + this.tabActiveKey = key; + }, + + setDeviceCount(count: number) { + this.deviceCount = count; + }, + + async getDetail(id: string) { + try { + const res = await deviceInfo(id); + if (res?.productObj?.productParam) { + res.productParamArray = JSON.parse(res.productObj.productParam) || []; + } + this.currentDevice = res; + + return res; + } catch (error) { + console.error('获取设备详情失败:', error); + throw error; + } + }, + + reset() { + this.currentDevice = {}; + this.tabActiveKey = 'BasicInfo'; + this.deviceCount = 0; + }, + }, +}); diff --git a/apps/web-antd/src/views/device/device/data.ts b/apps/web-antd/src/views/device/device/data.ts new file mode 100644 index 0000000..1bacd3a --- /dev/null +++ b/apps/web-antd/src/views/device/device/data.ts @@ -0,0 +1,126 @@ +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { deviceStateOptions, deviceTypeOptions } from '#/constants/dicts'; + +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'deviceKey', + label: '设备KEY', + }, + { + component: 'Input', + fieldName: 'deviceName', + label: '设备名称', + }, + { + component: 'Select', + fieldName: 'productId', + label: '所属产品', + }, + { + component: 'Select', + componentProps: { + options: deviceStateOptions, + }, + fieldName: 'deviceState', + label: '设备状态', + }, + { + component: 'Select', + componentProps: { + options: deviceTypeOptions, + }, + fieldName: 'deviceType', + label: '设备类型', + }, +]; + +// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新 +// export const columns: () => VxeGridProps['columns'] = () => [ +export const columns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + // { + // title: '编号', + // field: 'id', + // }, + { + title: '设备KEY', + field: 'deviceKey', + }, + { + title: '设备名称', + field: 'deviceName', + }, + { + title: '所属产品', + field: 'productName', + }, + { + title: '设备类型', + field: 'deviceType', + slots: { default: 'deviceType' }, + }, + { + title: '设备状态', + field: 'deviceState', + slots: { default: 'deviceState' }, + }, + { + title: '描述', + field: 'description', + }, + { + field: 'action', + fixed: 'right', + slots: { default: 'action' }, + title: '操作', + width: 180, + }, +]; + +export const drawerSchema: FormSchemaGetter = () => [ + { + label: '编号', + fieldName: 'id', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '设备KEY', + fieldName: 'deviceKey', + component: 'Input', + rules: 'required', + }, + { + label: '设备名称', + fieldName: 'deviceName', + component: 'Input', + rules: 'required', + }, + { + label: '所属产品', + fieldName: 'productId', + component: 'Select', + componentProps: {}, + rules: 'selectRequired', + }, + { + label: '设备图片', + fieldName: 'imgId', + component: 'ImageUpload', + componentProps: { + // accept: 'image/*', // 可选拓展名或者mime类型 ,拼接 + // maxCount: 1, // 最大上传文件数 默认为1 为1会绑定为string而非string[]类型 + }, + }, + { + label: '描述', + fieldName: 'description', + component: 'Textarea', + }, +]; diff --git a/apps/web-antd/src/views/device/device/detail/components/BasicInfo.vue b/apps/web-antd/src/views/device/device/detail/components/BasicInfo.vue new file mode 100644 index 0000000..9bcd216 --- /dev/null +++ b/apps/web-antd/src/views/device/device/detail/components/BasicInfo.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/apps/web-antd/src/views/device/device/detail/components/RunningStatus.vue b/apps/web-antd/src/views/device/device/detail/components/RunningStatus.vue new file mode 100644 index 0000000..a8b41c4 --- /dev/null +++ b/apps/web-antd/src/views/device/device/detail/components/RunningStatus.vue @@ -0,0 +1,571 @@ + + + + + diff --git a/apps/web-antd/src/views/device/device/detail/index.vue b/apps/web-antd/src/views/device/device/detail/index.vue new file mode 100644 index 0000000..d0dadb8 --- /dev/null +++ b/apps/web-antd/src/views/device/device/detail/index.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/apps/web-antd/src/views/device/device/device-drawer.vue b/apps/web-antd/src/views/device/device/device-drawer.vue new file mode 100644 index 0000000..c83d720 --- /dev/null +++ b/apps/web-antd/src/views/device/device/device-drawer.vue @@ -0,0 +1,141 @@ + + + diff --git a/apps/web-antd/src/views/device/device/index.vue b/apps/web-antd/src/views/device/device/index.vue new file mode 100644 index 0000000..ff065e9 --- /dev/null +++ b/apps/web-antd/src/views/device/device/index.vue @@ -0,0 +1,212 @@ + + + diff --git a/apps/web-antd/src/views/device/product/data.ts b/apps/web-antd/src/views/device/product/data.ts index 2794bcd..03ff1e3 100644 --- a/apps/web-antd/src/views/device/product/data.ts +++ b/apps/web-antd/src/views/device/product/data.ts @@ -9,7 +9,7 @@ export const querySchema: FormSchemaGetter = () => [ { component: 'Input', fieldName: 'productKey', - label: '产品编码', + label: '产品KEY', }, { component: 'Input', @@ -51,7 +51,7 @@ export const columns: VxeGridProps['columns'] = [ // field: 'id', // }, { - title: '产品编码', + title: '产品KEY', field: 'productKey', }, { @@ -96,7 +96,7 @@ export const drawerSchema: FormSchemaGetter = () => [ }, }, { - label: '产品编码', + label: '产品KEY', fieldName: 'productKey', component: 'Input', }, @@ -140,8 +140,7 @@ export const drawerSchema: FormSchemaGetter = () => [ 'span', { class: 'text-[14px] text-black/25 truncate', - style: - 'max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', + style: 'max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;', }, option.tooltip, ), diff --git a/apps/web-antd/src/views/device/product/detail/components/AccessSelector.vue b/apps/web-antd/src/views/device/product/detail/components/AccessSelector.vue index 7d7d831..8721ff9 100644 --- a/apps/web-antd/src/views/device/product/detail/components/AccessSelector.vue +++ b/apps/web-antd/src/views/device/product/detail/components/AccessSelector.vue @@ -18,7 +18,7 @@ import { } from 'ant-design-vue'; import { gatewayList } from '#/api/operations/gateway'; -import { enabledOptions, networkTypeOptions } from '#/constants/dicts'; +import { networkTypeOptions } from '#/constants/dicts'; const emit = defineEmits<{ close: []; @@ -32,7 +32,6 @@ const selectedGateway = ref(null); const searchForm = ref({ name: '', provider: undefined, - enabled: undefined, }); // 分页配置 @@ -42,8 +41,7 @@ const pagination = reactive({ total: 0, showSizeChanger: true, showQuickJumper: true, - showTotal: (total: number, range: [number, number]) => - `第 ${range[0]}-${range[1]}条/总共${total}条`, + showTotal: (total: number, range: [number, number]) => `第 ${range[0]}-${range[1]}条/总共${total}条`, }); // 网关列表数据 @@ -57,8 +55,7 @@ const providerOptions = [ value: 'child-device', channel: 'child-device', transport: 'Gateway', - description: - '需要通过网关与平台进行数据通信的设备,将作为网关子设备接入到平台。', + description: '需要通过网关与平台进行数据通信的设备,将作为网关子设备接入到平台。', }, ]; @@ -69,6 +66,7 @@ const loadGatewayList = async () => { const response = await gatewayList({ pageNum: pagination.current, pageSize: pagination.pageSize, + enabled: '1', ...searchForm.value, }); @@ -91,7 +89,6 @@ const loadGatewayList = async () => { const resetSearch = () => { searchForm.value.name = ''; searchForm.value.provider = undefined; - searchForm.value.enabled = undefined; pagination.current = 1; loadGatewayList(); }; @@ -199,16 +196,6 @@ onMounted(() => { @change="(value) => (searchForm.provider = value)" /> - - @@ -371,9 +348,7 @@ onMounted(() => {

存储策略

- +
@@ -389,27 +364,18 @@ onMounted(() => {
- + 保存
- +

{{ - accessInfo.provider === 'mqtt-server-gateway' || - accessInfo.provider === 'mqtt-client-gateway' + accessInfo.provider === 'mqtt-server-gateway' || accessInfo.provider === 'mqtt-client-gateway' ? 'Topic信息' : 'URL信息' }} @@ -461,11 +427,7 @@ onMounted(() => { @cancel="handleAccessModalClose" :footer="null" > - +

diff --git a/apps/web-antd/src/views/device/product/detail/components/EventDrawer.vue b/apps/web-antd/src/views/device/product/detail/components/EventDrawer.vue index dd9fa97..1300413 100644 --- a/apps/web-antd/src/views/device/product/detail/components/EventDrawer.vue +++ b/apps/web-antd/src/views/device/product/detail/components/EventDrawer.vue @@ -163,6 +163,7 @@ const handleSave = async () => { emit('save', { ...formData.value }); message.success('保存成功'); visible.value = false; + resetForm(); } catch { message.error('保存失败'); } finally { @@ -196,12 +197,7 @@ watch(