@hupan56/wlkj 2.6.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/bin/cli.js +269 -11
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -0
  4. package/templates/qoder/agents/insight-research.md +61 -0
  5. package/templates/qoder/agents/prd-reference.md +8 -2
  6. package/templates/qoder/commands/optional/wl-insight.md +275 -0
  7. package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
  8. package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
  9. package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
  10. package/templates/qoder/commands/wl-code.md +10 -2
  11. package/templates/qoder/commands/wl-commit.md +1 -1
  12. package/templates/qoder/commands/wl-design-draw.md +78 -0
  13. package/templates/qoder/commands/wl-design-scan.md +108 -0
  14. package/templates/qoder/commands/wl-design-spec.md +154 -0
  15. package/templates/qoder/commands/wl-design.md +32 -0
  16. package/templates/qoder/commands/wl-init.md +24 -3
  17. package/templates/qoder/commands/wl-prd-full.md +226 -0
  18. package/templates/qoder/commands/wl-prd-quick.md +134 -0
  19. package/templates/qoder/commands/wl-prd-review.md +104 -0
  20. package/templates/qoder/commands/wl-prd.md +17 -311
  21. package/templates/qoder/commands/wl-search.md +66 -30
  22. package/templates/qoder/commands/wl-task.md +290 -59
  23. package/templates/qoder/commands/wl-test.md +92 -24
  24. package/templates/qoder/config.yaml +51 -15
  25. package/templates/qoder/hooks/inject-workflow-state.py +2 -2
  26. package/templates/qoder/hooks/session-start.py +82 -51
  27. package/templates/qoder/rules/wl-pipeline.md +216 -105
  28. package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
  29. package/templates/qoder/scripts/archive_prd.py +377 -0
  30. package/templates/qoder/scripts/autotest.py +1715 -0
  31. package/templates/qoder/scripts/autotest_batch.py +224 -0
  32. package/templates/qoder/scripts/autotest_run.py +297 -0
  33. package/templates/qoder/scripts/benchmark.py +210 -209
  34. package/templates/qoder/scripts/build_style_index.py +444 -4
  35. package/templates/qoder/scripts/check_carriers.py +238 -0
  36. package/templates/qoder/scripts/check_mcp.py +298 -0
  37. package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
  38. package/templates/qoder/scripts/common/events.py +46 -0
  39. package/templates/qoder/scripts/common/extract.py +419 -0
  40. package/templates/qoder/scripts/common/graph_traverse.py +533 -0
  41. package/templates/qoder/scripts/common/paths.py +89 -0
  42. package/templates/qoder/scripts/common/pip_install.py +144 -0
  43. package/templates/qoder/scripts/common/platform_guard.py +61 -0
  44. package/templates/qoder/scripts/common/search_engine.py +205 -205
  45. package/templates/qoder/scripts/common/terms.py +57 -0
  46. package/templates/qoder/scripts/common/ts_extract.py +536 -0
  47. package/templates/qoder/scripts/context_pack.py +73 -13
  48. package/templates/qoder/scripts/enrich_prompt.py +226 -0
  49. package/templates/qoder/scripts/eval_prd.py +318 -231
  50. package/templates/qoder/scripts/export.py +487 -487
  51. package/templates/qoder/scripts/extract_api_params.py +246 -0
  52. package/templates/qoder/scripts/extract_routes.py +54 -0
  53. package/templates/qoder/scripts/extract_routes_tree.py +78 -0
  54. package/templates/qoder/scripts/fill_prototype.py +707 -0
  55. package/templates/qoder/scripts/gen_design_doc.py +394 -0
  56. package/templates/qoder/scripts/git_sync.py +27 -15
  57. package/templates/qoder/scripts/init_doctor.py +292 -40
  58. package/templates/qoder/scripts/install_qoderwork.py +366 -9
  59. package/templates/qoder/scripts/kg.py +708 -0
  60. package/templates/qoder/scripts/kg_auto_login.py +196 -0
  61. package/templates/qoder/scripts/kg_build.py +612 -0
  62. package/templates/qoder/scripts/kg_build_db.py +327 -0
  63. package/templates/qoder/scripts/kg_duckdb.py +549 -0
  64. package/templates/qoder/scripts/kg_incremental.py +393 -0
  65. package/templates/qoder/scripts/kg_link_db.py +224 -0
  66. package/templates/qoder/scripts/kg_mcp_server.py +801 -0
  67. package/templates/qoder/scripts/kg_semantic.py +150 -0
  68. package/templates/qoder/scripts/kg_test_runner.py +241 -0
  69. package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
  70. package/templates/qoder/scripts/learn.py +118 -39
  71. package/templates/qoder/scripts/learn_aggregate.py +201 -0
  72. package/templates/qoder/scripts/mcp_launcher.py +359 -0
  73. package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
  74. package/templates/qoder/scripts/repo_root.py +106 -0
  75. package/templates/qoder/scripts/role.py +12 -0
  76. package/templates/qoder/scripts/run_weekly_update.bat +5 -0
  77. package/templates/qoder/scripts/run_weekly_update.sh +5 -0
  78. package/templates/qoder/scripts/search_index.py +307 -60
  79. package/templates/qoder/scripts/secure-ls.js +5640 -0
  80. package/templates/qoder/scripts/setup.py +706 -641
  81. package/templates/qoder/scripts/setup_lanhu.py +963 -0
  82. package/templates/qoder/scripts/status.py +250 -11
  83. package/templates/qoder/scripts/sync_carriers.py +259 -0
  84. package/templates/qoder/scripts/syncgate.py +2 -2
  85. package/templates/qoder/scripts/task.py +75 -0
  86. package/templates/qoder/scripts/team_sync.py +60 -4
  87. package/templates/qoder/scripts/workspace_init.py +1 -1
  88. package/templates/qoder/skills/design-import/SKILL.md +226 -0
  89. package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
  90. package/templates/qoder/skills/design-review/SKILL.md +82 -25
  91. package/templates/qoder/skills/prd-generator/SKILL.md +184 -60
  92. package/templates/qoder/skills/prd-review/SKILL.md +18 -1
  93. package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
  94. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
  95. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  96. package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
  97. package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
  98. package/templates/qoder/skills/test-generator/SKILL.md +15 -2
  99. package/templates/qoder/skills/wl-code/SKILL.md +55 -36
  100. package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
  101. package/templates/qoder/skills/wl-design/SKILL.md +55 -0
  102. package/templates/qoder/skills/wl-init/SKILL.md +76 -67
  103. package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
  104. package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
  105. package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
  106. package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
  107. package/templates/qoder/skills/wl-report/SKILL.md +131 -107
  108. package/templates/qoder/skills/wl-search/SKILL.md +141 -75
  109. package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
  110. package/templates/qoder/skills/wl-status/SKILL.md +83 -61
  111. package/templates/qoder/skills/wl-task/SKILL.md +132 -58
  112. package/templates/qoder/skills/wl-test/SKILL.md +406 -40
  113. package/templates/qoder/templates/prototype-app.html +13 -8
  114. package/templates/qoder/templates/prototype-web.html +376 -93
  115. package/templates/root/AGENTS.md +89 -34
  116. package/templates/root/requirements.txt +21 -0
  117. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
  118. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
  119. package/templates/qoder/agents/prd-planning.md +0 -57
  120. package/templates/qoder/agents/prd-research.md +0 -34
  121. package/templates/qoder/commands/wl-insight.md +0 -51
  122. package/templates/qoder/scripts/__pycache__/init_doctor.cpython-39.pyc +0 -0
  123. package/templates/qoder/scripts/__pycache__/setup.cpython-39.pyc +0 -0
  124. package/templates/qoder/scripts/common/__pycache__/developer.cpython-39.pyc +0 -0
  125. package/templates/qoder/skills/wl-prd/SKILL.md +0 -92
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ extract_api_params.py - 从 TS 类型 + Java Bo 提取 API 参数, 生成智能默认值
5
+ 读主库 kg.duckdb(只读), 写 api-params.json(避免 DuckDB 写入性能问题)。
6
+ 后续 MySQL 接入后, 用真实数据覆盖默认值。
7
+ """
8
+ import sys, os, re, json, time, glob, duckdb
9
+ from datetime import datetime
10
+
11
+ try:
12
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
13
+ sys.stderr.reconfigure(encoding='utf-8', errors='replace')
14
+ except Exception:
15
+ pass
16
+
17
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
18
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19
+ KG_PATH = os.path.join(BASE, 'data', 'index', 'kg.duckdb')
20
+ PARAMS_FILE = os.path.join(BASE, 'data', 'index', 'api-params.json')
21
+
22
+
23
+ # ── TS 解析 ──────────────────────────────────────────────────
24
+
25
+ def parse_ts_param_types():
26
+ """扫所有 TS api 文件, 建 {fn_name: param_type} 映射。"""
27
+ fn_map = {}
28
+ ts_root = os.path.join(BASE, 'data', 'code', 'fywl-ui', 'apps', 'ics', 'src', 'api')
29
+ for tf in glob.glob(os.path.join(ts_root, '**', '*.ts'), recursive=True):
30
+ try:
31
+ if os.path.getsize(tf) > 50000:
32
+ continue
33
+ txt = open(tf, encoding='utf-8', errors='ignore').read()
34
+ except Exception:
35
+ continue
36
+ for m in re.finditer(r'export\s+(?:async\s+)?function\s+(\w+)\s*\(\s*(?:\w+)?\s*(?::\s*(\w+|Partial<[^>]+>))?', txt):
37
+ fn_map[m.group(1)] = m.group(2) or 'none'
38
+ for m in re.finditer(r'export\s+const\s+(\w+)\s*=\s*\(\s*(?:\w+)?\s*(?::\s*(\w+|Partial<[^>]+>|Record<[^>]+>))?', txt):
39
+ fn_map[m.group(1)] = m.group(2) or 'none'
40
+ return fn_map
41
+
42
+
43
+ # ── Java Bo 解析 ─────────────────────────────────────────────
44
+
45
+ def build_bo_index():
46
+ """预扫所有 Bo/Dto/Query 类, 建 {类名: 路径} 索引。"""
47
+ index = {}
48
+ root = os.path.join(BASE, 'data', 'code', 'fywl-ics')
49
+ for pattern in ['**/*Bo.java', '**/*Dto.java', '**/*QueryBo.java', '**/*Query.java']:
50
+ for f in glob.glob(os.path.join(root, pattern), recursive=True):
51
+ if 'target' in f or '/classes/' in f:
52
+ continue
53
+ base_name = os.path.basename(f).replace('.java', '')
54
+ for suffix in ['QueryBo', 'Bo', 'Dto', 'Query']:
55
+ if base_name.endswith(suffix):
56
+ index[base_name] = f
57
+ short = base_name[:-len(suffix)]
58
+ if short not in index:
59
+ index[short] = f
60
+ break
61
+ else:
62
+ index[base_name] = f
63
+ return index
64
+
65
+
66
+ def gen_default(java_type, field_name):
67
+ """按 Java 类型 + 字段名生成智能默认值。"""
68
+ FIELD_DEFAULTS = {
69
+ 'pageNum': 1, 'pageSize': 10, 'page': 1, 'size': 10, 'current': 1,
70
+ 'id': 1, 'parentId': 0, 'pid': 0, 'deptId': 100,
71
+ 'userId': 1, 'tenantId': '000000',
72
+ 'status': 0, 'type': '', 'code': '', 'name': 'test',
73
+ 'phone': '13800000000', 'mobile': '13800000000', 'email': 'test@test.com',
74
+ 'isAsc': 'asc', 'orderByColumn': 'id',
75
+ }
76
+ if field_name in FIELD_DEFAULTS:
77
+ return FIELD_DEFAULTS[field_name]
78
+ fn = (field_name or '').lower()
79
+ if 'phone' in fn or 'mobile' in fn:
80
+ return '13800000000'
81
+ if 'email' in fn:
82
+ return 'test@test.com'
83
+ if 'date' in fn or 'time' in fn:
84
+ return '2026-01-01'
85
+ if 'name' in fn:
86
+ return 'test'
87
+ if 'code' in fn:
88
+ return 'TEST001'
89
+ if fn == 'id' or fn.endswith('id'):
90
+ return 1
91
+ jt = (java_type or '').strip()
92
+ if jt in ('Long', 'long', 'Integer', 'int', 'BigInteger', 'BigDecimal', 'Number'):
93
+ return 1
94
+ if jt in ('String', 'CharSequence', 'char'):
95
+ return 'test'
96
+ if jt in ('Boolean', 'boolean'):
97
+ return False
98
+ if jt in ('Date', 'LocalDate', 'LocalDateTime', 'Timestamp'):
99
+ return '2026-01-01'
100
+ if jt in ('Double', 'double', 'Float', 'float'):
101
+ return 1.0
102
+ if jt.startswith('List') or jt.startswith('Set') or jt.startswith('Collection'):
103
+ return []
104
+ if jt.startswith('Map'):
105
+ return {}
106
+ return None
107
+
108
+
109
+ def parse_bo_fields(java_path):
110
+ """解析 Java Bo 类的字段+类型+校验注解。"""
111
+ try:
112
+ if not os.path.isfile(java_path) or os.path.getsize(java_path) > 80000:
113
+ return []
114
+ txt = open(java_path, encoding='utf-8', errors='ignore').read()
115
+ except Exception:
116
+ return []
117
+ fields = []
118
+ try:
119
+ for m in re.finditer(
120
+ r'((?:@\w+(?:\([^)]*\))?\s*)*)'
121
+ r'(?:private\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*;', txt):
122
+ anns, jtype, fname = m.group(1), m.group(2), m.group(3)
123
+ if fname in ('serialVersionUID', 'logger', 'LOG') or fname.isupper():
124
+ continue
125
+ required = bool(re.search(r'@(?:NotNull|NotBlank|NotEmpty|Valid)\b', anns))
126
+ fields.append({'name': fname, 'type': jtype, 'required': required})
127
+ except Exception:
128
+ pass
129
+ return fields
130
+
131
+
132
+ _BO_CACHE = {}
133
+
134
+
135
+ def bo_to_params(bo_class, bo_index):
136
+ """从 Bo 类名解析出参数 JSON (带缓存)。"""
137
+ if not bo_class or bo_class in _BO_CACHE:
138
+ return _BO_CACHE.get(bo_class)
139
+ jf = bo_index.get(bo_class) or bo_index.get(bo_class + 'Bo') or bo_index.get(bo_class + 'Dto')
140
+ if not jf:
141
+ _BO_CACHE[bo_class] = None
142
+ return None
143
+ fields = parse_bo_fields(jf)
144
+ if not fields:
145
+ _BO_CACHE[bo_class] = None
146
+ return None
147
+ params = {}
148
+ for f in fields:
149
+ val = gen_default(f['type'], f['name'])
150
+ if f['required'] and val is None:
151
+ val = 1 if 'id' in f['name'].lower() else 'test'
152
+ params[f['name']] = val
153
+ _BO_CACHE[bo_class] = params
154
+ return params
155
+
156
+
157
+ # ── 参数生成 ─────────────────────────────────────────────────
158
+
159
+ def gen_params(ptype, bo_index=None):
160
+ """按 TS 参数类型生成默认参数 JSON。Bo 类型解析 Java 字段。"""
161
+ if ptype == 'PageQuery':
162
+ return {'pageNum': 1, 'pageSize': 10, 'isAsc': 'asc', 'orderByColumn': 'id'}, 'PageQuery'
163
+ if ptype in ('ID', 'Id'):
164
+ return {'id': 1}, 'ID'
165
+ if ptype == 'IDS':
166
+ return {'ids': [1]}, 'IDS'
167
+ if ptype == 'string':
168
+ return {'id': '1'}, 'string'
169
+ if ptype == 'number':
170
+ return {'id': 1}, 'number'
171
+ if ptype and ptype not in ('any', 'none', 'Recordable', 'Partial', 'Record', 'boolean', 'Blob'):
172
+ if bo_index:
173
+ bp = bo_to_params(ptype, bo_index)
174
+ if bp:
175
+ return bp, 'Bo:' + ptype
176
+ return {}, 'Bo:' + ptype
177
+ if ptype == 'any':
178
+ return {}, 'any'
179
+ return {}, 'none'
180
+
181
+
182
+ # ── 主流程 ───────────────────────────────────────────────────
183
+
184
+ def main():
185
+ t0 = time.time()
186
+
187
+ # 1. 读主库(只读)拿接口列表
188
+ kg = duckdb.connect(KG_PATH, read_only=True)
189
+ rows = kg.execute("SELECT fn_name, url, verb FROM api_functions WHERE url IS NOT NULL").fetchall()
190
+ kg.close()
191
+ sys.stderr.write('[%.1fs] DuckDB: %d rows\n' % (time.time() - t0, len(rows)))
192
+
193
+ # 2. TS 参数类型
194
+ fn_map = parse_ts_param_types()
195
+ sys.stderr.write('[%.1fs] TS: %d fns\n' % (time.time() - t0, len(fn_map)))
196
+
197
+ # 2b. Bo 类索引
198
+ bo_index = build_bo_index()
199
+ sys.stderr.write('[%.1fs] Bo index: %d\n' % (time.time() - t0, len(bo_index)))
200
+
201
+ # 3. 生成参数
202
+ results = []
203
+ source_dist = {}
204
+ for fn, url, verb in rows:
205
+ if not url or url.startswith('$'):
206
+ continue
207
+ ptype = fn_map.get(fn, 'none')
208
+ params, source = gen_params(ptype, bo_index)
209
+ req = [k for k, v in params.items() if v is not None]
210
+ results.append({
211
+ 'url': url, 'verb': verb or '', 'param_source': source,
212
+ 'params': params, 'required': req,
213
+ })
214
+ skey = source.split(':')[0]
215
+ source_dist[skey] = source_dist.get(skey, 0) + 1
216
+
217
+ sys.stderr.write('[%.1fs] Generated: %d\n' % (time.time() - t0, len(results)))
218
+
219
+ # 4. 写 JSON
220
+ output = {
221
+ 'version': '1.0',
222
+ 'extracted_at': datetime.now().isoformat()[:19],
223
+ 'total': len(results),
224
+ 'apis': results,
225
+ }
226
+ with open(PARAMS_FILE, 'w', encoding='utf-8') as f:
227
+ json.dump(output, f, ensure_ascii=False, indent=2)
228
+ sys.stderr.write('[%.1fs] Written: %s\n' % (time.time() - t0, PARAMS_FILE))
229
+
230
+ # 5. 报告
231
+ sys.stderr.write('\n=== API 参数提取 ===\n')
232
+ sys.stderr.write('总接口: %d, 已提取: %d\n' % (len(rows), len(results)))
233
+ for s, c in sorted(source_dist.items(), key=lambda x: -x[1]):
234
+ sys.stderr.write(' %-15s %d\n' % (s, c))
235
+
236
+ # 样本
237
+ for r in results:
238
+ if r['param_source'] not in ('none', 'any') and r['params']:
239
+ sys.stderr.write('\n样本: %s [%s] %s\n' % (
240
+ r['url'][:50], r['param_source'],
241
+ json.dumps(r['params'], ensure_ascii=False)[:60]))
242
+ break
243
+
244
+
245
+ if __name__ == '__main__':
246
+ main()
@@ -0,0 +1,54 @@
1
+ import sys, os, re, glob, json
2
+ sys.stdout.reconfigure(encoding='utf-8')
3
+
4
+ # 扫前端路由文件, 提取 path -> component 映射
5
+ router_dir = 'E:/YFHJ/new/AiProject/qoderAll/data/code/fywl-ui/apps/ics/src/router'
6
+ routes = []
7
+
8
+ for rf in glob.glob(os.path.join(router_dir, '**', '*.ts'), recursive=True):
9
+ try:
10
+ txt = open(rf, encoding='utf-8', errors='ignore').read()
11
+ except:
12
+ continue
13
+ # 匹配 path: 'xxx' 和 component: () => import('xxx')
14
+ # 或 path: 'xxx', name: 'yyy'
15
+ for m in re.finditer(
16
+ r"path:\s*['\"`]([^'\"`]+)['\"`]"
17
+ r"[^}]*?(?:name:\s*['\"]([^'\"]+)['\"])?"
18
+ r"[^}]*?(?:component:\s*\([^)]*\)\s*=>\s*import\(['\"]([^'\"]+)['\"]\))?",
19
+ txt, re.S):
20
+ path, name, component = m.group(1), m.group(2) or '', m.group(3) or ''
21
+ if path and not path.startswith(':') and len(path) > 1:
22
+ routes.append({'path': path, 'name': name, 'component': component})
23
+
24
+ # 也扫后台菜单路由(通常在数据库 sys_menu 表, 但前端可能有硬编码)
25
+ # 搜 views 目录下的 Vue 文件路径作为页面标识
26
+ vue_views = glob.glob('E:/YFHJ/new/AiProject/qoderAll/data/code/fywl-ui/apps/ics/src/views/**/*.vue', recursive=True)
27
+ view_paths = [v.replace('\\', '/').split('/views/')[-1].replace('.vue', '') for v in vue_views]
28
+
29
+ print('路由定义: %d 条' % len(routes))
30
+ print('Vue 页面: %d 个' % len(view_paths))
31
+
32
+ # 保存路由索引
33
+ output = {
34
+ 'version': '1.0',
35
+ 'total_routes': len(routes),
36
+ 'total_views': len(view_paths),
37
+ 'routes': routes,
38
+ 'views': sorted(view_paths),
39
+ }
40
+ out_path = 'E:/YFHJ/new/AiProject/qoderAll/data/index/frontend-routes.json'
41
+ with open(out_path, 'w', encoding='utf-8') as f:
42
+ json.dump(output, f, ensure_ascii=False, indent=2)
43
+ print('已保存: %s' % out_path)
44
+
45
+ # 样本
46
+ print('\n样本路由:')
47
+ for r in routes[:8]:
48
+ print(' %s -> %s' % (r['path'], r['component'][:50] if r['component'] else r['name']))
49
+
50
+ # 保险相关
51
+ print('\n保险相关路由:')
52
+ for r in routes:
53
+ if 'insur' in r['path'].lower() or '保险' in r.get('name', ''):
54
+ print(' %s' % r['path'])
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ """extract_routes_tree.py — 从 getRouters API 拉完整菜单树, 递归拼接准确 path, 存成 menu-routes-tree.json.
3
+ 解决 menu-routes.json 扁平丢失父子关系、search_index 用 component 猜 URL 导致 404 的问题。
4
+ 用法: python extract_routes_tree.py
5
+ """
6
+ import json, os, sys, requests
7
+
8
+ SCRIPTS = os.path.dirname(os.path.abspath(__file__))
9
+ PROJECT = os.path.normpath(os.path.join(SCRIPTS, '..', '..'))
10
+
11
+
12
+ def read_developer():
13
+ f = os.path.join(PROJECT, '.qoder', '.developer')
14
+ for ln in open(f, encoding='utf-8'):
15
+ if ln.strip().startswith('name='):
16
+ return ln.strip().split('=', 1)[1].strip()
17
+ raise RuntimeError('无 .developer, 先 /wl-init')
18
+
19
+
20
+ dev = read_developer()
21
+ auth_file = os.path.join(PROJECT, 'workspace', 'members', dev, 'auth-state.json')
22
+ if not os.path.exists(auth_file):
23
+ print('无 auth-state, 先登录: python .qoder/scripts/kg_auto_login.py'); sys.exit(1)
24
+ auth = json.load(open(auth_file, encoding='utf-8'))
25
+ token = auth['access_token']
26
+ base = auth['base_url']; prefix = auth.get('api_prefix', '/test-api')
27
+
28
+ print('拉取 getRouters...')
29
+ r = requests.get(base + prefix + '/system/menu/getRouters',
30
+ headers={'Authorization': 'Bearer ' + token, 'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e'},
31
+ timeout=20)
32
+ d = r.json()
33
+ if d.get('code') != 200:
34
+ print('getRouters 失败:', d.get('msg')); sys.exit(1)
35
+ tree = d.get('data') or []
36
+
37
+
38
+ def walk(nodes, parent_path=''):
39
+ """递归拼接准确 path, 返回 [{full_path, name, component}].
40
+ name 优先用 meta.title(中文), 回退 name(i18n key)."""
41
+ out = []
42
+ for n in nodes:
43
+ seg = n.get('path', '').lstrip('/')
44
+ meta = n.get('meta') or {}
45
+ name = meta.get('title', '') or n.get('name', '')
46
+ comp = n.get('component', '')
47
+ full = (parent_path.rstrip('/') + '/' + seg) if parent_path else '/' + seg
48
+ while '//' in full:
49
+ full = full.replace('//', '/')
50
+ kids = n.get('children') or []
51
+ if kids:
52
+ out.extend(walk(kids, full))
53
+ elif comp and comp != '#' and ':' not in full:
54
+ out.append({'path': full, 'name': name, 'component': comp, 'i18n_key': n.get('name', '')})
55
+ return out
56
+
57
+
58
+ pages = walk(tree)
59
+ # 去重
60
+ seen = set(); uniq = []
61
+ for p in pages:
62
+ if p['path'] not in seen:
63
+ seen.add(p['path']); uniq.append(p)
64
+
65
+ out_file = os.path.join(PROJECT, 'data', 'index', 'menu-routes-tree.json')
66
+ json.dump({
67
+ 'source': 'getRouters API (父子树递归拼接, 准确 path)',
68
+ 'total': len(uniq),
69
+ 'routes': uniq,
70
+ }, open(out_file, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
71
+
72
+ print('已存 %d 条准确路由 -> %s' % (len(uniq), out_file))
73
+ print()
74
+ # 验证关键页面
75
+ for kw in ['abnormalRecords', 'insurance', 'ticket']:
76
+ hits = [p for p in uniq if kw.lower() in (p['path'] + p['name']).lower()]
77
+ for h in hits[:2]:
78
+ print(' %-12s -> %s' % (h['name'][:10], h['path']))