diff --git a/apps/web-antd/src/api-client/types.gen.ts b/apps/web-antd/src/api-client/types.gen.ts index 96355cb..097086f 100644 --- a/apps/web-antd/src/api-client/types.gen.ts +++ b/apps/web-antd/src/api-client/types.gen.ts @@ -5085,16 +5085,14 @@ export type IoTDBTreeModelDeviceDataPageDataInput = { }; export type IoTDBTreeModelDeviceDataDto = { - concurrencyStamp?: null | string; - creationTime?: string; - readonly extraProperties?: null | { - [key: string]: unknown; - }; - id?: string; - isDefault?: boolean; - isPublic?: boolean; - isStatic?: boolean; - name?: null | string; + SystemName?: null | string; + ProjectId?: null | string; + ProjectName?: null | string; + IoTDataType?: null | string; + DeviceType?: null | string; + DeviceId?: null | string; + Timestamps?: null | string; + APPData?: null | string; }; export type IoTDBTreeModelDeviceDataPageListResultDto = { diff --git a/apps/web-antd/src/locales/langs/zh-CN/abp.json b/apps/web-antd/src/locales/langs/zh-CN/abp.json index 5298be6..be68eaa 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/abp.json +++ b/apps/web-antd/src/locales/langs/zh-CN/abp.json @@ -265,7 +265,7 @@ "BusinessSystemEnum": "系统类型" }, "IoTDBDynamicObjectData": { - "DeviceId": "设备ID", + "APPData": "设备数据", "DeviceName": "设备名称", "DeviceType": "设备类型", "DeviceStatus": "设备状态", diff --git a/apps/web-antd/src/views/dataManger/deviceData/README.md b/apps/web-antd/src/views/dataManger/deviceData/README.md new file mode 100644 index 0000000..74bf825 --- /dev/null +++ b/apps/web-antd/src/views/dataManger/deviceData/README.md @@ -0,0 +1,181 @@ +# 动态列解决方案 + +这个解决方案用于处理后端返回字段列数不固定的IoTDB设备数据。 + +## 问题描述 + +原来的 `IoTDBTreeModelDeviceDataPageAllResponse` 类型被限定为固定的字段结构,但后端返回的字段列数不固定,导致前端无法正确显示所有数据。 + +## 解决方案 + +### 1. 动态列生成 + +- **文件**: `dynamicColumns.ts` +- **功能**: 根据实际数据动态生成表格列 +- **特点**: + - 支持字段名映射 + - 支持字段类型配置 + - 自动过滤不需要显示的字段 + +### 2. 类型安全 + +- **文件**: `types.ts` +- **功能**: 提供类型定义,确保类型安全 +- **特点**: + - 使用 `[key: string]: any` 允许任意字段 + - 继承基础类型,保持向后兼容 + +### 3. 数据处理 + +- **文件**: `example.ts` +- **功能**: 提供数据处理工具函数 +- **特点**: + - 数据验证和清理 + - 动态字段检测 + - 字段重要性排序 + +## 使用方法 + +### 基本使用 + +```typescript +// 1. 导入必要的函数 +import { generateDynamicColumns } from './dynamicColumns'; +import { validateAndCleanData } from './example'; + +// 2. 在API调用后处理数据 +const { data } = await postTreeModelDeviceDataInfoPage(options); + +if (data?.items && data.items.length > 0) { + // 验证和清理数据 + const cleanedData = validateAndCleanData(data.items); + + // 动态生成列定义 + const columns = generateDynamicColumns(cleanedData); + + // 更新表格列 + dynamicColumns.value = columns; +} +``` + +### 自定义字段映射 + +```typescript +// 在 dynamicColumns.ts 中添加字段映射 +export const fieldNameMapping: FieldMapping = { + SystemName: '系统名称', + ProjectId: '项目ID', + // 添加新的字段映射 + CustomField: '自定义字段', +}; +``` + +### 自定义字段类型配置 + +```typescript +// 在 dynamicColumns.ts 中添加字段类型配置 +export const fieldTypeConfig: FieldTypeConfig = { + Timestamps: { + formatter: (value: string) => { + return new Date(value).toLocaleString(); + }, + }, + // 添加新的字段类型配置 + CustomField: { + width: 200, + formatter: (value: any) => { + return `自定义格式: ${value}`; + }, + }, +}; +``` + +## 配置选项 + +### 字段映射配置 + +| 字段名 | 显示名称 | 说明 | +|--------|----------|------| +| SystemName | 系统名称 | 系统名称字段 | +| ProjectId | 项目ID | 项目标识符 | +| ProjectName | 项目名称 | 项目名称 | +| IoTDataType | IoT数据类型 | 数据类型标识 | +| DeviceType | 设备类型 | 设备类型 | +| DeviceId | 设备ID | 设备标识符 | +| Timestamps | 时间戳 | 数据时间戳 | +| APPData | 应用数据 | 应用相关数据 | + +### 排除字段 + +默认排除以下字段: +- `id` +- `key` +- `__typename` + +可以在 `generateDynamicColumns` 函数中修改 `excludeFields` 数组来自定义。 + +## 高级功能 + +### 1. 混合模式 + +支持固定列和动态列混合显示: + +```typescript +// 固定列定义 +const fixedColumns = computed(() => [ + { title: '序号', type: 'seq', width: 50 }, + { field: 'SystemName', title: '系统名称', width: 150 }, +]); + +// 动态列 +const dynamicColumns = ref([]); + +// 合并显示 +const allColumns = computed(() => [ + ...fixedColumns.value, + ...dynamicColumns.value, +]); +``` + +### 2. 字段重要性排序 + +```typescript +import { sortFieldsByImportance } from './example'; + +const fields = ['CustomField', 'Timestamps', 'SystemName']; +const sortedFields = sortFieldsByImportance(fields); +// 结果: ['Timestamps', 'SystemName', 'CustomField'] +``` + +### 3. 数据验证 + +```typescript +import { validateAndCleanData } from './example'; + +const rawData = [/* 原始数据 */]; +const cleanedData = validateAndCleanData(rawData); +``` + +## 注意事项 + +1. **性能考虑**: 动态列生成会增加一些计算开销,建议在数据量较大时进行优化 +2. **类型安全**: 虽然使用了 `any` 类型,但通过类型定义和验证函数确保数据安全 +3. **向后兼容**: 保持与原有固定字段的兼容性 +4. **扩展性**: 可以轻松添加新的字段映射和类型配置 + +## 故障排除 + +### 常见问题 + +1. **列不显示**: 检查字段是否在 `excludeFields` 中 +2. **字段名显示错误**: 检查 `fieldNameMapping` 配置 +3. **数据格式错误**: 使用 `validateAndCleanData` 函数处理数据 + +### 调试技巧 + +```typescript +// 启用调试日志 +console.log('原始数据:', data); +console.log('清理后数据:', cleanedData); +console.log('生成的列:', dynamicColumns.value); +``` diff --git a/apps/web-antd/src/views/dataManger/deviceData/dynamicColumns.ts b/apps/web-antd/src/views/dataManger/deviceData/dynamicColumns.ts new file mode 100644 index 0000000..501fedb --- /dev/null +++ b/apps/web-antd/src/views/dataManger/deviceData/dynamicColumns.ts @@ -0,0 +1,76 @@ +import type { FieldMapping, FieldTypeConfig, ColumnConfig, DynamicDeviceData } from './types'; + +// 字段名映射配置 +export const fieldNameMapping: FieldMapping = { + SystemName: '系统名称', + ProjectId: '项目ID', + ProjectName: '项目名称', + IoTDataType: 'IoT数据类型', + DeviceType: '设备类型', + DeviceId: '设备ID', + Timestamps: '时间戳', + APPData: '应用数据', + // 可以根据需要添加更多映射 +}; + +// 字段类型配置 +export const fieldTypeConfig: FieldTypeConfig = { + Timestamps: { + formatter: (value: string) => { + if (!value) return ''; + try { + return new Date(value).toLocaleString(); + } catch { + return value; + } + }, + }, + // 可以根据需要添加更多类型配置 +}; + +// 动态生成表格列 +export const generateDynamicColumns = (data: DynamicDeviceData[]): ColumnConfig[] => { + if (!data || data.length === 0) return []; + + // 获取第一条数据的所有字段 + const firstRow = data[0]; + if (!firstRow) return []; + const fields = Object.keys(firstRow); + + // 过滤掉不需要显示的字段 + const excludeFields = ['id', 'key', '__typename']; + + return fields + .filter(field => !excludeFields.includes(field)) + .map(field => { + const columnConfig: any = { + field, + title: fieldNameMapping[field] || field, + minWidth: '150', + showOverflow: true, + }; + + // 应用字段类型配置 + if (fieldTypeConfig[field]) { + Object.assign(columnConfig, fieldTypeConfig[field]); + } + + return columnConfig; + }); +}; + +// 获取所有可能的字段(用于预定义列) +export const getAllPossibleFields = () => { + return Object.keys(fieldNameMapping); +}; + +// 预定义列配置(可选) +export const getPredefinedColumns = () => { + return getAllPossibleFields().map(field => ({ + field, + title: fieldNameMapping[field] || field, + minWidth: '150', + showOverflow: true, + ...fieldTypeConfig[field], + })); +}; diff --git a/apps/web-antd/src/views/dataManger/deviceData/example.ts b/apps/web-antd/src/views/dataManger/deviceData/example.ts new file mode 100644 index 0000000..870e56b --- /dev/null +++ b/apps/web-antd/src/views/dataManger/deviceData/example.ts @@ -0,0 +1,134 @@ +import type { DynamicDeviceData, DynamicPageResponse } from './types'; + +// 示例1:处理标准IoTDB数据 +export const handleStandardIoTDBData = (response: any): DynamicPageResponse => { + // 确保响应格式正确 + if (!response || !response.items) { + return { items: [], totalCount: 0 }; + } + + // 转换数据,确保所有字段都被包含 + const items = response.items.map((item: any) => ({ + SystemName: item.SystemName || '', + ProjectId: item.ProjectId || '', + ProjectName: item.ProjectName || '', + IoTDataType: item.IoTDataType || '', + DeviceType: item.DeviceType || '', + DeviceId: item.DeviceId || '', + Timestamps: item.Timestamps || '', + APPData: item.APPData || '', + // 包含所有其他字段 + ...item, + })); + + return { + items, + totalCount: response.totalCount || items.length, + }; +}; + +// 示例2:处理自定义字段数据 +export const handleCustomFieldData = (response: any): DynamicPageResponse => { + if (!response || !response.items) { + return { items: [], totalCount: 0 }; + } + + // 直接使用原始数据,允许任意字段 + return { + items: response.items, + totalCount: response.totalCount || response.items.length, + }; +}; + +// 示例3:数据验证和清理 +export const validateAndCleanData = (data: any[]): DynamicDeviceData[] => { + return data.map(item => { + const cleaned: DynamicDeviceData = {}; + + // 遍历所有字段,进行类型转换和清理 + Object.keys(item).forEach(key => { + const value = item[key]; + + // 根据字段名进行特殊处理 + switch (key) { + case 'Timestamps': + cleaned[key] = value ? String(value) : ''; + break; + case 'SystemName': + case 'ProjectName': + case 'DeviceType': + case 'IoTDataType': + cleaned[key] = value ? String(value) : ''; + break; + case 'ProjectId': + case 'DeviceId': + cleaned[key] = value ? String(value) : ''; + break; + case 'APPData': + // 尝试解析JSON数据 + try { + if (typeof value === 'string') { + cleaned[key] = JSON.parse(value); + } else { + cleaned[key] = value; + } + } catch { + cleaned[key] = value; + } + break; + default: + // 对于未知字段,保持原值 + cleaned[key] = value; + } + }); + + return cleaned; + }); +}; + +// 示例4:动态字段检测 +export const detectDynamicFields = (data: DynamicDeviceData[]): string[] => { + if (!data || data.length === 0) return []; + + const allFields = new Set(); + + data.forEach(item => { + Object.keys(item).forEach(key => { + allFields.add(key); + }); + }); + + return Array.from(allFields); +}; + +// 示例5:字段重要性排序 +export const sortFieldsByImportance = (fields: string[]): string[] => { + const importanceOrder = [ + 'Timestamps', + 'SystemName', + 'ProjectName', + 'DeviceType', + 'DeviceId', + 'IoTDataType', + 'APPData', + ]; + + return fields.sort((a, b) => { + const aIndex = importanceOrder.indexOf(a); + const bIndex = importanceOrder.indexOf(b); + + // 如果都在预定义列表中,按列表顺序排序 + if (aIndex !== -1 && bIndex !== -1) { + return aIndex - bIndex; + } + + // 如果只有a在预定义列表中,a排在前面 + if (aIndex !== -1) return -1; + + // 如果只有b在预定义列表中,b排在前面 + if (bIndex !== -1) return 1; + + // 都不在预定义列表中,按字母顺序排序 + return a.localeCompare(b); + }); +}; diff --git a/apps/web-antd/src/views/dataManger/deviceData/index.vue b/apps/web-antd/src/views/dataManger/deviceData/index.vue index 5cafdca..4bb681f 100644 --- a/apps/web-antd/src/views/dataManger/deviceData/index.vue +++ b/apps/web-antd/src/views/dataManger/deviceData/index.vue @@ -3,6 +3,7 @@ import type { VbenFormProps } from '#/adapter/form'; import type { VxeGridProps } from '#/adapter/vxe-table'; import { useRoute } from 'vue-router'; +import { nextTick, watch, ref, computed } from 'vue'; import { Page } from '@vben/common-ui'; @@ -10,6 +11,8 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { postTreeModelDeviceDataInfoPage } from '#/api-client'; import { querySchema, tableSchema } from './schema'; +import { generateDynamicColumns } from './dynamicColumns'; +import { handleStandardIoTDBData, validateAndCleanData } from './example'; defineOptions({ name: 'DeviceData', @@ -17,18 +20,51 @@ defineOptions({ const route = useRoute(); const { DeviceType, DeviceId, FocusAddress } = route.query; + +// 动态列定义 +const dynamicColumns = ref([]); + +// 固定列定义(始终显示) +const fixedColumns = computed(() => [ + { title: '序号', type: 'seq', width: 50 }, + // 可以在这里添加其他固定列 +]); + +// 合并固定列和动态列 +const allColumns = computed(() => [ + ...fixedColumns.value, + ...dynamicColumns.value, +]); + const formOptions: VbenFormProps = { schema: querySchema.value, initialValues: { FocusAddress: FocusAddress as string, }, + // 禁用表单值变化时自动提交,使用自定义处理函数 + submitOnChange: false, + // 添加表单值变化的处理函数 + handleValuesChange: async (values, changedFields) => { + // 当系统类型发生变化时,刷新表格数据 + if (changedFields.includes('SystemName')) { + console.log('SystemName changed, values:', values); + console.log('Changed fields:', changedFields); + + // 使用 setTimeout 确保表单值已经完全更新 + setTimeout(async () => { + const latestValues = await gridApi.formApi.getValues(); + console.log('Latest values after timeout:', latestValues); + gridApi.reload(latestValues); + }, 0); + } + }, }; const gridOptions: VxeGridProps = { checkboxConfig: { highlight: true, labelField: 'name', }, - columns: tableSchema.value, + columns: allColumns, height: 'auto', keepSource: true, pagerConfig: {}, @@ -52,13 +88,29 @@ const gridOptions: VxeGridProps = { FocusAddress, }, }); + + // 处理数据并动态生成列定义 + if (data?.items && data.items.length > 0) { + // 验证和清理数据 + const cleanedData = validateAndCleanData(data.items); + + // 动态生成列定义 + dynamicColumns.value = generateDynamicColumns(cleanedData); + + // 返回处理后的数据 + return { + ...data, + items: cleanedData, + }; + } + return data; }, }, }, }; -const [Grid] = useVbenVxeGrid({ formOptions, gridOptions }); +const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });