From 3744069aa22a108607ae9bb6ff41022578a3ef53 Mon Sep 17 00:00:00 2001 From: haohao <1036606149@qq.com> Date: Mon, 15 Dec 2025 22:48:09 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=E3=80=90antd=E3=80=91=E3=80=90iot?= =?UTF-8?q?=E3=80=91=E4=BA=A7=E5=93=81=E8=AF=A6=E6=83=85=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=88=B0=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web-antd/src/router/routes/modules/iot.ts | 2 +- .../src/views/iot/product/product/data.ts | 40 ++++------ .../product/{modules => }/detail/index.vue | 2 +- .../{modules => }/detail/modules/header.vue | 2 +- .../{modules => }/detail/modules/info.vue | 0 .../iot/product/product/modules/form.vue | 78 +++++++++---------- 6 files changed, 55 insertions(+), 69 deletions(-) rename apps/web-antd/src/views/iot/product/product/{modules => }/detail/index.vue (93%) rename apps/web-antd/src/views/iot/product/product/{modules => }/detail/modules/header.vue (98%) rename apps/web-antd/src/views/iot/product/product/{modules => }/detail/modules/info.vue (100%) diff --git a/apps/web-antd/src/router/routes/modules/iot.ts b/apps/web-antd/src/router/routes/modules/iot.ts index 3e7f87caf..1c885a4c6 100644 --- a/apps/web-antd/src/router/routes/modules/iot.ts +++ b/apps/web-antd/src/router/routes/modules/iot.ts @@ -19,7 +19,7 @@ const routes: RouteRecordRaw[] = [ activePath: '/iot/device/product', }, component: () => - import('#/views/iot/product/product/modules/detail/index.vue'), + import('#/views/iot/product/product/detail/index.vue'), }, { path: 'device/detail/:id', diff --git a/apps/web-antd/src/views/iot/product/product/data.ts b/apps/web-antd/src/views/iot/product/product/data.ts index e9e7809bf..6d21d05b2 100644 --- a/apps/web-antd/src/views/iot/product/product/data.ts +++ b/apps/web-antd/src/views/iot/product/product/data.ts @@ -14,18 +14,13 @@ import { getSimpleProductCategoryList } from '#/api/iot/product/category'; /** 产品分类列表缓存 */ let categoryList: IotProductCategoryApi.ProductCategory[] = []; - -/** 加载产品分类数据 */ -async function loadCategoryData() { - categoryList = await getSimpleProductCategoryList(); -} - -// 初始化加载分类数据 -// TODO @haohao:可以参考 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/system/tenant/data.ts 简洁一点。 -loadCategoryData(); +getSimpleProductCategoryList().then((data) => (categoryList = data)); /** 新增/修改产品的表单 */ -export function useFormSchema(formApi?: any): VbenFormSchema[] { +export function useFormSchema( + formApi?: any, + generateProductKey?: () => string, +): VbenFormSchema[] { return [ { component: 'Input', @@ -59,7 +54,9 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] { { type: 'default', onClick: () => { - formApi?.setFieldValue('productKey', generateProductKey()); + if (generateProductKey) { + formApi?.setFieldValue('productKey', generateProductKey()); + } }, }, { default: () => '重新生成' }, @@ -175,7 +172,10 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] { } /** 基础表单字段(不含图标、图片、描述) */ -export function useBasicFormSchema(formApi?: any): VbenFormSchema[] { +export function useBasicFormSchema( + formApi?: any, + generateProductKey?: () => string, +): VbenFormSchema[] { return [ { component: 'Input', @@ -208,7 +208,9 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] { { type: 'default', onClick: () => { - formApi?.setFieldValue('productKey', generateProductKey()); + if (generateProductKey) { + formApi?.setFieldValue('productKey', generateProductKey()); + } }, }, { default: () => '重新生成' }, @@ -299,6 +301,7 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] { buttonStyle: 'solid', optionType: 'button', }, + defaultValue: 0, rules: 'required', }, { @@ -339,7 +342,6 @@ export function useAdvancedFormSchema(): VbenFormSchema[] { placeholder: '请输入产品描述', rows: 3, }, - formItemClass: 'col-span-2', // 让描述占满两列 }, ]; } @@ -404,13 +406,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { ]; } -/** 生成 ProductKey(包含大小写字母和数字) */ -export function generateProductKey(): string { - const chars = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < 16; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; -} diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/index.vue b/apps/web-antd/src/views/iot/product/product/detail/index.vue similarity index 93% rename from apps/web-antd/src/views/iot/product/product/modules/detail/index.vue rename to apps/web-antd/src/views/iot/product/product/detail/index.vue index 1769c8017..3ca0daee1 100644 --- a/apps/web-antd/src/views/iot/product/product/modules/detail/index.vue +++ b/apps/web-antd/src/views/iot/product/product/detail/index.vue @@ -28,7 +28,6 @@ const activeTab = ref('info'); provide('product', product); // 提供产品信息给子组件 /** 获取产品详情 */ -// TODO @haohao:因为 detail 是独立界面,所以不放在 modules 里,应该放在 web-antd/src/views/iot/product/product/detail/index.vue 里,更合理一些哈。 async function getProductData(productId: number) { loading.value = true; try { @@ -91,3 +90,4 @@ onMounted(async () => { + diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/modules/header.vue b/apps/web-antd/src/views/iot/product/product/detail/modules/header.vue similarity index 98% rename from apps/web-antd/src/views/iot/product/product/modules/detail/modules/header.vue rename to apps/web-antd/src/views/iot/product/product/detail/modules/header.vue index e298afe1d..52ae5ee85 100644 --- a/apps/web-antd/src/views/iot/product/product/modules/detail/modules/header.vue +++ b/apps/web-antd/src/views/iot/product/product/detail/modules/header.vue @@ -10,7 +10,7 @@ import { Button, Card, Descriptions, message, Modal } from 'ant-design-vue'; import { updateProductStatus } from '#/api/iot/product/product'; -import Form from '../../form.vue'; +import Form from '../../modules/form.vue'; interface Props { product: IotProductApi.Product; diff --git a/apps/web-antd/src/views/iot/product/product/modules/detail/modules/info.vue b/apps/web-antd/src/views/iot/product/product/detail/modules/info.vue similarity index 100% rename from apps/web-antd/src/views/iot/product/product/modules/detail/modules/info.vue rename to apps/web-antd/src/views/iot/product/product/detail/modules/info.vue diff --git a/apps/web-antd/src/views/iot/product/product/modules/form.vue b/apps/web-antd/src/views/iot/product/product/modules/form.vue index 1ead43b64..6f7fdb26f 100644 --- a/apps/web-antd/src/views/iot/product/product/modules/form.vue +++ b/apps/web-antd/src/views/iot/product/product/modules/form.vue @@ -1,7 +1,7 @@ + + + + + + + + {{ item.label }} + + + + 上行 + 下行 + + + + 搜索 + + + + + + + + + {{ formatDateTime(row.ts) }} + + + + {{ row.upstream ? '上行' : '下行' }} + + + + {{ methodOptions.find((item) => item.value === row.method)?.label }} + + + + {{ + `{"code":${row.code},"msg":"${row.msg}","data":${row.data}\}` + }} + + {{ row.params }} + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue new file mode 100644 index 000000000..f349d9366 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue @@ -0,0 +1,612 @@ + + + + + + + + + + + + 指令调试 + + + + + + + + + + + + + + + + + + {{ record.property?.dataType ?? '-' }} + + + + + + + + + + + + 设置属性值后,点击「发送属性上报」按钮 + + + 发送属性上报 + + + + + + + + + + + + {{ record.event?.dataType ?? '-' }} + + + + + + + + + + 上报事件 + + + + + + + + + + + + + 设备上线 + + + 设备下线 + + + + + + + + + + + + + + + + + {{ record.property?.dataType ?? '-' }} + + + + + + + + + + + + 设置属性值后,点击「发送属性设置」按钮 + + + 发送属性设置 + + + + + + + + + + + + + + + + + + + 服务调用 + + + + + + + + + + + + + + + + + + + 设备消息 + + + + + + + + + + + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/modules/sub-device.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue similarity index 100% rename from apps/web-antd/src/views/iot/device/device/modules/detail/modules/sub-device.vue rename to apps/web-antd/src/views/iot/device/device/detail/modules/sub-device.vue diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-event.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-event.vue new file mode 100644 index 000000000..12fc1846d --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-event.vue @@ -0,0 +1,264 @@ + + + + + + + + + 标识符: + + + {{ event.name }}({{ event.identifier }}) + + + + + 时间范围: + + + + + + + + 搜索 + + + + + + 重置 + + + + + + + + {{ + row.request?.reportTime + ? formatDateTime(row.request.reportTime) + : '-' + }} + + + + {{ row.request?.identifier }} + + + + {{ getEventName(row.request?.identifier) }} + + + {{ getEventType(row.request?.identifier) }} + + + {{ parseParams(row.request?.params) }} + + + + diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property-history.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property-history.vue similarity index 89% rename from apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property-history.vue rename to apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property-history.vue index cdc84d7f4..d137c3815 100644 --- a/apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property-history.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property-history.vue @@ -57,32 +57,54 @@ const queryParams = reactive({ const chartRef = ref(); const { renderEcharts } = useEcharts(chartRef); +/** 不支持图表展示的数据类型列表 */ +const CHART_DISABLED_DATA_TYPES = [ + IoTDataSpecsDataTypeEnum.ARRAY, // 数组 + IoTDataSpecsDataTypeEnum.STRUCT, // 结构体 + IoTDataSpecsDataTypeEnum.TEXT, // 文本型 + IoTDataSpecsDataTypeEnum.BOOL, // 布尔型 + IoTDataSpecsDataTypeEnum.ENUM, // 枚举型 + IoTDataSpecsDataTypeEnum.DATE, // 时间型 +] as const; + +/** 判断是否支持图表展示(仅数值类型支持:int、float、double) */ +const canShowChart = computed(() => { + if (!thingModelDataType.value) return false; + return !CHART_DISABLED_DATA_TYPES.includes( + thingModelDataType.value as (typeof CHART_DISABLED_DATA_TYPES)[number], + ); +}); + +/** 判断是否为复杂数据类型(用于格式化显示) */ const isComplexDataType = computed(() => { if (!thingModelDataType.value) return false; - return [ - IoTDataSpecsDataTypeEnum.ARRAY, - IoTDataSpecsDataTypeEnum.STRUCT, - ].includes(thingModelDataType.value as any); -}); // 判断是否为复杂数据类型(struct 或 array) + return ( + thingModelDataType.value === IoTDataSpecsDataTypeEnum.ARRAY || + thingModelDataType.value === IoTDataSpecsDataTypeEnum.STRUCT + ); +}); +/** 最大值统计 */ const maxValue = computed(() => { - if (isComplexDataType.value || list.value.length === 0) return '-'; + if (!canShowChart.value || list.value.length === 0) return '-'; const values = list.value .map((item) => Number(item.value)) .filter((v) => !Number.isNaN(v)); return values.length > 0 ? Math.max(...values).toFixed(2) : '-'; -}); // 统计数据 +}); +/** 最小值统计 */ const minValue = computed(() => { - if (isComplexDataType.value || list.value.length === 0) return '-'; + if (!canShowChart.value || list.value.length === 0) return '-'; const values = list.value .map((item) => Number(item.value)) .filter((v) => !Number.isNaN(v)); return values.length > 0 ? Math.min(...values).toFixed(2) : '-'; }); +/** 平均值统计 */ const avgValue = computed(() => { - if (isComplexDataType.value || list.value.length === 0) return '-'; + if (!canShowChart.value || list.value.length === 0) return '-'; const values = list.value .map((item) => Number(item.value)) .filter((v) => !Number.isNaN(v)); @@ -142,10 +164,10 @@ async function getList() { ) as IotDeviceApi.DevicePropertyDetail[]; total.value = list.value.length; - // 如果是图表模式且不是复杂数据类型,等待渲染图表 + // 如果是图表模式且支持图表展示,等待渲染图表 if ( viewMode.value === 'chart' && - !isComplexDataType.value && + canShowChart.value && list.value.length > 0 ) { await renderChartWhenReady(); @@ -287,8 +309,8 @@ async function open(deviceId: number, identifier: string, dataType: string) { // 更新查询参数的时间 queryParams.times = formatDateRangeWithTime(dateRange.value); - // 如果物模型是 struct、array,需要默认使用 list 模式 - viewMode.value = isComplexDataType.value ? 'list' : 'chart'; + // 如果不支持图表展示,默认使用列表模式 + viewMode.value = canShowChart.value ? 'chart' : 'list'; // 等待弹窗完全渲染后再获取数据 await nextTick(); @@ -380,11 +402,7 @@ function formatComplexValue(value: any) { /** 监听视图模式变化,重新渲染图表 */ watch(viewMode, async (newMode) => { - if ( - newMode === 'chart' && - !isComplexDataType.value && - list.value.length > 0 - ) { + if (newMode === 'chart' && canShowChart.value && list.value.length > 0) { await renderChartWhenReady(); } }); @@ -396,7 +414,6 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗 v-model:open="dialogVisible" title="查看数据" width="1200px" - :destroy-on-close="true" @cancel="handleClose" > @@ -436,7 +453,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗 @@ -459,7 +476,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗 共 {{ total }} 条数据 - + 最大值: {{ maxValue }} | 最小值: {{ minValue }} | 平均值: {{ avgValue }} @@ -529,10 +546,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗 .chart-container, .table-container { padding: 16px; - background-color: hsl( - var(--card) - ); // TODO @haohao:看看这个能不能 fix 下~ idea 爆红了; - + background-color: hsl(var(--card) / 100%); border: 1px solid hsl(var(--border) / 60%); border-radius: 8px; } diff --git a/apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property.vue similarity index 53% rename from apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property.vue rename to apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property.vue index 0849fd05a..965f90e9b 100644 --- a/apps/web-antd/src/views/iot/device/device/modules/detail/modules/thing-model-property.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/thing-model-property.vue @@ -1,13 +1,13 @@ - + @@ -134,13 +293,13 @@ onMounted(() => { @@ -214,7 +373,7 @@ onMounted(() => { 更新时间 - {{ item.updateTime ? formatDate(item.updateTime) : '-' }} + {{ item.updateTime ? formatDateTime(item.updateTime) : '-' }} @@ -225,45 +384,31 @@ onMounted(() => { - - - - - - - {{ formatValueWithUnit(record) }} - - - - - {{ record.updateTime ? formatDate(record.updateTime) : '-' }} - - - - - - 查看数据 - - - - + + + {{ formatValueWithUnit(row) }} + + + {{ row.updateTime ? formatDateTime(row.updateTime) : '-' }} + + + + 查看数据 + + + - +