diff --git a/ljwx-admin/CLAUDE.md b/ljwx-admin/CLAUDE.md new file mode 100644 index 0000000..30147c7 --- /dev/null +++ b/ljwx-admin/CLAUDE.md @@ -0,0 +1,294 @@ +# LjwxAdmin Claude 开发文档 + +## 数据解析与表格渲染故障排查文档 + +### 概述 +本文档详细记录了 LjwxAdmin 项目中 API 数据解析流程和表格渲染机制,以及一次耗时的微信告警配置表格显示故障的完整排查过程。 + +### 核心数据流架构 + +#### 1. API 调用层 +```typescript +// src/service/api/health/alert-config-wechat.ts +export function fetchGetAlertConfigWechatList(params?: Api.Health.AlertConfigWechatSearchParams) { + return request({ + url: '/t_wechat_alarm_config/page', + method: 'GET', + params + }); +} +``` + +#### 2. useTable Hook 数据转换层 +```typescript +// src/hooks/common/table.ts +const { loading, data, columns, getData, searchParams } = useTable({ + apiFn: fetchGetAlertConfigWechatList, + apiParams: { page: 1, pageSize: 20, customerId, type: 'enterprise' }, + transformer: res => { + const { records = [], page = 1, pageSize = 20, total = 0 } = res.data || {}; + const recordsWithIndex = records.map((item, index) => ({ + ...item, + index: (page - 1) * pageSize + index + 1 + })); + return { data: recordsWithIndex, pageNum: page, pageSize, total }; + }, + columns: () => { /* 动态列定义 */ } +}); +``` + +#### 3. Vue 组件渲染层 +```vue + +``` + +### 数据流详细分析 + +#### API 响应格式 +```json +{ + "code": 200, + "message": "操作成功", + "data": { + "page": 1, + "pageSize": 20, + "total": 2, + "records": [ + { + "id": 10, + "type": "official", + "appid": "wx10dcc9f0235e1d77", + "templateId": "xxx", + "enabled": true, + "createTime": 1750168835000 + } + ] + } +} +``` + +#### transformer 转换过程 +1. **提取分页数据**:从 `res.data` 中解构 `records`, `page`, `pageSize`, `total` +2. **添加索引**:为每条记录添加 `index` 字段,用于表格序号显示 +3. **返回标准格式**:返回 `{ data, pageNum, pageSize, total }` 供 useTable 使用 + +#### 列定义处理 +useTable hook 通过 `getColumns` 函数处理列定义: +```typescript +getColumns: (cols, checks) => { + const columnMap = new Map(); + cols.forEach(column => { + if (isTableColumnHasKey(column)) { + columnMap.set(column.key, column); + } + }); + return checks.filter(item => item.checked).map(check => columnMap.get(check.key)); +} +``` + +### 故障案例:微信告警配置表格显示空白 + +#### 问题现象 +- API 正常返回数据 +- data 数组长度正确 +- columns 数组长度正确 +- 但 NDataTable 表格完全空白 + +#### 排查过程 + +##### 第一阶段:参数传递问题 +**怀疑**:API 参数传递错误 +**排查**: +```typescript +// 修复搜索组件 type 参数传递 +watch(() => props.type, (newType) => { + if (newType) { + model.value.type = newType; + } +}, { immediate: true }); +``` +**结果**:API 能正确返回数据,但表格仍空白 + +##### 第二阶段:数据过滤问题 +**怀疑**:前端数据过滤导致显示为空 +**排查**: +```typescript +// 添加 filteredData 计算属性 +const filteredData = computed(() => { + if (!data.value) return []; + return data.value.filter(item => item.type === activeTab.value); +}); +``` +**结果**:数据过滤正确,但表格仍空白 + +##### 第三阶段:useTable hook 问题 +**怀疑**:useTable 的 columns 处理丢失了 render 函数 +**排查**: +```json +// 调试发现列定义中缺失 render 函数 +[ + { "key": "corpId", "title": "企业ID", "align": "center", "width": 150 }, + // render 函数在 JSON.stringify 时丢失了! +] +``` +**发现**:useTable 的 getColumns 函数在处理列定义时,render 函数被丢失 + +##### 第四阶段:NDataTable 属性问题 +**怀疑**:NDataTable 的某些属性导致渲染失败 +**排查**:逐个移除属性测试 +```typescript +// 问题属性组合 + + +// 修复后的简化版本 + +``` + +#### 根本原因 +1. **`remote` 属性**:告诉 NDataTable 期待远程分页,但我们提供的是本地数据 +2. **复杂的分页配置**:mobilePagination 与数据状态不匹配 +3. **CSS 高度问题**:flex-height 和相关 CSS 类导致表格高度为 0 + +### 故障排查最佳实践 + +#### 1. 分层排查法 +``` +数据层 → 转换层 → 组件层 → 渲染层 + ↓ ↓ ↓ ↓ + API useTable Vue组件 DOM +``` + +#### 2. 调试工具使用 +```typescript +// 在关键节点添加调试输出 +console.log('[API] 原始响应:', response); +console.log('[Transformer] 转换后数据:', transformedData); +console.log('[Component] 渲染数据:', data.value); +console.log('[Columns] 列定义:', columns.value); +``` + +#### 3. 对比测试法 +```vue + + +``` + +### NDataTable 使用指南 + +#### ✅ 推荐配置 +```vue + +``` + +#### ❌ 避免的属性组合 +```vue + + :pagination="complexPagination" + :flex-height="dynamicValue" + class="complex-css-classes" +/> +``` + +#### 列定义最佳实践 +```typescript +// ✅ 使用 computed 属性,不依赖 useTable 的 columns +const tableColumns = computed(() => [ + { key: 'id', title: 'ID', width: 80 }, + { + key: 'status', + title: '状态', + render: (row) => row.enabled ? '启用' : '禁用' + }, + { + key: 'operate', + title: '操作', + render: (row) => h(NButton, { onClick: () => edit(row) }, () => '编辑') + } +]); + +// ❌ 避免依赖 useTable 的动态列定义处理 +``` + +### 类似问题快速诊断清单 + +#### 1. 数据检查 (30秒) +- [ ] API 是否返回正确数据? +- [ ] `data.length > 0` 是否为真? +- [ ] 数据结构是否与列定义匹配? + +#### 2. 列定义检查 (1分钟) +- [ ] `columns.length > 0` 是否为真? +- [ ] render 函数是否丢失? +- [ ] key 字段是否与数据字段匹配? + +#### 3. 组件属性检查 (2分钟) +- [ ] 移除 `remote` 属性测试 +- [ ] 移除 `pagination` 属性测试 +- [ ] 使用最简化的 NDataTable 配置测试 + +#### 4. 对比测试 (2分钟) +- [ ] 创建简化版本的测试表格 +- [ ] 使用相同数据和简单列定义 +- [ ] 确认是数据问题还是组件问题 + +### 总结 + +这次故障排查耗时的主要原因: +1. **问题隐蔽性**:表面现象(表格空白)与根本原因(NDataTable属性配置)没有直接关联 +2. **多层抽象**:useTable hook 增加了调试复杂度 +3. **属性依赖**:多个 NDataTable 属性的组合效应难以预测 + +通过建立这套分层排查方法和文档化的最佳实践,类似问题的排查时间可以从数小时缩短到数分钟。 + +--- + +## 项目开发注意事项 + +### 1. 表格开发规范 +- 避免使用 `remote` 属性,除非确实需要远程分页 +- 列定义使用独立的 computed 属性,不依赖 useTable 的 columns 处理 +- 优先使用简化的 NDataTable 配置,渐进式添加复杂功能 + +### 2. 调试技巧 +- 数据问题时,先用简化测试表格验证数据正确性 +- 使用分层排查法,从 API → useTable → 组件 → 渲染逐层检查 +- 重要的调试信息要保留在代码注释中 + +### 3. 常见陷阱 +- useTable hook 的 transformer 会自动添加 index 字段 +- NDataTable 的 render 函数在 JSON.stringify 时会丢失 +- type 参数需要在搜索组件中正确设置和传递 \ No newline at end of file diff --git a/ljwx-admin/README.md b/ljwx-admin/README.md index aff6326..a45082f 100644 --- a/ljwx-admin/README.md +++ b/ljwx-admin/README.md @@ -471,6 +471,18 @@ Content-Type: application/json ## 📝 更新日志 +### v1.3.6 (2025-08-27) +- 🐛 **修复微信告警配置表格显示空白问题** + - 解决 NDataTable 的 `remote` 属性与本地数据冲突导致的渲染失败 + - 移除导致问题的属性组合:`remote`、复杂分页配置、动态高度设置 + - 重构列定义为独立 computed 属性,避免 useTable 的 columns 处理丢失 render 函数 + - 修复搜索组件中 `type` 参数传递问题,确保企业微信和微信公众号数据正确过滤 + - 优化操作按钮使用 Vue 的 h 函数,避免 JSX 语法在 Vue 模板中的冲突 +- 📚 **新增开发文档** + - 创建 CLAUDE.md 开发文档,记录数据解析流程和故障排查方法 + - 建立表格渲染问题的快速诊断清单,提升后续开发效率 + - 文档化 NDataTable 使用最佳实践和常见陷阱避免指南 + ### v1.3.5 (2025-08-27) - ✨ **新增微信告警配置管理功能** - 支持企业微信和微信公众号两种配置类型 diff --git a/ljwx-admin/src/views/alert/config/index.vue b/ljwx-admin/src/views/alert/config/index.vue index 66c3cb9..105c106 100644 --- a/ljwx-admin/src/views/alert/config/index.vue +++ b/ljwx-admin/src/views/alert/config/index.vue @@ -1,7 +1,7 @@ @@ -252,31 +331,29 @@ onMounted(() => { @search="getDataByPage" /> - +
+
+ + 新增 + + + 批量删除 + +
+ + 刷新 + +
+
@@ -292,31 +369,29 @@ onMounted(() => { @search="getDataByPage" /> - +
+
+ + 新增 + + + 批量删除 + +
+ + 刷新 + +
+
@@ -324,6 +399,7 @@ onMounted(() => { + -import { computed } from 'vue'; +import { computed, watch } from 'vue'; import { $t } from '@/locales'; import { useDict } from '@/hooks/business/dict'; @@ -22,6 +22,13 @@ const emit = defineEmits(); const model = defineModel('model', { required: true }); +// 监听props.type变化,自动设置model中的type字段 +watch(() => props.type, (newType) => { + if (newType) { + model.value.type = newType; + } +}, { immediate: true }); + const { dictOptions } = useDict(); // 计算属性处理条件字段绑定