ソースを参照

merge: 合并同事分支liyu_2026_04_07(管理员管理+移除系统用户表)

- 接受同事的管理员管理功能(AdminPermission实体、权限菜单过滤)
- 保留我们的内容管理、用户类别、方案模拟、Y轴参考等功能
- 解决17个文件冲突(rename/rename、modify/delete、content冲突)
liaoxg 3 ヶ月 前
コミット
79dc54a750
67 ファイル変更9006 行追加114 行削除
  1. 26 0
      .playwright-mcp/console-2026-04-28T10-49-14-797Z.log
  2. 0 0
      .playwright-mcp/page-2026-04-28T08-38-26-745Z.yml
  3. 14 0
      .playwright-mcp/page-2026-04-28T10-49-15-214Z.yml
  4. 147 0
      .playwright-mcp/page-2026-04-28T10-51-21-616Z.yml
  5. 154 0
      .playwright-mcp/page-2026-04-28T10-53-11-586Z.yml
  6. 190 0
      .playwright-mcp/page-2026-04-28T10-53-43-756Z.yml
  7. 154 0
      .playwright-mcp/page-2026-04-28T10-55-09-114Z.yml
  8. 248 0
      .playwright-mcp/page-2026-04-28T10-56-11-260Z.yml
  9. 129 0
      .playwright-mcp/page-2026-04-28T11-11-39-453Z.yml
  10. 529 0
      .playwright-mcp/page-2026-04-28T11-14-42-909Z.yml
  11. 442 0
      .playwright-mcp/page-2026-04-28T11-16-10-702Z.yml
  12. 14 0
      .playwright-mcp/page-2026-05-19T13-07-56-365Z.yml
  13. 11 0
      .sisyphus/run-continuation/ses_23363b0cdffeAeukEMIH5qxAa7.json
  14. 529 0
      acupoint-page.yml
  15. 113 0
      code/backend/.factorypath
  16. 33 33
      code/backend/src/main/java/com/aijiuyi/admin/common/constant/ResultCode.java
  17. 11 11
      code/backend/src/main/java/com/aijiuyi/admin/controller/AcupointController.java
  18. 12 23
      code/backend/src/main/java/com/aijiuyi/admin/entity/SysUser.java
  19. 87 0
      code/backend/src/test/java/com/aijiuyi/admin/common/util/TokenUtilTest.java
  20. 10 6
      code/frontend/src/layout/index.vue
  21. 24 24
      code/frontend/src/router/index.js
  22. 17 17
      code/frontend/src/views/user/app-user/index.vue
  23. 881 0
      code/logs/aijiuyi-admin-dev.log
  24. 147 0
      current-page.yml
  25. BIN
      device-management.png
  26. 0 0
      login-page-full.yml
  27. 14 0
      login-page.yml
  28. 14 0
      login-snapshot.yml
  29. BIN
      login-success.png
  30. 147 0
      main-menu.yml
  31. 133 0
      openspec/backend/src/main/java/com/aijiuyi/admin/common/util/TokenUtil.java
  32. 147 0
      openspec/backend/src/main/java/com/aijiuyi/admin/controller/AdminController.java
  33. 28 0
      openspec/backend/src/main/java/com/aijiuyi/admin/controller/dto/AdminQueryDTO.java
  34. 41 0
      openspec/backend/src/main/java/com/aijiuyi/admin/entity/AdminPermission.java
  35. 12 0
      openspec/backend/src/main/java/com/aijiuyi/admin/mapper/AdminPermissionMapper.java
  36. 83 0
      openspec/backend/src/main/java/com/aijiuyi/admin/service/AdminService.java
  37. 236 0
      openspec/backend/src/main/java/com/aijiuyi/admin/service/impl/AdminServiceImpl.java
  38. 272 0
      openspec/backend/src/main/java/com/aijiuyi/admin/service/impl/AppUserServiceImpl.java
  39. 331 0
      openspec/backend/src/main/resources/sql/init.sql
  40. 80 0
      openspec/frontend/src/api/admin.js
  41. 494 0
      openspec/frontend/src/views/admin/index.vue
  42. 29 0
      openspec/管理员管理.md
  43. 79 0
      package-lock.json
  44. 442 0
      plan-page.yml
  45. BIN
      test-e2e/---app.png
  46. BIN
      test-e2e/---user-app.png
  47. BIN
      test-e2e/---user-profile.png
  48. BIN
      test-e2e/account-page.png
  49. BIN
      test-e2e/acupoint.png
  50. 146 0
      test-e2e/test-results/app-登录模块-TC-001-正常登录-chromium/error-context.md
  51. 155 0
      test-e2e/test-results/app-登录模块-TC-002-用户名为空验证-chromium/error-context.md
  52. 162 0
      test-e2e/test-results/app-登录模块-TC-003-密码为空验证-chromium/error-context.md
  53. 169 0
      test-e2e/test-results/app-登录模块-TC-004-错误账号登录-chromium/error-context.md
  54. 180 0
      test-e2e/test-results/app-账号管理模块-TC-010-查看账号管理页面-chromium/error-context.md
  55. 180 0
      test-e2e/test-results/app-账号管理模块-TC-011-账号列表搜索-手机号-chromium/error-context.md
  56. 180 0
      test-e2e/test-results/app-账号管理模块-TC-012-账号列表搜索-昵称-chromium/error-context.md
  57. 180 0
      test-e2e/test-results/app-账号管理模块-TC-013-新增用户按钮存在-chromium/error-context.md
  58. 180 0
      test-e2e/test-results/app-账号管理模块-TC-014-编辑功能-chromium/error-context.md
  59. 180 0
      test-e2e/test-results/app-账号管理模块-TC-015-禁用功能-chromium/error-context.md
  60. BIN
      test-e2e/user-profile.png
  61. 4 0
      test-results/.last-run.json
  62. 190 0
      user-form.yml
  63. 248 0
      user-profile.yml
  64. 348 0
      测试工作区/测试用例/e2e/app.spec.js.backup
  65. BIN
      测试工作区/验收文档/艾灸椅管理后台系统技术文档.docx
  66. BIN
      测试工作区/验收文档/艾灸椅管理后台系统软件部署手册.docx
  67. BIN
      测试工作区/验收文档/艾灸椅管理后台系统需求设计文档.docx

+ 26 - 0
.playwright-mcp/console-2026-04-28T10-49-14-797Z.log

@@ -0,0 +1,26 @@
+[  267774ms] [WARNING] ElementPlusError: [el-radio] [API] label act as value is about to be deprecated in version 3.0.0, please use value instead.
+For more detail, please visit: https://element-plus.org/en-US/component/radio.html
+
+    at Sd (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:45148)
+    at G.immediate (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:56129)
+    at _n (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:20068)
+    at vn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:20136)
+    at u.call (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23338)
+    at w (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:19329)
+    at hn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:19539)
+    at qn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23536)
+    at Kn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23083)
+    at vf (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:56111) @ https://ajy.danclub.cn/assets/router-CCKAFQwS.js:1
+[  267777ms] [WARNING] ElementPlusError: [el-radio] [API] label act as value is about to be deprecated in version 3.0.0, please use value instead.
+For more detail, please visit: https://element-plus.org/en-US/component/radio.html
+
+    at Sd (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:45148)
+    at G.immediate (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:56129)
+    at _n (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:20068)
+    at vn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:20136)
+    at u.call (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23338)
+    at w (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:19329)
+    at hn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:19539)
+    at qn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23536)
+    at Kn (https://ajy.danclub.cn/assets/vue-router-B5tRNYlR.js:1:23083)
+    at vf (https://ajy.danclub.cn/assets/router-CCKAFQwS.js:2:56111) @ https://ajy.danclub.cn/assets/router-CCKAFQwS.js:1

+ 0 - 0
.playwright-mcp/page-2026-04-28T08-38-26-745Z.yml


+ 14 - 0
.playwright-mcp/page-2026-04-28T10-49-15-214Z.yml

@@ -0,0 +1,14 @@
+- generic [ref=e4]:
+  - generic [ref=e5]:
+    - generic [ref=e7]: 医
+    - heading "艾灸椅管理后台" [level=1] [ref=e8]
+    - paragraph [ref=e9]: 欢迎回来,请登录您的账号
+  - generic [ref=e10]:
+    - generic [ref=e14]:
+      - img [ref=e17]
+      - textbox "请输入用户名" [ref=e19]
+    - generic [ref=e23]:
+      - img [ref=e26]
+      - textbox "请输入密码" [ref=e29]
+    - button "登 录" [ref=e32] [cursor=pointer]:
+      - generic [ref=e33]: 登 录

+ 147 - 0
.playwright-mcp/page-2026-04-28T10-51-21-616Z.yml

@@ -0,0 +1,147 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页

+ 154 - 0
.playwright-mcp/page-2026-04-28T10-53-11-586Z.yml

@@ -0,0 +1,154 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页

+ 190 - 0
.playwright-mcp/page-2026-04-28T10-53-43-756Z.yml

@@ -0,0 +1,190 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页
+        - dialog "新增账号" [ref=e287]:
+          - generic [active] [ref=e288]:
+            - generic [ref=e289]:
+              - heading "新增账号" [level=2] [ref=e290]
+              - button "关闭此对话框" [ref=e291] [cursor=pointer]:
+                - img [ref=e293]
+            - generic [ref=e296]:
+              - generic [ref=e297]:
+                - generic [ref=e298]: "* 手机号"
+                - textbox "* 手机号" [ref=e302]:
+                  - /placeholder: 请输入手机号
+              - generic [ref=e303]:
+                - generic [ref=e304]: 昵称
+                - textbox "昵称" [ref=e308]:
+                  - /placeholder: 请输入昵称
+              - generic [ref=e309]:
+                - generic [ref=e310]: "* 用户类型"
+                - generic [ref=e313] [cursor=pointer]:
+                  - generic:
+                    - combobox "* 用户类型" [ref=e315]
+                    - generic [ref=e316]: 普通用户
+                  - img [ref=e319]
+              - generic [ref=e321]:
+                - generic [ref=e322]: 状态
+                - radiogroup "状态" [ref=e324]:
+                  - generic [ref=e325] [cursor=pointer]:
+                    - radio "正常" [checked] [ref=e327]
+                    - generic [ref=e329]: 正常
+                  - generic [ref=e330] [cursor=pointer]:
+                    - radio "禁用" [ref=e332]
+                    - generic [ref=e334]: 禁用
+            - generic [ref=e335]:
+              - button "取消" [ref=e336] [cursor=pointer]:
+                - generic [ref=e337]: 取消
+              - button "确定" [ref=e338] [cursor=pointer]:
+                - generic [ref=e339]: 确定

+ 154 - 0
.playwright-mcp/page-2026-04-28T10-55-09-114Z.yml

@@ -0,0 +1,154 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [active] [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页

+ 248 - 0
.playwright-mcp/page-2026-04-28T10-56-11-260Z.yml

@@ -0,0 +1,248 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [active] [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "用户列表" [ref=e340]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e341]:
+        - generic [ref=e344]:
+          - generic [ref=e345]:
+            - generic [ref=e346]: 姓名/手机号
+            - textbox "姓名/手机号" [ref=e350]:
+              - /placeholder: 请输入姓名或手机号
+          - generic [ref=e351]:
+            - generic [ref=e352]: 设备编号
+            - textbox "设备编号" [ref=e356]:
+              - /placeholder: 请输入设备编号
+          - generic [ref=e357]:
+            - generic [ref=e358]: 性别
+            - generic [ref=e361] [cursor=pointer]:
+              - generic:
+                - combobox "性别" [ref=e363]
+                - generic [ref=e364]: 全部
+              - img [ref=e367]
+          - generic [ref=e369]:
+            - generic [ref=e370]: 注册时间
+            - generic [ref=e372]:
+              - img [ref=e374]
+              - combobox "注册时间" [ref=e376]
+              - generic [ref=e377]: 至
+              - combobox "结束日期" [ref=e378]
+          - generic [ref=e379]:
+            - generic [ref=e380]: 穴位表
+            - generic [ref=e383] [cursor=pointer]:
+              - generic:
+                - combobox "穴位表" [ref=e385]
+                - generic [ref=e386]: 全部
+              - img [ref=e389]
+          - generic [ref=e392]:
+            - button "查询" [ref=e393] [cursor=pointer]:
+              - img [ref=e395]
+              - generic [ref=e397]: 查询
+            - button "重置" [ref=e398] [cursor=pointer]:
+              - img [ref=e400]
+              - generic [ref=e402]: 重置
+        - generic [ref=e403]:
+          - generic [ref=e405]:
+            - generic [ref=e406]: 用户管理
+            - generic [ref=e407]:
+              - button "导入" [ref=e410] [cursor=pointer]:
+                - button "导入" [ref=e411]:
+                  - img [ref=e413]
+                  - generic [ref=e415]: 导入
+              - button "导出 Excel" [ref=e416] [cursor=pointer]:
+                - img [ref=e418]
+                - generic [ref=e420]: 导出 Excel
+              - button "新增" [ref=e421] [cursor=pointer]:
+                - img [ref=e423]
+                - generic [ref=e425]: 新增
+              - button "批量删除" [disabled] [ref=e426]:
+                - img [ref=e428]
+                - generic [ref=e430]: 批量删除
+          - generic [ref=e431]:
+            - generic [ref=e433]:
+              - table [ref=e435]:
+                - rowgroup [ref=e453]:
+                  - row "选择所有行 序号 用户ID 姓名 性别 年龄 手机号 设备编号 肩宽(cm) 身长(cm) 脊柱长(cm) 身高(cm) 体重(kg) 穴位表 注册时间 操作" [ref=e454]:
+                    - columnheader "选择所有行" [ref=e455]:
+                      - generic "选择所有行" [ref=e457] [cursor=pointer]:
+                        - generic [ref=e458]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e460]:
+                      - generic [ref=e461]: 序号
+                    - columnheader "用户ID" [ref=e462]:
+                      - generic [ref=e463]: 用户ID
+                    - columnheader "姓名" [ref=e464]:
+                      - generic [ref=e465]: 姓名
+                    - columnheader "性别" [ref=e466]:
+                      - generic [ref=e467]: 性别
+                    - columnheader "年龄" [ref=e468]:
+                      - generic [ref=e469]: 年龄
+                    - columnheader "手机号" [ref=e470]:
+                      - generic [ref=e471]: 手机号
+                    - columnheader "设备编号" [ref=e472]:
+                      - generic [ref=e473]: 设备编号
+                    - columnheader "肩宽(cm)" [ref=e474]:
+                      - generic [ref=e475]: 肩宽(cm)
+                    - columnheader "身长(cm)" [ref=e476]:
+                      - generic [ref=e477]: 身长(cm)
+                    - columnheader "脊柱长(cm)" [ref=e478]:
+                      - generic [ref=e479]: 脊柱长(cm)
+                    - columnheader "身高(cm)" [ref=e480]:
+                      - generic [ref=e481]: 身高(cm)
+                    - columnheader "体重(kg)" [ref=e482]:
+                      - generic [ref=e483]: 体重(kg)
+                    - columnheader "穴位表" [ref=e484]:
+                      - generic [ref=e485]: 穴位表
+                    - columnheader "注册时间" [ref=e486]:
+                      - generic [ref=e487]: 注册时间
+                    - columnheader "操作" [ref=e488]:
+                      - generic [ref=e489]: 操作
+              - table [ref=e494]:
+                - rowgroup [ref=e512]:
+                  - row "选择当前行 1 2 邓浩 男 36岁 16812345678 AJY202605010011 47 170 55 - - 已生成 2026-04-15T10:09:04 编辑 设备管理 删除" [ref=e513]:
+                    - cell "选择当前行" [ref=e514]:
+                      - generic "选择当前行" [ref=e516] [cursor=pointer]:
+                        - generic [ref=e517]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e519]:
+                      - generic [ref=e521]: "1"
+                    - cell "2" [ref=e522]:
+                      - generic [ref=e523]: "2"
+                    - cell "邓浩" [ref=e524]:
+                      - generic [ref=e525]: 邓浩
+                    - cell "男" [ref=e526]:
+                      - generic [ref=e529]: 男
+                    - cell "36岁" [ref=e530]:
+                      - generic [ref=e531]: 36岁
+                    - cell "16812345678" [ref=e532]:
+                      - generic [ref=e533]: "16812345678"
+                    - cell "AJY202605010011" [ref=e534]:
+                      - generic [ref=e535]: AJY202605010011
+                    - cell "47" [ref=e536]:
+                      - generic [ref=e537]: "47"
+                    - cell "170" [ref=e538]:
+                      - generic [ref=e539]: "170"
+                    - cell "55" [ref=e540]:
+                      - generic [ref=e541]: "55"
+                    - cell "-" [ref=e542]:
+                      - generic [ref=e543]: "-"
+                    - cell "-" [ref=e544]:
+                      - generic [ref=e545]: "-"
+                    - cell "已生成" [ref=e546]:
+                      - generic [ref=e549]: 已生成
+                    - cell "2026-04-15T10:09:04" [ref=e550]:
+                      - generic [ref=e551]: 2026-04-15T10:09:04
+                    - cell "编辑 设备管理 删除" [ref=e552]:
+                      - generic [ref=e553]:
+                        - button "编辑" [ref=e554] [cursor=pointer]:
+                          - generic [ref=e555]: 编辑
+                        - button "设备管理" [ref=e556] [cursor=pointer]:
+                          - generic [ref=e557]: 设备管理
+                        - button "删除" [ref=e558] [cursor=pointer]:
+                          - generic [ref=e559]: 删除
+                  - row "选择当前行 2 1 贾璞 男 25岁 17320687388 AJY202605010011 43 85 60 180 130 已生成 2026-04-09T17:19:11 编辑 设备管理 删除" [ref=e560]:
+                    - cell "选择当前行" [ref=e561]:
+                      - generic "选择当前行" [ref=e563] [cursor=pointer]:
+                        - generic [ref=e564]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e566]:
+                      - generic [ref=e568]: "2"
+                    - cell "1" [ref=e569]:
+                      - generic [ref=e570]: "1"
+                    - cell "贾璞" [ref=e571]:
+                      - generic [ref=e572]: 贾璞
+                    - cell "男" [ref=e573]:
+                      - generic [ref=e576]: 男
+                    - cell "25岁" [ref=e577]:
+                      - generic [ref=e578]: 25岁
+                    - cell "17320687388" [ref=e579]:
+                      - generic [ref=e580]: "17320687388"
+                    - cell "AJY202605010011" [ref=e581]:
+                      - generic [ref=e582]: AJY202605010011
+                    - cell "43" [ref=e583]:
+                      - generic [ref=e584]: "43"
+                    - cell "85" [ref=e585]:
+                      - generic [ref=e586]: "85"
+                    - cell "60" [ref=e587]:
+                      - generic [ref=e588]: "60"
+                    - cell "180" [ref=e589]:
+                      - generic [ref=e590]: "180"
+                    - cell "130" [ref=e591]:
+                      - generic [ref=e592]: "130"
+                    - cell "已生成" [ref=e593]:
+                      - generic [ref=e596]: 已生成
+                    - cell "2026-04-09T17:19:11" [ref=e597]:
+                      - generic [ref=e598]: 2026-04-09T17:19:11
+                    - cell "编辑 设备管理 删除" [ref=e599]:
+                      - generic [ref=e600]:
+                        - button "编辑" [ref=e601] [cursor=pointer]:
+                          - generic [ref=e602]: 编辑
+                        - button "设备管理" [ref=e603] [cursor=pointer]:
+                          - generic [ref=e604]: 设备管理
+                        - button "删除" [ref=e605] [cursor=pointer]:
+                          - generic [ref=e606]: 删除
+            - generic [ref=e608]:
+              - generic [ref=e609]: 共 2 条
+              - generic [ref=e612] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e614]
+                  - generic [ref=e615]: 10条/页
+                - img [ref=e618]
+              - button "上一页" [disabled] [ref=e620]:
+                - generic:
+                  - img
+              - list [ref=e621]:
+                - listitem "第 1 页" [ref=e622]: "1"
+              - button "下一页" [disabled] [ref=e623]:
+                - generic:
+                  - img
+              - generic [ref=e624]:
+                - generic [ref=e625]: 前往
+                - spinbutton "页" [ref=e628]: "1"
+                - generic [ref=e629]: 页

+ 129 - 0
.playwright-mcp/page-2026-04-28T11-11-39-453Z.yml

@@ -0,0 +1,129 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "账号管理" [ref=e58]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e71]:
+        - generic [ref=e74]:
+          - generic [ref=e75]:
+            - generic [ref=e76]: 手机号
+            - textbox "手机号" [ref=e80]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e81]:
+            - generic [ref=e82]: 昵称
+            - textbox "昵称" [ref=e86]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e87]:
+            - generic [ref=e88]: 用户类型
+            - generic [ref=e91] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e93]
+                - generic [ref=e94]: 全部
+              - img [ref=e97]
+          - generic [ref=e99]:
+            - generic [ref=e100]: 状态
+            - generic [ref=e103] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e105]
+                - generic [ref=e106]: 全部
+              - img [ref=e109]
+          - generic [ref=e112]:
+            - button "搜索" [ref=e113] [cursor=pointer]:
+              - img [ref=e115]
+              - generic [ref=e117]: 搜索
+            - button "重置" [ref=e118] [cursor=pointer]:
+              - img [ref=e120]
+              - generic [ref=e122]: 重置
+        - generic [ref=e123]:
+          - generic [ref=e125]:
+            - generic [ref=e126]: 账号管理列表
+            - button "新增用户" [ref=e127] [cursor=pointer]:
+              - img [ref=e129]
+              - generic [ref=e131]: 新增用户
+          - generic [ref=e132]:
+            - generic [ref=e133]:
+              - generic [ref=e134]:
+                - table [ref=e136]:
+                  - rowgroup [ref=e147]:
+                    - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e148]:
+                      - columnheader "#" [ref=e149]:
+                        - generic [ref=e150]: "#"
+                      - columnheader "手机号" [ref=e151]:
+                        - generic [ref=e152]: 手机号
+                      - columnheader "昵称" [ref=e153]:
+                        - generic [ref=e154]: 昵称
+                      - columnheader "用户类型" [ref=e155]:
+                        - generic [ref=e156]: 用户类型
+                      - columnheader "状态" [ref=e157]:
+                        - generic [ref=e158]: 状态
+                      - columnheader "绑定设备" [ref=e159]:
+                        - generic [ref=e160]: 绑定设备
+                      - columnheader "子用户" [ref=e161]:
+                        - generic [ref=e162]: 子用户
+                      - columnheader "注册时间" [ref=e163]:
+                        - generic [ref=e164]: 注册时间
+                      - columnheader "操作" [ref=e165]:
+                        - generic [ref=e166]: 操作
+                - generic [ref=e170]:
+                  - table:
+                    - rowgroup
+                  - generic [ref=e172]: 暂无数据
+              - img [ref=e176]
+            - generic [ref=e178]:
+              - generic [ref=e179]: 共 0 条
+              - generic [ref=e182] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e184]
+                  - generic [ref=e185]: 10条/页
+                - img [ref=e188]
+              - button "上一页" [disabled] [ref=e190]:
+                - generic:
+                  - img
+              - list [ref=e191]:
+                - listitem "第 1 页" [ref=e192]: "1"
+              - button "下一页" [disabled] [ref=e193]:
+                - generic:
+                  - img
+              - generic [ref=e194]:
+                - generic [ref=e195]: 前往
+                - spinbutton "页" [ref=e198]: "1"
+                - generic [ref=e199]: 页

+ 529 - 0
.playwright-mcp/page-2026-04-28T11-14-42-909Z.yml

@@ -0,0 +1,529 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [active] [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "穴位管理" [ref=e239]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e240]:
+        - generic [ref=e243]:
+          - generic [ref=e244]:
+            - generic [ref=e245]: 穴位名称
+            - textbox "穴位名称" [ref=e249]:
+              - /placeholder: 请输入穴位名称
+          - generic [ref=e250]:
+            - generic [ref=e251]: 所属经络
+            - generic [ref=e254] [cursor=pointer]:
+              - generic:
+                - combobox "所属经络" [ref=e256]
+                - generic [ref=e257]: 全部
+              - img [ref=e260]
+          - generic [ref=e262]:
+            - generic [ref=e263]: 状态
+            - generic [ref=e266] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e268]
+                - generic [ref=e269]: 全部
+              - img [ref=e272]
+          - generic [ref=e274]:
+            - generic [ref=e275]: 解析状态
+            - generic [ref=e278] [cursor=pointer]:
+              - generic:
+                - combobox "解析状态" [ref=e280]
+                - generic [ref=e281]: 全部
+              - img [ref=e284]
+          - generic [ref=e287]:
+            - button "查询" [ref=e288] [cursor=pointer]:
+              - img [ref=e290]
+              - generic [ref=e292]: 查询
+            - button "重置" [ref=e293] [cursor=pointer]:
+              - img [ref=e295]
+              - generic [ref=e297]: 重置
+        - generic [ref=e298]:
+          - generic [ref=e300]:
+            - generic [ref=e301]: 穴位列表
+            - generic [ref=e302]:
+              - button "导入" [ref=e305] [cursor=pointer]:
+                - button "导入" [ref=e306]:
+                  - img [ref=e308]
+                  - generic [ref=e310]: 导入
+              - button "导出 Excel" [ref=e311] [cursor=pointer]:
+                - img [ref=e313]
+                - generic [ref=e315]: 导出 Excel
+              - button "新增" [ref=e316] [cursor=pointer]:
+                - img [ref=e318]
+                - generic [ref=e320]: 新增
+              - button "批量删除" [disabled] [ref=e321]:
+                - img [ref=e323]
+                - generic [ref=e325]: 批量删除
+          - generic [ref=e326]:
+            - generic [ref=e328]:
+              - table [ref=e330]:
+                - rowgroup [ref=e346]:
+                  - row "选择所有行 序号 穴位编码 穴位名称 所属经络 身体部位 分区 Y轴术语 解析状态 解析说明 产品穴位 功效 状态 操作" [ref=e347]:
+                    - columnheader "选择所有行" [ref=e348]:
+                      - generic "选择所有行" [ref=e350] [cursor=pointer]:
+                        - generic [ref=e351]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e353]:
+                      - generic [ref=e354]: 序号
+                    - columnheader "穴位编码" [ref=e355]:
+                      - generic [ref=e356]: 穴位编码
+                    - columnheader "穴位名称" [ref=e357]:
+                      - generic [ref=e358]: 穴位名称
+                    - columnheader "所属经络" [ref=e359]:
+                      - generic [ref=e360]: 所属经络
+                    - columnheader "身体部位" [ref=e361]:
+                      - generic [ref=e362]: 身体部位
+                    - columnheader "分区" [ref=e363]:
+                      - generic [ref=e364]: 分区
+                    - columnheader "Y轴术语" [ref=e365]:
+                      - generic [ref=e366]: Y轴术语
+                    - columnheader "解析状态" [ref=e367]:
+                      - generic [ref=e368]: 解析状态
+                    - columnheader "解析说明" [ref=e369]:
+                      - generic [ref=e370]: 解析说明
+                    - columnheader "产品穴位" [ref=e371]:
+                      - generic [ref=e372]: 产品穴位
+                    - columnheader "功效" [ref=e373]:
+                      - generic [ref=e374]: 功效
+                    - columnheader "状态" [ref=e375]:
+                      - generic [ref=e376]: 状态
+                    - columnheader "操作" [ref=e377]:
+                      - generic [ref=e378]: 操作
+              - table [ref=e383]:
+                - rowgroup [ref=e399]:
+                  - row "选择当前行 1 feijing-07 列缺 手太阴肺经 上肢 前臂前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 是 - 活跃 查看 编辑 删除" [ref=e400]:
+                    - cell "选择当前行" [ref=e401]:
+                      - generic "选择当前行" [ref=e403] [cursor=pointer]:
+                        - generic [ref=e404]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e406]:
+                      - generic [ref=e408]: "1"
+                    - cell "feijing-07" [ref=e409]:
+                      - generic [ref=e410]: feijing-07
+                    - cell "列缺" [ref=e411]:
+                      - generic [ref=e412]: 列缺
+                    - cell "手太阴肺经" [ref=e413]:
+                      - generic [ref=e414]: 手太阴肺经
+                    - cell "上肢" [ref=e415]:
+                      - generic [ref=e416]: 上肢
+                    - cell "前臂前区" [ref=e417]:
+                      - generic [ref=e418]: 前臂前区
+                    - cell "-" [ref=e419]:
+                      - generic [ref=e420]: "-"
+                    - cell "待复核" [ref=e421]:
+                      - generic [ref=e424]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e425]:
+                      - generic [ref=e426]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "是" [ref=e427]:
+                      - generic [ref=e430]: 是
+                    - cell "-" [ref=e431]:
+                      - generic [ref=e432]: "-"
+                    - cell "活跃" [ref=e433]:
+                      - generic [ref=e436]: 活跃
+                    - cell "查看 编辑 删除" [ref=e437]:
+                      - generic [ref=e438]:
+                        - button "查看" [ref=e439] [cursor=pointer]:
+                          - generic [ref=e440]: 查看
+                        - button "编辑" [ref=e441] [cursor=pointer]:
+                          - generic [ref=e442]: 编辑
+                        - button "删除" [ref=e443] [cursor=pointer]:
+                          - generic [ref=e444]: 删除
+                  - row "选择当前行 2 feijing-09 太渊 手太阴肺经 上肢 手腕前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e445]:
+                    - cell "选择当前行" [ref=e446]:
+                      - generic "选择当前行" [ref=e448] [cursor=pointer]:
+                        - generic [ref=e449]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e451]:
+                      - generic [ref=e453]: "2"
+                    - cell "feijing-09" [ref=e454]:
+                      - generic [ref=e455]: feijing-09
+                    - cell "太渊" [ref=e456]:
+                      - generic [ref=e457]: 太渊
+                    - cell "手太阴肺经" [ref=e458]:
+                      - generic [ref=e459]: 手太阴肺经
+                    - cell "上肢" [ref=e460]:
+                      - generic [ref=e461]: 上肢
+                    - cell "手腕前区" [ref=e462]:
+                      - generic [ref=e463]: 手腕前区
+                    - cell "-" [ref=e464]:
+                      - generic [ref=e465]: "-"
+                    - cell "待复核" [ref=e466]:
+                      - generic [ref=e469]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e470]:
+                      - generic [ref=e471]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e472]:
+                      - generic [ref=e475]: 否
+                    - cell "-" [ref=e476]:
+                      - generic [ref=e477]: "-"
+                    - cell "活跃" [ref=e478]:
+                      - generic [ref=e481]: 活跃
+                    - cell "查看 编辑 删除" [ref=e482]:
+                      - generic [ref=e483]:
+                        - button "查看" [ref=e484] [cursor=pointer]:
+                          - generic [ref=e485]: 查看
+                        - button "编辑" [ref=e486] [cursor=pointer]:
+                          - generic [ref=e487]: 编辑
+                        - button "删除" [ref=e488] [cursor=pointer]:
+                          - generic [ref=e489]: 删除
+                  - row "选择当前行 3 feijing-02 云门 手太阴肺经 胸部 前胸区 - 待复核 已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准 否 - 活跃 查看 编辑 删除" [ref=e490]:
+                    - cell "选择当前行" [ref=e491]:
+                      - generic "选择当前行" [ref=e493] [cursor=pointer]:
+                        - generic [ref=e494]:
+                          - checkbox "选择当前行"
+                    - cell "3" [ref=e496]:
+                      - generic [ref=e498]: "3"
+                    - cell "feijing-02" [ref=e499]:
+                      - generic [ref=e500]: feijing-02
+                    - cell "云门" [ref=e501]:
+                      - generic [ref=e502]: 云门
+                    - cell "手太阴肺经" [ref=e503]:
+                      - generic [ref=e504]: 手太阴肺经
+                    - cell "胸部" [ref=e505]:
+                      - generic [ref=e506]: 胸部
+                    - cell "前胸区" [ref=e507]:
+                      - generic [ref=e508]: 前胸区
+                    - cell "-" [ref=e509]:
+                      - generic [ref=e510]: "-"
+                    - cell "待复核" [ref=e511]:
+                      - generic [ref=e514]: 待复核
+                    - cell "已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准" [ref=e515]:
+                      - generic [ref=e516]: 已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准
+                    - cell "否" [ref=e517]:
+                      - generic [ref=e520]: 否
+                    - cell "-" [ref=e521]:
+                      - generic [ref=e522]: "-"
+                    - cell "活跃" [ref=e523]:
+                      - generic [ref=e526]: 活跃
+                    - cell "查看 编辑 删除" [ref=e527]:
+                      - generic [ref=e528]:
+                        - button "查看" [ref=e529] [cursor=pointer]:
+                          - generic [ref=e530]: 查看
+                        - button "编辑" [ref=e531] [cursor=pointer]:
+                          - generic [ref=e532]: 编辑
+                        - button "删除" [ref=e533] [cursor=pointer]:
+                          - generic [ref=e534]: 删除
+                  - row "选择当前行 4 feijing-08 经渠 手太阴肺经 上肢 前臂前区 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e535]:
+                    - cell "选择当前行" [ref=e536]:
+                      - generic "选择当前行" [ref=e538] [cursor=pointer]:
+                        - generic [ref=e539]:
+                          - checkbox "选择当前行"
+                    - cell "4" [ref=e541]:
+                      - generic [ref=e543]: "4"
+                    - cell "feijing-08" [ref=e544]:
+                      - generic [ref=e545]: feijing-08
+                    - cell "经渠" [ref=e546]:
+                      - generic [ref=e547]: 经渠
+                    - cell "手太阴肺经" [ref=e548]:
+                      - generic [ref=e549]: 手太阴肺经
+                    - cell "上肢" [ref=e550]:
+                      - generic [ref=e551]: 上肢
+                    - cell "前臂前区" [ref=e552]:
+                      - generic [ref=e553]: 前臂前区
+                    - cell "-" [ref=e554]:
+                      - generic [ref=e555]: "-"
+                    - cell "暂不支持" [ref=e556]:
+                      - generic [ref=e559]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e560]:
+                      - generic [ref=e561]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e562]:
+                      - generic [ref=e565]: 否
+                    - cell "-" [ref=e566]:
+                      - generic [ref=e567]: "-"
+                    - cell "活跃" [ref=e568]:
+                      - generic [ref=e571]: 活跃
+                    - cell "查看 编辑 删除" [ref=e572]:
+                      - generic [ref=e573]:
+                        - button "查看" [ref=e574] [cursor=pointer]:
+                          - generic [ref=e575]: 查看
+                        - button "编辑" [ref=e576] [cursor=pointer]:
+                          - generic [ref=e577]: 编辑
+                        - button "删除" [ref=e578] [cursor=pointer]:
+                          - generic [ref=e579]: 删除
+                  - row "选择当前行 5 feijing-01 中府 手太阴肺经 胸部 前胸区 - 待复核 已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准 是 - 活跃 查看 编辑 删除" [ref=e580]:
+                    - cell "选择当前行" [ref=e581]:
+                      - generic "选择当前行" [ref=e583] [cursor=pointer]:
+                        - generic [ref=e584]:
+                          - checkbox "选择当前行"
+                    - cell "5" [ref=e586]:
+                      - generic [ref=e588]: "5"
+                    - cell "feijing-01" [ref=e589]:
+                      - generic [ref=e590]: feijing-01
+                    - cell "中府" [ref=e591]:
+                      - generic [ref=e592]: 中府
+                    - cell "手太阴肺经" [ref=e593]:
+                      - generic [ref=e594]: 手太阴肺经
+                    - cell "胸部" [ref=e595]:
+                      - generic [ref=e596]: 胸部
+                    - cell "前胸区" [ref=e597]:
+                      - generic [ref=e598]: 前胸区
+                    - cell "-" [ref=e599]:
+                      - generic [ref=e600]: "-"
+                    - cell "待复核" [ref=e601]:
+                      - generic [ref=e604]: 待复核
+                    - cell "已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准" [ref=e605]:
+                      - generic [ref=e606]: 已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准
+                    - cell "是" [ref=e607]:
+                      - generic [ref=e610]: 是
+                    - cell "-" [ref=e611]:
+                      - generic [ref=e612]: "-"
+                    - cell "活跃" [ref=e613]:
+                      - generic [ref=e616]: 活跃
+                    - cell "查看 编辑 删除" [ref=e617]:
+                      - generic [ref=e618]:
+                        - button "查看" [ref=e619] [cursor=pointer]:
+                          - generic [ref=e620]: 查看
+                        - button "编辑" [ref=e621] [cursor=pointer]:
+                          - generic [ref=e622]: 编辑
+                        - button "删除" [ref=e623] [cursor=pointer]:
+                          - generic [ref=e624]: 删除
+                  - row "选择当前行 6 jingwai-51 气端 经外奇穴 下肢 足趾 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e625]:
+                    - cell "选择当前行" [ref=e626]:
+                      - generic "选择当前行" [ref=e628] [cursor=pointer]:
+                        - generic [ref=e629]:
+                          - checkbox "选择当前行"
+                    - cell "6" [ref=e631]:
+                      - generic [ref=e633]: "6"
+                    - cell "jingwai-51" [ref=e634]:
+                      - generic [ref=e635]: jingwai-51
+                    - cell "气端" [ref=e636]:
+                      - generic [ref=e637]: 气端
+                    - cell "经外奇穴" [ref=e638]:
+                      - generic [ref=e639]: 经外奇穴
+                    - cell "下肢" [ref=e640]:
+                      - generic [ref=e641]: 下肢
+                    - cell "足趾" [ref=e642]:
+                      - generic [ref=e643]: 足趾
+                    - cell "-" [ref=e644]:
+                      - generic [ref=e645]: "-"
+                    - cell "暂不支持" [ref=e646]:
+                      - generic [ref=e649]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e650]:
+                      - generic [ref=e651]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e652]:
+                      - generic [ref=e655]: 否
+                    - cell "-" [ref=e656]:
+                      - generic [ref=e657]: "-"
+                    - cell "活跃" [ref=e658]:
+                      - generic [ref=e661]: 活跃
+                    - cell "查看 编辑 删除" [ref=e662]:
+                      - generic [ref=e663]:
+                        - button "查看" [ref=e664] [cursor=pointer]:
+                          - generic [ref=e665]: 查看
+                        - button "编辑" [ref=e666] [cursor=pointer]:
+                          - generic [ref=e667]: 编辑
+                        - button "删除" [ref=e668] [cursor=pointer]:
+                          - generic [ref=e669]: 删除
+                  - row "选择当前行 7 feijing-03 天府 手太阴肺经 上肢 上臂前区 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e670]:
+                    - cell "选择当前行" [ref=e671]:
+                      - generic "选择当前行" [ref=e673] [cursor=pointer]:
+                        - generic [ref=e674]:
+                          - checkbox "选择当前行"
+                    - cell "7" [ref=e676]:
+                      - generic [ref=e678]: "7"
+                    - cell "feijing-03" [ref=e679]:
+                      - generic [ref=e680]: feijing-03
+                    - cell "天府" [ref=e681]:
+                      - generic [ref=e682]: 天府
+                    - cell "手太阴肺经" [ref=e683]:
+                      - generic [ref=e684]: 手太阴肺经
+                    - cell "上肢" [ref=e685]:
+                      - generic [ref=e686]: 上肢
+                    - cell "上臂前区" [ref=e687]:
+                      - generic [ref=e688]: 上臂前区
+                    - cell "-" [ref=e689]:
+                      - generic [ref=e690]: "-"
+                    - cell "暂不支持" [ref=e691]:
+                      - generic [ref=e694]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e695]:
+                      - generic [ref=e696]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e697]:
+                      - generic [ref=e700]: 否
+                    - cell "-" [ref=e701]:
+                      - generic [ref=e702]: "-"
+                    - cell "活跃" [ref=e703]:
+                      - generic [ref=e706]: 活跃
+                    - cell "查看 编辑 删除" [ref=e707]:
+                      - generic [ref=e708]:
+                        - button "查看" [ref=e709] [cursor=pointer]:
+                          - generic [ref=e710]: 查看
+                        - button "编辑" [ref=e711] [cursor=pointer]:
+                          - generic [ref=e712]: 编辑
+                        - button "删除" [ref=e713] [cursor=pointer]:
+                          - generic [ref=e714]: 删除
+                  - row "选择当前行 8 feijing-10 鱼际 手太阴肺经 上肢 手外侧 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e715]:
+                    - cell "选择当前行" [ref=e716]:
+                      - generic "选择当前行" [ref=e718] [cursor=pointer]:
+                        - generic [ref=e719]:
+                          - checkbox "选择当前行"
+                    - cell "8" [ref=e721]:
+                      - generic [ref=e723]: "8"
+                    - cell "feijing-10" [ref=e724]:
+                      - generic [ref=e725]: feijing-10
+                    - cell "鱼际" [ref=e726]:
+                      - generic [ref=e727]: 鱼际
+                    - cell "手太阴肺经" [ref=e728]:
+                      - generic [ref=e729]: 手太阴肺经
+                    - cell "上肢" [ref=e730]:
+                      - generic [ref=e731]: 上肢
+                    - cell "手外侧" [ref=e732]:
+                      - generic [ref=e733]: 手外侧
+                    - cell "-" [ref=e734]:
+                      - generic [ref=e735]: "-"
+                    - cell "待复核" [ref=e736]:
+                      - generic [ref=e739]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e740]:
+                      - generic [ref=e741]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e742]:
+                      - generic [ref=e745]: 否
+                    - cell "-" [ref=e746]:
+                      - generic [ref=e747]: "-"
+                    - cell "活跃" [ref=e748]:
+                      - generic [ref=e751]: 活跃
+                    - cell "查看 编辑 删除" [ref=e752]:
+                      - generic [ref=e753]:
+                        - button "查看" [ref=e754] [cursor=pointer]:
+                          - generic [ref=e755]: 查看
+                        - button "编辑" [ref=e756] [cursor=pointer]:
+                          - generic [ref=e757]: 编辑
+                        - button "删除" [ref=e758] [cursor=pointer]:
+                          - generic [ref=e759]: 删除
+                  - row "选择当前行 9 feijing-06 孔最 手太阴肺经 上肢 前臂前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 是 - 活跃 查看 编辑 删除" [ref=e760]:
+                    - cell "选择当前行" [ref=e761]:
+                      - generic "选择当前行" [ref=e763] [cursor=pointer]:
+                        - generic [ref=e764]:
+                          - checkbox "选择当前行"
+                    - cell "9" [ref=e766]:
+                      - generic [ref=e768]: "9"
+                    - cell "feijing-06" [ref=e769]:
+                      - generic [ref=e770]: feijing-06
+                    - cell "孔最" [ref=e771]:
+                      - generic [ref=e772]: 孔最
+                    - cell "手太阴肺经" [ref=e773]:
+                      - generic [ref=e774]: 手太阴肺经
+                    - cell "上肢" [ref=e775]:
+                      - generic [ref=e776]: 上肢
+                    - cell "前臂前区" [ref=e777]:
+                      - generic [ref=e778]: 前臂前区
+                    - cell "-" [ref=e779]:
+                      - generic [ref=e780]: "-"
+                    - cell "待复核" [ref=e781]:
+                      - generic [ref=e784]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e785]:
+                      - generic [ref=e786]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "是" [ref=e787]:
+                      - generic [ref=e790]: 是
+                    - cell "-" [ref=e791]:
+                      - generic [ref=e792]: "-"
+                    - cell "活跃" [ref=e793]:
+                      - generic [ref=e796]: 活跃
+                    - cell "查看 编辑 删除" [ref=e797]:
+                      - generic [ref=e798]:
+                        - button "查看" [ref=e799] [cursor=pointer]:
+                          - generic [ref=e800]: 查看
+                        - button "编辑" [ref=e801] [cursor=pointer]:
+                          - generic [ref=e802]: 编辑
+                        - button "删除" [ref=e803] [cursor=pointer]:
+                          - generic [ref=e804]: 删除
+                  - row "选择当前行 10 feijing-05 尺泽 手太阴肺经 上肢 肘部前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e805]:
+                    - cell "选择当前行" [ref=e806]:
+                      - generic "选择当前行" [ref=e808] [cursor=pointer]:
+                        - generic [ref=e809]:
+                          - checkbox "选择当前行"
+                    - cell "10" [ref=e811]:
+                      - generic [ref=e813]: "10"
+                    - cell "feijing-05" [ref=e814]:
+                      - generic [ref=e815]: feijing-05
+                    - cell "尺泽" [ref=e816]:
+                      - generic [ref=e817]: 尺泽
+                    - cell "手太阴肺经" [ref=e818]:
+                      - generic [ref=e819]: 手太阴肺经
+                    - cell "上肢" [ref=e820]:
+                      - generic [ref=e821]: 上肢
+                    - cell "肘部前区" [ref=e822]:
+                      - generic [ref=e823]: 肘部前区
+                    - cell "-" [ref=e824]:
+                      - generic [ref=e825]: "-"
+                    - cell "待复核" [ref=e826]:
+                      - generic [ref=e829]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e830]:
+                      - generic [ref=e831]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e832]:
+                      - generic [ref=e835]: 否
+                    - cell "-" [ref=e836]:
+                      - generic [ref=e837]: "-"
+                    - cell "活跃" [ref=e838]:
+                      - generic [ref=e841]: 活跃
+                    - cell "查看 编辑 删除" [ref=e842]:
+                      - generic [ref=e843]:
+                        - button "查看" [ref=e844] [cursor=pointer]:
+                          - generic [ref=e845]: 查看
+                        - button "编辑" [ref=e846] [cursor=pointer]:
+                          - generic [ref=e847]: 编辑
+                        - button "删除" [ref=e848] [cursor=pointer]:
+                          - generic [ref=e849]: 删除
+            - generic [ref=e851]:
+              - generic [ref=e852]: 共 425 条
+              - generic [ref=e855] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e857]
+                  - generic [ref=e858]: 10条/页
+                - img [ref=e861]
+              - button "上一页" [disabled] [ref=e863]:
+                - generic:
+                  - img
+              - list [ref=e864]:
+                - listitem "第 1 页" [ref=e865]: "1"
+                - listitem "第 2 页" [ref=e866] [cursor=pointer]: "2"
+                - listitem "第 3 页" [ref=e867] [cursor=pointer]: "3"
+                - listitem "第 4 页" [ref=e868] [cursor=pointer]: "4"
+                - listitem "第 5 页" [ref=e869] [cursor=pointer]: "5"
+                - listitem "第 6 页" [ref=e870] [cursor=pointer]: "6"
+                - listitem "向后 5 页" [ref=e871] [cursor=pointer]:
+                  - img
+                - listitem "第 43 页" [ref=e872] [cursor=pointer]: "43"
+              - button "下一页" [ref=e873] [cursor=pointer]:
+                - generic:
+                  - img
+              - generic [ref=e874]:
+                - generic [ref=e875]: 前往
+                - spinbutton "页" [ref=e878]: "1"
+                - generic [ref=e879]: 页

+ 442 - 0
.playwright-mcp/page-2026-04-28T11-16-10-702Z.yml

@@ -0,0 +1,442 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [active] [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "方案管理" [ref=e880]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e881]:
+        - generic [ref=e884]:
+          - generic [ref=e885]:
+            - generic [ref=e886]: 方案名称
+            - textbox "方案名称" [ref=e890]:
+              - /placeholder: 请输入方案名称
+          - generic [ref=e891]:
+            - generic [ref=e892]: 模式类型
+            - generic [ref=e895] [cursor=pointer]:
+              - generic:
+                - combobox "模式类型" [ref=e897]
+                - generic [ref=e898]: 全部
+              - img [ref=e901]
+          - generic [ref=e903]:
+            - generic [ref=e904]: 功效类型
+            - generic [ref=e907] [cursor=pointer]:
+              - generic:
+                - combobox "功效类型" [ref=e909]
+                - generic [ref=e910]: 全部
+              - img [ref=e913]
+          - generic [ref=e915]:
+            - generic [ref=e916]: 创作人
+            - textbox "创作人" [ref=e920]:
+              - /placeholder: 请输入创作人
+          - generic [ref=e921]:
+            - generic [ref=e922]: 状态
+            - generic [ref=e925] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e927]
+                - generic [ref=e928]: 全部
+              - img [ref=e931]
+          - generic [ref=e933]:
+            - generic [ref=e934]: 创建时间
+            - generic [ref=e936]:
+              - img [ref=e938]
+              - combobox "创建时间" [ref=e940]
+              - generic [ref=e941]: ~
+              - combobox "结束日期" [ref=e942]
+          - generic [ref=e944]:
+            - button "查询" [ref=e945] [cursor=pointer]:
+              - img [ref=e947]
+              - generic [ref=e949]: 查询
+            - button "重置" [ref=e950] [cursor=pointer]:
+              - img [ref=e952]
+              - generic [ref=e954]: 重置
+        - generic [ref=e955]:
+          - generic [ref=e957]:
+            - generic [ref=e958]: 方案列表
+            - generic [ref=e959]:
+              - button "新增" [ref=e960] [cursor=pointer]:
+                - img [ref=e962]
+                - generic [ref=e964]: 新增
+              - button "批量发布" [disabled] [ref=e965]:
+                - generic [ref=e966]: 批量发布
+              - button "批量下架" [disabled] [ref=e967]:
+                - generic [ref=e968]: 批量下架
+              - button "批量删除" [disabled] [ref=e969]:
+                - img [ref=e971]
+                - generic [ref=e973]: 批量删除
+          - generic [ref=e974]:
+            - generic [ref=e976]:
+              - table [ref=e978]:
+                - rowgroup [ref=e990]:
+                  - row "选择所有行 序号 方案ID 方案名称 模式类型 功效类型 创作人 状态 创建时间 操作" [ref=e991]:
+                    - columnheader "选择所有行" [ref=e992]:
+                      - generic "选择所有行" [ref=e994] [cursor=pointer]:
+                        - generic [ref=e995]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e997]:
+                      - generic [ref=e998]: 序号
+                    - columnheader "方案ID" [ref=e999]:
+                      - generic [ref=e1000]: 方案ID
+                    - columnheader "方案名称" [ref=e1001]:
+                      - generic [ref=e1002]: 方案名称
+                    - columnheader "模式类型" [ref=e1003]:
+                      - generic [ref=e1004]: 模式类型
+                    - columnheader "功效类型" [ref=e1005]:
+                      - generic [ref=e1006]: 功效类型
+                    - columnheader "创作人" [ref=e1007]:
+                      - generic [ref=e1008]: 创作人
+                    - columnheader "状态" [ref=e1009]:
+                      - generic [ref=e1010]: 状态
+                    - columnheader "创建时间" [ref=e1011]:
+                      - generic [ref=e1012]: 创建时间
+                    - columnheader "操作" [ref=e1013]:
+                      - generic [ref=e1014]: 操作
+              - table [ref=e1019]:
+                - rowgroup [ref=e1031]:
+                  - row "选择当前行 1 S009 专业扶阳方案 专业模式 扶阳 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1032]:
+                    - cell "选择当前行" [ref=e1033]:
+                      - generic "选择当前行" [ref=e1035] [cursor=pointer]:
+                        - generic [ref=e1036]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e1038]:
+                      - generic [ref=e1040]: "1"
+                    - cell "S009" [ref=e1041]:
+                      - generic [ref=e1042]: S009
+                    - cell "专业扶阳方案" [ref=e1043]:
+                      - generic [ref=e1044]: 专业扶阳方案
+                    - cell "专业模式" [ref=e1045]:
+                      - generic [ref=e1048]: 专业模式
+                    - cell "扶阳" [ref=e1049]:
+                      - generic [ref=e1050]: 扶阳
+                    - cell "ADMIN" [ref=e1051]:
+                      - generic [ref=e1052]: ADMIN
+                    - cell "已发布" [ref=e1053]:
+                      - generic [ref=e1056]: 已发布
+                    - cell "2026-04-15" [ref=e1057]:
+                      - generic [ref=e1058]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1059]:
+                      - generic [ref=e1060]:
+                        - button "查看" [ref=e1061] [cursor=pointer]:
+                          - generic [ref=e1062]: 查看
+                        - button "编辑" [ref=e1063] [cursor=pointer]:
+                          - generic [ref=e1064]: 编辑
+                        - button "下架" [ref=e1065] [cursor=pointer]:
+                          - generic [ref=e1066]: 下架
+                  - row "选择当前行 2 S008 专业养颜方案 专业模式 养颜 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1067]:
+                    - cell "选择当前行" [ref=e1068]:
+                      - generic "选择当前行" [ref=e1070] [cursor=pointer]:
+                        - generic [ref=e1071]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e1073]:
+                      - generic [ref=e1075]: "2"
+                    - cell "S008" [ref=e1076]:
+                      - generic [ref=e1077]: S008
+                    - cell "专业养颜方案" [ref=e1078]:
+                      - generic [ref=e1079]: 专业养颜方案
+                    - cell "专业模式" [ref=e1080]:
+                      - generic [ref=e1083]: 专业模式
+                    - cell "养颜" [ref=e1084]:
+                      - generic [ref=e1085]: 养颜
+                    - cell "ADMIN" [ref=e1086]:
+                      - generic [ref=e1087]: ADMIN
+                    - cell "已发布" [ref=e1088]:
+                      - generic [ref=e1091]: 已发布
+                    - cell "2026-04-15" [ref=e1092]:
+                      - generic [ref=e1093]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1094]:
+                      - generic [ref=e1095]:
+                        - button "查看" [ref=e1096] [cursor=pointer]:
+                          - generic [ref=e1097]: 查看
+                        - button "编辑" [ref=e1098] [cursor=pointer]:
+                          - generic [ref=e1099]: 编辑
+                        - button "下架" [ref=e1100] [cursor=pointer]:
+                          - generic [ref=e1101]: 下架
+                  - row "选择当前行 3 S007 专业化痰方案 专业模式 化痰 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1102]:
+                    - cell "选择当前行" [ref=e1103]:
+                      - generic "选择当前行" [ref=e1105] [cursor=pointer]:
+                        - generic [ref=e1106]:
+                          - checkbox "选择当前行"
+                    - cell "3" [ref=e1108]:
+                      - generic [ref=e1110]: "3"
+                    - cell "S007" [ref=e1111]:
+                      - generic [ref=e1112]: S007
+                    - cell "专业化痰方案" [ref=e1113]:
+                      - generic [ref=e1114]: 专业化痰方案
+                    - cell "专业模式" [ref=e1115]:
+                      - generic [ref=e1118]: 专业模式
+                    - cell "化痰" [ref=e1119]:
+                      - generic [ref=e1120]: 化痰
+                    - cell "ADMIN" [ref=e1121]:
+                      - generic [ref=e1122]: ADMIN
+                    - cell "已发布" [ref=e1123]:
+                      - generic [ref=e1126]: 已发布
+                    - cell "2026-04-15" [ref=e1127]:
+                      - generic [ref=e1128]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1129]:
+                      - generic [ref=e1130]:
+                        - button "查看" [ref=e1131] [cursor=pointer]:
+                          - generic [ref=e1132]: 查看
+                        - button "编辑" [ref=e1133] [cursor=pointer]:
+                          - generic [ref=e1134]: 编辑
+                        - button "下架" [ref=e1135] [cursor=pointer]:
+                          - generic [ref=e1136]: 下架
+                  - row "选择当前行 4 S006 专业活血方案 专业模式 活血 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1137]:
+                    - cell "选择当前行" [ref=e1138]:
+                      - generic "选择当前行" [ref=e1140] [cursor=pointer]:
+                        - generic [ref=e1141]:
+                          - checkbox "选择当前行"
+                    - cell "4" [ref=e1143]:
+                      - generic [ref=e1145]: "4"
+                    - cell "S006" [ref=e1146]:
+                      - generic [ref=e1147]: S006
+                    - cell "专业活血方案" [ref=e1148]:
+                      - generic [ref=e1149]: 专业活血方案
+                    - cell "专业模式" [ref=e1150]:
+                      - generic [ref=e1153]: 专业模式
+                    - cell "活血" [ref=e1154]:
+                      - generic [ref=e1155]: 活血
+                    - cell "ADMIN" [ref=e1156]:
+                      - generic [ref=e1157]: ADMIN
+                    - cell "已发布" [ref=e1158]:
+                      - generic [ref=e1161]: 已发布
+                    - cell "2026-04-15" [ref=e1162]:
+                      - generic [ref=e1163]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1164]:
+                      - generic [ref=e1165]:
+                        - button "查看" [ref=e1166] [cursor=pointer]:
+                          - generic [ref=e1167]: 查看
+                        - button "编辑" [ref=e1168] [cursor=pointer]:
+                          - generic [ref=e1169]: 编辑
+                        - button "下架" [ref=e1170] [cursor=pointer]:
+                          - generic [ref=e1171]: 下架
+                  - row "选择当前行 5 S005 专业化瘀方案 专业模式 化瘀 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1172]:
+                    - cell "选择当前行" [ref=e1173]:
+                      - generic "选择当前行" [ref=e1175] [cursor=pointer]:
+                        - generic [ref=e1176]:
+                          - checkbox "选择当前行"
+                    - cell "5" [ref=e1178]:
+                      - generic [ref=e1180]: "5"
+                    - cell "S005" [ref=e1181]:
+                      - generic [ref=e1182]: S005
+                    - cell "专业化瘀方案" [ref=e1183]:
+                      - generic [ref=e1184]: 专业化瘀方案
+                    - cell "专业模式" [ref=e1185]:
+                      - generic [ref=e1188]: 专业模式
+                    - cell "化瘀" [ref=e1189]:
+                      - generic [ref=e1190]: 化瘀
+                    - cell "ADMIN" [ref=e1191]:
+                      - generic [ref=e1192]: ADMIN
+                    - cell "已发布" [ref=e1193]:
+                      - generic [ref=e1196]: 已发布
+                    - cell "2026-04-15" [ref=e1197]:
+                      - generic [ref=e1198]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1199]:
+                      - generic [ref=e1200]:
+                        - button "查看" [ref=e1201] [cursor=pointer]:
+                          - generic [ref=e1202]: 查看
+                        - button "编辑" [ref=e1203] [cursor=pointer]:
+                          - generic [ref=e1204]: 编辑
+                        - button "下架" [ref=e1205] [cursor=pointer]:
+                          - generic [ref=e1206]: 下架
+                  - row "选择当前行 6 S004 专业祛风方案 专业模式 祛风 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1207]:
+                    - cell "选择当前行" [ref=e1208]:
+                      - generic "选择当前行" [ref=e1210] [cursor=pointer]:
+                        - generic [ref=e1211]:
+                          - checkbox "选择当前行"
+                    - cell "6" [ref=e1213]:
+                      - generic [ref=e1215]: "6"
+                    - cell "S004" [ref=e1216]:
+                      - generic [ref=e1217]: S004
+                    - cell "专业祛风方案" [ref=e1218]:
+                      - generic [ref=e1219]: 专业祛风方案
+                    - cell "专业模式" [ref=e1220]:
+                      - generic [ref=e1223]: 专业模式
+                    - cell "祛风" [ref=e1224]:
+                      - generic [ref=e1225]: 祛风
+                    - cell "ADMIN" [ref=e1226]:
+                      - generic [ref=e1227]: ADMIN
+                    - cell "已发布" [ref=e1228]:
+                      - generic [ref=e1231]: 已发布
+                    - cell "2026-04-15" [ref=e1232]:
+                      - generic [ref=e1233]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1234]:
+                      - generic [ref=e1235]:
+                        - button "查看" [ref=e1236] [cursor=pointer]:
+                          - generic [ref=e1237]: 查看
+                        - button "编辑" [ref=e1238] [cursor=pointer]:
+                          - generic [ref=e1239]: 编辑
+                        - button "下架" [ref=e1240] [cursor=pointer]:
+                          - generic [ref=e1241]: 下架
+                  - row "选择当前行 7 S001 一键艾灸(默认) 一键艾灸 - ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1242]:
+                    - cell "选择当前行" [ref=e1243]:
+                      - generic "选择当前行" [ref=e1245] [cursor=pointer]:
+                        - generic [ref=e1246]:
+                          - checkbox "选择当前行"
+                    - cell "7" [ref=e1248]:
+                      - generic [ref=e1250]: "7"
+                    - cell "S001" [ref=e1251]:
+                      - generic [ref=e1252]: S001
+                    - cell "一键艾灸(默认)" [ref=e1253]:
+                      - generic [ref=e1254]: 一键艾灸(默认)
+                    - cell "一键艾灸" [ref=e1255]:
+                      - generic [ref=e1258]: 一键艾灸
+                    - cell "-" [ref=e1259]:
+                      - generic [ref=e1260]: "-"
+                    - cell "ADMIN" [ref=e1261]:
+                      - generic [ref=e1262]: ADMIN
+                    - cell "已发布" [ref=e1263]:
+                      - generic [ref=e1266]: 已发布
+                    - cell "2026-04-15" [ref=e1267]:
+                      - generic [ref=e1268]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1269]:
+                      - generic [ref=e1270]:
+                        - button "查看" [ref=e1271] [cursor=pointer]:
+                          - generic [ref=e1272]: 查看
+                        - button "编辑" [ref=e1273] [cursor=pointer]:
+                          - generic [ref=e1274]: 编辑
+                        - button "下架" [ref=e1275] [cursor=pointer]:
+                          - generic [ref=e1276]: 下架
+                  - row "选择当前行 8 S003 专业祛湿方案 专业模式 祛湿 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1277]:
+                    - cell "选择当前行" [ref=e1278]:
+                      - generic "选择当前行" [ref=e1280] [cursor=pointer]:
+                        - generic [ref=e1281]:
+                          - checkbox "选择当前行"
+                    - cell "8" [ref=e1283]:
+                      - generic [ref=e1285]: "8"
+                    - cell "S003" [ref=e1286]:
+                      - generic [ref=e1287]: S003
+                    - cell "专业祛湿方案" [ref=e1288]:
+                      - generic [ref=e1289]: 专业祛湿方案
+                    - cell "专业模式" [ref=e1290]:
+                      - generic [ref=e1293]: 专业模式
+                    - cell "祛湿" [ref=e1294]:
+                      - generic [ref=e1295]: 祛湿
+                    - cell "ADMIN" [ref=e1296]:
+                      - generic [ref=e1297]: ADMIN
+                    - cell "已发布" [ref=e1298]:
+                      - generic [ref=e1301]: 已发布
+                    - cell "2026-04-15" [ref=e1302]:
+                      - generic [ref=e1303]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1304]:
+                      - generic [ref=e1305]:
+                        - button "查看" [ref=e1306] [cursor=pointer]:
+                          - generic [ref=e1307]: 查看
+                        - button "编辑" [ref=e1308] [cursor=pointer]:
+                          - generic [ref=e1309]: 编辑
+                        - button "下架" [ref=e1310] [cursor=pointer]:
+                          - generic [ref=e1311]: 下架
+                  - row "选择当前行 9 S002 专业驱寒方案 专业模式 驱寒 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1312]:
+                    - cell "选择当前行" [ref=e1313]:
+                      - generic "选择当前行" [ref=e1315] [cursor=pointer]:
+                        - generic [ref=e1316]:
+                          - checkbox "选择当前行"
+                    - cell "9" [ref=e1318]:
+                      - generic [ref=e1320]: "9"
+                    - cell "S002" [ref=e1321]:
+                      - generic [ref=e1322]: S002
+                    - cell "专业驱寒方案" [ref=e1323]:
+                      - generic [ref=e1324]: 专业驱寒方案
+                    - cell "专业模式" [ref=e1325]:
+                      - generic [ref=e1328]: 专业模式
+                    - cell "驱寒" [ref=e1329]:
+                      - generic [ref=e1330]: 驱寒
+                    - cell "ADMIN" [ref=e1331]:
+                      - generic [ref=e1332]: ADMIN
+                    - cell "已发布" [ref=e1333]:
+                      - generic [ref=e1336]: 已发布
+                    - cell "2026-04-15" [ref=e1337]:
+                      - generic [ref=e1338]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1339]:
+                      - generic [ref=e1340]:
+                        - button "查看" [ref=e1341] [cursor=pointer]:
+                          - generic [ref=e1342]: 查看
+                        - button "编辑" [ref=e1343] [cursor=pointer]:
+                          - generic [ref=e1344]: 编辑
+                        - button "下架" [ref=e1345] [cursor=pointer]:
+                          - generic [ref=e1346]: 下架
+                  - row "选择当前行 10 PRO001 驱寒模式 专业模式 驱寒 系统预设 已发布 2026-04-13 查看 编辑 下架" [ref=e1347]:
+                    - cell "选择当前行" [ref=e1348]:
+                      - generic "选择当前行" [ref=e1350] [cursor=pointer]:
+                        - generic [ref=e1351]:
+                          - checkbox "选择当前行"
+                    - cell "10" [ref=e1353]:
+                      - generic [ref=e1355]: "10"
+                    - cell "PRO001" [ref=e1356]:
+                      - generic [ref=e1357]: PRO001
+                    - cell "驱寒模式" [ref=e1358]:
+                      - generic [ref=e1359]: 驱寒模式
+                    - cell "专业模式" [ref=e1360]:
+                      - generic [ref=e1363]: 专业模式
+                    - cell "驱寒" [ref=e1364]:
+                      - generic [ref=e1365]: 驱寒
+                    - cell "系统预设" [ref=e1366]:
+                      - generic [ref=e1367]: 系统预设
+                    - cell "已发布" [ref=e1368]:
+                      - generic [ref=e1371]: 已发布
+                    - cell "2026-04-13" [ref=e1372]:
+                      - generic [ref=e1373]: 2026-04-13
+                    - cell "查看 编辑 下架" [ref=e1374]:
+                      - generic [ref=e1375]:
+                        - button "查看" [ref=e1376] [cursor=pointer]:
+                          - generic [ref=e1377]: 查看
+                        - button "编辑" [ref=e1378] [cursor=pointer]:
+                          - generic [ref=e1379]: 编辑
+                        - button "下架" [ref=e1380] [cursor=pointer]:
+                          - generic [ref=e1381]: 下架
+            - generic [ref=e1383]:
+              - generic [ref=e1384]: 共 17 条
+              - generic [ref=e1387] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e1389]
+                  - generic [ref=e1390]: 10条/页
+                - img [ref=e1393]
+              - button "上一页" [disabled] [ref=e1395]:
+                - generic:
+                  - img
+              - list [ref=e1396]:
+                - listitem "第 1 页" [ref=e1397]: "1"
+                - listitem "第 2 页" [ref=e1398] [cursor=pointer]: "2"
+              - button "下一页" [ref=e1399] [cursor=pointer]:
+                - generic:
+                  - img
+              - generic [ref=e1400]:
+                - generic [ref=e1401]: 前往
+                - spinbutton "页" [ref=e1404]: "1"
+                - generic [ref=e1405]: 页

+ 14 - 0
.playwright-mcp/page-2026-05-19T13-07-56-365Z.yml

@@ -0,0 +1,14 @@
+- generic [ref=e4]:
+  - generic [ref=e5]:
+    - generic [ref=e7]: 医
+    - heading "艾灸椅管理后台" [level=1] [ref=e8]
+    - paragraph [ref=e9]: 欢迎回来,请登录您的账号
+  - generic [ref=e10]:
+    - generic [ref=e14]:
+      - img [ref=e17]
+      - textbox "请输入用户名" [ref=e19]
+    - generic [ref=e24]:
+      - img [ref=e27]
+      - textbox "请输入密码" [ref=e30]
+    - button "登 录" [ref=e34] [cursor=pointer]:
+      - generic [ref=e35]: 登 录

+ 11 - 0
.sisyphus/run-continuation/ses_23363b0cdffeAeukEMIH5qxAa7.json

@@ -0,0 +1,11 @@
+{
+  "sessionID": "ses_23363b0cdffeAeukEMIH5qxAa7",
+  "updatedAt": "2026-04-30T04:49:51.343Z",
+  "sources": {
+    "stop": {
+      "state": "stopped",
+      "reason": "continuation stopped",
+      "updatedAt": "2026-04-30T04:49:51.343Z"
+    }
+  }
+}

+ 529 - 0
acupoint-page.yml

@@ -0,0 +1,529 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [active] [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "穴位管理" [ref=e239]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e240]:
+        - generic [ref=e243]:
+          - generic [ref=e244]:
+            - generic [ref=e245]: 穴位名称
+            - textbox "穴位名称" [ref=e249]:
+              - /placeholder: 请输入穴位名称
+          - generic [ref=e250]:
+            - generic [ref=e251]: 所属经络
+            - generic [ref=e254] [cursor=pointer]:
+              - generic:
+                - combobox "所属经络" [ref=e256]
+                - generic [ref=e257]: 全部
+              - img [ref=e260]
+          - generic [ref=e262]:
+            - generic [ref=e263]: 状态
+            - generic [ref=e266] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e268]
+                - generic [ref=e269]: 全部
+              - img [ref=e272]
+          - generic [ref=e274]:
+            - generic [ref=e275]: 解析状态
+            - generic [ref=e278] [cursor=pointer]:
+              - generic:
+                - combobox "解析状态" [ref=e280]
+                - generic [ref=e281]: 全部
+              - img [ref=e284]
+          - generic [ref=e287]:
+            - button "查询" [ref=e288] [cursor=pointer]:
+              - img [ref=e290]
+              - generic [ref=e292]: 查询
+            - button "重置" [ref=e293] [cursor=pointer]:
+              - img [ref=e295]
+              - generic [ref=e297]: 重置
+        - generic [ref=e298]:
+          - generic [ref=e300]:
+            - generic [ref=e301]: 穴位列表
+            - generic [ref=e302]:
+              - button "导入" [ref=e305] [cursor=pointer]:
+                - button "导入" [ref=e306]:
+                  - img [ref=e308]
+                  - generic [ref=e310]: 导入
+              - button "导出 Excel" [ref=e311] [cursor=pointer]:
+                - img [ref=e313]
+                - generic [ref=e315]: 导出 Excel
+              - button "新增" [ref=e316] [cursor=pointer]:
+                - img [ref=e318]
+                - generic [ref=e320]: 新增
+              - button "批量删除" [disabled] [ref=e321]:
+                - img [ref=e323]
+                - generic [ref=e325]: 批量删除
+          - generic [ref=e326]:
+            - generic [ref=e328]:
+              - table [ref=e330]:
+                - rowgroup [ref=e346]:
+                  - row "选择所有行 序号 穴位编码 穴位名称 所属经络 身体部位 分区 Y轴术语 解析状态 解析说明 产品穴位 功效 状态 操作" [ref=e347]:
+                    - columnheader "选择所有行" [ref=e348]:
+                      - generic "选择所有行" [ref=e350] [cursor=pointer]:
+                        - generic [ref=e351]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e353]:
+                      - generic [ref=e354]: 序号
+                    - columnheader "穴位编码" [ref=e355]:
+                      - generic [ref=e356]: 穴位编码
+                    - columnheader "穴位名称" [ref=e357]:
+                      - generic [ref=e358]: 穴位名称
+                    - columnheader "所属经络" [ref=e359]:
+                      - generic [ref=e360]: 所属经络
+                    - columnheader "身体部位" [ref=e361]:
+                      - generic [ref=e362]: 身体部位
+                    - columnheader "分区" [ref=e363]:
+                      - generic [ref=e364]: 分区
+                    - columnheader "Y轴术语" [ref=e365]:
+                      - generic [ref=e366]: Y轴术语
+                    - columnheader "解析状态" [ref=e367]:
+                      - generic [ref=e368]: 解析状态
+                    - columnheader "解析说明" [ref=e369]:
+                      - generic [ref=e370]: 解析说明
+                    - columnheader "产品穴位" [ref=e371]:
+                      - generic [ref=e372]: 产品穴位
+                    - columnheader "功效" [ref=e373]:
+                      - generic [ref=e374]: 功效
+                    - columnheader "状态" [ref=e375]:
+                      - generic [ref=e376]: 状态
+                    - columnheader "操作" [ref=e377]:
+                      - generic [ref=e378]: 操作
+              - table [ref=e383]:
+                - rowgroup [ref=e399]:
+                  - row "选择当前行 1 feijing-07 列缺 手太阴肺经 上肢 前臂前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 是 - 活跃 查看 编辑 删除" [ref=e400]:
+                    - cell "选择当前行" [ref=e401]:
+                      - generic "选择当前行" [ref=e403] [cursor=pointer]:
+                        - generic [ref=e404]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e406]:
+                      - generic [ref=e408]: "1"
+                    - cell "feijing-07" [ref=e409]:
+                      - generic [ref=e410]: feijing-07
+                    - cell "列缺" [ref=e411]:
+                      - generic [ref=e412]: 列缺
+                    - cell "手太阴肺经" [ref=e413]:
+                      - generic [ref=e414]: 手太阴肺经
+                    - cell "上肢" [ref=e415]:
+                      - generic [ref=e416]: 上肢
+                    - cell "前臂前区" [ref=e417]:
+                      - generic [ref=e418]: 前臂前区
+                    - cell "-" [ref=e419]:
+                      - generic [ref=e420]: "-"
+                    - cell "待复核" [ref=e421]:
+                      - generic [ref=e424]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e425]:
+                      - generic [ref=e426]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "是" [ref=e427]:
+                      - generic [ref=e430]: 是
+                    - cell "-" [ref=e431]:
+                      - generic [ref=e432]: "-"
+                    - cell "活跃" [ref=e433]:
+                      - generic [ref=e436]: 活跃
+                    - cell "查看 编辑 删除" [ref=e437]:
+                      - generic [ref=e438]:
+                        - button "查看" [ref=e439] [cursor=pointer]:
+                          - generic [ref=e440]: 查看
+                        - button "编辑" [ref=e441] [cursor=pointer]:
+                          - generic [ref=e442]: 编辑
+                        - button "删除" [ref=e443] [cursor=pointer]:
+                          - generic [ref=e444]: 删除
+                  - row "选择当前行 2 feijing-09 太渊 手太阴肺经 上肢 手腕前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e445]:
+                    - cell "选择当前行" [ref=e446]:
+                      - generic "选择当前行" [ref=e448] [cursor=pointer]:
+                        - generic [ref=e449]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e451]:
+                      - generic [ref=e453]: "2"
+                    - cell "feijing-09" [ref=e454]:
+                      - generic [ref=e455]: feijing-09
+                    - cell "太渊" [ref=e456]:
+                      - generic [ref=e457]: 太渊
+                    - cell "手太阴肺经" [ref=e458]:
+                      - generic [ref=e459]: 手太阴肺经
+                    - cell "上肢" [ref=e460]:
+                      - generic [ref=e461]: 上肢
+                    - cell "手腕前区" [ref=e462]:
+                      - generic [ref=e463]: 手腕前区
+                    - cell "-" [ref=e464]:
+                      - generic [ref=e465]: "-"
+                    - cell "待复核" [ref=e466]:
+                      - generic [ref=e469]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e470]:
+                      - generic [ref=e471]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e472]:
+                      - generic [ref=e475]: 否
+                    - cell "-" [ref=e476]:
+                      - generic [ref=e477]: "-"
+                    - cell "活跃" [ref=e478]:
+                      - generic [ref=e481]: 活跃
+                    - cell "查看 编辑 删除" [ref=e482]:
+                      - generic [ref=e483]:
+                        - button "查看" [ref=e484] [cursor=pointer]:
+                          - generic [ref=e485]: 查看
+                        - button "编辑" [ref=e486] [cursor=pointer]:
+                          - generic [ref=e487]: 编辑
+                        - button "删除" [ref=e488] [cursor=pointer]:
+                          - generic [ref=e489]: 删除
+                  - row "选择当前行 3 feijing-02 云门 手太阴肺经 胸部 前胸区 - 待复核 已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准 否 - 活跃 查看 编辑 删除" [ref=e490]:
+                    - cell "选择当前行" [ref=e491]:
+                      - generic "选择当前行" [ref=e493] [cursor=pointer]:
+                        - generic [ref=e494]:
+                          - checkbox "选择当前行"
+                    - cell "3" [ref=e496]:
+                      - generic [ref=e498]: "3"
+                    - cell "feijing-02" [ref=e499]:
+                      - generic [ref=e500]: feijing-02
+                    - cell "云门" [ref=e501]:
+                      - generic [ref=e502]: 云门
+                    - cell "手太阴肺经" [ref=e503]:
+                      - generic [ref=e504]: 手太阴肺经
+                    - cell "胸部" [ref=e505]:
+                      - generic [ref=e506]: 胸部
+                    - cell "前胸区" [ref=e507]:
+                      - generic [ref=e508]: 前胸区
+                    - cell "-" [ref=e509]:
+                      - generic [ref=e510]: "-"
+                    - cell "待复核" [ref=e511]:
+                      - generic [ref=e514]: 待复核
+                    - cell "已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准" [ref=e515]:
+                      - generic [ref=e516]: 已解析横向旁开:6寸;包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位;已找到横向旁开距离,但缺少可计算Y轴基准
+                    - cell "否" [ref=e517]:
+                      - generic [ref=e520]: 否
+                    - cell "-" [ref=e521]:
+                      - generic [ref=e522]: "-"
+                    - cell "活跃" [ref=e523]:
+                      - generic [ref=e526]: 活跃
+                    - cell "查看 编辑 删除" [ref=e527]:
+                      - generic [ref=e528]:
+                        - button "查看" [ref=e529] [cursor=pointer]:
+                          - generic [ref=e530]: 查看
+                        - button "编辑" [ref=e531] [cursor=pointer]:
+                          - generic [ref=e532]: 编辑
+                        - button "删除" [ref=e533] [cursor=pointer]:
+                          - generic [ref=e534]: 删除
+                  - row "选择当前行 4 feijing-08 经渠 手太阴肺经 上肢 前臂前区 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e535]:
+                    - cell "选择当前行" [ref=e536]:
+                      - generic "选择当前行" [ref=e538] [cursor=pointer]:
+                        - generic [ref=e539]:
+                          - checkbox "选择当前行"
+                    - cell "4" [ref=e541]:
+                      - generic [ref=e543]: "4"
+                    - cell "feijing-08" [ref=e544]:
+                      - generic [ref=e545]: feijing-08
+                    - cell "经渠" [ref=e546]:
+                      - generic [ref=e547]: 经渠
+                    - cell "手太阴肺经" [ref=e548]:
+                      - generic [ref=e549]: 手太阴肺经
+                    - cell "上肢" [ref=e550]:
+                      - generic [ref=e551]: 上肢
+                    - cell "前臂前区" [ref=e552]:
+                      - generic [ref=e553]: 前臂前区
+                    - cell "-" [ref=e554]:
+                      - generic [ref=e555]: "-"
+                    - cell "暂不支持" [ref=e556]:
+                      - generic [ref=e559]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e560]:
+                      - generic [ref=e561]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e562]:
+                      - generic [ref=e565]: 否
+                    - cell "-" [ref=e566]:
+                      - generic [ref=e567]: "-"
+                    - cell "活跃" [ref=e568]:
+                      - generic [ref=e571]: 活跃
+                    - cell "查看 编辑 删除" [ref=e572]:
+                      - generic [ref=e573]:
+                        - button "查看" [ref=e574] [cursor=pointer]:
+                          - generic [ref=e575]: 查看
+                        - button "编辑" [ref=e576] [cursor=pointer]:
+                          - generic [ref=e577]: 编辑
+                        - button "删除" [ref=e578] [cursor=pointer]:
+                          - generic [ref=e579]: 删除
+                  - row "选择当前行 5 feijing-01 中府 手太阴肺经 胸部 前胸区 - 待复核 已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准 是 - 活跃 查看 编辑 删除" [ref=e580]:
+                    - cell "选择当前行" [ref=e581]:
+                      - generic "选择当前行" [ref=e583] [cursor=pointer]:
+                        - generic [ref=e584]:
+                          - checkbox "选择当前行"
+                    - cell "5" [ref=e586]:
+                      - generic [ref=e588]: "5"
+                    - cell "feijing-01" [ref=e589]:
+                      - generic [ref=e590]: feijing-01
+                    - cell "中府" [ref=e591]:
+                      - generic [ref=e592]: 中府
+                    - cell "手太阴肺经" [ref=e593]:
+                      - generic [ref=e594]: 手太阴肺经
+                    - cell "胸部" [ref=e595]:
+                      - generic [ref=e596]: 胸部
+                    - cell "前胸区" [ref=e597]:
+                      - generic [ref=e598]: 前胸区
+                    - cell "-" [ref=e599]:
+                      - generic [ref=e600]: "-"
+                    - cell "待复核" [ref=e601]:
+                      - generic [ref=e604]: 待复核
+                    - cell "已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准" [ref=e605]:
+                      - generic [ref=e606]: 已解析横向旁开:6寸;包含胸腹体表标志,但当前用户模型缺少可稳定换算为大椎相对Y轴的基准;已找到横向旁开距离,但缺少可计算Y轴基准
+                    - cell "是" [ref=e607]:
+                      - generic [ref=e610]: 是
+                    - cell "-" [ref=e611]:
+                      - generic [ref=e612]: "-"
+                    - cell "活跃" [ref=e613]:
+                      - generic [ref=e616]: 活跃
+                    - cell "查看 编辑 删除" [ref=e617]:
+                      - generic [ref=e618]:
+                        - button "查看" [ref=e619] [cursor=pointer]:
+                          - generic [ref=e620]: 查看
+                        - button "编辑" [ref=e621] [cursor=pointer]:
+                          - generic [ref=e622]: 编辑
+                        - button "删除" [ref=e623] [cursor=pointer]:
+                          - generic [ref=e624]: 删除
+                  - row "选择当前行 6 jingwai-51 气端 经外奇穴 下肢 足趾 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e625]:
+                    - cell "选择当前行" [ref=e626]:
+                      - generic "选择当前行" [ref=e628] [cursor=pointer]:
+                        - generic [ref=e629]:
+                          - checkbox "选择当前行"
+                    - cell "6" [ref=e631]:
+                      - generic [ref=e633]: "6"
+                    - cell "jingwai-51" [ref=e634]:
+                      - generic [ref=e635]: jingwai-51
+                    - cell "气端" [ref=e636]:
+                      - generic [ref=e637]: 气端
+                    - cell "经外奇穴" [ref=e638]:
+                      - generic [ref=e639]: 经外奇穴
+                    - cell "下肢" [ref=e640]:
+                      - generic [ref=e641]: 下肢
+                    - cell "足趾" [ref=e642]:
+                      - generic [ref=e643]: 足趾
+                    - cell "-" [ref=e644]:
+                      - generic [ref=e645]: "-"
+                    - cell "暂不支持" [ref=e646]:
+                      - generic [ref=e649]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e650]:
+                      - generic [ref=e651]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e652]:
+                      - generic [ref=e655]: 否
+                    - cell "-" [ref=e656]:
+                      - generic [ref=e657]: "-"
+                    - cell "活跃" [ref=e658]:
+                      - generic [ref=e661]: 活跃
+                    - cell "查看 编辑 删除" [ref=e662]:
+                      - generic [ref=e663]:
+                        - button "查看" [ref=e664] [cursor=pointer]:
+                          - generic [ref=e665]: 查看
+                        - button "编辑" [ref=e666] [cursor=pointer]:
+                          - generic [ref=e667]: 编辑
+                        - button "删除" [ref=e668] [cursor=pointer]:
+                          - generic [ref=e669]: 删除
+                  - row "选择当前行 7 feijing-03 天府 手太阴肺经 上肢 上臂前区 - 暂不支持 未识别到可稳定计算的Y轴基准和横向旁开距离 否 - 活跃 查看 编辑 删除" [ref=e670]:
+                    - cell "选择当前行" [ref=e671]:
+                      - generic "选择当前行" [ref=e673] [cursor=pointer]:
+                        - generic [ref=e674]:
+                          - checkbox "选择当前行"
+                    - cell "7" [ref=e676]:
+                      - generic [ref=e678]: "7"
+                    - cell "feijing-03" [ref=e679]:
+                      - generic [ref=e680]: feijing-03
+                    - cell "天府" [ref=e681]:
+                      - generic [ref=e682]: 天府
+                    - cell "手太阴肺经" [ref=e683]:
+                      - generic [ref=e684]: 手太阴肺经
+                    - cell "上肢" [ref=e685]:
+                      - generic [ref=e686]: 上肢
+                    - cell "上臂前区" [ref=e687]:
+                      - generic [ref=e688]: 上臂前区
+                    - cell "-" [ref=e689]:
+                      - generic [ref=e690]: "-"
+                    - cell "暂不支持" [ref=e691]:
+                      - generic [ref=e694]: 暂不支持
+                    - cell "未识别到可稳定计算的Y轴基准和横向旁开距离" [ref=e695]:
+                      - generic [ref=e696]: 未识别到可稳定计算的Y轴基准和横向旁开距离
+                    - cell "否" [ref=e697]:
+                      - generic [ref=e700]: 否
+                    - cell "-" [ref=e701]:
+                      - generic [ref=e702]: "-"
+                    - cell "活跃" [ref=e703]:
+                      - generic [ref=e706]: 活跃
+                    - cell "查看 编辑 删除" [ref=e707]:
+                      - generic [ref=e708]:
+                        - button "查看" [ref=e709] [cursor=pointer]:
+                          - generic [ref=e710]: 查看
+                        - button "编辑" [ref=e711] [cursor=pointer]:
+                          - generic [ref=e712]: 编辑
+                        - button "删除" [ref=e713] [cursor=pointer]:
+                          - generic [ref=e714]: 删除
+                  - row "选择当前行 8 feijing-10 鱼际 手太阴肺经 上肢 手外侧 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e715]:
+                    - cell "选择当前行" [ref=e716]:
+                      - generic "选择当前行" [ref=e718] [cursor=pointer]:
+                        - generic [ref=e719]:
+                          - checkbox "选择当前行"
+                    - cell "8" [ref=e721]:
+                      - generic [ref=e723]: "8"
+                    - cell "feijing-10" [ref=e724]:
+                      - generic [ref=e725]: feijing-10
+                    - cell "鱼际" [ref=e726]:
+                      - generic [ref=e727]: 鱼际
+                    - cell "手太阴肺经" [ref=e728]:
+                      - generic [ref=e729]: 手太阴肺经
+                    - cell "上肢" [ref=e730]:
+                      - generic [ref=e731]: 上肢
+                    - cell "手外侧" [ref=e732]:
+                      - generic [ref=e733]: 手外侧
+                    - cell "-" [ref=e734]:
+                      - generic [ref=e735]: "-"
+                    - cell "待复核" [ref=e736]:
+                      - generic [ref=e739]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e740]:
+                      - generic [ref=e741]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e742]:
+                      - generic [ref=e745]: 否
+                    - cell "-" [ref=e746]:
+                      - generic [ref=e747]: "-"
+                    - cell "活跃" [ref=e748]:
+                      - generic [ref=e751]: 活跃
+                    - cell "查看 编辑 删除" [ref=e752]:
+                      - generic [ref=e753]:
+                        - button "查看" [ref=e754] [cursor=pointer]:
+                          - generic [ref=e755]: 查看
+                        - button "编辑" [ref=e756] [cursor=pointer]:
+                          - generic [ref=e757]: 编辑
+                        - button "删除" [ref=e758] [cursor=pointer]:
+                          - generic [ref=e759]: 删除
+                  - row "选择当前行 9 feijing-06 孔最 手太阴肺经 上肢 前臂前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 是 - 活跃 查看 编辑 删除" [ref=e760]:
+                    - cell "选择当前行" [ref=e761]:
+                      - generic "选择当前行" [ref=e763] [cursor=pointer]:
+                        - generic [ref=e764]:
+                          - checkbox "选择当前行"
+                    - cell "9" [ref=e766]:
+                      - generic [ref=e768]: "9"
+                    - cell "feijing-06" [ref=e769]:
+                      - generic [ref=e770]: feijing-06
+                    - cell "孔最" [ref=e771]:
+                      - generic [ref=e772]: 孔最
+                    - cell "手太阴肺经" [ref=e773]:
+                      - generic [ref=e774]: 手太阴肺经
+                    - cell "上肢" [ref=e775]:
+                      - generic [ref=e776]: 上肢
+                    - cell "前臂前区" [ref=e777]:
+                      - generic [ref=e778]: 前臂前区
+                    - cell "-" [ref=e779]:
+                      - generic [ref=e780]: "-"
+                    - cell "待复核" [ref=e781]:
+                      - generic [ref=e784]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e785]:
+                      - generic [ref=e786]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "是" [ref=e787]:
+                      - generic [ref=e790]: 是
+                    - cell "-" [ref=e791]:
+                      - generic [ref=e792]: "-"
+                    - cell "活跃" [ref=e793]:
+                      - generic [ref=e796]: 活跃
+                    - cell "查看 编辑 删除" [ref=e797]:
+                      - generic [ref=e798]:
+                        - button "查看" [ref=e799] [cursor=pointer]:
+                          - generic [ref=e800]: 查看
+                        - button "编辑" [ref=e801] [cursor=pointer]:
+                          - generic [ref=e802]: 编辑
+                        - button "删除" [ref=e803] [cursor=pointer]:
+                          - generic [ref=e804]: 删除
+                  - row "选择当前行 10 feijing-05 尺泽 手太阴肺经 上肢 肘部前区 - 待复核 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位 否 - 活跃 查看 编辑 删除" [ref=e805]:
+                    - cell "选择当前行" [ref=e806]:
+                      - generic "选择当前行" [ref=e808] [cursor=pointer]:
+                        - generic [ref=e809]:
+                          - checkbox "选择当前行"
+                    - cell "10" [ref=e811]:
+                      - generic [ref=e813]: "10"
+                    - cell "feijing-05" [ref=e814]:
+                      - generic [ref=e815]: feijing-05
+                    - cell "尺泽" [ref=e816]:
+                      - generic [ref=e817]: 尺泽
+                    - cell "手太阴肺经" [ref=e818]:
+                      - generic [ref=e819]: 手太阴肺经
+                    - cell "上肢" [ref=e820]:
+                      - generic [ref=e821]: 上肢
+                    - cell "肘部前区" [ref=e822]:
+                      - generic [ref=e823]: 肘部前区
+                    - cell "-" [ref=e824]:
+                      - generic [ref=e825]: "-"
+                    - cell "待复核" [ref=e826]:
+                      - generic [ref=e829]: 待复核
+                    - cell "包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位" [ref=e830]:
+                      - generic [ref=e831]: 包含凹陷/肌腱/骨间/连线等触诊描述,需人工复核精确点位
+                    - cell "否" [ref=e832]:
+                      - generic [ref=e835]: 否
+                    - cell "-" [ref=e836]:
+                      - generic [ref=e837]: "-"
+                    - cell "活跃" [ref=e838]:
+                      - generic [ref=e841]: 活跃
+                    - cell "查看 编辑 删除" [ref=e842]:
+                      - generic [ref=e843]:
+                        - button "查看" [ref=e844] [cursor=pointer]:
+                          - generic [ref=e845]: 查看
+                        - button "编辑" [ref=e846] [cursor=pointer]:
+                          - generic [ref=e847]: 编辑
+                        - button "删除" [ref=e848] [cursor=pointer]:
+                          - generic [ref=e849]: 删除
+            - generic [ref=e851]:
+              - generic [ref=e852]: 共 425 条
+              - generic [ref=e855] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e857]
+                  - generic [ref=e858]: 10条/页
+                - img [ref=e861]
+              - button "上一页" [disabled] [ref=e863]:
+                - generic:
+                  - img
+              - list [ref=e864]:
+                - listitem "第 1 页" [ref=e865]: "1"
+                - listitem "第 2 页" [ref=e866] [cursor=pointer]: "2"
+                - listitem "第 3 页" [ref=e867] [cursor=pointer]: "3"
+                - listitem "第 4 页" [ref=e868] [cursor=pointer]: "4"
+                - listitem "第 5 页" [ref=e869] [cursor=pointer]: "5"
+                - listitem "第 6 页" [ref=e870] [cursor=pointer]: "6"
+                - listitem "向后 5 页" [ref=e871] [cursor=pointer]:
+                  - img
+                - listitem "第 43 页" [ref=e872] [cursor=pointer]: "43"
+              - button "下一页" [ref=e873] [cursor=pointer]:
+                - generic:
+                  - img
+              - generic [ref=e874]:
+                - generic [ref=e875]: 前往
+                - spinbutton "页" [ref=e878]: "1"
+                - generic [ref=e879]: 页

+ 113 - 0
code/backend/.factorypath

@@ -0,0 +1,113 @@
+<factorypath>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-web/2.7.18/spring-boot-starter-web-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter/2.7.18/spring-boot-starter-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot/2.7.18/spring-boot-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-logging/2.7.18/spring-boot-starter-logging-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-classic/1.2.12/logback-classic-1.2.12.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-core/1.2.12/logback-core-1.2.12.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-json/2.7.18/spring-boot-starter-json-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.5/jackson-datatype-jdk8-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.5/jackson-datatype-jsr310-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.5/jackson-module-parameter-names-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/2.7.18/spring-boot-starter-tomcat-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/9.0.83/tomcat-embed-core-9.0.83.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.83/tomcat-embed-websocket-9.0.83.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-web/5.3.31/spring-web-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-beans/5.3.31/spring-beans-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-webmvc/5.3.31/spring-webmvc-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context/5.3.31/spring-context-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-expression/5.3.31/spring-expression-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-aop/2.7.18/spring-boot-starter-aop-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aop/5.3.31/spring-aop-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-data-redis/2.7.18/spring-boot-starter-data-redis-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-redis/2.7.18/spring-data-redis-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-keyvalue/2.7.18/spring-data-keyvalue-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-commons/2.7.18/spring-data-commons-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-tx/5.3.31/spring-tx-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-oxm/5.3.31/spring-oxm-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context-support/5.3.31/spring-context-support-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/lettuce/lettuce-core/6.1.10.RELEASE/lettuce-core-6.1.10.RELEASE.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-common/4.1.101.Final/netty-common-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-handler/4.1.101.Final/netty-handler-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-resolver/4.1.101.Final/netty-resolver-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-buffer/4.1.101.Final/netty-buffer-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-transport-native-unix-common/4.1.101.Final/netty-transport-native-unix-common-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-codec/4.1.101.Final/netty-codec-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/netty/netty-transport/4.1.101.Final/netty-transport-4.1.101.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/projectreactor/reactor-core/3.4.34/reactor-core-3.4.34.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-pool2/2.11.1/commons-pool2-2.11.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-validation/2.7.18/spring-boot-starter-validation-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/9.0.83/tomcat-embed-el-9.0.83.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/validator/hibernate-validator/6.2.5.Final/hibernate-validator-6.2.5.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/jakarta/validation/jakarta.validation-api/2.0.2/jakarta.validation-api-2.0.2.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/logging/jboss-logging/3.4.3.Final/jboss-logging-3.4.3.Final.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/baomidou/mybatis-plus-boot-starter/3.5.3.1/mybatis-plus-boot-starter-3.5.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/baomidou/mybatis-plus/3.5.3.1/mybatis-plus-3.5.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/baomidou/mybatis-plus-extension/3.5.3.1/mybatis-plus-extension-3.5.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/baomidou/mybatis-plus-core/3.5.3.1/mybatis-plus-core-3.5.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/baomidou/mybatis-plus-annotation/3.5.3.1/mybatis-plus-annotation-3.5.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/github/jsqlparser/jsqlparser/4.4/jsqlparser-4.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/mybatis/mybatis/3.5.10/mybatis-3.5.10.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/mybatis/mybatis-spring/2.0.7/mybatis-spring-2.0.7.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/2.7.18/spring-boot-autoconfigure-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/2.7.18/spring-boot-starter-jdbc-2.7.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jdbc/5.3.31/spring-jdbc-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt/0.9.1/jjwt-0.9.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.13.5/jackson-databind-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.13.5/jackson-annotations-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.13.5/jackson-core-2.13.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/alibaba/fastjson/1.2.83/fastjson-1.2.83.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-core/5.3.31/spring-core-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jcl/5.3.31/spring-jcl-5.3.31.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/dysmsapi20170525/2.0.24/dysmsapi20170525-2.0.24.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/tea-util/0.2.18/tea-util-0.2.18.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/google/code/gson/gson/2.9.1/gson-2.9.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/endpoint-util/0.0.7/endpoint-util-0.0.7.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/tea/1.2.0/tea-1.2.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/squareup/okhttp3/okhttp/4.9.3/okhttp-4.9.3.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/squareup/okio/okio/2.8.0/okio-2.8.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jetbrains/annotations/13.0/annotations-13.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jacoco/org.jacoco.agent/0.8.4/org.jacoco.agent-0.8.4-runtime.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/tea-openapi/0.2.8/tea-openapi-0.2.8.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/credentials-java/0.2.4/credentials-java-0.2.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/sun/xml/bind/jaxb-core/2.3.0/jaxb-core-2.3.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/sun/xml/bind/jaxb-impl/2.3.0/jaxb-impl-2.3.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/alibabacloud-gateway-spi/0.0.1/alibabacloud-gateway-spi-0.0.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/tea-xml/0.1.5/tea-xml-0.1.5.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/dom4j/dom4j/2.0.3/dom4j-2.0.3.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/openapiutil/0.2.1/openapiutil-0.2.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/bouncycastle/bcpkix-jdk15on/1.70/bcpkix-jdk15on-1.70.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/bouncycastle/bcutil-jdk15on/1.70/bcutil-jdk15on-1.70.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/commons-codec/commons-codec/1.15/commons-codec-1.15.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/oss/aliyun-sdk-oss/3.17.4/aliyun-sdk-oss-3.17.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/jdom/jdom2/2.0.6.1/jdom2-2.0.6.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/codehaus/jettison/jettison/1.5.4/jettison-1.5.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/aliyun-java-sdk-core/4.5.10/aliyun-java-sdk-core-4.5.10.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/commons-logging/commons-logging/1.2/commons-logging-1.2.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/ini4j/ini4j/0.5.4/ini4j-0.5.4.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/opentracing/opentracing-api/0.33.0/opentracing-api-0.33.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/opentracing/opentracing-util/0.33.0/opentracing-util-0.33.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/io/opentracing/opentracing-noop/0.33.0/opentracing-noop-0.33.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/aliyun-java-sdk-ram/3.1.0/aliyun-java-sdk-ram-3.1.0.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/com/aliyun/aliyun-java-sdk-kms/2.11.0/aliyun-java-sdk-kms-2.11.0.jar" enabled="true" runInBatchMode="false"/>
+</factorypath>

+ 33 - 33
code/backend/src/main/java/com/aijiuyi/admin/common/constant/ResultCode.java

@@ -111,39 +111,39 @@ public enum ResultCode {
     /** 不能禁用自身账号 */
     ADMIN_CANNOT_DISABLE_SELF(1802, "不能禁用当前登录账号"),
     /** 管理员角色不合法 */
-    ADMIN_ROLE_INVALID(1803, "管理员角色不合法"),
-
-    // ======================== 内容管理相关 1900-1949 ========================
-    /** 文章不存在 */
-    ARTICLE_NOT_FOUND(1900, "文章不存在"),
-    /** 文章状态不允许该操作 */
-    ARTICLE_STATUS_INVALID(1901, "当前状态下不允许该操作"),
-    /** 无权限操作文章 */
-    ARTICLE_PERMISSION_DENIED(1902, "没有权限执行该操作"),
-    /** 拒绝原因不能为空 */
-    ARTICLE_REJECT_REASON_REQUIRED(1903, "拒绝审核必须填写原因"),
-
-    // ======================== 文件上传相关 1950-1969 ========================
-    /** 文件为空 */
-    FILE_EMPTY(1950, "上传文件不能为空"),
-    /** 文件类型不支持 */
-    FILE_TYPE_NOT_SUPPORTED(1951, "文件类型不支持"),
-    /** 文件大小超限 */
-    FILE_SIZE_EXCEEDED(1952, "文件大小超过限制"),
-    /** 文件上传失败 */
-    FILE_UPLOAD_FAILED(1953, "文件上传失败"),
-
-    // ======================== 用户类别相关 1970-1989 ========================
-    /** 用户类别不存在 */
-    USER_CATEGORY_NOT_FOUND(1970, "用户类别不存在"),
-    /** 用户类别编码已存在 */
-    USER_CATEGORY_CODE_EXISTS(1971, "用户类别编码已存在"),
-    /** 用户类别名称已存在 */
-    USER_CATEGORY_NAME_EXISTS(1972, "用户类别名称已存在"),
-    /** 内置用户类别不可删除 */
-    USER_CATEGORY_BUILTIN_FORBIDDEN(1973, "内置用户类别不可删除或修改编码"),
-    /** 用户类别已被使用 */
-    USER_CATEGORY_IN_USE(1974, "用户类别已被用户使用,不可删除");
+	ADMIN_ROLE_INVALID(1803, "管理员角色不合法"),
+
+	// ======================== 内容管理相关 1900-1949 ========================
+	/** 文章不存在 */
+	ARTICLE_NOT_FOUND(1900, "文章不存在"),
+	/** 文章状态不允许该操作 */
+	ARTICLE_STATUS_INVALID(1901, "当前状态下不允许该操作"),
+	/** 无权限操作文章 */
+	ARTICLE_PERMISSION_DENIED(1902, "没有权限执行该操作"),
+	/** 拒绝原因不能为空 */
+	ARTICLE_REJECT_REASON_REQUIRED(1903, "拒绝审核必须填写原因"),
+
+	// ======================== 文件上传相关 1950-1969 ========================
+	/** 文件为空 */
+	FILE_EMPTY(1950, "上传文件不能为空"),
+	/** 文件类型不支持 */
+	FILE_TYPE_NOT_SUPPORTED(1951, "文件类型不支持"),
+	/** 文件大小超限 */
+	FILE_SIZE_EXCEEDED(1952, "文件大小超过限制"),
+	/** 文件上传失败 */
+	FILE_UPLOAD_FAILED(1953, "文件上传失败"),
+
+	// ======================== 用户类别相关 1970-1989 ========================
+	/** 用户类别不存在 */
+	USER_CATEGORY_NOT_FOUND(1970, "用户类别不存在"),
+	/** 用户类别编码已存在 */
+	USER_CATEGORY_CODE_EXISTS(1971, "用户类别编码已存在"),
+	/** 用户类别名称已存在 */
+	USER_CATEGORY_NAME_EXISTS(1972, "用户类别名称已存在"),
+	/** 内置用户类别不可删除 */
+	USER_CATEGORY_BUILTIN_FORBIDDEN(1973, "内置用户类别不可删除或修改编码"),
+	/** 用户类别已被使用 */
+	USER_CATEGORY_IN_USE(1974, "用户类别已被用户使用,不可删除");
 
     // ======================== 其他业务错误码在此区间之后继续添加 ========================
 

+ 11 - 11
code/backend/src/main/java/com/aijiuyi/admin/controller/AcupointController.java

@@ -42,18 +42,18 @@ public class AcupointController {
         return Result.success(list);
     }
 
-    /**
-     * 获取Y轴医学定位术语选项(用于新增/编辑穴位时选择)
-     *
-     * @return Y轴定位术语选项
-     */
-    @GetMapping("/y-reference-options")
-    @Log(value = "获取穴位Y轴定位术语", module = "穴位管理", operationType = OperationType.QUERY)
-    public Result<List<YReferenceOptionDTO>> yReferenceOptions() {
-        return Result.success(acupointService.listYReferenceOptions());
-    }
+	/**
+	 * 获取Y轴医学定位术语选项(用于新增/编辑穴位时选择)
+	 *
+	 * @return Y轴定位术语选项
+	 */
+	@GetMapping("/y-reference-options")
+	@Log(value = "获取穴位Y轴定位术语", module = "穴位管理", operationType = OperationType.QUERY)
+	public Result<List<YReferenceOptionDTO>> yReferenceOptions() {
+		return Result.success(acupointService.listYReferenceOptions());
+	}
 
-    /**
+	/**
      * 分页查询穴位列表
      *
      * @param queryDTO 查询条件

+ 12 - 23
code/backend/src/main/java/com/aijiuyi/admin/entity/SysUser.java

@@ -6,37 +6,26 @@ import lombok.Data;
 import java.time.LocalDateTime;
 
 /**
- * 系统用户实体
- * 对应数据库 sys_user 表
+ * 管理员权限配置实体
+ * 存储每个管理员可访问的前端路由路径列表(JSON格式)
+ * 超级管理员(adminRole=1)不受此表限制,默认拥有全部权限
  */
 @Data
-@TableName("sys_user")
-public class SysUser {
+@TableName("admin_permission")
+public class AdminPermission {
 
     /** 主键ID */
     @TableId(type = IdType.ASSIGN_ID)
     private Long id;
 
-    /** 用户名(唯一,用于登录) */
-    private String username;
+    /** 管理员ID(关联 app_user.id) */
+    private Long adminId;
 
-    /** 密码(MD5加密存储,不允许返回给前端) */
-    private String password;
-
-    /** 昵称(显示名称) */
-    private String nickname;
-
-    /** 邮箱 */
-    private String email;
-
-    /** 手机号 */
-    private String phone;
-
-    /** 头像URL */
-    private String avatar;
-
-    /** 状态:1=正常,0=禁用 */
-    private Integer status;
+    /**
+     * 可访问的路由路径(JSON数组字符串,如 ["/dashboard","/device","/plan"])
+     * 超级管理员此字段为 null 或不使用
+     */
+    private String routePaths;
 
     /** 逻辑删除:0=未删除,1=已删除 */
     @TableLogic

+ 87 - 0
code/backend/src/test/java/com/aijiuyi/admin/common/util/TokenUtilTest.java

@@ -0,0 +1,87 @@
+package com.aijiuyi.admin.common.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TokenUtilTest {
+
+    @Test
+    void generateToken_withAllParameters() {
+        String token = TokenUtil.generateToken(1L, "admin", 3);
+        assertNotNull(token);
+        assertTrue(token.length() > 0);
+    }
+
+    @Test
+    void generateToken_withDefaultParameters() {
+        String token = TokenUtil.generateToken(1L, "admin");
+        assertNotNull(token);
+        assertTrue(token.length() > 0);
+    }
+
+    @Test
+    void parseToken_validToken() {
+        String token = TokenUtil.generateToken(1L, "admin", 3);
+        assertNotNull(TokenUtil.parseToken(token));
+    }
+
+    @Test
+    void parseToken_nullInput() {
+        assertNull(TokenUtil.parseToken(null));
+    }
+
+    @Test
+    void parseToken_emptyInput() {
+        assertNull(TokenUtil.parseToken(""));
+    }
+
+    @Test
+    void parseToken_invalidToken() {
+        assertNull(TokenUtil.parseToken("invalid.token.here"));
+    }
+
+    @Test
+    void parseToken_withBearerPrefix() {
+        String token = "Bearer " + TokenUtil.generateToken(1L, "admin");
+        assertNotNull(TokenUtil.parseToken(token));
+    }
+
+    @Test
+    void getUserId_validToken() {
+        String token = TokenUtil.generateToken(123L, "testuser");
+        assertEquals(Long.valueOf(123), TokenUtil.getUserId(token));
+    }
+
+    @Test
+    void getUserId_invalidToken() {
+        assertNull(TokenUtil.getUserId("invalid"));
+    }
+
+    @Test
+    void getUsername_validToken() {
+        String token = TokenUtil.generateToken(1L, "adminUser");
+        assertEquals("adminUser", TokenUtil.getUsername(token));
+    }
+
+    @Test
+    void getUsername_invalidToken() {
+        assertNull(TokenUtil.getUsername("invalid"));
+    }
+
+    @Test
+    void validateToken_validToken() {
+        String token = TokenUtil.generateToken(1L, "admin");
+        assertTrue(TokenUtil.validateToken(token));
+    }
+
+    @Test
+    void validateToken_invalidToken() {
+        assertFalse(TokenUtil.validateToken("invalid.token"));
+    }
+
+    @Test
+    void validateToken_nullToken() {
+        assertFalse(TokenUtil.validateToken(null));
+    }
+}

+ 10 - 6
code/frontend/src/layout/index.vue

@@ -114,13 +114,17 @@ const activeMenu = computed(() => route.path)
 // 当前路由
 const currentRoute = computed(() => route)
 
-// 动态菜单(从路由配置中读取 Layout 下的子路由,并保留层级关系)
+// 动态菜单(从路由配置中读取 Layout 下的子路由,使用绝对路径避免相对路径导航问题)
+// 同时根据当前用户权限过滤:超级管理员(adminRole=1)或 permissions=null 显示全部,其他按配置过滤
 const menuRoutes = computed(() => {
-  const layoutRoute = router.options.routes.find(r => r.path === '/')
-  if (!layoutRoute || !layoutRoute.children) {
-    return []
-  }
-  return layoutRoute.children.filter(r => r.meta && r.meta.title && !r.meta.hidden)
+  const allRoutes = router.getRoutes().filter(r =>
+    r.meta?.title &&
+    !r.meta?.hidden &&
+    r.path !== '/' &&
+    r.path !== '/login' &&
+    r.path !== '/404'
+  )
+  return allRoutes.filter(r => userStore.hasPermission(r.path))
 })
 
 // 切换侧边栏折叠

+ 24 - 24
code/frontend/src/router/index.js

@@ -94,33 +94,33 @@ const routes = [
         component: () => import('@/views/content/index.vue'),
         meta: { requiresAuth: true, title: '内容管理', icon: 'Document' }
       },
+{
+    path: 'system',
+    name: 'System',
+    meta: { requiresAuth: true, title: '系统管理', icon: 'Setting' },
+    redirect: '/system/admin',
+    children: [
       {
-        path: 'system',
-        name: 'System',
-        meta: { title: '系统管理', icon: 'Setting' },
-        redirect: '/system/admin',
-        children: [
-          {
-            path: 'admin',
-            name: 'Admin',
-            component: () => import('@/views/admin/index.vue'),
-            meta: { requiresAuth: true, title: '管理员', icon: 'Avatar' }
-          },
-          {
-            path: 'user-category',
-            name: 'UserCategory',
-            component: () => import('@/views/system/user-category/index.vue'),
-            meta: { requiresAuth: true, title: '用户类别', icon: 'Collection' }
-          },
-          {
-            path: 'log',
-            name: 'Log',
-            component: () => import('@/views/log/index.vue'),
-            meta: { requiresAuth: true, title: '系统日志', icon: 'Document' }
-          }
-        ]
+        path: 'admin',
+        name: 'Admin',
+        component: () => import('@/views/admin/index.vue'),
+        meta: { requiresAuth: true, title: '管理员管理', icon: 'Avatar' }
+      },
+      {
+        path: 'user-category',
+        name: 'UserCategory',
+        component: () => import('@/views/system/user-category/index.vue'),
+        meta: { requiresAuth: true, title: '用户类别', icon: 'Collection' }
+      },
+      {
+        path: 'log',
+        name: 'Log',
+        component: () => import('@/views/log/index.vue'),
+        meta: { requiresAuth: true, title: '系统日志', icon: 'Document' }
       }
     ]
+  }
+    ]
   },
   {
     path: '/404',

+ 17 - 17
code/frontend/src/views/user/app-user/index.vue

@@ -11,12 +11,12 @@
         </el-form-item>
         <el-form-item label="用户类型">
           <el-select v-model="queryForm.userType" placeholder="全部" clearable style="width:120px">
-            <el-option
-              v-for="item in userCategoryOptions"
-              :key="item.code"
-              :label="item.name"
-              :value="item.code"
-            />
+                <el-option
+                  v-for="item in userCategoryOptions"
+                  :key="item.code"
+                  :label="item.name"
+                  :value="item.code"
+                />
           </el-select>
         </el-form-item>
         <el-form-item label="状态">
@@ -47,11 +47,11 @@
         <el-table-column prop="nickname" label="昵称" />
         <el-table-column prop="userType" label="用户类型" width="100" align="center">
           <template #default="{ row }">
-            <el-tag
-              :type="userTypeTagType(row.userType)"
-              size="small"
-            >
-              {{ userTypeText(row.userType) }}
+                <el-tag
+                  :type="userTypeTagType(row.userType)"
+                  size="small"
+                >
+                  {{ userTypeText(row.userType) }}
             </el-tag>
           </template>
         </el-table-column>
@@ -111,12 +111,12 @@
         </el-form-item>
         <el-form-item label="用户类型" prop="userType">
           <el-select v-model="formData.userType" placeholder="请选择" style="width:100%">
-            <el-option
-              v-for="item in userCategoryOptions"
-              :key="item.code"
-              :label="item.name"
-              :value="item.code"
-            />
+                <el-option
+                  v-for="item in userCategoryOptions"
+                  :key="item.code"
+                  :label="item.name"
+                  :value="item.code"
+                />
           </el-select>
         </el-form-item>
         <el-form-item label="状态" prop="status">

ファイルの差分が大きいため隠しています
+ 881 - 0
code/logs/aijiuyi-admin-dev.log


+ 147 - 0
current-page.yml

@@ -0,0 +1,147 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "账号管理" [ref=e58]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e71]:
+        - generic [ref=e74]:
+          - generic [ref=e75]:
+            - generic [ref=e76]: 手机号
+            - textbox "手机号" [ref=e80]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e81]:
+            - generic [ref=e82]: 昵称
+            - textbox "昵称" [ref=e86]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e87]:
+            - generic [ref=e88]: 用户类型
+            - generic [ref=e91] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e93]
+                - generic [ref=e94]: 全部
+              - img [ref=e97]
+          - generic [ref=e99]:
+            - generic [ref=e100]: 状态
+            - generic [ref=e103] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e105]
+                - generic [ref=e106]: 全部
+              - img [ref=e109]
+          - generic [ref=e112]:
+            - button "搜索" [ref=e113] [cursor=pointer]:
+              - img [ref=e115]
+              - generic [ref=e117]: 搜索
+            - button "重置" [ref=e118] [cursor=pointer]:
+              - img [ref=e120]
+              - generic [ref=e122]: 重置
+        - generic [ref=e123]:
+          - generic [ref=e125]:
+            - generic [ref=e126]: 账号管理列表
+            - button "新增用户" [ref=e127] [cursor=pointer]:
+              - img [ref=e129]
+              - generic [ref=e131]: 新增用户
+          - generic [ref=e132]:
+            - generic [ref=e134]:
+              - table [ref=e136]:
+                - rowgroup [ref=e147]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e148]:
+                    - columnheader "#" [ref=e149]:
+                      - generic [ref=e150]: "#"
+                    - columnheader "手机号" [ref=e151]:
+                      - generic [ref=e152]: 手机号
+                    - columnheader "昵称" [ref=e153]:
+                      - generic [ref=e154]: 昵称
+                    - columnheader "用户类型" [ref=e155]:
+                      - generic [ref=e156]: 用户类型
+                    - columnheader "状态" [ref=e157]:
+                      - generic [ref=e158]: 状态
+                    - columnheader "绑定设备" [ref=e159]:
+                      - generic [ref=e160]: 绑定设备
+                    - columnheader "子用户" [ref=e161]:
+                      - generic [ref=e162]: 子用户
+                    - columnheader "注册时间" [ref=e163]:
+                      - generic [ref=e164]: 注册时间
+                    - columnheader "操作" [ref=e165]:
+                      - generic [ref=e166]: 操作
+              - table [ref=e200]:
+                - rowgroup [ref=e211]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e212]:
+                    - cell "1" [ref=e213]:
+                      - generic [ref=e215]: "1"
+                    - cell [ref=e216]
+                    - cell "超级管理员" [ref=e217]:
+                      - generic [ref=e218]: 超级管理员
+                    - cell "管理员" [ref=e219]:
+                      - generic [ref=e222]: 管理员
+                    - cell "正常" [ref=e223]:
+                      - generic [ref=e226]: 正常
+                    - cell [ref=e227]
+                    - cell [ref=e228]
+                    - cell "2026-04-09T16:58:56" [ref=e229]:
+                      - generic [ref=e230]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e231]:
+                      - generic [ref=e232]:
+                        - button "编辑" [ref=e233] [cursor=pointer]:
+                          - generic [ref=e234]: 编辑
+                        - button "禁用" [ref=e235] [cursor=pointer]:
+                          - generic [ref=e236]: 禁用
+                        - button "删除" [ref=e237] [cursor=pointer]:
+                          - generic [ref=e238]: 删除
+            - generic [ref=e178]:
+              - generic [ref=e179]: 共 1 条
+              - generic [ref=e182] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e184]
+                  - generic [ref=e185]: 10条/页
+                - img [ref=e188]
+              - button "上一页" [disabled] [ref=e190]:
+                - generic:
+                  - img
+              - list [ref=e191]:
+                - listitem "第 1 页" [ref=e192]: "1"
+              - button "下一页" [disabled] [ref=e193]:
+                - generic:
+                  - img
+              - generic [ref=e194]:
+                - generic [ref=e195]: 前往
+                - spinbutton "页" [ref=e198]: "1"
+                - generic [ref=e199]: 页

BIN
device-management.png


+ 0 - 0
login-page-full.yml


+ 14 - 0
login-page.yml

@@ -0,0 +1,14 @@
+- generic [ref=e4]:
+  - generic [ref=e5]:
+    - generic [ref=e7]: 医
+    - heading "艾灸椅管理后台" [level=1] [ref=e8]
+    - paragraph [ref=e9]: 欢迎回来,请登录您的账号
+  - generic [ref=e10]:
+    - generic [ref=e14]:
+      - img [ref=e17]
+      - textbox "请输入用户名" [ref=e19]
+    - generic [ref=e23]:
+      - img [ref=e26]
+      - textbox "请输入密码" [ref=e29]
+    - button "登 录" [ref=e32] [cursor=pointer]:
+      - generic [ref=e33]: 登 录

+ 14 - 0
login-snapshot.yml

@@ -0,0 +1,14 @@
+- generic [ref=e4]:
+  - generic [ref=e5]:
+    - generic [ref=e7]: 医
+    - heading "艾灸椅管理后台" [level=1] [ref=e8]
+    - paragraph [ref=e9]: 欢迎回来,请登录您的账号
+  - generic [ref=e10]:
+    - generic [ref=e14]:
+      - img [ref=e17]
+      - textbox "请输入用户名" [ref=e19]
+    - generic [ref=e23]:
+      - img [ref=e26]
+      - textbox "请输入密码" [ref=e29]
+    - button "登 录" [ref=e32] [cursor=pointer]:
+      - generic [ref=e33]: 登 录

BIN
login-success.png


+ 147 - 0
main-menu.yml

@@ -0,0 +1,147 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页

+ 133 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/common/util/TokenUtil.java

@@ -0,0 +1,133 @@
+package com.aijiuyi.admin.common.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.util.StringUtils;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JWT Token 工具类
+ * 提供 Token 的生成、解析、验证功能
+ * Token 存储用户ID和用户名,默认3年有效期(有效期内可通过 Redis 控制失效)
+ */
+public class TokenUtil {
+
+    /** JWT 签名密钥(生产环境请修改为复杂密钥并从配置文件读取) */
+    private static final String SECRET = "aijiuyi_admin_jwt_secret_key_2024_change_in_prod";
+
+    /** Token 默认有效期:3年(毫秒) */
+    private static final long EXPIRATION = 3L * 365 * 24 * 60 * 60 * 1000;
+
+    /** Bearer 前缀 */
+    private static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 生成 JWT Token(携带 userType)
+     *
+     * @param userId   用户ID
+     * @param username 用户名
+     * @param userType 用户类型(角色)
+     * @return JWT Token 字符串
+     */
+    public static String generateToken(Long userId, String username, Integer userType) {
+        Map<String, Object> claims = new HashMap<>(4);
+        claims.put("userId", userId);
+        claims.put("username", username);
+        claims.put("userType", userType);
+
+        return Jwts.builder()
+                .setClaims(claims)
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
+                .signWith(SignatureAlgorithm.HS256, SECRET)
+                .compact();
+    }
+
+    /**
+     * 生成 JWT Token
+     *
+     * @param userId   用户ID
+     * @param username 用户名
+     * @return JWT Token 字符串
+     */
+    public static String generateToken(Long userId, String username) {
+        Map<String, Object> claims = new HashMap<>(4);
+        claims.put("userId", userId);
+        claims.put("username", username);
+
+        return Jwts.builder()
+                .setClaims(claims)
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
+                .signWith(SignatureAlgorithm.HS256, SECRET)
+                .compact();
+    }
+
+    /**
+     * 解析 JWT Token,获取 Claims
+     * 若 Token 无效或已过期,返回 null
+     *
+     * @param token Token 字符串(可带 Bearer 前缀)
+     * @return Claims,解析失败返回 null
+     */
+    public static Claims parseToken(String token) {
+        if (!StringUtils.hasText(token)) {
+            return null;
+        }
+        // 去掉 Bearer 前缀
+        if (token.startsWith(TOKEN_PREFIX)) {
+            token = token.substring(TOKEN_PREFIX.length());
+        }
+        try {
+            return Jwts.parser()
+                    .setSigningKey(SECRET)
+                    .parseClaimsJws(token)
+                    .getBody();
+        } catch (Exception e) {
+            // Token 无效或已过期
+            return null;
+        }
+    }
+
+    /**
+     * 从 Token 中获取用户ID
+     *
+     * @param token Token 字符串
+     * @return 用户ID,解析失败返回 null
+     */
+    public static Long getUserId(String token) {
+        Claims claims = parseToken(token);
+        if (claims == null) {
+            return null;
+        }
+        return ((Number) claims.get("userId")).longValue();
+    }
+
+    /**
+     * 从 Token 中获取用户名
+     *
+     * @param token Token 字符串
+     * @return 用户名,解析失败返回 null
+     */
+    public static String getUsername(String token) {
+        Claims claims = parseToken(token);
+        if (claims == null) {
+            return null;
+        }
+        return (String) claims.get("username");
+    }
+
+    /**
+     * 验证 Token 是否有效(格式正确且未过期)
+     *
+     * @param token Token 字符串
+     * @return true=有效,false=无效
+     */
+    public static boolean validateToken(String token) {
+        return parseToken(token) != null;
+    }
+}

+ 147 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/controller/AdminController.java

@@ -0,0 +1,147 @@
+package com.aijiuyi.admin.controller;
+
+import com.aijiuyi.admin.common.annotation.Log;
+import com.aijiuyi.admin.common.entity.Result;
+import com.aijiuyi.admin.common.enums.OperationType;
+import com.aijiuyi.admin.controller.dto.AdminQueryDTO;
+import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.service.AdminService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 管理员管理 Controller
+ * 提供管理员账号的增删改查、状态管理及页面权限配置接口
+ */
+@RestController
+@RequestMapping("/admin")
+public class AdminController {
+
+    @Autowired
+    private AdminService adminService;
+
+    /**
+     * 分页查询管理员列表
+     *
+     * @param queryDTO 查询参数(用户名/姓名模糊、角色、状态)
+     * @return 分页管理员列表
+     */
+    @GetMapping("/page")
+    @Log(value = "查询管理员列表", module = "管理员管理", operationType = OperationType.QUERY)
+    public Result<IPage<AppUser>> page(AdminQueryDTO queryDTO) {
+        return Result.success(adminService.pageList(queryDTO));
+    }
+
+    /**
+     * 查询管理员详情
+     *
+     * @param id 管理员ID
+     * @return 管理员信息
+     */
+    @GetMapping("/{id}")
+    @Log(value = "查询管理员详情", module = "管理员管理", operationType = OperationType.QUERY)
+    public Result<AppUser> getById(@PathVariable Long id) {
+        return Result.success(adminService.getById(id));
+    }
+
+    /**
+     * 新增管理员
+     *
+     * @param admin 管理员信息(username/password/nickname/adminRole/status 必填)
+     * @return 操作结果
+     */
+    @PostMapping
+    @Log(value = "新增管理员", module = "管理员管理", operationType = OperationType.INSERT)
+    public Result<Void> add(@RequestBody AppUser admin) {
+        adminService.addAdmin(admin);
+        return Result.success();
+    }
+
+    /**
+     * 修改管理员信息
+     *
+     * @param admin 管理员信息(需携带 id;密码为空则不修改)
+     * @return 操作结果
+     */
+    @PutMapping
+    @Log(value = "修改管理员信息", module = "管理员管理", operationType = OperationType.UPDATE)
+    public Result<Void> update(@RequestBody AppUser admin) {
+        adminService.updateAdmin(admin);
+        return Result.success();
+    }
+
+    /**
+     * 删除管理员(逻辑删除,超级管理员不可删除)
+     *
+     * @param id 管理员ID
+     * @return 操作结果
+     */
+    @DeleteMapping("/{id}")
+    @Log(value = "删除管理员", module = "管理员管理", operationType = OperationType.DELETE)
+    public Result<Void> delete(@PathVariable Long id) {
+        adminService.deleteAdmin(id);
+        return Result.success();
+    }
+
+    /**
+     * 批量删除管理员
+     *
+     * @param ids 管理员ID列表
+     * @return 操作结果
+     */
+    @DeleteMapping("/batch")
+    @Log(value = "批量删除管理员", module = "管理员管理", operationType = OperationType.DELETE)
+    public Result<Void> batchDelete(@RequestBody List<Long> ids) {
+        adminService.batchDeleteAdmin(ids);
+        return Result.success();
+    }
+
+    /**
+     * 修改管理员状态(禁用/启用)
+     *
+     * @param id     管理员ID
+     * @param status 目标状态:1=启用,0=禁用
+     * @return 操作结果
+     */
+    @PutMapping("/{id}/status")
+    @Log(value = "修改管理员状态", module = "管理员管理", operationType = OperationType.UPDATE)
+    public Result<Void> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
+        adminService.updateStatus(id, status);
+        return Result.success();
+    }
+
+    /**
+     * 查询管理员权限路由列表
+     * 超级管理员返回 null,前端视为全部权限
+     *
+     * @param id 管理员ID
+     * @return 路由路径 JSON 数组字符串
+     */
+    @GetMapping("/{id}/permission")
+    @Log(value = "查询管理员权限", module = "管理员管理", operationType = OperationType.QUERY)
+    public Result<Map<String, Object>> getPermission(@PathVariable Long id) {
+        String routePaths = adminService.getPermissions(id);
+        Map<String, Object> result = new HashMap<>(2);
+        result.put("routePaths", routePaths);
+        return Result.success(result);
+    }
+
+    /**
+     * 更新管理员权限路由列表
+     *
+     * @param id      管理员ID
+     * @param payload 请求体,包含 routePaths(JSON数组字符串)
+     * @return 操作结果
+     */
+    @PutMapping("/{id}/permission")
+    @Log(value = "更新管理员权限", module = "管理员管理", operationType = OperationType.UPDATE)
+    public Result<Void> updatePermission(@PathVariable Long id, @RequestBody Map<String, String> payload) {
+        adminService.updatePermissions(id, payload.get("routePaths"));
+        return Result.success();
+    }
+}

+ 28 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/controller/dto/AdminQueryDTO.java

@@ -0,0 +1,28 @@
+package com.aijiuyi.admin.controller.dto;
+
+import lombok.Data;
+
+/**
+ * 管理员分页查询参数 DTO
+ */
+@Data
+public class AdminQueryDTO {
+
+    /** 用户名(模糊查询) */
+    private String username;
+
+    /** 姓名/昵称(模糊查询) */
+    private String nickname;
+
+    /** 管理员角色:1=超级管理员,2=运营人员,3=只读用户 */
+    private Integer adminRole;
+
+    /** 状态:1=启用,0=禁用 */
+    private Integer status;
+
+    /** 当前页码,默认第1页 */
+    private Integer pageNum = 1;
+
+    /** 每页条数,默认10条 */
+    private Integer pageSize = 10;
+}

+ 41 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/entity/AdminPermission.java

@@ -0,0 +1,41 @@
+package com.aijiuyi.admin.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 管理员权限配置实体
+ * 存储每个管理员可访问的前端路由路径列表(JSON格式)
+ * 超级管理员(adminRole=1)不受此表限制,默认拥有全部权限
+ */
+@Data
+@TableName("admin_permission")
+public class AdminPermission {
+
+    /** 主键ID */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 管理员ID(关联 app_user.id) */
+    private Long adminId;
+
+    /**
+     * 可访问的路由路径(JSON数组字符串,如 ["/dashboard","/device","/plan"])
+     * 超级管理员此字段为 null 或不使用
+     */
+    private String routePaths;
+
+    /** 逻辑删除:0=未删除,1=已删除 */
+    @TableLogic
+    private Integer deleted;
+
+    /** 创建时间(自动填充) */
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /** 更新时间(自动填充) */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+}

+ 12 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/mapper/AdminPermissionMapper.java

@@ -0,0 +1,12 @@
+package com.aijiuyi.admin.mapper;
+
+import com.aijiuyi.admin.entity.AdminPermission;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 管理员权限配置 Mapper
+ */
+@Mapper
+public interface AdminPermissionMapper extends BaseMapper<AdminPermission> {
+}

+ 83 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/service/AdminService.java

@@ -0,0 +1,83 @@
+package com.aijiuyi.admin.service;
+
+import com.aijiuyi.admin.controller.dto.AdminQueryDTO;
+import com.aijiuyi.admin.entity.AppUser;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * 管理员管理 Service 接口
+ * 对 app_user 表中 userType=3 的管理员进行管理,
+ * 同时提供管理员权限配置的查询与更新
+ */
+public interface AdminService extends IService<AppUser> {
+
+    /**
+     * 分页查询管理员列表
+     *
+     * @param queryDTO 查询参数
+     * @return 分页结果
+     */
+    IPage<AppUser> pageList(AdminQueryDTO queryDTO);
+
+    /**
+     * 新增管理员
+     * 对密码进行 MD5 加密,校验用户名唯一性
+     *
+     * @param admin 管理员信息
+     */
+    void addAdmin(AppUser admin);
+
+    /**
+     * 修改管理员信息
+     * 若传入密码则重新加密,否则保留原密码
+     *
+     * @param admin 管理员信息(需携带 id)
+     */
+    void updateAdmin(AppUser admin);
+
+    /**
+     * 删除管理员(逻辑删除)
+     * 超级管理员不可删除
+     *
+     * @param id 管理员ID
+     */
+    void deleteAdmin(Long id);
+
+    /**
+     * 批量删除管理员(逻辑删除)
+     * 超级管理员不可删除
+     *
+     * @param ids 管理员ID列表
+     */
+    void batchDeleteAdmin(List<Long> ids);
+
+    /**
+     * 修改管理员状态(禁用/启用)
+     * 不允许禁用当前登录账号自身
+     *
+     * @param id     管理员ID
+     * @param status 目标状态:1=启用,0=禁用
+     */
+    void updateStatus(Long id, Integer status);
+
+    /**
+     * 查询管理员权限路由列表
+     * 超级管理员返回 null(前端解释为全部权限)
+     *
+     * @param adminId 管理员ID
+     * @return 路由路径列表(JSON数组字符串,如 ["/dashboard","/device"]);超级管理员返回 null
+     */
+    String getPermissions(Long adminId);
+
+    /**
+     * 更新管理员权限路由列表
+     * 若超级管理员调用此方法,视为无效操作直接返回
+     *
+     * @param adminId    管理员ID
+     * @param routePaths 路由路径列表(JSON数组字符串)
+     */
+    void updatePermissions(Long adminId, String routePaths);
+}

+ 236 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/service/impl/AdminServiceImpl.java

@@ -0,0 +1,236 @@
+package com.aijiuyi.admin.service.impl;
+
+import com.aijiuyi.admin.common.constant.ResultCode;
+import com.aijiuyi.admin.common.context.RequestContext;
+import com.aijiuyi.admin.common.exception.BusinessException;
+import com.aijiuyi.admin.common.util.LogUtil;
+import com.aijiuyi.admin.controller.dto.AdminQueryDTO;
+import com.aijiuyi.admin.entity.AdminPermission;
+import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.mapper.AdminPermissionMapper;
+import com.aijiuyi.admin.mapper.AppUserMapper;
+import com.aijiuyi.admin.service.AdminService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * 管理员管理 Service 实现类
+ * 操作 app_user 表中 userType=3 的管理员记录
+ * 同时维护 admin_permission 表中的路由权限配置
+ */
+@Service
+public class AdminServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AdminService {
+
+    @Autowired
+    private AdminPermissionMapper adminPermissionMapper;
+
+    /**
+     * 分页查询管理员列表(仅 userType=3)
+     *
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    @Override
+    public IPage<AppUser> pageList(AdminQueryDTO queryDTO) {
+        Page<AppUser> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
+        LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<AppUser>()
+                .eq(AppUser::getUserType, 3)
+                .eq(queryDTO.getAdminRole() != null, AppUser::getAdminRole, queryDTO.getAdminRole())
+                .eq(queryDTO.getStatus() != null, AppUser::getStatus, queryDTO.getStatus())
+                .orderByAsc(AppUser::getId);
+        // 用户名/姓名 OR 模糊查询
+        boolean hasKeyword = StringUtils.hasText(queryDTO.getUsername()) || StringUtils.hasText(queryDTO.getNickname());
+        if (hasKeyword) {
+            String kw = StringUtils.hasText(queryDTO.getUsername()) ? queryDTO.getUsername() : queryDTO.getNickname();
+            wrapper.and(w -> w.like(AppUser::getUsername, kw).or().like(AppUser::getNickname, kw));
+        }
+        return page(page, wrapper);
+    }
+
+    /**
+     * 新增管理员
+     * 校验用户名唯一性,对密码进行 MD5 加密,userType 强制设为 3
+     *
+     * @param admin 管理员信息
+     */
+    @Override
+    public void addAdmin(AppUser admin) {
+        // 校验用户名唯一性
+        if (!StringUtils.hasText(admin.getUsername())) {
+            throw new BusinessException(ResultCode.PARAM_ERROR);
+        }
+        long usernameCount = lambdaQuery().eq(AppUser::getUsername, admin.getUsername()).count();
+        if (usernameCount > 0) {
+            throw new BusinessException(ResultCode.USERNAME_EXISTS);
+        }
+        // 强制 userType=3(管理员)
+        admin.setUserType(3);
+        // 密码加密
+        if (StringUtils.hasText(admin.getPassword())) {
+            admin.setPassword(DigestUtils.md5DigestAsHex(admin.getPassword().getBytes()));
+        }
+        save(admin);
+        LogUtil.info(AdminServiceImpl.class, "新增管理员[{}],角色[{}]", admin.getUsername(), admin.getAdminRole());
+    }
+
+    /**
+     * 修改管理员信息
+     * 若传入密码则重新加密,否则保留原密码;不允许修改 userType
+     *
+     * @param admin 管理员信息(需携带 id)
+     */
+    @Override
+    public void updateAdmin(AppUser admin) {
+        AppUser exist = getById(admin.getId());
+        if (exist == null || !Integer.valueOf(3).equals(exist.getUserType())) {
+            throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
+        }
+        // 若修改了用户名,校验唯一性
+        if (StringUtils.hasText(admin.getUsername()) && !admin.getUsername().equals(exist.getUsername())) {
+            long count = lambdaQuery().eq(AppUser::getUsername, admin.getUsername()).count();
+            if (count > 0) {
+                throw new BusinessException(ResultCode.USERNAME_EXISTS);
+            }
+        }
+        // 密码处理
+        if (StringUtils.hasText(admin.getPassword())) {
+            admin.setPassword(DigestUtils.md5DigestAsHex(admin.getPassword().getBytes()));
+        } else {
+            admin.setPassword(null);
+        }
+        // 强制 userType 不变
+        admin.setUserType(null);
+        updateById(admin);
+        LogUtil.info(AdminServiceImpl.class, "修改管理员[{}]", admin.getId());
+    }
+
+    /**
+     * 删除管理员(逻辑删除)
+     * adminRole=1 超级管理员不可删除
+     *
+     * @param id 管理员ID
+     */
+    @Override
+    public void deleteAdmin(Long id) {
+        AppUser admin = getById(id);
+        if (admin == null || !Integer.valueOf(3).equals(admin.getUserType())) {
+            throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
+        }
+        if (Integer.valueOf(1).equals(admin.getAdminRole())) {
+            throw new BusinessException(ResultCode.ADMIN_CANNOT_DELETE);
+        }
+        removeById(id);
+        LogUtil.info(AdminServiceImpl.class, "删除管理员[{}]", id);
+    }
+
+    /**
+     * 批量删除管理员(逻辑删除)
+     * 超级管理员(adminRole=1)不可被批量删除
+     *
+     * @param ids 管理员ID列表
+     */
+    @Override
+    public void batchDeleteAdmin(List<Long> ids) {
+        for (Long id : ids) {
+            AppUser admin = getById(id);
+            if (admin != null && Integer.valueOf(1).equals(admin.getAdminRole())) {
+                throw new BusinessException(ResultCode.ADMIN_CANNOT_DELETE);
+            }
+        }
+        // 仅删除 userType=3 的记录,防止误删其他类型用户
+        LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<AppUser>()
+                .in(AppUser::getId, ids)
+                .eq(AppUser::getUserType, 3);
+        remove(wrapper);
+        LogUtil.info(AdminServiceImpl.class, "批量删除管理员,ID列表:{}", ids);
+    }
+
+    /**
+     * 修改管理员状态(禁用/启用)
+     * 不允许禁用当前登录账号自身
+     *
+     * @param id     管理员ID
+     * @param status 目标状态:1=启用,0=禁用
+     */
+    @Override
+    public void updateStatus(Long id, Integer status) {
+        // 不允许禁用自身
+        Long currentUserId = RequestContext.getUserId();
+        if (Integer.valueOf(0).equals(status) && id.equals(currentUserId)) {
+            throw new BusinessException(ResultCode.ADMIN_CANNOT_DISABLE_SELF);
+        }
+        AppUser admin = getById(id);
+        if (admin == null || !Integer.valueOf(3).equals(admin.getUserType())) {
+            throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
+        }
+        AppUser update = new AppUser();
+        update.setId(id);
+        update.setStatus(status);
+        updateById(update);
+        LogUtil.info(AdminServiceImpl.class, "管理员[{}]状态修改为[{}]", id, status);
+    }
+
+    /**
+     * 查询管理员权限路由列表
+     *
+     * @param adminId 管理员ID
+     * @return 路由路径 JSON 数组字符串;超级管理员返回 null
+     */
+    @Override
+    public String getPermissions(Long adminId) {
+        AppUser admin = getById(adminId);
+        if (admin == null) {
+            throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
+        }
+        // 超级管理员返回 null,前端视为全部权限
+        if (Integer.valueOf(1).equals(admin.getAdminRole())) {
+            return null;
+        }
+        AdminPermission permission = adminPermissionMapper.selectOne(
+                new LambdaQueryWrapper<AdminPermission>()
+                        .eq(AdminPermission::getAdminId, adminId)
+        );
+        return permission != null ? permission.getRoutePaths() : "[]";
+    }
+
+    /**
+     * 更新管理员权限路由列表
+     * 超级管理员(adminRole=1)调用时直接忽略,不修改
+     *
+     * @param adminId    管理员ID
+     * @param routePaths 路由路径 JSON 数组字符串
+     */
+    @Override
+    public void updatePermissions(Long adminId, String routePaths) {
+        AppUser admin = getById(adminId);
+        if (admin == null || !Integer.valueOf(3).equals(admin.getUserType())) {
+            throw new BusinessException(ResultCode.ADMIN_NOT_FOUND);
+        }
+        // 超级管理员不设置权限
+        if (Integer.valueOf(1).equals(admin.getAdminRole())) {
+            return;
+        }
+        AdminPermission exist = adminPermissionMapper.selectOne(
+                new LambdaQueryWrapper<AdminPermission>()
+                        .eq(AdminPermission::getAdminId, adminId)
+        );
+        if (exist == null) {
+            AdminPermission permission = new AdminPermission();
+            permission.setAdminId(adminId);
+            permission.setRoutePaths(routePaths);
+            adminPermissionMapper.insert(permission);
+        } else {
+            exist.setRoutePaths(routePaths);
+            adminPermissionMapper.updateById(exist);
+        }
+        LogUtil.info(AdminServiceImpl.class, "更新管理员[{}]权限:{}", adminId, routePaths);
+    }
+}

+ 272 - 0
openspec/backend/src/main/java/com/aijiuyi/admin/service/impl/AppUserServiceImpl.java

@@ -0,0 +1,272 @@
+package com.aijiuyi.admin.service.impl;
+
+import com.aijiuyi.admin.common.constant.ResultCode;
+import com.aijiuyi.admin.common.context.RequestContext;
+import com.aijiuyi.admin.common.entity.Result;
+import com.aijiuyi.admin.common.exception.BusinessException;
+import com.aijiuyi.admin.common.interceptor.TokenInterceptor;
+import com.aijiuyi.admin.common.util.LogUtil;
+import com.aijiuyi.admin.common.util.RedisUtil;
+import com.aijiuyi.admin.common.util.TokenUtil;
+import com.aijiuyi.admin.controller.dto.AppUserQueryDTO;
+import com.aijiuyi.admin.controller.dto.LoginDTO;
+import com.aijiuyi.admin.entity.AdminPermission;
+import com.aijiuyi.admin.entity.AppUser;
+import com.aijiuyi.admin.mapper.AdminPermissionMapper;
+import com.aijiuyi.admin.mapper.AppUserMapper;
+import com.aijiuyi.admin.service.AppUserService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 应用用户 Service 实现类
+ * 统一用户中心:普通用户、专家、管理员均使用此表
+ */
+@Service
+public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AppUserService {
+
+    /** Token 在 Redis 中的 key 前缀 */
+    private static final String TOKEN_REDIS_PREFIX = TokenInterceptor.REDIS_TOKEN_KEY_PREFIX;
+
+    /** Token 在 Redis 中的过期时间:3年(秒) */
+    private static final long TOKEN_EXPIRE_SECONDS = 3L * 365 * 24 * 60 * 60;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private AdminPermissionMapper adminPermissionMapper;
+
+    /**
+     * 管理员登录
+     * 通过用户名+密码校验身份,生成 JWT Token 并存入 Redis
+     *
+     * @param loginDTO 登录参数
+     * @return 包含 token 和用户基本信息的 Result
+     */
+    @Override
+    public Result<Map<String, Object>> login(LoginDTO loginDTO) {
+        LogUtil.info(AppUserServiceImpl.class, "用户[{}]尝试登录", loginDTO.getUsername());
+
+        // 根据用户名查询用户(仅管理员可通过用户名登录)
+        AppUser user = lambdaQuery()
+                .eq(AppUser::getUsername, loginDTO.getUsername())
+                .one();
+
+        // 用户不存在
+        if (user == null) {
+            LogUtil.warn(AppUserServiceImpl.class, "用户[{}]不存在", loginDTO.getUsername());
+            throw new BusinessException(ResultCode.USER_NOT_FOUND);
+        }
+
+        // 验证密码(前端传明文,后端 MD5 加密后比对)
+        String md5Password = DigestUtils.md5DigestAsHex(loginDTO.getPassword().getBytes());
+        if (!md5Password.equals(user.getPassword())) {
+            LogUtil.warn(AppUserServiceImpl.class, "用户[{}]密码错误", loginDTO.getUsername());
+            throw new BusinessException(ResultCode.USER_PASSWORD_ERROR);
+        }
+
+        // 验证用户状态
+        if (user.getStatus() != null && user.getStatus() == 0) {
+            LogUtil.warn(AppUserServiceImpl.class, "用户[{}]已被禁用", loginDTO.getUsername());
+            throw new BusinessException(ResultCode.USER_DISABLED);
+        }
+
+        // 验证角色(仅 userType=3=管理员 可登录后台)
+        if (user.getUserType() == null || user.getUserType() != 3) {
+            LogUtil.warn(AppUserServiceImpl.class, "用户[{}]无后台登录权限", loginDTO.getUsername());
+            throw new BusinessException(ResultCode.FORBIDDEN);
+        }
+
+        // 生成 JWT Token(携带 userType)
+        String token = TokenUtil.generateToken(user.getId(), user.getUsername(), user.getUserType());
+
+        // 将 Token 存入 Redis,3年过期
+        redisUtil.set(TOKEN_REDIS_PREFIX + token, user.getId(), TOKEN_EXPIRE_SECONDS);
+
+        // 更新最后登录时间
+        AppUser updateLogin = new AppUser();
+        updateLogin.setId(user.getId());
+        updateLogin.setLastLoginTime(LocalDateTime.now());
+        updateById(updateLogin);
+
+        // 登录成功后将用户信息写入 RequestContext,供 LogAspect 正确记录操作人
+        RequestContext.setUserId(user.getId());
+        RequestContext.setUserName(user.getUsername());
+        RequestContext.setUserType(user.getUserType());
+
+        LogUtil.info(AppUserServiceImpl.class, "用户[{}]登录成功", user.getUsername());
+
+        // 查询权限路由(超级管理员返回 null,代表全部权限)
+        String routePaths = null;
+        if (!Integer.valueOf(1).equals(user.getAdminRole())) {
+            AdminPermission permission = adminPermissionMapper.selectOne(
+                    new LambdaQueryWrapper<AdminPermission>()
+                            .eq(AdminPermission::getAdminId, user.getId())
+            );
+            routePaths = permission != null ? permission.getRoutePaths() : "[]";
+        }
+
+        // 组装返回数据
+        Map<String, Object> resultData = new HashMap<>(8);
+        resultData.put("token", token);
+        resultData.put("userId", user.getId());
+        resultData.put("username", user.getUsername());
+        resultData.put("nickname", user.getNickname());
+        resultData.put("avatar", user.getAvatar());
+        resultData.put("userType", user.getUserType());
+        resultData.put("adminRole", user.getAdminRole());
+        resultData.put("permissions", routePaths);
+
+        return Result.success(resultData);
+    }
+
+    /**
+     * 退出登录
+     * 从 Redis 中删除 Token,使 Token 立即失效
+     *
+     * @param token 当前用户的 Token
+     */
+    @Override
+    public void logout(String token) {
+        if (StringUtils.hasText(token)) {
+            // 去掉 Bearer 前缀
+            if (token.startsWith("Bearer ")) {
+                token = token.substring(7);
+            }
+            // 从 Redis 删除 Token
+            redisUtil.del(TOKEN_REDIS_PREFIX + token);
+            LogUtil.info(AppUserServiceImpl.class, "用户退出登录,Token 已失效");
+        }
+    }
+
+    /**
+     * 分页查询应用用户列表
+     *
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    @Override
+    public IPage<AppUser> pageList(AppUserQueryDTO queryDTO) {
+        Page<AppUser> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
+        LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<>();
+        wrapper.like(StringUtils.hasText(queryDTO.getPhone()), AppUser::getPhone, queryDTO.getPhone())
+               .like(StringUtils.hasText(queryDTO.getNickname()), AppUser::getNickname, queryDTO.getNickname())
+               .eq(queryDTO.getUserType() != null, AppUser::getUserType, queryDTO.getUserType())
+               .eq(queryDTO.getStatus() != null, AppUser::getStatus, queryDTO.getStatus())
+               .orderByDesc(AppUser::getCreateTime);
+        return page(page, wrapper);
+    }
+
+    /**
+     * 修改用户状态(禁用/启用)
+     *
+     * @param id     用户ID
+     * @param status 目标状态:1=启用,0=禁用
+     */
+    @Override
+    public void updateStatus(Long id, Integer status) {
+        AppUser user = getById(id);
+        if (user == null) {
+            throw new BusinessException(ResultCode.APP_USER_NOT_FOUND);
+        }
+        AppUser update = new AppUser();
+        update.setId(id);
+        update.setStatus(status);
+        updateById(update);
+        LogUtil.info(AppUserServiceImpl.class, "应用用户[{}]状态修改为[{}]", id, status);
+    }
+
+    /**
+     * 新增应用用户
+     * 若为管理员(userType=3)则校验用户名唯一性并对密码做 MD5 加密
+     *
+     * @param appUser 用户信息
+     */
+    @Override
+    public void addUser(AppUser appUser) {
+        // 手机号唯一性校验(手机号不为空时校验)
+        if (StringUtils.hasText(appUser.getPhone())) {
+            long phoneCount = lambdaQuery().eq(AppUser::getPhone, appUser.getPhone()).count();
+            if (phoneCount > 0) {
+                throw new BusinessException(ResultCode.PHONE_EXISTS);
+            }
+        }
+        // 管理员需校验用户名唯一性,并对密码加密
+        if (Integer.valueOf(3).equals(appUser.getUserType())) {
+            if (StringUtils.hasText(appUser.getUsername())) {
+                long usernameCount = lambdaQuery().eq(AppUser::getUsername, appUser.getUsername()).count();
+                if (usernameCount > 0) {
+                    throw new BusinessException(ResultCode.USERNAME_EXISTS);
+                }
+            }
+            // 密码 MD5 加密
+            if (StringUtils.hasText(appUser.getPassword())) {
+                appUser.setPassword(DigestUtils.md5DigestAsHex(appUser.getPassword().getBytes()));
+            }
+        }
+        save(appUser);
+        LogUtil.info(AppUserServiceImpl.class, "新增应用用户,手机号[{}],类型[{}]", appUser.getPhone(), appUser.getUserType());
+    }
+
+    /**
+     * 修改应用用户
+     *
+     * @param appUser 用户信息
+     */
+    @Override
+    public void updateUser(AppUser appUser) {
+        AppUser exist = getById(appUser.getId());
+        if (exist == null) {
+            throw new BusinessException(ResultCode.APP_USER_NOT_FOUND);
+        }
+        // 如果修改了手机号,校验唯一性
+        if (StringUtils.hasText(appUser.getPhone()) && !appUser.getPhone().equals(exist.getPhone())) {
+            long count = lambdaQuery().eq(AppUser::getPhone, appUser.getPhone()).count();
+            if (count > 0) {
+                throw new BusinessException(ResultCode.PHONE_EXISTS);
+            }
+        }
+        // 如果修改了用户名,校验唯一性
+        if (StringUtils.hasText(appUser.getUsername()) && !appUser.getUsername().equals(exist.getUsername())) {
+            long count = lambdaQuery().eq(AppUser::getUsername, appUser.getUsername()).count();
+            if (count > 0) {
+                throw new BusinessException(ResultCode.USERNAME_EXISTS);
+            }
+        }
+        // 如果修改了密码,重新加密
+        if (StringUtils.hasText(appUser.getPassword())) {
+            appUser.setPassword(DigestUtils.md5DigestAsHex(appUser.getPassword().getBytes()));
+        } else {
+            // 不修改密码字段
+            appUser.setPassword(null);
+        }
+        updateById(appUser);
+        LogUtil.info(AppUserServiceImpl.class, "修改应用用户[{}]", appUser.getId());
+    }
+
+    /**
+     * 删除应用用户(逻辑删除)
+     *
+     * @param id 用户ID
+     */
+    @Override
+    public void deleteUser(Long id) {
+        AppUser user = getById(id);
+        if (user == null) {
+            throw new BusinessException(ResultCode.APP_USER_NOT_FOUND);
+        }
+        removeById(id);
+        LogUtil.info(AppUserServiceImpl.class, "删除应用用户[{}]", id);
+    }
+}

+ 331 - 0
openspec/backend/src/main/resources/sql/init.sql

@@ -0,0 +1,331 @@
+-- ==================== 艾灸椅管理后台初始化 SQL ====================
+-- 执行前请确保已创建数据库:CREATE DATABASE aijiuyi_admin DEFAULT CHARACTER SET utf8mb4;
+-- 数据库连接:jdbc:mysql://localhost:3306/aijiuyi_admin
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- 系统操作日志表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `sys_log` (
+    `id`               BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `user_id`          BIGINT                COMMENT '请求用户ID',
+    `user_name`        VARCHAR(100)          COMMENT '请求用户名',
+    `user_type`        TINYINT               COMMENT '操作者类型:1=普通用户,2=专家,3=管理员(统一使用app_user表)',
+    `request_url`      VARCHAR(500)          COMMENT '请求接口路径',
+    `request_params`   TEXT                  COMMENT '请求参数(JSON格式)',
+    `request_result`   TEXT                  COMMENT '请求结果(JSON格式,超2000字符截断)',
+    `api_desc`         VARCHAR(200)          COMMENT '接口说明(来自@Log注解)',
+    `operation_type`   VARCHAR(20)           COMMENT '操作方式(查询/新增/更新/删除/其他)',
+    `request_ip`       VARCHAR(50)           COMMENT '请求客户端IP',
+    `operation_module` VARCHAR(100)          COMMENT '操作模块(来自@Log注解)',
+    `request_id`       VARCHAR(50)           COMMENT '请求唯一ID(同一请求的所有日志共享)',
+    `create_time`      DATETIME              COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_request_id` (`request_id`),
+    KEY `idx_user_id` (`user_id`),
+    KEY `idx_create_time` (`create_time`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '系统操作日志表';
+
+-- ----------------------------
+-- 应用用户表(手机号验证码登录的普通用户/专家)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `app_user` (
+    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `phone`       VARCHAR(20)           COMMENT '手机号(唯一,用于登录)',
+    `username`    VARCHAR(64)           COMMENT '用户名(管理员登录用,唯一)',
+    `password`    VARCHAR(64)           COMMENT '密码(MD5加密,管理员用)',
+    `nickname`    VARCHAR(100)          COMMENT '昵称',
+    `avatar`      VARCHAR(500)          COMMENT '头像URL',
+    `user_type`   TINYINT      NOT NULL DEFAULT 1 COMMENT '用户类型:1=普通用户,2=专家,3=管理员',
+    `status`      TINYINT      NOT NULL DEFAULT 1 COMMENT '状态:1=正常,0=禁用',
+    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time` DATETIME              COMMENT '创建时间',
+    `update_time` DATETIME              COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_phone` (`phone`),
+    UNIQUE KEY `uk_username` (`username`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '应用用户表';
+
+-- 初始化管理员账号(密码:admin123,MD5后为:0192023a7bbd73250516f069df18b500)
+INSERT INTO `app_user` (`username`, `password`, `nickname`, `user_type`, `status`, `deleted`, `create_time`, `update_time`)
+VALUES ('admin', MD5('admin123'), '超级管理员', 3, 1, 0, NOW(), NOW());
+
+-- ----------------------------
+-- 子用户信息表(属于某个应用用户)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `user_profile` (
+    `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `user_id`           BIGINT                COMMENT '所属应用用户ID(关联 app_user.id)',
+    `name`              VARCHAR(50)  NOT NULL COMMENT '姓名',
+    `gender`            TINYINT               COMMENT '性别:1=男,2=女',
+    `age`               INT                   COMMENT '年龄',
+    `phone`             VARCHAR(20)           COMMENT '手机号(唯一)',
+    `shoulder_width`    DECIMAL(5,1)          COMMENT '肩宽(cm)',
+    `body_height`       DECIMAL(5,1)          COMMENT '身长/坐高(cm)',
+    `finger_width_1`    DECIMAL(4,2)          COMMENT '1寸指宽(cm,选填,提高穴位精度)',
+    `finger_width_15`   DECIMAL(4,2)          COMMENT '1.5寸指宽(cm,选填)',
+    `finger_width_3`    DECIMAL(4,2)          COMMENT '3寸指宽(cm,选填)',
+    `acupoint_table_id` BIGINT                COMMENT '用户穴位表ID',
+    `deleted`           TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`       DATETIME              COMMENT '创建时间',
+    `update_time`       DATETIME              COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_user_id` (`user_id`),
+    KEY `idx_phone` (`phone`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '子用户信息表';
+
+-- ----------------------------
+-- 用户设备绑定表(子用户与设备绑定)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `user_device` (
+    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `user_id`     BIGINT       NOT NULL COMMENT '所属子用户ID(关联 user_profile.id)',
+    `device_code`   VARCHAR(100) NOT NULL COMMENT '设备唯一编码',
+    `device_name`   VARCHAR(100)          COMMENT '设备名称',
+    `device_model`  VARCHAR(100)          COMMENT '设备型号',
+    `is_primary`    TINYINT      NOT NULL DEFAULT 0 COMMENT '是否主设备:1=是,0=否',
+    `online_status` TINYINT      NOT NULL DEFAULT 0 COMMENT '在线状态:1=在线,0=离线',
+    `status`        TINYINT      NOT NULL DEFAULT 1 COMMENT '绑定状态:1=已绑定,0=已解绑',
+    `bind_time`   DATETIME              COMMENT '绑定时间',
+    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time` DATETIME              COMMENT '创建时间',
+    `update_time` DATETIME              COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_user_id` (`user_id`),
+    KEY `idx_device_code` (`device_code`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '用户设备绑定表(子用户绑定设备)';
+
+-- ----------------------------
+-- 艾灸椅设备表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `device` (
+    `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `device_code`       VARCHAR(50)  NOT NULL COMMENT '设备编号(唯一,如:AJY001)',
+    `device_name`       VARCHAR(100) NOT NULL COMMENT '设备名称(如:艾灸椅-001)',
+    `device_model`      VARCHAR(50)           COMMENT '设备型号(AJY-2026 / AJY-2025)',
+    `serial_no`         VARCHAR(50)           COMMENT '设备序列号(唯一)',
+    `firmware_version`  VARCHAR(20)           COMMENT '固件版本(如:v1.2.0)',
+    `online_status`     TINYINT      NOT NULL DEFAULT 0 COMMENT '在线状态:1=在线,0=离线',
+    `last_online_time`  DATETIME              COMMENT '最后在线时间',
+    `remark`            VARCHAR(200)          COMMENT '备注',
+    `deleted`           TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`       DATETIME              COMMENT '创建时间',
+    `update_time`       DATETIME              COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_device_code` (`device_code`),
+    UNIQUE KEY `uk_serial_no` (`serial_no`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '艾灸椅设备表';
+
+-- ----------------------------
+-- 穴位信息表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `acupoint` (
+    `id`                BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `acupoint_code`     VARCHAR(100)  NOT NULL COMMENT '穴位编码(唯一标识,如 dazhui)',
+    `name`              VARCHAR(50)   NOT NULL COMMENT '穴位名称(如:大椎穴)',
+    `meridian`          VARCHAR(100)           COMMENT '所属经络(如:督脉、足太阳膀胱经)',
+    `effects`           VARCHAR(500)           COMMENT '功效描述(逗号分隔,如:驱寒,祛湿)',
+    `location_type`     TINYINT                COMMENT '定位方式:1=相对定位,2=绝对定位',
+    `longitude_offset`  DECIMAL(6,2)           COMMENT '相对大椎穴纵向偏移(寸)',
+    `lateral_offset`    DECIMAL(6,2)           COMMENT '相对大椎穴横向偏移(寸,双侧穴位填写)',
+    `side`              TINYINT                COMMENT '侧别:1=中心,2=左侧,3=右侧,4=双侧',
+    `finger_width_type` TINYINT                COMMENT '指寸类型:1=一寸,2=1.5寸,3=三寸',
+    `abs_x`             DECIMAL(8,2)           COMMENT '绝对坐标X(mm)',
+    `abs_y`             DECIMAL(8,2)           COMMENT '绝对坐标Y(mm)',
+    `abs_z`             DECIMAL(8,2)           COMMENT '绝对坐标Z(mm)',
+    `gender`            TINYINT       NOT NULL DEFAULT 0 COMMENT '适用性别:0=不限,1=仅男,2=仅女',
+    `status`            TINYINT       NOT NULL DEFAULT 1 COMMENT '状态:1=活跃,0=待完善',
+    `deleted`           TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`       DATETIME               COMMENT '创建时间',
+    `update_time`       DATETIME               COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_acupoint_code` (`acupoint_code`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '穴位信息表';
+
+-- 初始化12个常用穴位
+INSERT INTO `acupoint` (`acupoint_code`, `name`, `meridian`, `effects`, `location_type`, `longitude_offset`, `lateral_offset`, `side`, `finger_width_type`, `gender`, `status`, `deleted`, `create_time`, `update_time`) VALUES
+('dazhui',   '大椎穴', '督脉',         '驱寒,祛湿',   1, 0.00,  NULL,  1, 1, 0, 1, 0, NOW(), NOW()),
+('fengmen',  '风门穴', '足太阳膀胱经', '祛风,解表',   1, 2.00,  1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('feishu',   '肺俞穴', '足太阳膀胱经', '宣肺,止咳',   1, 3.00,  1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('xinshu',   '心俞穴', '足太阳膀胱经', '养心,安神',   1, 5.00,  1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('ganshu',   '肝俞穴', '足太阳膀胱经', '疏肝,理气',   1, 9.00,  1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('pishu',    '脾俞穴', '足太阳膀胱经', '健脾,祛湿',   1, 11.00, 1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('weishu',   '胃俞穴', '足太阳膀胱经', '和胃,消食',   1, 12.00, 1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('shenshu',  '肾俞穴', '足太阳膀胱经', '补肾,扶阳',   1, 14.00, 1.50,  4, 1, 0, 1, 0, NOW(), NOW()),
+('mingmen',  '命门穴', '督脉',         '扶阳,固本',   1, 14.00, NULL,  1, 1, 0, 1, 0, NOW(), NOW()),
+('zhongwan', '中脘穴', '任脉',         '健脾,和胃',   2, NULL,  NULL,  1, NULL, 0, 1, 0, NOW(), NOW()),
+('qihai',    '气海穴', '任脉',         '扶阳,益气',   2, NULL,  NULL,  1, NULL, 0, 1, 0, NOW(), NOW()),
+('guanyuan', '关元穴', '任脉',         '温肾,扶阳',   2, NULL,  NULL,  1, NULL, 0, 1, 0, NOW(), NOW());
+
+-- ----------------------------
+-- 用户穴位坐标表(根据用户体型数据和穴位信息计算生成)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `user_acupoint` (
+    `id`             BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `user_id`        BIGINT        NOT NULL COMMENT '子用户ID(关联 user_profile.id)',
+    `acupoint_id`    BIGINT        NOT NULL COMMENT '穴位ID(关联 acupoint.id)',
+    `acupoint_name`  VARCHAR(50)   NOT NULL COMMENT '穴位名称(冗余存储,加速查询)',
+    `acupoint_side`  VARCHAR(10)   NOT NULL DEFAULT 'center' COMMENT '侧别:center=中心,left=左侧,right=右侧',
+    `cun_value`      DECIMAL(6,2)           COMMENT '相对大椎穴纵向偏移(寸)',
+    `cun_type`       VARCHAR(10)            COMMENT '指寸类型:1寸/1.5寸/3寸',
+    `coordinate_x`   DECIMAL(8,2)           COMMENT '计算后的X坐标(mm,相对大椎穴,向右为正)',
+    `coordinate_y`   DECIMAL(8,2)           COMMENT '计算后的Y坐标(mm,相对大椎穴,向下为正)',
+    `create_time`    DATETIME               COMMENT '首次生成时间',
+    `update_time`    DATETIME               COMMENT '最后更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_user_id` (`user_id`),
+    KEY `idx_acupoint_id` (`acupoint_id`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '用户穴位坐标表(按用户体型计算的个性化穴位坐标)';
+
+-- ----------------------------
+-- 艾灸手法表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `moxibustion_technique` (
+    `id`               BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `technique_code`   VARCHAR(50)   NOT NULL COMMENT '手法编码(唯一标识,如 oneclick)',
+    `name`             VARCHAR(50)   NOT NULL COMMENT '手法名称(如:一键艾灸)',
+    `motion_mode`      VARCHAR(20)   NOT NULL COMMENT '运动模式:default=默认工艺,fixed=固定不动,updown=上下移动,circular=圆周运动,dotted=点状移动',
+    `temp_min`         DECIMAL(4,1)  NOT NULL COMMENT '温度范围最小值(°C,35-50)',
+    `temp_max`         DECIMAL(4,1)  NOT NULL COMMENT '温度范围最大值(°C,35-50)',
+    `time_min`         INT           NOT NULL COMMENT '时间范围最小值(分钟,1-30)',
+    `time_max`         INT           NOT NULL COMMENT '时间范围最大值(分钟,1-30)',
+    `motion_amplitude` DECIMAL(4,1)           COMMENT '运动幅度(mm,1-20,运动模式非固定不动时必填)',
+    `motion_speed`     DECIMAL(4,1)           COMMENT '运动速度(mm/s,1-10,运动模式非固定不动时必填)',
+    `status`           TINYINT       NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用',
+    `remark`           VARCHAR(200)           COMMENT '备注(最长200字符)',
+    `deleted`          TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`      DATETIME               COMMENT '创建时间',
+    `update_time`      DATETIME               COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_technique_code` (`technique_code`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '艾灸手法表';
+
+-- 初始化5个内置手法
+INSERT INTO `moxibustion_technique` (`technique_code`, `name`, `motion_mode`, `temp_min`, `temp_max`, `time_min`, `time_max`, `motion_amplitude`, `motion_speed`, `status`, `deleted`, `create_time`, `update_time`) VALUES
+('oneclick', '一键艾灸', 'default',   42.0, 45.0,  30, 30,  NULL, NULL, 1, 0, NOW(), NOW()),
+('gentle',   '温和灸',   'fixed',     35.0, 50.0,   1, 30,  NULL, NULL, 1, 0, NOW(), NOW()),
+('pecking',  '雀啄灸',   'updown',    35.0, 50.0,   1, 30,  10.0,  5.0, 1, 0, NOW(), NOW()),
+('circular', '回旋灸',   'circular',  35.0, 50.0,   1, 30,  15.0,  3.0, 1, 0, NOW(), NOW()),
+('sparrow',  '麻雀灸',   'dotted',    35.0, 50.0,   1, 30,   8.0,  4.0, 1, 0, NOW(), NOW());
+
+-- ----------------------------
+-- 方案表(艾灸方案)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `plan` (
+    `id`               BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `plan_code`        VARCHAR(50)   NOT NULL COMMENT '方案编码(唯一标识,如 S001)',
+    `name`             VARCHAR(100)  NOT NULL COMMENT '方案名称(最长100字符)',
+    `mode_type`        TINYINT       NOT NULL COMMENT '模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手',
+    `effect_type`      VARCHAR(20)            COMMENT '功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳',
+    `symptoms`         VARCHAR(500)           COMMENT '适用症状(逗号分隔:怕冷,感冒,咳嗽,...)',
+    `applicable_gender` TINYINT      NOT NULL DEFAULT 0 COMMENT '适用人群:0=不限,1=仅男,2=仅女',
+    `age_min`          INT           NOT NULL DEFAULT 1 COMMENT '适用年龄最小值(岁)',
+    `age_max`          INT           NOT NULL DEFAULT 120 COMMENT '适用年龄最大值(岁)',
+    `description`      VARCHAR(200)           COMMENT '方案描述(最长200字符)',
+    `author_type`      TINYINT       NOT NULL DEFAULT 1 COMMENT '创作人类型:1=管理员,2=普通用户,3=专家',
+    `author_id`        BIGINT                 COMMENT '创作人ID(对应 app_user.id)',
+    `author_name`      VARCHAR(100)           COMMENT '创作人名称(冗余存储,加速查询)',
+    `status`           TINYINT       NOT NULL DEFAULT 0 COMMENT '状态:0=草稿,1=已发布,2=已下架',
+    `use_count`        INT           NOT NULL DEFAULT 0 COMMENT '使用人数',
+    `avg_rating`       DECIMAL(3,1)           COMMENT '平均评分',
+    `last_use_time`    DATETIME               COMMENT '最后使用时间',
+    `deleted`          TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`      DATETIME               COMMENT '创建时间',
+    `update_time`      DATETIME               COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_plan_code` (`plan_code`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '方案表(艾灸方案)';
+
+-- ----------------------------
+-- 方案步骤表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `plan_step` (
+    `id`             BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `plan_id`        BIGINT        NOT NULL COMMENT '方案ID(关联 plan.id)',
+    `step_order`     INT           NOT NULL COMMENT '步骤序号(从1开始)',
+    `acupoint_id`    BIGINT                 COMMENT '穴位ID(关联 acupoint.id)',
+    `acupoint_name`  VARCHAR(50)   NOT NULL COMMENT '穴位名称(冗余存储)',
+    `side`           TINYINT       NOT NULL COMMENT '侧别:1=中心,2=左侧,3=右侧,4=双侧',
+    `temperature`    DECIMAL(4,1)  NOT NULL COMMENT '温度(°C,35-50)',
+    `duration`       INT           NOT NULL COMMENT '时间(分钟,1-30)',
+    `technique_id`   BIGINT                 COMMENT '艾灸手法ID(关联 moxibustion_technique.id)',
+    `technique_name` VARCHAR(50)            COMMENT '艾灸手法名称(冗余存储)',
+    `remark`         VARCHAR(100)           COMMENT '备注(最长100字符)',
+    `deleted`        TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time`    DATETIME               COMMENT '创建时间',
+    `update_time`    DATETIME               COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_plan_id` (`plan_id`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '方案步骤表';
+
+-- ----------------------------
+-- 用户方案表(用户个性化工艺参数方案)
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `user_plan` (
+    `id`          BIGINT        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `user_id`     BIGINT        NOT NULL COMMENT '子用户ID(关联 user_profile.id)',
+    `plan_id`     BIGINT        NOT NULL COMMENT '方案ID(关联 plan.id)',
+    `plan_code`   VARCHAR(50)   NOT NULL COMMENT '方案编码(冗余存储)',
+    `plan_name`   VARCHAR(100)  NOT NULL COMMENT '方案名称(冗余存储)',
+    `mode_type`   TINYINT       NOT NULL COMMENT '模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手',
+    `author_name` VARCHAR(100)           COMMENT '创作人名称(冗余存储)',
+    `step_count`  INT           NOT NULL DEFAULT 0 COMMENT '步骤数量',
+    `use_count`   INT           NOT NULL DEFAULT 0 COMMENT '使用次数',
+    `steps_json`  TEXT                   COMMENT '个性化工艺步骤(JSON格式,含坐标/温度/时间等)',
+    `deleted`     TINYINT       NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time` DATETIME               COMMENT '订阅/生成时间',
+    `update_time` DATETIME               COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_user_id` (`user_id`),
+    KEY `idx_plan_id` (`plan_id`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '用户方案表(用户个性化工艺参数方案)';
+
+-- ----------------------------
+-- 管理员权限配置表
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `admin_permission` (
+    `id`          BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `admin_id`    BIGINT       NOT NULL COMMENT '管理员ID(关联 app_user.id)',
+    `route_paths` TEXT                  COMMENT '可访问的路由路径(JSON数组,如 ["/dashboard","/device"])',
+    `deleted`     TINYINT      NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=未删除,1=已删除',
+    `create_time` DATETIME              COMMENT '创建时间',
+    `update_time` DATETIME              COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_admin_id` (`admin_id`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4
+  COMMENT = '管理员权限配置表';
+
+-- app_user 表追加管理员相关字段(首次执行时添加,若字段已存在请注释掉此段)
+ALTER TABLE `app_user`
+    ADD COLUMN `admin_role`      TINYINT  DEFAULT NULL COMMENT '管理员角色:1=超级管理员,2=运营人员,3=只读用户(仅 userType=3 时有效)',
+    ADD COLUMN `last_login_time` DATETIME DEFAULT NULL COMMENT '最后登录时间';
+
+-- 更新初始管理员为超级管理员角色
+UPDATE `app_user` SET `admin_role` = 1 WHERE `username` = 'admin' AND `admin_role` IS NULL;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 80 - 0
openspec/frontend/src/api/admin.js

@@ -0,0 +1,80 @@
+import request from '@/utils/request'
+
+/**
+ * 管理员管理模块 API
+ * 对应后端 AdminController
+ */
+
+/**
+ * 分页查询管理员列表
+ * @param {Object} params 查询参数:username/nickname/adminRole/status/pageNum/pageSize
+ */
+export function getAdminPage(params) {
+  return request.get('/admin/page', { params })
+}
+
+/**
+ * 查询管理员详情
+ * @param {number} id 管理员ID
+ */
+export function getAdminById(id) {
+  return request.get(`/admin/${id}`)
+}
+
+/**
+ * 新增管理员
+ * @param {Object} data 管理员信息(username/password/nickname/adminRole/status)
+ */
+export function addAdmin(data) {
+  return request.post('/admin', data)
+}
+
+/**
+ * 修改管理员信息
+ * @param {Object} data 管理员信息(需携带 id;密码为空则不修改)
+ */
+export function updateAdmin(data) {
+  return request.put('/admin', data)
+}
+
+/**
+ * 删除管理员(逻辑删除)
+ * @param {number} id 管理员ID
+ */
+export function deleteAdmin(id) {
+  return request.delete(`/admin/${id}`)
+}
+
+/**
+ * 批量删除管理员
+ * @param {number[]} ids 管理员ID列表
+ */
+export function batchDeleteAdmin(ids) {
+  return request.delete('/admin/batch', { data: ids })
+}
+
+/**
+ * 修改管理员状态(禁用/启用)
+ * @param {number} id     管理员ID
+ * @param {number} status 目标状态:1=启用,0=禁用
+ */
+export function updateAdminStatus(id, status) {
+  return request.put(`/admin/${id}/status`, null, { params: { status } })
+}
+
+/**
+ * 查询管理员权限路由列表
+ * @param {number} id 管理员ID
+ */
+export function getAdminPermission(id) {
+  return request.get(`/admin/${id}/permission`)
+}
+
+/**
+ * 更新管理员权限路由列表
+ * @param {number} id         管理员ID
+ * @param {string} routePaths 路由路径 JSON 数组字符串(如 '["/dashboard","/device"]')
+ */
+export function updateAdminPermission(id, routePaths) {
+  return request.put(`/admin/${id}/permission`, { routePaths })
+}

+ 494 - 0
openspec/frontend/src/views/admin/index.vue

@@ -0,0 +1,494 @@
+<template>
+  <div class="page-container">
+    <!-- 搜索栏 -->
+    <el-card class="search-card" shadow="never">
+      <el-form :model="query" inline>
+        <el-form-item label="用户名/姓名">
+          <el-input
+            v-model="query.keyword"
+            placeholder="请输入用户名或姓名"
+            clearable
+            style="width: 200px"
+            @keyup.enter="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="角色">
+          <el-select v-model="query.adminRole" placeholder="全部" clearable style="width: 130px">
+            <el-option label="超级管理员" :value="1" />
+            <el-option label="运营人员" :value="2" />
+            <el-option label="只读用户" :value="3" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="query.status" placeholder="全部" clearable style="width: 100px">
+            <el-option label="启用" :value="1" />
+            <el-option label="禁用" :value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 表格卡片 -->
+    <el-card class="table-card" shadow="never">
+      <!-- 操作按钮 -->
+      <div class="toolbar">
+        <div class="toolbar-left">
+          <el-button type="primary" :icon="Plus" @click="handleAdd">新增管理员</el-button>
+          <el-button
+            type="danger"
+            :icon="Delete"
+            :disabled="selectedIds.length === 0"
+            @click="handleBatchDelete"
+          >
+            批量删除 {{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 数据表格 -->
+      <el-table
+        v-loading="loading"
+        :data="tableData"
+        border
+        stripe
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="序号" type="index" width="60" align="center" />
+        <el-table-column label="管理员ID" prop="id" width="100" align="center" />
+        <el-table-column label="用户名" prop="username" min-width="120" />
+        <el-table-column label="姓名" prop="nickname" min-width="120" />
+        <el-table-column label="角色" prop="adminRole" width="120" align="center">
+          <template #default="{ row }">
+            <el-tag :type="roleTagType(row.adminRole)" size="small">
+              {{ roleLabel(row.adminRole) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" prop="status" width="90" align="center">
+          <template #default="{ row }">
+            <el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
+              {{ row.status === 1 ? '启用' : '禁用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="最后登录时间" prop="lastLoginTime" width="170" align="center">
+          <template #default="{ row }">{{ row.lastLoginTime || '—' }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="230" align="center" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
+            <el-button
+              :type="row.status === 1 ? 'warning' : 'success'"
+              link
+              size="small"
+              @click="handleToggleStatus(row)"
+            >
+              {{ row.status === 1 ? '禁用' : '启用' }}
+            </el-button>
+            <el-button
+              type="primary"
+              link
+              size="small"
+              :disabled="row.adminRole === 1"
+              @click="handlePermission(row)"
+            >
+              权限配置
+            </el-button>
+            <el-button
+              type="danger"
+              link
+              size="small"
+              :disabled="row.adminRole === 1"
+              @click="handleDelete(row)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="pagination">
+        <el-pagination
+          v-model:current-page="query.pageNum"
+          v-model:page-size="query.pageSize"
+          :page-sizes="[10, 20, 50]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          background
+          @size-change="loadData"
+          @current-change="loadData"
+        />
+      </div>
+    </el-card>
+
+    <!-- 新增/编辑对话框 -->
+    <el-dialog
+      v-model="formVisible"
+      :title="formMode === 'add' ? '新增管理员' : '编辑管理员'"
+      width="500px"
+      :close-on-click-modal="false"
+      @close="handleFormClose"
+    >
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px">
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="formData.username" placeholder="4-20个字符,唯一" :disabled="formMode === 'edit'" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input
+            v-model="formData.password"
+            type="password"
+            show-password
+            :placeholder="formMode === 'edit' ? '留空则不修改密码' : '8-20位,含字母和数字'"
+          />
+        </el-form-item>
+        <el-form-item label="姓名" prop="nickname">
+          <el-input v-model="formData.nickname" placeholder="最长50字符" />
+        </el-form-item>
+        <el-form-item label="角色" prop="adminRole">
+          <el-select v-model="formData.adminRole" placeholder="请选择角色" style="width: 100%">
+            <el-option label="超级管理员" :value="1" />
+            <el-option label="运营人员" :value="2" />
+            <el-option label="只读用户" :value="3" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="formData.status">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="formVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 权限配置对话框 -->
+    <el-dialog
+      v-model="permissionVisible"
+      title="页面权限配置"
+      width="520px"
+      :close-on-click-modal="false"
+    >
+      <div class="permission-tip">
+        <el-alert
+          title="配置该管理员可访问的页面菜单,超级管理员默认拥有全部权限(不可配置)"
+          type="info"
+          :closable="false"
+          show-icon
+        />
+      </div>
+      <div class="permission-admin-name">
+        当前管理员:<strong>{{ currentAdmin?.nickname }} ({{ currentAdmin?.username }})</strong>
+      </div>
+      <el-checkbox-group v-model="selectedRoutes" class="permission-checkboxes">
+        <el-row>
+          <el-col v-for="item in allMenuRoutes" :key="item.path" :span="12">
+            <el-checkbox :label="item.path" style="margin-bottom: 10px;">
+              {{ item.title }}
+            </el-checkbox>
+          </el-col>
+        </el-row>
+      </el-checkbox-group>
+      <template #footer>
+        <el-button @click="permissionVisible = false">取消</el-button>
+        <el-button type="primary" :loading="permissionLoading" @click="handlePermissionSubmit">
+          保存权限
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Search, Refresh, Plus, Delete } from '@element-plus/icons-vue'
+import { adminApi } from '@/api'
+
+// ======================== 可配置的菜单路由列表 ========================
+const allMenuRoutes = [
+  { path: '/user/profile', title: '用户管理' },
+  { path: '/acupoint', title: '穴位管理' },
+  { path: '/device', title: '设备管理' },
+  { path: '/moxibustion', title: '艾灸手法管理' },
+  { path: '/plan', title: '方案管理' },
+  { path: '/admin', title: '管理员管理' },
+  { path: '/log', title: '日志管理' },
+]
+
+// ======================== 列表 ========================
+const loading = ref(false)
+const tableData = ref([])
+const total = ref(0)
+const selectedIds = ref([])
+
+const query = reactive({
+  keyword: '',
+  adminRole: null,
+  status: null,
+  pageNum: 1,
+  pageSize: 10
+})
+
+async function loadData() {
+  loading.value = true
+  try {
+    const params = {
+      username: query.keyword || undefined,
+      nickname: query.keyword || undefined,
+      adminRole: query.adminRole ?? undefined,
+      status: query.status ?? undefined,
+      pageNum: query.pageNum,
+      pageSize: query.pageSize
+    }
+    const res = await adminApi.getAdminPage(params)
+    tableData.value = res.data.records || []
+    total.value = res.data.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+function handleSearch() {
+  query.pageNum = 1
+  loadData()
+}
+
+function handleReset() {
+  query.keyword = ''
+  query.adminRole = null
+  query.status = null
+  query.pageNum = 1
+  loadData()
+}
+
+function handleSelectionChange(rows) {
+  selectedIds.value = rows.map(r => r.id)
+}
+
+// ======================== 角色标签 ========================
+function roleLabel(role) {
+  const map = { 1: '超级管理员', 2: '运营人员', 3: '只读用户' }
+  return map[role] || '未知'
+}
+
+function roleTagType(role) {
+  const map = { 1: 'danger', 2: 'primary', 3: 'info' }
+  return map[role] || 'default'
+}
+
+// ======================== 新增/编辑表单 ========================
+const formVisible = ref(false)
+const formMode = ref('add')
+const submitLoading = ref(false)
+const formRef = ref()
+
+const formData = reactive({
+  id: null,
+  username: '',
+  password: '',
+  nickname: '',
+  adminRole: null,
+  status: 1
+})
+
+const formRules = {
+  username: [
+    { required: true, message: '请输入用户名', trigger: 'blur' },
+    { min: 4, max: 20, message: '用户名长度4-20字符', trigger: 'blur' }
+  ],
+  password: [
+    {
+      validator: (rule, value, callback) => {
+        if (formMode.value === 'add' && !value) {
+          callback(new Error('请输入密码'))
+        } else if (value && (value.length < 8 || value.length > 20)) {
+          callback(new Error('密码长度8-20位'))
+        } else if (value && !/(?=.*[a-zA-Z])(?=.*\d)/.test(value)) {
+          callback(new Error('密码必须包含字母和数字'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
+  nickname: [
+    { required: true, message: '请输入姓名', trigger: 'blur' },
+    { max: 50, message: '姓名最长50字符', trigger: 'blur' }
+  ],
+  adminRole: [{ required: true, message: '请选择角色', trigger: 'change' }],
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+}
+
+function handleAdd() {
+  formMode.value = 'add'
+  Object.assign(formData, { id: null, username: '', password: '', nickname: '', adminRole: null, status: 1 })
+  formVisible.value = true
+}
+
+function handleEdit(row) {
+  formMode.value = 'edit'
+  Object.assign(formData, {
+    id: row.id,
+    username: row.username,
+    password: '',
+    nickname: row.nickname,
+    adminRole: row.adminRole,
+    status: row.status
+  })
+  formVisible.value = true
+}
+
+function handleFormClose() {
+  formRef.value?.resetFields()
+}
+
+async function handleSubmit() {
+  await formRef.value?.validate()
+  submitLoading.value = true
+  try {
+    const payload = { ...formData }
+    if (formMode.value === 'add') {
+      await adminApi.addAdmin(payload)
+      ElMessage.success('新增管理员成功')
+    } else {
+      if (!payload.password) delete payload.password
+      await adminApi.updateAdmin(payload)
+      ElMessage.success('修改成功')
+    }
+    formVisible.value = false
+    loadData()
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+// ======================== 状态切换 ========================
+async function handleToggleStatus(row) {
+  const newStatus = row.status === 1 ? 0 : 1
+  const label = newStatus === 1 ? '启用' : '禁用'
+  await ElMessageBox.confirm(`确认${label}管理员【${row.nickname}】吗?`, '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+  await adminApi.updateAdminStatus(row.id, newStatus)
+  ElMessage.success(`${label}成功`)
+  loadData()
+}
+
+// ======================== 删除 ========================
+async function handleDelete(row) {
+  await ElMessageBox.confirm(`确认删除管理员【${row.nickname}】吗?此操作不可恢复`, '警告', {
+    confirmButtonText: '确认删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+  await adminApi.deleteAdmin(row.id)
+  ElMessage.success('删除成功')
+  loadData()
+}
+
+async function handleBatchDelete() {
+  await ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条记录吗?`, '批量删除', {
+    confirmButtonText: '确认删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+  await adminApi.batchDeleteAdmin(selectedIds.value)
+  ElMessage.success('批量删除成功')
+  selectedIds.value = []
+  loadData()
+}
+
+// ======================== 权限配置 ========================
+const permissionVisible = ref(false)
+const permissionLoading = ref(false)
+const currentAdmin = ref(null)
+const selectedRoutes = ref([])
+
+async function handlePermission(row) {
+  currentAdmin.value = row
+  const res = await adminApi.getAdminPermission(row.id)
+  const routePaths = res.data?.routePaths
+  if (routePaths) {
+    try {
+      selectedRoutes.value = JSON.parse(routePaths)
+    } catch {
+      selectedRoutes.value = []
+    }
+  } else {
+    selectedRoutes.value = []
+  }
+  permissionVisible.value = true
+}
+
+async function handlePermissionSubmit() {
+  permissionLoading.value = true
+  try {
+    const routePathsStr = JSON.stringify(selectedRoutes.value)
+    await adminApi.updateAdminPermission(currentAdmin.value.id, routePathsStr)
+    ElMessage.success('权限配置保存成功')
+    permissionVisible.value = false
+  } finally {
+    permissionLoading.value = false
+  }
+}
+
+// ======================== 初始化 ========================
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.search-card :deep(.el-card__body) {
+  padding-bottom: 0;
+}
+
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.pagination {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.permission-tip {
+  margin-bottom: 16px;
+}
+
+.permission-admin-name {
+  margin-bottom: 16px;
+  font-size: 14px;
+  color: #606266;
+}
+
+.permission-checkboxes {
+  max-height: 300px;
+  overflow-y: auto;
+  padding: 8px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+}
+</style>

+ 29 - 0
openspec/管理员管理.md

@@ -0,0 +1,29 @@
+### 7.5 系统管理
+
+#### 7.5.1 管理员管理
+
+**功能说明**:管理后台系统管理员账号,包括新增管理员、分配权限、禁用/启用账号。
+
+**管理员列表**:
+
+| 多选框 | 序号 | 管理员ID | 用户名 | 姓名 | 角色 | 状态 | 最后登录时间 | 操作 |
+|--------|------|----------|--------|------|------|------|--------------|------|
+| ☐ | 1 | A001 | admin | 张管理员 | 超级管理员 | 启用 | 2026-04-01 | 编辑/禁用 |
+| ☐ | 2 | A002 | operator | 李操作员 | 运营人员 | 启用 | 2026-03-31 | 编辑/禁用 |
+
+**查询条件**:用户名/姓名(模糊搜索)、角色(下拉)、状态(下拉)
+
+**操作按钮**:新增、批量删除
+
+**新增表单字段**:
+
+| 字段名 | 类型 | 必填 | 验证规则 | 说明 |
+|--------|------|------|----------|------|
+| 用户名 | 文本 | 是 | 4-20字符,唯一值 | 登录账号 |
+| 密码 | 密码 | 是 | 8-20字符,含字母和数字 | - |
+| 姓名 | 文本 | 是 | 最长50字符 | - |
+| 角色 | 下拉选择 | 是 | 超级管理员 / 运营人员 / 只读用户 | - |
+| 状态 | 单选 | 是 | 启用 / 禁用 | - |
+
+
+用应用用户表,帮我设计实现能添加管理员并且能给管理员操作一些页面的权限配置。

+ 79 - 0
package-lock.json

@@ -0,0 +1,79 @@
+{
+  "name": "ajy",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "ajy",
+      "version": "1.0.0",
+      "license": "ISC",
+      "devDependencies": {
+        "@playwright/test": "^1.59.1"
+      }
+    },
+    "node_modules/@playwright/test": {
+      "version": "1.59.1",
+      "resolved": "https://registry.npmmirror.com/@playwright/test/-/test-1.59.1.tgz",
+      "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "playwright": "1.59.1"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/playwright": {
+      "version": "1.59.1",
+      "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.59.1.tgz",
+      "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "playwright-core": "1.59.1"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "fsevents": "2.3.2"
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.59.1",
+      "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.59.1.tgz",
+      "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    }
+  }
+}

+ 442 - 0
plan-page.yml

@@ -0,0 +1,442 @@
+- generic [ref=e3]:
+  - complementary [ref=e4]:
+    - generic [ref=e5]:
+      - img "logo" [ref=e6]
+      - generic [ref=e7]: 艾灸椅管理
+    - menubar [ref=e8]:
+      - menuitem "用户管理" [ref=e9]:
+        - generic [ref=e10] [cursor=pointer]:
+          - img [ref=e12]
+          - generic [ref=e14]: 用户管理
+          - img [ref=e16]
+      - menuitem "穴位管理" [ref=e18] [cursor=pointer]:
+        - img [ref=e20]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e23] [cursor=pointer]:
+        - img [ref=e25]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e27] [cursor=pointer]:
+        - img [ref=e29]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [active] [ref=e31] [cursor=pointer]:
+        - img [ref=e33]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e36] [cursor=pointer]:
+        - img [ref=e38]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e40]:
+        - generic [ref=e41] [cursor=pointer]:
+          - img [ref=e43]
+          - generic [ref=e45]: 系统管理
+          - img [ref=e47]
+  - generic [ref=e49]:
+    - banner [ref=e50]:
+      - img [ref=e52] [cursor=pointer]
+      - navigation "面包屑" [ref=e54]:
+        - generic [ref=e55]:
+          - link "首页" [ref=e56]
+          - text: /
+        - link "方案管理" [ref=e880]
+      - button "超级管理员" [ref=e61] [cursor=pointer]:
+        - img [ref=e64]
+        - generic [ref=e66]: 超级管理员
+        - img [ref=e68]
+    - main [ref=e70]:
+      - generic [ref=e881]:
+        - generic [ref=e884]:
+          - generic [ref=e885]:
+            - generic [ref=e886]: 方案名称
+            - textbox "方案名称" [ref=e890]:
+              - /placeholder: 请输入方案名称
+          - generic [ref=e891]:
+            - generic [ref=e892]: 模式类型
+            - generic [ref=e895] [cursor=pointer]:
+              - generic:
+                - combobox "模式类型" [ref=e897]
+                - generic [ref=e898]: 全部
+              - img [ref=e901]
+          - generic [ref=e903]:
+            - generic [ref=e904]: 功效类型
+            - generic [ref=e907] [cursor=pointer]:
+              - generic:
+                - combobox "功效类型" [ref=e909]
+                - generic [ref=e910]: 全部
+              - img [ref=e913]
+          - generic [ref=e915]:
+            - generic [ref=e916]: 创作人
+            - textbox "创作人" [ref=e920]:
+              - /placeholder: 请输入创作人
+          - generic [ref=e921]:
+            - generic [ref=e922]: 状态
+            - generic [ref=e925] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e927]
+                - generic [ref=e928]: 全部
+              - img [ref=e931]
+          - generic [ref=e933]:
+            - generic [ref=e934]: 创建时间
+            - generic [ref=e936]:
+              - img [ref=e938]
+              - combobox "创建时间" [ref=e940]
+              - generic [ref=e941]: ~
+              - combobox "结束日期" [ref=e942]
+          - generic [ref=e944]:
+            - button "查询" [ref=e945] [cursor=pointer]:
+              - img [ref=e947]
+              - generic [ref=e949]: 查询
+            - button "重置" [ref=e950] [cursor=pointer]:
+              - img [ref=e952]
+              - generic [ref=e954]: 重置
+        - generic [ref=e955]:
+          - generic [ref=e957]:
+            - generic [ref=e958]: 方案列表
+            - generic [ref=e959]:
+              - button "新增" [ref=e960] [cursor=pointer]:
+                - img [ref=e962]
+                - generic [ref=e964]: 新增
+              - button "批量发布" [disabled] [ref=e965]:
+                - generic [ref=e966]: 批量发布
+              - button "批量下架" [disabled] [ref=e967]:
+                - generic [ref=e968]: 批量下架
+              - button "批量删除" [disabled] [ref=e969]:
+                - img [ref=e971]
+                - generic [ref=e973]: 批量删除
+          - generic [ref=e974]:
+            - generic [ref=e976]:
+              - table [ref=e978]:
+                - rowgroup [ref=e990]:
+                  - row "选择所有行 序号 方案ID 方案名称 模式类型 功效类型 创作人 状态 创建时间 操作" [ref=e991]:
+                    - columnheader "选择所有行" [ref=e992]:
+                      - generic "选择所有行" [ref=e994] [cursor=pointer]:
+                        - generic [ref=e995]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e997]:
+                      - generic [ref=e998]: 序号
+                    - columnheader "方案ID" [ref=e999]:
+                      - generic [ref=e1000]: 方案ID
+                    - columnheader "方案名称" [ref=e1001]:
+                      - generic [ref=e1002]: 方案名称
+                    - columnheader "模式类型" [ref=e1003]:
+                      - generic [ref=e1004]: 模式类型
+                    - columnheader "功效类型" [ref=e1005]:
+                      - generic [ref=e1006]: 功效类型
+                    - columnheader "创作人" [ref=e1007]:
+                      - generic [ref=e1008]: 创作人
+                    - columnheader "状态" [ref=e1009]:
+                      - generic [ref=e1010]: 状态
+                    - columnheader "创建时间" [ref=e1011]:
+                      - generic [ref=e1012]: 创建时间
+                    - columnheader "操作" [ref=e1013]:
+                      - generic [ref=e1014]: 操作
+              - table [ref=e1019]:
+                - rowgroup [ref=e1031]:
+                  - row "选择当前行 1 S009 专业扶阳方案 专业模式 扶阳 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1032]:
+                    - cell "选择当前行" [ref=e1033]:
+                      - generic "选择当前行" [ref=e1035] [cursor=pointer]:
+                        - generic [ref=e1036]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e1038]:
+                      - generic [ref=e1040]: "1"
+                    - cell "S009" [ref=e1041]:
+                      - generic [ref=e1042]: S009
+                    - cell "专业扶阳方案" [ref=e1043]:
+                      - generic [ref=e1044]: 专业扶阳方案
+                    - cell "专业模式" [ref=e1045]:
+                      - generic [ref=e1048]: 专业模式
+                    - cell "扶阳" [ref=e1049]:
+                      - generic [ref=e1050]: 扶阳
+                    - cell "ADMIN" [ref=e1051]:
+                      - generic [ref=e1052]: ADMIN
+                    - cell "已发布" [ref=e1053]:
+                      - generic [ref=e1056]: 已发布
+                    - cell "2026-04-15" [ref=e1057]:
+                      - generic [ref=e1058]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1059]:
+                      - generic [ref=e1060]:
+                        - button "查看" [ref=e1061] [cursor=pointer]:
+                          - generic [ref=e1062]: 查看
+                        - button "编辑" [ref=e1063] [cursor=pointer]:
+                          - generic [ref=e1064]: 编辑
+                        - button "下架" [ref=e1065] [cursor=pointer]:
+                          - generic [ref=e1066]: 下架
+                  - row "选择当前行 2 S008 专业养颜方案 专业模式 养颜 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1067]:
+                    - cell "选择当前行" [ref=e1068]:
+                      - generic "选择当前行" [ref=e1070] [cursor=pointer]:
+                        - generic [ref=e1071]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e1073]:
+                      - generic [ref=e1075]: "2"
+                    - cell "S008" [ref=e1076]:
+                      - generic [ref=e1077]: S008
+                    - cell "专业养颜方案" [ref=e1078]:
+                      - generic [ref=e1079]: 专业养颜方案
+                    - cell "专业模式" [ref=e1080]:
+                      - generic [ref=e1083]: 专业模式
+                    - cell "养颜" [ref=e1084]:
+                      - generic [ref=e1085]: 养颜
+                    - cell "ADMIN" [ref=e1086]:
+                      - generic [ref=e1087]: ADMIN
+                    - cell "已发布" [ref=e1088]:
+                      - generic [ref=e1091]: 已发布
+                    - cell "2026-04-15" [ref=e1092]:
+                      - generic [ref=e1093]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1094]:
+                      - generic [ref=e1095]:
+                        - button "查看" [ref=e1096] [cursor=pointer]:
+                          - generic [ref=e1097]: 查看
+                        - button "编辑" [ref=e1098] [cursor=pointer]:
+                          - generic [ref=e1099]: 编辑
+                        - button "下架" [ref=e1100] [cursor=pointer]:
+                          - generic [ref=e1101]: 下架
+                  - row "选择当前行 3 S007 专业化痰方案 专业模式 化痰 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1102]:
+                    - cell "选择当前行" [ref=e1103]:
+                      - generic "选择当前行" [ref=e1105] [cursor=pointer]:
+                        - generic [ref=e1106]:
+                          - checkbox "选择当前行"
+                    - cell "3" [ref=e1108]:
+                      - generic [ref=e1110]: "3"
+                    - cell "S007" [ref=e1111]:
+                      - generic [ref=e1112]: S007
+                    - cell "专业化痰方案" [ref=e1113]:
+                      - generic [ref=e1114]: 专业化痰方案
+                    - cell "专业模式" [ref=e1115]:
+                      - generic [ref=e1118]: 专业模式
+                    - cell "化痰" [ref=e1119]:
+                      - generic [ref=e1120]: 化痰
+                    - cell "ADMIN" [ref=e1121]:
+                      - generic [ref=e1122]: ADMIN
+                    - cell "已发布" [ref=e1123]:
+                      - generic [ref=e1126]: 已发布
+                    - cell "2026-04-15" [ref=e1127]:
+                      - generic [ref=e1128]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1129]:
+                      - generic [ref=e1130]:
+                        - button "查看" [ref=e1131] [cursor=pointer]:
+                          - generic [ref=e1132]: 查看
+                        - button "编辑" [ref=e1133] [cursor=pointer]:
+                          - generic [ref=e1134]: 编辑
+                        - button "下架" [ref=e1135] [cursor=pointer]:
+                          - generic [ref=e1136]: 下架
+                  - row "选择当前行 4 S006 专业活血方案 专业模式 活血 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1137]:
+                    - cell "选择当前行" [ref=e1138]:
+                      - generic "选择当前行" [ref=e1140] [cursor=pointer]:
+                        - generic [ref=e1141]:
+                          - checkbox "选择当前行"
+                    - cell "4" [ref=e1143]:
+                      - generic [ref=e1145]: "4"
+                    - cell "S006" [ref=e1146]:
+                      - generic [ref=e1147]: S006
+                    - cell "专业活血方案" [ref=e1148]:
+                      - generic [ref=e1149]: 专业活血方案
+                    - cell "专业模式" [ref=e1150]:
+                      - generic [ref=e1153]: 专业模式
+                    - cell "活血" [ref=e1154]:
+                      - generic [ref=e1155]: 活血
+                    - cell "ADMIN" [ref=e1156]:
+                      - generic [ref=e1157]: ADMIN
+                    - cell "已发布" [ref=e1158]:
+                      - generic [ref=e1161]: 已发布
+                    - cell "2026-04-15" [ref=e1162]:
+                      - generic [ref=e1163]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1164]:
+                      - generic [ref=e1165]:
+                        - button "查看" [ref=e1166] [cursor=pointer]:
+                          - generic [ref=e1167]: 查看
+                        - button "编辑" [ref=e1168] [cursor=pointer]:
+                          - generic [ref=e1169]: 编辑
+                        - button "下架" [ref=e1170] [cursor=pointer]:
+                          - generic [ref=e1171]: 下架
+                  - row "选择当前行 5 S005 专业化瘀方案 专业模式 化瘀 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1172]:
+                    - cell "选择当前行" [ref=e1173]:
+                      - generic "选择当前行" [ref=e1175] [cursor=pointer]:
+                        - generic [ref=e1176]:
+                          - checkbox "选择当前行"
+                    - cell "5" [ref=e1178]:
+                      - generic [ref=e1180]: "5"
+                    - cell "S005" [ref=e1181]:
+                      - generic [ref=e1182]: S005
+                    - cell "专业化瘀方案" [ref=e1183]:
+                      - generic [ref=e1184]: 专业化瘀方案
+                    - cell "专业模式" [ref=e1185]:
+                      - generic [ref=e1188]: 专业模式
+                    - cell "化瘀" [ref=e1189]:
+                      - generic [ref=e1190]: 化瘀
+                    - cell "ADMIN" [ref=e1191]:
+                      - generic [ref=e1192]: ADMIN
+                    - cell "已发布" [ref=e1193]:
+                      - generic [ref=e1196]: 已发布
+                    - cell "2026-04-15" [ref=e1197]:
+                      - generic [ref=e1198]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1199]:
+                      - generic [ref=e1200]:
+                        - button "查看" [ref=e1201] [cursor=pointer]:
+                          - generic [ref=e1202]: 查看
+                        - button "编辑" [ref=e1203] [cursor=pointer]:
+                          - generic [ref=e1204]: 编辑
+                        - button "下架" [ref=e1205] [cursor=pointer]:
+                          - generic [ref=e1206]: 下架
+                  - row "选择当前行 6 S004 专业祛风方案 专业模式 祛风 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1207]:
+                    - cell "选择当前行" [ref=e1208]:
+                      - generic "选择当前行" [ref=e1210] [cursor=pointer]:
+                        - generic [ref=e1211]:
+                          - checkbox "选择当前行"
+                    - cell "6" [ref=e1213]:
+                      - generic [ref=e1215]: "6"
+                    - cell "S004" [ref=e1216]:
+                      - generic [ref=e1217]: S004
+                    - cell "专业祛风方案" [ref=e1218]:
+                      - generic [ref=e1219]: 专业祛风方案
+                    - cell "专业模式" [ref=e1220]:
+                      - generic [ref=e1223]: 专业模式
+                    - cell "祛风" [ref=e1224]:
+                      - generic [ref=e1225]: 祛风
+                    - cell "ADMIN" [ref=e1226]:
+                      - generic [ref=e1227]: ADMIN
+                    - cell "已发布" [ref=e1228]:
+                      - generic [ref=e1231]: 已发布
+                    - cell "2026-04-15" [ref=e1232]:
+                      - generic [ref=e1233]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1234]:
+                      - generic [ref=e1235]:
+                        - button "查看" [ref=e1236] [cursor=pointer]:
+                          - generic [ref=e1237]: 查看
+                        - button "编辑" [ref=e1238] [cursor=pointer]:
+                          - generic [ref=e1239]: 编辑
+                        - button "下架" [ref=e1240] [cursor=pointer]:
+                          - generic [ref=e1241]: 下架
+                  - row "选择当前行 7 S001 一键艾灸(默认) 一键艾灸 - ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1242]:
+                    - cell "选择当前行" [ref=e1243]:
+                      - generic "选择当前行" [ref=e1245] [cursor=pointer]:
+                        - generic [ref=e1246]:
+                          - checkbox "选择当前行"
+                    - cell "7" [ref=e1248]:
+                      - generic [ref=e1250]: "7"
+                    - cell "S001" [ref=e1251]:
+                      - generic [ref=e1252]: S001
+                    - cell "一键艾灸(默认)" [ref=e1253]:
+                      - generic [ref=e1254]: 一键艾灸(默认)
+                    - cell "一键艾灸" [ref=e1255]:
+                      - generic [ref=e1258]: 一键艾灸
+                    - cell "-" [ref=e1259]:
+                      - generic [ref=e1260]: "-"
+                    - cell "ADMIN" [ref=e1261]:
+                      - generic [ref=e1262]: ADMIN
+                    - cell "已发布" [ref=e1263]:
+                      - generic [ref=e1266]: 已发布
+                    - cell "2026-04-15" [ref=e1267]:
+                      - generic [ref=e1268]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1269]:
+                      - generic [ref=e1270]:
+                        - button "查看" [ref=e1271] [cursor=pointer]:
+                          - generic [ref=e1272]: 查看
+                        - button "编辑" [ref=e1273] [cursor=pointer]:
+                          - generic [ref=e1274]: 编辑
+                        - button "下架" [ref=e1275] [cursor=pointer]:
+                          - generic [ref=e1276]: 下架
+                  - row "选择当前行 8 S003 专业祛湿方案 专业模式 祛湿 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1277]:
+                    - cell "选择当前行" [ref=e1278]:
+                      - generic "选择当前行" [ref=e1280] [cursor=pointer]:
+                        - generic [ref=e1281]:
+                          - checkbox "选择当前行"
+                    - cell "8" [ref=e1283]:
+                      - generic [ref=e1285]: "8"
+                    - cell "S003" [ref=e1286]:
+                      - generic [ref=e1287]: S003
+                    - cell "专业祛湿方案" [ref=e1288]:
+                      - generic [ref=e1289]: 专业祛湿方案
+                    - cell "专业模式" [ref=e1290]:
+                      - generic [ref=e1293]: 专业模式
+                    - cell "祛湿" [ref=e1294]:
+                      - generic [ref=e1295]: 祛湿
+                    - cell "ADMIN" [ref=e1296]:
+                      - generic [ref=e1297]: ADMIN
+                    - cell "已发布" [ref=e1298]:
+                      - generic [ref=e1301]: 已发布
+                    - cell "2026-04-15" [ref=e1302]:
+                      - generic [ref=e1303]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1304]:
+                      - generic [ref=e1305]:
+                        - button "查看" [ref=e1306] [cursor=pointer]:
+                          - generic [ref=e1307]: 查看
+                        - button "编辑" [ref=e1308] [cursor=pointer]:
+                          - generic [ref=e1309]: 编辑
+                        - button "下架" [ref=e1310] [cursor=pointer]:
+                          - generic [ref=e1311]: 下架
+                  - row "选择当前行 9 S002 专业驱寒方案 专业模式 驱寒 ADMIN 已发布 2026-04-15 查看 编辑 下架" [ref=e1312]:
+                    - cell "选择当前行" [ref=e1313]:
+                      - generic "选择当前行" [ref=e1315] [cursor=pointer]:
+                        - generic [ref=e1316]:
+                          - checkbox "选择当前行"
+                    - cell "9" [ref=e1318]:
+                      - generic [ref=e1320]: "9"
+                    - cell "S002" [ref=e1321]:
+                      - generic [ref=e1322]: S002
+                    - cell "专业驱寒方案" [ref=e1323]:
+                      - generic [ref=e1324]: 专业驱寒方案
+                    - cell "专业模式" [ref=e1325]:
+                      - generic [ref=e1328]: 专业模式
+                    - cell "驱寒" [ref=e1329]:
+                      - generic [ref=e1330]: 驱寒
+                    - cell "ADMIN" [ref=e1331]:
+                      - generic [ref=e1332]: ADMIN
+                    - cell "已发布" [ref=e1333]:
+                      - generic [ref=e1336]: 已发布
+                    - cell "2026-04-15" [ref=e1337]:
+                      - generic [ref=e1338]: 2026-04-15
+                    - cell "查看 编辑 下架" [ref=e1339]:
+                      - generic [ref=e1340]:
+                        - button "查看" [ref=e1341] [cursor=pointer]:
+                          - generic [ref=e1342]: 查看
+                        - button "编辑" [ref=e1343] [cursor=pointer]:
+                          - generic [ref=e1344]: 编辑
+                        - button "下架" [ref=e1345] [cursor=pointer]:
+                          - generic [ref=e1346]: 下架
+                  - row "选择当前行 10 PRO001 驱寒模式 专业模式 驱寒 系统预设 已发布 2026-04-13 查看 编辑 下架" [ref=e1347]:
+                    - cell "选择当前行" [ref=e1348]:
+                      - generic "选择当前行" [ref=e1350] [cursor=pointer]:
+                        - generic [ref=e1351]:
+                          - checkbox "选择当前行"
+                    - cell "10" [ref=e1353]:
+                      - generic [ref=e1355]: "10"
+                    - cell "PRO001" [ref=e1356]:
+                      - generic [ref=e1357]: PRO001
+                    - cell "驱寒模式" [ref=e1358]:
+                      - generic [ref=e1359]: 驱寒模式
+                    - cell "专业模式" [ref=e1360]:
+                      - generic [ref=e1363]: 专业模式
+                    - cell "驱寒" [ref=e1364]:
+                      - generic [ref=e1365]: 驱寒
+                    - cell "系统预设" [ref=e1366]:
+                      - generic [ref=e1367]: 系统预设
+                    - cell "已发布" [ref=e1368]:
+                      - generic [ref=e1371]: 已发布
+                    - cell "2026-04-13" [ref=e1372]:
+                      - generic [ref=e1373]: 2026-04-13
+                    - cell "查看 编辑 下架" [ref=e1374]:
+                      - generic [ref=e1375]:
+                        - button "查看" [ref=e1376] [cursor=pointer]:
+                          - generic [ref=e1377]: 查看
+                        - button "编辑" [ref=e1378] [cursor=pointer]:
+                          - generic [ref=e1379]: 编辑
+                        - button "下架" [ref=e1380] [cursor=pointer]:
+                          - generic [ref=e1381]: 下架
+            - generic [ref=e1383]:
+              - generic [ref=e1384]: 共 17 条
+              - generic [ref=e1387] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e1389]
+                  - generic [ref=e1390]: 10条/页
+                - img [ref=e1393]
+              - button "上一页" [disabled] [ref=e1395]:
+                - generic:
+                  - img
+              - list [ref=e1396]:
+                - listitem "第 1 页" [ref=e1397]: "1"
+                - listitem "第 2 页" [ref=e1398] [cursor=pointer]: "2"
+              - button "下一页" [ref=e1399] [cursor=pointer]:
+                - generic:
+                  - img
+              - generic [ref=e1400]:
+                - generic [ref=e1401]: 前往
+                - spinbutton "页" [ref=e1404]: "1"
+                - generic [ref=e1405]: 页

BIN
test-e2e/---app.png


BIN
test-e2e/---user-app.png


BIN
test-e2e/---user-profile.png


BIN
test-e2e/account-page.png


BIN
test-e2e/acupoint.png


+ 146 - 0
test-e2e/test-results/app-登录模块-TC-001-正常登录-chromium/error-context.md

@@ -0,0 +1,146 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 登录模块 >> TC-001: 正常登录
+- Location: e2e/app.spec.js:7:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+> 8   |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+  42  |     await page.goto(BASE_URL);
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+```

+ 155 - 0
test-e2e/test-results/app-登录模块-TC-002-用户名为空验证-chromium/error-context.md

@@ -0,0 +1,155 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 登录模块 >> TC-002: 用户名为空验证
+- Location: e2e/app.spec.js:16:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+> 17  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+  42  |     await page.goto(BASE_URL);
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+```

+ 162 - 0
test-e2e/test-results/app-登录模块-TC-003-密码为空验证-chromium/error-context.md

@@ -0,0 +1,162 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 登录模块 >> TC-003: 密码为空验证
+- Location: e2e/app.spec.js:23:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+> 24  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+  42  |     await page.goto(BASE_URL);
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+```

+ 169 - 0
test-e2e/test-results/app-登录模块-TC-004-错误账号登录-chromium/error-context.md

@@ -0,0 +1,169 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 登录模块 >> TC-004: 错误账号登录
+- Location: e2e/app.spec.js:30:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+> 31  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+  42  |     await page.goto(BASE_URL);
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-010-查看账号管理页面-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-010: 查看账号管理页面
+- Location: e2e/app.spec.js:50:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-011-账号列表搜索-手机号-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-011: 账号列表搜索-手机号
+- Location: e2e/app.spec.js:54:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-012-账号列表搜索-昵称-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-012: 账号列表搜索-昵称
+- Location: e2e/app.spec.js:60:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-013-新增用户按钮存在-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-013: 新增用户按钮存在
+- Location: e2e/app.spec.js:66:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-014-编辑功能-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-014: 编辑功能
+- Location: e2e/app.spec.js:70:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

+ 180 - 0
test-e2e/test-results/app-账号管理模块-TC-015-禁用功能-chromium/error-context.md

@@ -0,0 +1,180 @@
+# Instructions
+
+- Following Playwright test failed.
+- Explain why, be concise, respect Playwright best practices.
+- Provide a snippet of code with the fix, if possible.
+
+# Test info
+
+- Name: app.spec.js >> 账号管理模块 >> TC-015: 禁用功能
+- Location: e2e/app.spec.js:78:3
+
+# Error details
+
+```
+Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+Call log:
+  - navigating to "https://ajy.danclub.cn/", waiting until "load"
+
+```
+
+# Page snapshot
+
+```yaml
+- generic [ref=e3]:
+  - generic [ref=e6]:
+    - heading "This site can’t be reached" [level=1] [ref=e7]
+    - paragraph [ref=e8]:
+      - strong [ref=e9]: https://ajy.danclub.cn/
+      - text: is unreachable.
+    - generic [ref=e10]: ERR_ADDRESS_UNREACHABLE
+  - button "Reload" [ref=e13] [cursor=pointer]
+```
+
+# Test source
+
+```ts
+  1   | const { test, expect } = require('@playwright/test');
+  2   | 
+  3   | const BASE_URL = 'https://ajy.danclub.cn';
+  4   | const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+  5   | 
+  6   | test.describe('登录模块', () => {
+  7   |   test('TC-001: 正常登录', async ({ page }) => {
+  8   |     await page.goto(BASE_URL);
+  9   |     await page.waitForLoadState('networkidle');
+  10  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  11  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  12  |     await page.getByRole('button', { name: '登 录' }).click();
+  13  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  14  |   });
+  15  | 
+  16  |   test('TC-002: 用户名为空验证', async ({ page }) => {
+  17  |     await page.goto(BASE_URL);
+  18  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  19  |     await page.getByRole('button', { name: '登 录' }).click();
+  20  |     await expect(page.getByText('请输入用户名')).toBeVisible();
+  21  |   });
+  22  | 
+  23  |   test('TC-003: 密码为空验证', async ({ page }) => {
+  24  |     await page.goto(BASE_URL);
+  25  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  26  |     await page.getByRole('button', { name: '登 录' }).click();
+  27  |     await expect(page.getByText('请输入密码')).toBeVisible();
+  28  |   });
+  29  | 
+  30  |   test('TC-004: 错误账号登录', async ({ page }) => {
+  31  |     await page.goto(BASE_URL);
+  32  |     await page.getByPlaceholder('请输入用户名').fill('wronguser');
+  33  |     await page.getByPlaceholder('请输入密码').fill('wrongpass');
+  34  |     await page.getByRole('button', { name: '登 录' }).click();
+  35  |     await page.waitForTimeout(2000);
+  36  |     await expect(page.getByRole('button', { name: '登 录' })).toBeVisible();
+  37  |   });
+  38  | });
+  39  | 
+  40  | test.describe('账号管理模块', () => {
+  41  |   test.beforeEach(async ({ page }) => {
+> 42  |     await page.goto(BASE_URL);
+      |                ^ Error: page.goto: net::ERR_ADDRESS_UNREACHABLE at https://ajy.danclub.cn/
+  43  |     await page.waitForLoadState('networkidle');
+  44  |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  45  |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  46  |     await page.getByRole('button', { name: '登 录' }).click();
+  47  |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  48  |   });
+  49  | 
+  50  |   test('TC-010: 查看账号管理页面', async ({ page }) => {
+  51  |     await expect(page.locator('.el-table__body')).toBeVisible();
+  52  |   });
+  53  | 
+  54  |   test('TC-011: 账号列表搜索-手机号', async ({ page }) => {
+  55  |     await page.getByPlaceholder('请输入手机号').fill('138');
+  56  |     await page.getByRole('button', { name: '搜索' }).click();
+  57  |     await page.waitForTimeout(1000);
+  58  |   });
+  59  | 
+  60  |   test('TC-012: 账号列表搜索-昵称', async ({ page }) => {
+  61  |     await page.getByPlaceholder('请输入昵称').fill('管理员');
+  62  |     await page.getByRole('button', { name: '搜索' }).click();
+  63  |     await page.waitForTimeout(1000);
+  64  |   });
+  65  | 
+  66  |   test('TC-013: 新增用户按钮存在', async ({ page }) => {
+  67  |     await expect(page.getByRole('button', { name: '新增用户' })).toBeVisible();
+  68  |   });
+  69  | 
+  70  |   test('TC-014: 编辑功能', async ({ page }) => {
+  71  |     const editBtn = page.locator('button:has-text("编辑")').first();
+  72  |     if (await editBtn.isVisible()) {
+  73  |       await editBtn.click();
+  74  |       await page.waitForTimeout(500);
+  75  |     }
+  76  |   });
+  77  | 
+  78  |   test('TC-015: 禁用功能', async ({ page }) => {
+  79  |     const disableBtn = page.locator('button:has-text("禁用")').first();
+  80  |     if (await disableBtn.isVisible()) {
+  81  |       await disableBtn.click();
+  82  |       await page.waitForTimeout(500);
+  83  |     }
+  84  |   });
+  85  | 
+  86  |   test('TC-017: 取消删除', async ({ page }) => {
+  87  |     const deleteBtn = page.locator('button:has-text("删除")').first();
+  88  |     if (await deleteBtn.isVisible()) {
+  89  |       await deleteBtn.click();
+  90  |       await page.waitForTimeout(500);
+  91  |       const cancelBtn = page.getByRole('button', { name: '取消' });
+  92  |       if (await cancelBtn.isVisible()) {
+  93  |         await cancelBtn.click();
+  94  |       }
+  95  |     }
+  96  |   });
+  97  | 
+  98  |   test('TC-017: 重置搜索条件', async ({ page }) => {
+  99  |     await page.getByPlaceholder('请输入手机号').fill('测试');
+  100 |     await page.getByRole('button', { name: '重置' }).click();
+  101 |     await page.waitForTimeout(500);
+  102 |   });
+  103 | });
+  104 | 
+  105 | test.describe('穴位管理模块', () => {
+  106 |   test.beforeEach(async ({ page }) => {
+  107 |     await page.goto(BASE_URL);
+  108 |     await page.waitForLoadState('networkidle');
+  109 |     await page.getByPlaceholder('请输入用户名').fill(TEST_ADMIN.username);
+  110 |     await page.getByPlaceholder('请输入密码').fill(TEST_ADMIN.password);
+  111 |     await page.getByRole('button', { name: '登 录' }).click();
+  112 |     await page.waitForURL(`${BASE_URL}/#/user/app`, { timeout: 10000 });
+  113 |   });
+  114 | 
+  115 |   test('TC-040: 查看穴位列表', async ({ page }) => {
+  116 |     await expect(page.locator('.el-table__body')).toBeVisible();
+  117 |   });
+  118 | 
+  119 |   test('TC-041: 穴位列表搜索-穴位名称', async ({ page }) => {
+  120 |     await page.goto(`${BASE_URL}/#/acupoint`);
+  121 |     await page.waitForLoadState('networkidle');
+  122 |     await page.getByPlaceholder('请输入穴位名称').first().fill('大椎');
+  123 |     await page.getByRole('button', { name: '查询' }).click();
+  124 |     await page.waitForTimeout(1000);
+  125 |   });
+  126 | 
+  127 |   test('TC-042: 新增按钮存在', async ({ page }) => {
+  128 |     await page.getByRole('button', { name: '新增' }).click();
+  129 |     await page.waitForTimeout(500);
+  130 |   });
+  131 | 
+  132 |   test('TC-043: 查看穴位详情', async ({ page }) => {
+  133 |     const viewBtn = page.locator('button:has-text("查看")').first();
+  134 |     if (await viewBtn.isVisible()) {
+  135 |       await viewBtn.click();
+  136 |       await page.waitForTimeout(500);
+  137 |     }
+  138 |   });
+  139 | 
+  140 |   test('TC-044: 编辑穴位', async ({ page }) => {
+  141 |     const editBtn = page.locator('button:has-text("编辑")').first();
+  142 |     if (await editBtn.isVisible()) {
+```

BIN
test-e2e/user-profile.png


+ 4 - 0
test-results/.last-run.json

@@ -0,0 +1,4 @@
+{
+  "status": "failed",
+  "failedTests": []
+}

+ 190 - 0
user-form.yml

@@ -0,0 +1,190 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "账号管理" [ref=e102]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e115]:
+        - generic [ref=e118]:
+          - generic [ref=e119]:
+            - generic [ref=e120]: 手机号
+            - textbox "手机号" [ref=e124]:
+              - /placeholder: 请输入手机号
+          - generic [ref=e125]:
+            - generic [ref=e126]: 昵称
+            - textbox "昵称" [ref=e130]:
+              - /placeholder: 请输入昵称
+          - generic [ref=e131]:
+            - generic [ref=e132]: 用户类型
+            - generic [ref=e135] [cursor=pointer]:
+              - generic:
+                - combobox "用户类型" [ref=e137]
+                - generic [ref=e138]: 全部
+              - img [ref=e141]
+          - generic [ref=e143]:
+            - generic [ref=e144]: 状态
+            - generic [ref=e147] [cursor=pointer]:
+              - generic:
+                - combobox "状态" [ref=e149]
+                - generic [ref=e150]: 全部
+              - img [ref=e153]
+          - generic [ref=e156]:
+            - button "搜索" [ref=e157] [cursor=pointer]:
+              - img [ref=e159]
+              - generic [ref=e161]: 搜索
+            - button "重置" [ref=e162] [cursor=pointer]:
+              - img [ref=e164]
+              - generic [ref=e166]: 重置
+        - generic [ref=e167]:
+          - generic [ref=e169]:
+            - generic [ref=e170]: 账号管理列表
+            - button "新增用户" [ref=e171] [cursor=pointer]:
+              - img [ref=e173]
+              - generic [ref=e175]: 新增用户
+          - generic [ref=e176]:
+            - generic [ref=e178]:
+              - table [ref=e180]:
+                - rowgroup [ref=e191]:
+                  - row "# 手机号 昵称 用户类型 状态 绑定设备 子用户 注册时间 操作" [ref=e192]:
+                    - columnheader "#" [ref=e193]:
+                      - generic [ref=e194]: "#"
+                    - columnheader "手机号" [ref=e195]:
+                      - generic [ref=e196]: 手机号
+                    - columnheader "昵称" [ref=e197]:
+                      - generic [ref=e198]: 昵称
+                    - columnheader "用户类型" [ref=e199]:
+                      - generic [ref=e200]: 用户类型
+                    - columnheader "状态" [ref=e201]:
+                      - generic [ref=e202]: 状态
+                    - columnheader "绑定设备" [ref=e203]:
+                      - generic [ref=e204]: 绑定设备
+                    - columnheader "子用户" [ref=e205]:
+                      - generic [ref=e206]: 子用户
+                    - columnheader "注册时间" [ref=e207]:
+                      - generic [ref=e208]: 注册时间
+                    - columnheader "操作" [ref=e209]:
+                      - generic [ref=e210]: 操作
+              - table [ref=e215]:
+                - rowgroup [ref=e226]:
+                  - row "1 超级管理员 管理员 正常 2026-04-09T16:58:56 编辑 禁用 删除" [ref=e227]:
+                    - cell "1" [ref=e228]:
+                      - generic [ref=e230]: "1"
+                    - cell [ref=e231]
+                    - cell "超级管理员" [ref=e232]:
+                      - generic [ref=e233]: 超级管理员
+                    - cell "管理员" [ref=e234]:
+                      - generic [ref=e237]: 管理员
+                    - cell "正常" [ref=e238]:
+                      - generic [ref=e241]: 正常
+                    - cell [ref=e242]
+                    - cell [ref=e243]
+                    - cell "2026-04-09T16:58:56" [ref=e244]:
+                      - generic [ref=e245]: 2026-04-09T16:58:56
+                    - cell "编辑 禁用 删除" [ref=e246]:
+                      - generic [ref=e247]:
+                        - button "编辑" [ref=e248] [cursor=pointer]:
+                          - generic [ref=e249]: 编辑
+                        - button "禁用" [ref=e250] [cursor=pointer]:
+                          - generic [ref=e251]: 禁用
+                        - button "删除" [ref=e252] [cursor=pointer]:
+                          - generic [ref=e253]: 删除
+            - generic [ref=e255]:
+              - generic [ref=e256]: 共 1 条
+              - generic [ref=e259] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e261]
+                  - generic [ref=e262]: 10条/页
+                - img [ref=e265]
+              - button "上一页" [disabled] [ref=e267]:
+                - generic:
+                  - img
+              - list [ref=e268]:
+                - listitem "第 1 页" [ref=e269]: "1"
+              - button "下一页" [disabled] [ref=e270]:
+                - generic:
+                  - img
+              - generic [ref=e271]:
+                - generic [ref=e272]: 前往
+                - spinbutton "页" [ref=e275]: "1"
+                - generic [ref=e276]: 页
+        - dialog "新增账号" [ref=e287]:
+          - generic [active] [ref=e288]:
+            - generic [ref=e289]:
+              - heading "新增账号" [level=2] [ref=e290]
+              - button "关闭此对话框" [ref=e291] [cursor=pointer]:
+                - img [ref=e293]
+            - generic [ref=e296]:
+              - generic [ref=e297]:
+                - generic [ref=e298]: "* 手机号"
+                - textbox "* 手机号" [ref=e302]:
+                  - /placeholder: 请输入手机号
+              - generic [ref=e303]:
+                - generic [ref=e304]: 昵称
+                - textbox "昵称" [ref=e308]:
+                  - /placeholder: 请输入昵称
+              - generic [ref=e309]:
+                - generic [ref=e310]: "* 用户类型"
+                - generic [ref=e313] [cursor=pointer]:
+                  - generic:
+                    - combobox "* 用户类型" [ref=e315]
+                    - generic [ref=e316]: 普通用户
+                  - img [ref=e319]
+              - generic [ref=e321]:
+                - generic [ref=e322]: 状态
+                - radiogroup "状态" [ref=e324]:
+                  - generic [ref=e325] [cursor=pointer]:
+                    - radio "正常" [checked] [ref=e327]
+                    - generic [ref=e329]: 正常
+                  - generic [ref=e330] [cursor=pointer]:
+                    - radio "禁用" [ref=e332]
+                    - generic [ref=e334]: 禁用
+            - generic [ref=e335]:
+              - button "取消" [ref=e336] [cursor=pointer]:
+                - generic [ref=e337]: 取消
+              - button "确定" [ref=e338] [cursor=pointer]:
+                - generic [ref=e339]: 确定

+ 248 - 0
user-profile.yml

@@ -0,0 +1,248 @@
+- generic [ref=e47]:
+  - complementary [ref=e48]:
+    - generic [ref=e49]:
+      - img "logo" [ref=e50]
+      - generic [ref=e51]: 艾灸椅管理
+    - menubar [ref=e52]:
+      - menuitem "用户管理" [expanded] [ref=e53]:
+        - generic [ref=e54] [cursor=pointer]:
+          - img [ref=e56]
+          - generic [ref=e58]: 用户管理
+          - img [ref=e60]
+        - menu [ref=e277]:
+          - menuitem "用户列表" [active] [ref=e278] [cursor=pointer]:
+            - img [ref=e280]
+            - text: 用户列表
+          - menuitem "用户方案管理" [ref=e282] [cursor=pointer]:
+            - img [ref=e284]
+            - text: 用户方案管理
+      - menuitem "穴位管理" [ref=e62] [cursor=pointer]:
+        - img [ref=e64]
+        - text: 穴位管理
+      - menuitem "设备管理" [ref=e67] [cursor=pointer]:
+        - img [ref=e69]
+        - text: 设备管理
+      - menuitem "艾灸手法管理" [ref=e71] [cursor=pointer]:
+        - img [ref=e73]
+        - text: 艾灸手法管理
+      - menuitem "方案管理" [ref=e75] [cursor=pointer]:
+        - img [ref=e77]
+        - text: 方案管理
+      - menuitem "方案模拟测试" [ref=e80] [cursor=pointer]:
+        - img [ref=e82]
+        - text: 方案模拟测试
+      - menuitem "系统管理" [ref=e84]:
+        - generic [ref=e85] [cursor=pointer]:
+          - img [ref=e87]
+          - generic [ref=e89]: 系统管理
+          - img [ref=e91]
+  - generic [ref=e93]:
+    - banner [ref=e94]:
+      - img [ref=e96] [cursor=pointer]
+      - navigation "面包屑" [ref=e98]:
+        - generic [ref=e99]:
+          - link "首页" [ref=e100]
+          - text: /
+        - link "用户列表" [ref=e340]
+      - button "超级管理员" [ref=e105] [cursor=pointer]:
+        - img [ref=e108]
+        - generic [ref=e110]: 超级管理员
+        - img [ref=e112]
+    - main [ref=e114]:
+      - generic [ref=e341]:
+        - generic [ref=e344]:
+          - generic [ref=e345]:
+            - generic [ref=e346]: 姓名/手机号
+            - textbox "姓名/手机号" [ref=e350]:
+              - /placeholder: 请输入姓名或手机号
+          - generic [ref=e351]:
+            - generic [ref=e352]: 设备编号
+            - textbox "设备编号" [ref=e356]:
+              - /placeholder: 请输入设备编号
+          - generic [ref=e357]:
+            - generic [ref=e358]: 性别
+            - generic [ref=e361] [cursor=pointer]:
+              - generic:
+                - combobox "性别" [ref=e363]
+                - generic [ref=e364]: 全部
+              - img [ref=e367]
+          - generic [ref=e369]:
+            - generic [ref=e370]: 注册时间
+            - generic [ref=e372]:
+              - img [ref=e374]
+              - combobox "注册时间" [ref=e376]
+              - generic [ref=e377]: 至
+              - combobox "结束日期" [ref=e378]
+          - generic [ref=e379]:
+            - generic [ref=e380]: 穴位表
+            - generic [ref=e383] [cursor=pointer]:
+              - generic:
+                - combobox "穴位表" [ref=e385]
+                - generic [ref=e386]: 全部
+              - img [ref=e389]
+          - generic [ref=e392]:
+            - button "查询" [ref=e393] [cursor=pointer]:
+              - img [ref=e395]
+              - generic [ref=e397]: 查询
+            - button "重置" [ref=e398] [cursor=pointer]:
+              - img [ref=e400]
+              - generic [ref=e402]: 重置
+        - generic [ref=e403]:
+          - generic [ref=e405]:
+            - generic [ref=e406]: 用户管理
+            - generic [ref=e407]:
+              - button "导入" [ref=e410] [cursor=pointer]:
+                - button "导入" [ref=e411]:
+                  - img [ref=e413]
+                  - generic [ref=e415]: 导入
+              - button "导出 Excel" [ref=e416] [cursor=pointer]:
+                - img [ref=e418]
+                - generic [ref=e420]: 导出 Excel
+              - button "新增" [ref=e421] [cursor=pointer]:
+                - img [ref=e423]
+                - generic [ref=e425]: 新增
+              - button "批量删除" [disabled] [ref=e426]:
+                - img [ref=e428]
+                - generic [ref=e430]: 批量删除
+          - generic [ref=e431]:
+            - generic [ref=e433]:
+              - table [ref=e435]:
+                - rowgroup [ref=e453]:
+                  - row "选择所有行 序号 用户ID 姓名 性别 年龄 手机号 设备编号 肩宽(cm) 身长(cm) 脊柱长(cm) 身高(cm) 体重(kg) 穴位表 注册时间 操作" [ref=e454]:
+                    - columnheader "选择所有行" [ref=e455]:
+                      - generic "选择所有行" [ref=e457] [cursor=pointer]:
+                        - generic [ref=e458]:
+                          - checkbox "选择所有行"
+                    - columnheader "序号" [ref=e460]:
+                      - generic [ref=e461]: 序号
+                    - columnheader "用户ID" [ref=e462]:
+                      - generic [ref=e463]: 用户ID
+                    - columnheader "姓名" [ref=e464]:
+                      - generic [ref=e465]: 姓名
+                    - columnheader "性别" [ref=e466]:
+                      - generic [ref=e467]: 性别
+                    - columnheader "年龄" [ref=e468]:
+                      - generic [ref=e469]: 年龄
+                    - columnheader "手机号" [ref=e470]:
+                      - generic [ref=e471]: 手机号
+                    - columnheader "设备编号" [ref=e472]:
+                      - generic [ref=e473]: 设备编号
+                    - columnheader "肩宽(cm)" [ref=e474]:
+                      - generic [ref=e475]: 肩宽(cm)
+                    - columnheader "身长(cm)" [ref=e476]:
+                      - generic [ref=e477]: 身长(cm)
+                    - columnheader "脊柱长(cm)" [ref=e478]:
+                      - generic [ref=e479]: 脊柱长(cm)
+                    - columnheader "身高(cm)" [ref=e480]:
+                      - generic [ref=e481]: 身高(cm)
+                    - columnheader "体重(kg)" [ref=e482]:
+                      - generic [ref=e483]: 体重(kg)
+                    - columnheader "穴位表" [ref=e484]:
+                      - generic [ref=e485]: 穴位表
+                    - columnheader "注册时间" [ref=e486]:
+                      - generic [ref=e487]: 注册时间
+                    - columnheader "操作" [ref=e488]:
+                      - generic [ref=e489]: 操作
+              - table [ref=e494]:
+                - rowgroup [ref=e512]:
+                  - row "选择当前行 1 2 邓浩 男 36岁 16812345678 AJY202605010011 47 170 55 - - 已生成 2026-04-15T10:09:04 编辑 设备管理 删除" [ref=e513]:
+                    - cell "选择当前行" [ref=e514]:
+                      - generic "选择当前行" [ref=e516] [cursor=pointer]:
+                        - generic [ref=e517]:
+                          - checkbox "选择当前行"
+                    - cell "1" [ref=e519]:
+                      - generic [ref=e521]: "1"
+                    - cell "2" [ref=e522]:
+                      - generic [ref=e523]: "2"
+                    - cell "邓浩" [ref=e524]:
+                      - generic [ref=e525]: 邓浩
+                    - cell "男" [ref=e526]:
+                      - generic [ref=e529]: 男
+                    - cell "36岁" [ref=e530]:
+                      - generic [ref=e531]: 36岁
+                    - cell "16812345678" [ref=e532]:
+                      - generic [ref=e533]: "16812345678"
+                    - cell "AJY202605010011" [ref=e534]:
+                      - generic [ref=e535]: AJY202605010011
+                    - cell "47" [ref=e536]:
+                      - generic [ref=e537]: "47"
+                    - cell "170" [ref=e538]:
+                      - generic [ref=e539]: "170"
+                    - cell "55" [ref=e540]:
+                      - generic [ref=e541]: "55"
+                    - cell "-" [ref=e542]:
+                      - generic [ref=e543]: "-"
+                    - cell "-" [ref=e544]:
+                      - generic [ref=e545]: "-"
+                    - cell "已生成" [ref=e546]:
+                      - generic [ref=e549]: 已生成
+                    - cell "2026-04-15T10:09:04" [ref=e550]:
+                      - generic [ref=e551]: 2026-04-15T10:09:04
+                    - cell "编辑 设备管理 删除" [ref=e552]:
+                      - generic [ref=e553]:
+                        - button "编辑" [ref=e554] [cursor=pointer]:
+                          - generic [ref=e555]: 编辑
+                        - button "设备管理" [ref=e556] [cursor=pointer]:
+                          - generic [ref=e557]: 设备管理
+                        - button "删除" [ref=e558] [cursor=pointer]:
+                          - generic [ref=e559]: 删除
+                  - row "选择当前行 2 1 贾璞 男 25岁 17320687388 AJY202605010011 43 85 60 180 130 已生成 2026-04-09T17:19:11 编辑 设备管理 删除" [ref=e560]:
+                    - cell "选择当前行" [ref=e561]:
+                      - generic "选择当前行" [ref=e563] [cursor=pointer]:
+                        - generic [ref=e564]:
+                          - checkbox "选择当前行"
+                    - cell "2" [ref=e566]:
+                      - generic [ref=e568]: "2"
+                    - cell "1" [ref=e569]:
+                      - generic [ref=e570]: "1"
+                    - cell "贾璞" [ref=e571]:
+                      - generic [ref=e572]: 贾璞
+                    - cell "男" [ref=e573]:
+                      - generic [ref=e576]: 男
+                    - cell "25岁" [ref=e577]:
+                      - generic [ref=e578]: 25岁
+                    - cell "17320687388" [ref=e579]:
+                      - generic [ref=e580]: "17320687388"
+                    - cell "AJY202605010011" [ref=e581]:
+                      - generic [ref=e582]: AJY202605010011
+                    - cell "43" [ref=e583]:
+                      - generic [ref=e584]: "43"
+                    - cell "85" [ref=e585]:
+                      - generic [ref=e586]: "85"
+                    - cell "60" [ref=e587]:
+                      - generic [ref=e588]: "60"
+                    - cell "180" [ref=e589]:
+                      - generic [ref=e590]: "180"
+                    - cell "130" [ref=e591]:
+                      - generic [ref=e592]: "130"
+                    - cell "已生成" [ref=e593]:
+                      - generic [ref=e596]: 已生成
+                    - cell "2026-04-09T17:19:11" [ref=e597]:
+                      - generic [ref=e598]: 2026-04-09T17:19:11
+                    - cell "编辑 设备管理 删除" [ref=e599]:
+                      - generic [ref=e600]:
+                        - button "编辑" [ref=e601] [cursor=pointer]:
+                          - generic [ref=e602]: 编辑
+                        - button "设备管理" [ref=e603] [cursor=pointer]:
+                          - generic [ref=e604]: 设备管理
+                        - button "删除" [ref=e605] [cursor=pointer]:
+                          - generic [ref=e606]: 删除
+            - generic [ref=e608]:
+              - generic [ref=e609]: 共 2 条
+              - generic [ref=e612] [cursor=pointer]:
+                - generic:
+                  - combobox [ref=e614]
+                  - generic [ref=e615]: 10条/页
+                - img [ref=e618]
+              - button "上一页" [disabled] [ref=e620]:
+                - generic:
+                  - img
+              - list [ref=e621]:
+                - listitem "第 1 页" [ref=e622]: "1"
+              - button "下一页" [disabled] [ref=e623]:
+                - generic:
+                  - img
+              - generic [ref=e624]:
+                - generic [ref=e625]: 前往
+                - spinbutton "页" [ref=e628]: "1"
+                - generic [ref=e629]: 页

+ 348 - 0
测试工作区/测试用例/e2e/app.spec.js.backup

@@ -0,0 +1,348 @@
+const { test, expect } = require('@playwright/test');
+
+const BASE_URL = 'http://localhost:3000';
+const TEST_ADMIN = { username: 'admin', password: 'admin123' };
+
+test.describe('登录模块', () => {
+  test('TC-001: 正常登录', async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.waitForLoadState('networkidle');
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+  test('TC-002: 用户名为空验证', async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await expect(page.locator('text=请输入用户名')).toBeVisible();
+  });
+
+  test('TC-003: 密码为空验证', async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.click('button:has-text("登 录")');
+    await expect(page.locator('text=请输入密码')).toBeVisible();
+  });
+
+  test('TC-004: 错误账号登录', async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', 'wronguser');
+    await page.fill('input[type="password"]', 'wrongpass');
+    await page.click('button:has-text("登 录")');
+    await page.waitForTimeout(2000);
+  });
+});
+
+test.describe('用户管理模块', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+test('TC-010: 查看用户列表', async ({ page }) => {
+    const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=用户列表')).toBeVisible();
+});
+
+test('TC-011: 手机号格式验证', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.fill('input[placeholder="请输入姓名"]', '测试用户');
+    await page.fill('input[placeholder="请输入手机号"]', '12345');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=手机号格式错误')).toBeVisible();
+  });
+
+  test('TC-012: 肩宽范围验证-男性', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.fill('input[placeholder="请输入姓名"]', '测试用户');
+    await page.click('.el-select:has-text("请选择性别")');
+    await page.click('li:has-text("男")');
+    await page.fill('input[placeholder="请输入肩宽(cm)"]', '35');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=男性肩宽范围为36-52cm')).toBeVisible();
+  });
+
+  test('TC-012B: 肩宽范围验证-女性', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.fill('input[placeholder="请输入姓名"]', '测试用户');
+    await page.click('.el-select:has-text("请选择性别")');
+    await page.click('li:has-text("女")');
+    await page.fill('input[placeholder="请输入肩宽(cm)"]', '50');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=女性肩宽范围为32-46cm')).toBeVisible();
+  });
+
+  test('TC-013: 姓名必填验证', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=姓名为必填项')).toBeVisible();
+  });
+
+  test('TC-014: 手机号必填验证', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.fill('input[placeholder="请输入姓名"]', '测试用户');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=手机号为必填项')).toBeVisible();
+  });
+
+  test('TC-015: 年龄范围验证', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增用户")');
+    await page.fill('input[placeholder="请输入姓名"]', '测试用户');
+    await page.fill('input[placeholder="请输入手机号"]', '13900000000');
+    await page.fill('input[placeholder="请输入年龄"]', '150');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=年龄范围')).toBeVisible();
+  });
+
+  test('TC-016: 编辑用户', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("编辑") >> nth=0');
+    await page.waitForTimeout(500);
+    await expect(page.locator('text=编辑用户')).toBeVisible();
+  });
+
+  test('TC-017: 删除用户-取消', async ({ page }) => {
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 5000 });
+        const collapseBtn = page.locator('.collapse-btn');
+    if (await collapseBtn.isVisible()) {
+        await collapseBtn.click();
+        await page.waitForTimeout(500);
+    }
+    await page.waitForSelector('text=用户管理', { state: 'visible', timeout: 10000 });
+    await page.click('text=用户管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("删除") >> nth=0');
+    await page.waitForTimeout(500);
+    await page.click('button:has-text("取 消")');
+    await expect(page.locator('text=用户列表')).toBeVisible();
+  });
+});
+
+test.describe('穴位管理模块', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+  test('TC-020: 查看穴位列表', async ({ page }) => {
+    await page.click('text=穴位管理');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=穴位列表')).toBeVisible();
+  });
+
+  test('TC-021: 新增穴位-名称必填验证', async ({ page }) => {
+    await page.click('text=穴位管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增穴位")');
+    await page.waitForTimeout(500);
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=穴位名称为必填项')).toBeVisible();
+  });
+});
+
+test.describe('方案管理模块', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+  test('TC-030: 查看方案列表', async ({ page }) => {
+    await page.click('text=方案管理');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=方案列表')).toBeVisible();
+  });
+
+  test('TC-031: 新增方案-名称必填验证', async ({ page }) => {
+    await page.click('text=方案管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增方案")');
+    await page.waitForTimeout(500);
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=方案名称为必填项')).toBeVisible();
+  });
+
+  test('TC-032: 查看方案详情', async ({ page }) => {
+    await page.click('text=方案管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("查看") >> nth=0');
+    await page.waitForTimeout(500);
+    await expect(page.locator('text=方案详情')).toBeVisible();
+  });
+
+  test('TC-033: 编辑方案', async ({ page }) => {
+    await page.click('text=方案管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("编辑") >> nth=0');
+    await page.waitForTimeout(500);
+    await expect(page.locator('text=编辑方案')).toBeVisible();
+  });
+});
+
+test.describe('设备管理模块', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+  test('TC-040: 查看设备列表', async ({ page }) => {
+    await page.click('text=设备管理');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=设备列表')).toBeVisible();
+  });
+
+  test('TC-041: 新增设备-编号必填验证', async ({ page }) => {
+    await page.click('text=设备管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增设备")');
+    await page.waitForTimeout(500);
+    await page.fill('input[placeholder="请输入设备名称"]', '测试设备');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=设备编号为必填项')).toBeVisible();
+  });
+
+  test('TC-042: 查看设备详情', async ({ page }) => {
+    await page.click('text=设备管理');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("查看") >> nth=0');
+    await page.waitForTimeout(500);
+    await expect(page.locator('text=设备详情')).toBeVisible();
+  });
+});
+
+test.describe('系统管理模块', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto(`${BASE_URL}/#/login`);
+    await page.fill('input[placeholder="请输入用户名"]', TEST_ADMIN.username);
+    await page.fill('input[type="password"]', TEST_ADMIN.password);
+    await page.click('button:has-text("登 录")');
+    await page.waitForURL(`${BASE_URL}/#/**`, { timeout: 10000 });
+  });
+
+  test('TC-050: 查看管理员列表', async ({ page }) => {
+    await page.click('text=系统管理');
+    await page.click('text=管理员');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=管理员列表')).toBeVisible();
+  });
+
+  test('TC-051: 查看操作日志', async ({ page }) => {
+    await page.click('text=系统管理');
+    await page.click('text=系统日志');
+    await page.waitForTimeout(1000);
+    await expect(page.locator('text=操作日志')).toBeVisible();
+  });
+
+  test('TC-052: 新增管理员-用户名必填验证', async ({ page }) => {
+    await page.click('text=系统管理');
+    await page.click('text=管理员');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增管理员")');
+    await page.waitForTimeout(500);
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=用户名为必填项')).toBeVisible();
+  });
+
+  test('TC-053: 新增管理员-密码格式验证', async ({ page }) => {
+    await page.click('text=系统管理');
+    await page.click('text=管理员');
+    await page.waitForTimeout(1000);
+    await page.click('button:has-text("新增管理员")');
+    await page.waitForTimeout(500);
+    await page.fill('input[placeholder="请输入用户名"]', 'testadmin');
+    await page.fill('input[placeholder="请输入密码"]', '123');
+    await page.click('button:has-text("确 定")');
+    await expect(page.locator('text=密码需8-20字符')).toBeVisible();
+  });
+});

BIN
测试工作区/验收文档/艾灸椅管理后台系统技术文档.docx


BIN
测试工作区/验收文档/艾灸椅管理后台系统软件部署手册.docx


BIN
测试工作区/验收文档/艾灸椅管理后台系统需求设计文档.docx


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません