generate_adminUI_menu_doc.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. # -*- coding: utf-8 -*-
  2. """
  3. Scan adminUI src/views and generate reverse-engineered menu structure document.
  4. """
  5. import os
  6. import re
  7. from collections import defaultdict
  8. from datetime import date
  9. ROOT = os.path.normpath(
  10. os.path.join(os.path.dirname(__file__), '..', '..', 'ylrz_saas_his_scrm_adminUI', 'src', 'views')
  11. )
  12. OUT = os.path.join(os.path.dirname(__file__), 'adminUI_views_menu_structure.md')
  13. SKIP_TOP = {'components', 'error', 'dashboard', 'icons', 'login', 'register', 'redirect'}
  14. SKIP_REL = re.compile(
  15. r'(components/|/profile/|authRole|authUser|selectUser|/data\.vue|/design\.vue|'
  16. r'/log\.vue|/editTable|/details\.vue|/my\.vue|sopLogsList|sopUserLogsSchedule|'
  17. r'/transferLog\.vue|/transfer\.vue|/darkRoom\.vue|/blackroom\.vue|config2\.vue|'
  18. r'/sopTempe/|mixins/)'
  19. )
  20. LIST_NAMES = {'index.vue', 'list.vue', 'myList.vue', 'order.vue', 'order1.vue'}
  21. ADMIN_ONLY_TOP = {
  22. 'admin', 'saas', 'sysUser', 'saler', 'shop', 'medical', 'food', 'todo',
  23. 'baidu', 'callRecord', 'aiSipCall', 'storeOrderOfflineItem', 'operation', 'hisStore',
  24. }
  25. TENANT_TOP = {
  26. 'qw': ('\u4f01\u5fae\u7ba1\u7406', 'qw'),
  27. 'wx': ('\u5fae\u4fe1\u7ba1\u7406', 'wx'),
  28. 'gw': ('\u5fae\u4fe1\u7ba1\u7406', 'wx'),
  29. 'crm': ('CRM\u5ba2\u6237', 'crm'),
  30. 'user': ('\u4f1a\u5458\u7ba1\u7406', 'member'),
  31. 'users': ('\u4f1a\u5458\u7ba1\u7406', 'member'),
  32. 'member': ('\u4f1a\u5458\u7ba1\u7406', 'member'),
  33. 'his': ('\u8bca\u6240\u7ba1\u7406', 'his'),
  34. 'store': ('\u5546\u57ce\u7ba1\u7406', 'store'),
  35. 'live': ('\u76f4\u64ad\u7ba1\u7406', 'live'),
  36. 'liveData': ('\u76f4\u64ad\u7ba1\u7406', 'live'),
  37. 'course': ('\u8bfe\u7a0b\u7ba1\u7406', 'course'),
  38. 'courseFinishTemp': ('\u8bfe\u7a0b\u7ba1\u7406', 'course'),
  39. 'fastGpt': ('AI\u804a\u5929', 'fastGpt'),
  40. 'FastGptExtUserTag': ('AI\u804a\u5929', 'fastGpt'),
  41. 'chat': ('AI\u804a\u5929', 'fastGpt'),
  42. 'aiob': ('AI\u804a\u5929', 'fastGpt'),
  43. 'lobster': ('\u9f99\u8679\u5f15\u64ce', 'lobster'),
  44. 'adv': ('\u5e7f\u544a\u6295\u653e', 'ad'),
  45. 'ad': ('\u5e7f\u544a\u6295\u653e', 'ad'),
  46. 'company': ('\u7cfb\u7edf\u7ba1\u7406', 'system'),
  47. 'system': ('\u7cfb\u7edf\u7ba1\u7406', 'system'),
  48. 'bill': ('\u8d22\u52a1\u7ba1\u7406', 'bill'),
  49. 'billing': ('\u8d22\u52a1\u7ba1\u7406', 'bill'),
  50. 'calendar': ('\u65e5\u7a0b\u7ba1\u7406', 'calendar'),
  51. 'statistics': ('\u6570\u636e\u7edf\u8ba1', 'statistics'),
  52. 'taskStatistics': ('\u6570\u636e\u7edf\u8ba1', 'statistics'),
  53. 'moduleUsage': ('\u6570\u636e\u7edf\u8ba1', 'statistics'),
  54. 'monitor': ('\u76d1\u63a7\u7ba1\u7406', 'watch'),
  55. 'watch': ('\u76d1\u63a7\u7ba1\u7406', 'watch'),
  56. 'tool': ('\u7cfb\u7edf\u5de5\u5177', 'system'),
  57. 'qwExternalContact': ('\u4f01\u5fae\u7ba1\u7406', 'qw'),
  58. }
  59. SECONDARY_HINTS = {
  60. 'qw': {
  61. 'msg': ['QwWorkTask', 'groupMsg', 'record', 'qwPushCount'],
  62. 'customer': ['externalContact', 'contactWay', 'drainageLink', 'assignRule', 'customerLink'],
  63. 'group': ['groupChat', 'groupLiveCode', 'groupActual'],
  64. 'moments': ['friendCircle', 'friendMaterial', 'friendWelcome'],
  65. 'drainage': ['appAdvertisingReport', 'appContactWay'],
  66. 'tag': ['tag', 'tagGroup', 'autoTags'],
  67. 'setting': ['qwDept', 'companyUser', 'material', 'welcome', 'applyIpad', 'userBehaviorData'],
  68. 'sop': ['sop', 'sopTemp', 'sopLogs', 'aiSop'],
  69. },
  70. 'store': {
  71. 'order': ['storeOrder', 'storeAfterSales', 'inquiryOrder', 'PromotionOrder', 'healthStoreOrder'],
  72. 'product': ['storeProduct', 'prescribe', 'shippingTemplates', 'package'],
  73. 'ops': ['storeShop', 'storeCoupon', 'homeArticle', 'exportTask', 'userPromoterApply', 'adv'],
  74. },
  75. 'his': {
  76. 'doctor': ['doctor', 'doctorBill', 'doctorExtract', 'doctorOperLog'],
  77. 'inquiry': ['inquiryOrder', 'inquiryOrderReport', 'patient'],
  78. 'store': ['storeOrder', 'storeProduct', 'storeBill'],
  79. 'content': ['article', 'articleCate', 'answer'],
  80. 'ai': ['aiWorkflow', 'aiDoctorRole', 'aiDoctorChatSession'],
  81. },
  82. 'system': {
  83. 'org': ['dept', 'post', 'user'],
  84. 'perm': ['role', 'menu'],
  85. 'config': ['dict', 'config', 'notice', 'keyword', 'set'],
  86. 'voice': ['companyVoiceDialog', 'companyVoiceRobotic'],
  87. },
  88. 'company': {
  89. 'org': ['companyDept', 'companyPost', 'companyUser'],
  90. 'perm': ['companyRole', 'companyMenu'],
  91. 'finance': ['companyRecharge', 'companyProfit', 'companyMoneyLogs', 'companyDeduct'],
  92. 'comm': ['companySms', 'companyVoice', 'companyWorkflow'],
  93. 'wx': ['wxAccount', 'wxUser', 'wxDialog'],
  94. },
  95. 'live': {
  96. 'ops': ['liveConsole', 'liveConfig', 'liveData', 'liveWatch'],
  97. 'order': ['liveOrder', 'liveAfterSales', 'healthLiveOrder'],
  98. 'interact': ['liveCoupon', 'liveQuestion', 'liveReward', 'comment'],
  99. },
  100. 'course': {
  101. 'content': ['videoResource', 'Material', 'period', 'courseFinishTemp'],
  102. 'study': ['userCourse', 'courseWatchLog', 'userWatch'],
  103. 'stat': ['statistics', 'courseUserStatistics', 'huaweiCloudStatistics'],
  104. },
  105. }
  106. GROUP_TITLES = {
  107. 'msg': '\u6d88\u606f\u7ba1\u7406', 'customer': '\u5ba2\u6237\u7ba1\u7406',
  108. 'group': '\u7fa4\u804a\u7ba1\u7406', 'moments': '\u670b\u53cb\u5708',
  109. 'drainage': '\u5f15\u6d41\u7ba1\u7406', 'tag': '\u6807\u7b7e\u7ba1\u7406',
  110. 'setting': '\u4f01\u5fae\u8bbe\u7f6e', 'sop': 'SOP/\u81ea\u52a8\u5316',
  111. 'order': '\u8ba2\u5355\u7ba1\u7406', 'product': '\u5546\u54c1\u7ba1\u7406',
  112. 'ops': '\u95e8\u5e97\u8fd0\u8425', 'doctor': '\u533b\u751f\u7ba1\u7406',
  113. 'inquiry': '\u95ee\u8bca\u7ba1\u7406', 'content': '\u5185\u5bb9\u7ba1\u7406',
  114. 'ai': 'AI/\u5de5\u4f5c\u6d41', 'org': '\u7ec4\u7ec7\u7ba1\u7406',
  115. 'perm': '\u6743\u9650\u7ba1\u7406', 'config': '\u7cfb\u7edf\u8bbe\u7f6e',
  116. 'voice': '\u8bed\u97f3/\u5916\u547c', 'finance': '\u8d22\u52a1',
  117. 'comm': '\u901a\u4fe1/\u5de5\u4f5c\u6d41', 'wx': '\u5fae\u4fe1\u8d26\u53f7',
  118. 'interact': '\u4e92\u52a8\u8425\u9500', 'study': '\u5b66\u4e60\u8ddf\u8e2a',
  119. 'stat': '\u7edf\u8ba1\u5206\u6790', 'general': '\u901a\u7528/\u5f85\u5206\u7ec4',
  120. }
  121. def to_component(rel_path):
  122. comp = rel_path.replace('\\', '/').replace('.vue', '')
  123. if comp.endswith('/index/index'):
  124. comp = comp[:-6]
  125. return comp
  126. def is_menu_page(rel_path, filename):
  127. if SKIP_REL.search(rel_path):
  128. return False
  129. parts = rel_path.replace('\\', '/').split('/')
  130. if parts[0] in SKIP_TOP:
  131. return False
  132. if filename in LIST_NAMES:
  133. return True
  134. if len(parts) == 2 and filename.endswith('.vue'):
  135. return True
  136. return False
  137. def scan_views():
  138. pages = []
  139. all_files = 0
  140. for dirpath, dirnames, filenames in os.walk(ROOT):
  141. dirnames[:] = [d for d in dirnames if d not in SKIP_TOP and d != 'mixins']
  142. for fn in filenames:
  143. if not fn.endswith('.vue'):
  144. continue
  145. all_files += 1
  146. full = os.path.join(dirpath, fn)
  147. rel = full[len(ROOT) + 1:]
  148. if is_menu_page(rel, fn):
  149. comp = to_component(rel)
  150. module = rel.replace('\\', '/').split('/')[1] if '/' in rel else fn.replace('.vue', '')
  151. pages.append({
  152. 'top': rel.replace('\\', '/').split('/')[0],
  153. 'module': module,
  154. 'component': comp,
  155. 'file': rel.replace('\\', '/'),
  156. })
  157. return pages, all_files
  158. def guess_secondary(top, module):
  159. hints = SECONDARY_HINTS.get(top, {})
  160. for group, keys in hints.items():
  161. for k in keys:
  162. if k.lower() in module.lower():
  163. return group
  164. return 'general'
  165. def build_markdown(pages, all_files):
  166. by_top = defaultdict(list)
  167. for p in pages:
  168. by_top[p['top']].append(p)
  169. agg_count = defaultdict(int)
  170. for p in pages:
  171. info = TENANT_TOP.get(p['top'])
  172. key = info[1] if info else ('other' if p['top'] in ADMIN_ONLY_TOP else 'other')
  173. agg_count[key] += 1
  174. L = []
  175. w = L.append
  176. w('# adminUI \u89c6\u56fe\u53cd\u5411\u68b3\u7406 \u2014 \u79df\u6237\u7ba1\u7406\u7aef\u83dc\u5355\u5efa\u8bae\u7ed3\u6784')
  177. w('')
  178. w('> \u6587\u6863\u7c7b\u578b\uff1a**\u53ea\u8bfb\u68b3\u7406**\uff0c\u6682\u4e0d\u4fee\u6539\u6570\u636e\u5e93\u6216\u4ee3\u7801\u3002')
  179. w('> \u626b\u63cf\u6765\u6e90\uff1a`ylrz_saas_his_scrm_adminUI/src/views`')
  180. w('> \u751f\u6210\u65e5\u671f\uff1a%s' % date.today().isoformat())
  181. w('> \u626b\u63cf\u7ed3\u679c\uff1a\u5171 **%d** \u4e2a `.vue` \u6587\u4ef6\uff0c\u8bc6\u522b **%d** \u4e2a\u53ef\u72ec\u7acb\u8def\u7531\u9875\u9762' % (all_files, len(pages)))
  182. w('')
  183. w('---')
  184. w('')
  185. w('## \u4e00\u3001\u68b3\u7406\u65b9\u6cd5')
  186. w('')
  187. w('| \u9879\u76ee | \u8bf4\u660e |')
  188. w('|------|------|')
  189. w('| \u9875\u9762\u8bc6\u522b | `index.vue` / `list.vue` / `myList.vue` \u53ca\u6a21\u5757\u6839\u76ee\u5f55\u5355\u5c42 `.vue` |')
  190. w('| \u6392\u9664 | `components/`\u3001\u8be6\u60c5\u9875\u3001\u6388\u6743\u9875\u3001\u5b57\u5178\u5b50\u9875\u3001\u8bbe\u8ba1\u5668\u3001\u65e5\u5fd7\u5b50\u9875\u7b49 |')
  191. w('| \u7ec4\u4ef6\u8def\u5ef6 | \u5bf9\u5e94\u540e\u7aef `sys_menu.component`\uff0c\u5982 `qw/externalContact/index` |')
  192. w('| \u8def\u7531\u52a0\u8f7d | \u540e\u7aef `getRouters` \u2192 `loadView(@/views/${component})` |')
  193. w('| \u53c2\u8003 | `src/views/admin/menu.js` \uff08\u603b\u540e\u53f0 `/admin/*` \u5bf9\u7167\u8868\uff09 |')
  194. w('')
  195. w('---')
  196. w('')
  197. w('## \u4e8c\u3001\u5efa\u8bae\u9876\u7ea7\u6a21\u5757\uff08\u79df\u6237 saasadminui \u9876\u680f\uff09')
  198. w('')
  199. w('| \u5e8f\u53f7 | \u6a21\u5757\u540d | path | \u89c6\u56fe\u6839\u76ee\u5f55 | \u9875\u9762\u6570 | \u5907\u6ce8 |')
  200. w('|------|--------|------|------------|--------|------|')
  201. tops = [
  202. (1, '\u9996\u9875', 'index', 'index.vue', 1, '\u4eea\u8868\u76d8\uff0c\u53ef hidden'),
  203. (2, '\u4f01\u5fae\u7ba1\u7406', 'qw', 'qw/', agg_count['qw'], ''),
  204. (3, '\u5fae\u4fe1\u7ba1\u7406', 'wx', 'wx/, gw/', agg_count['wx'], ''),
  205. (4, 'CRM\u5ba2\u6237', 'crm', 'crm/', agg_count['crm'], ''),
  206. (5, '\u4f1a\u5458\u7ba1\u7406', 'member', 'user/, users/, member/', agg_count['member'], ''),
  207. (6, '\u8bca\u6240\u7ba1\u7406', 'his', 'his/', agg_count['his'], ''),
  208. (7, '\u5546\u57ce\u7ba1\u7406', 'store', 'store/', agg_count['store'], 'canonical'),
  209. (8, '\u76f4\u64ad\u7ba1\u7406', 'live', 'live/, liveData/', agg_count['live'], ''),
  210. (9, '\u8bfe\u7a0b\u7ba1\u7406', 'course', 'course/, courseFinishTemp/', agg_count['course'], ''),
  211. (10, 'AI\u804a\u5929', 'fastGpt', 'fastGpt/, chat/, aiob/', agg_count['fastGpt'], ''),
  212. (11, '\u9f99\u8679\u5f15\u64ce', 'lobster', 'lobster/', agg_count['lobster'], ''),
  213. (12, '\u5e7f\u544a\u6295\u653e', 'ad', 'adv/, ad/', agg_count['ad'], ''),
  214. (13, '\u7cfb\u7edf\u7ba1\u7406', 'system', 'system/, company/', agg_count['system'], ''),
  215. (14, '\u8d22\u52a1\u7ba1\u7406', 'bill', 'bill/, billing/', agg_count['bill'], ''),
  216. (15, '\u65e5\u7a0b\u7ba1\u7406', 'calendar', 'calendar/', agg_count['calendar'], ''),
  217. (16, '\u6570\u636e\u7edf\u8ba1', 'statistics', 'statistics/, taskStatistics/', agg_count['statistics'], ''),
  218. (17, '\u76d1\u63a7\u7ba1\u7406', 'watch', 'watch/, monitor/', agg_count['watch'], ''),
  219. (18, '\u5176\u4ed6', 'other', '\u5e73\u53f0/\u9057\u7559', 0, '\u4e0d\u4e0b\u53d1\u79df\u6237\u9ed8\u8ba4\u83dc\u5355'),
  220. ]
  221. for row in tops:
  222. w('| %d | %s | `%s` | %s | %d | %s |' % row)
  223. w('')
  224. w('---')
  225. w('')
  226. w('## \u4e09\u3001\u603b\u540e\u53f0\u4e13\u7528\uff08\u5f52\u5165\u300c\u5176\u4ed6\u300d\uff09')
  227. w('')
  228. w('| \u76ee\u5f55 | \u9875\u9762\u6570 | \u8bf4\u660e |')
  229. w('|------|--------|------|')
  230. notes = {
  231. 'admin': '\u603b\u540e\u53f0\u4e1a\u52a1\uff08\u79df\u6237/\u4ee3\u7406/\u5916\u547c/\u77ed\u4fe1/\u8d22\u52a1\u5ba1\u8ba1\uff09',
  232. 'saas': 'SaaS \u8ba1\u8d39/\u79df\u6237\u5b57\u5178/\u79df\u6237\u83dc\u5355\u6a21\u677f',
  233. 'sysUser': '\u603b\u540e\u53f0\u5458\u5de5\uff08\u4e0e system/user \u4e0d\u540c\uff09',
  234. 'hisStore': '\u65e7\u7248\u5546\u57ce\uff0c\u4e0e store \u91cd\u590d',
  235. 'shop': '\u95e8\u5e97\u72ec\u7acb\u83dc\u5355\uff08\u9057\u7559\uff09',
  236. }
  237. for t in sorted(set(ADMIN_ONLY_TOP) | {'admin', 'saas'}):
  238. cnt = len(by_top.get(t, []))
  239. if cnt:
  240. w('| `%s/` | %d | %s |' % (t, cnt, notes.get(t, '\u5e73\u53f0\u6216\u9057\u7559')))
  241. w('')
  242. w('### 3.1 admin/menu.js \u5bf9\u7167\uff08\u603b\u540e\u53f0 /admin/*\uff09')
  243. w('')
  244. w('| \u83dc\u5355\u6807\u9898 | path | component |')
  245. w('|----------|------|-----------|')
  246. admin_rows = [
  247. ('\u6570\u636e\u770b\u677f', 'dashboard', 'admin/dashboard/index'),
  248. ('\u79df\u6237\u7ba1\u7406', 'company', 'admin/sysCompany/index'),
  249. ('\u79df\u6237\u6a21\u5757\u4f7f\u7528\u7edf\u8ba1', 'moduleUsage', 'admin/moduleUsage/index'),
  250. ('\u79df\u6237\u7ba1\u7406\u7aef\u83dc\u5355', 'tenantMenu', 'admin/tenantMenu/index'),
  251. ('\u79df\u6237\u9500\u552e\u7aef\u83dc\u5355', 'tenantCompany', 'admin/tenantCompany/index'),
  252. ('\u4ee3\u7406\u7ba1\u7406', 'proxy', 'admin/proxy/index'),
  253. ('\u6536\u8d39\u914d\u7f6e', 'serviceCost', 'admin/serviceCost/index'),
  254. ('\u5458\u5de5\u7ba1\u7406', 'sysUser', 'admin/sysUser/index'),
  255. ('\u89d2\u8272\u7ba1\u7406', 'role', 'system/role/index'),
  256. ('\u83dc\u5355\u7ba1\u7406', 'menu', 'system/menu/index'),
  257. ('\u5916\u547c\u7ba1\u7406', 'voice', 'admin/voice/index'),
  258. ('\u77ed\u4fe1\u7ba1\u7406', 'sms', 'admin/sms/index'),
  259. ('AI\u6a21\u578b\u914d\u7f6e', 'aiModel', 'admin/aiModel/index'),
  260. ('AI\u751f\u6210\u5de5\u4f5c\u6d41', 'workflowGenerate', 'lobster/workflow-generate/index'),
  261. ]
  262. for title, path, comp in admin_rows:
  263. w('| %s | `/admin/%s` | `%s` |' % (title, path, comp))
  264. w('')
  265. w('---')
  266. w('')
  267. w('## \u56db\u3001\u5404\u4e1a\u52a1\u6a21\u5757\u4e8c\u7ea7\u5206\u7ec4\u4e0e\u9875\u9762\u6e05\u5355')
  268. w('')
  269. detail = [
  270. ('qw', '\u4f01\u5fae\u7ba1\u7406', 'qw'),
  271. ('crm', 'CRM', 'crm'),
  272. ('store', '\u5546\u57ce', 'store'),
  273. ('his', '\u8bca\u6240', 'his'),
  274. ('live', '\u76f4\u64ad', 'live'),
  275. ('course', '\u8bfe\u7a0b', 'course'),
  276. ('fastGpt', 'AI', 'fastGpt'),
  277. ('company', '\u4f01\u4e1a/\u7ec4\u7ec7', 'company'),
  278. ('system', '\u7cfb\u7edf', 'system'),
  279. ('lobster', '\u9f99\u8679', 'lobster'),
  280. ('adv', '\u5e7f\u544a', 'ad'),
  281. ('wx', '\u5fae\u4fe1', 'wx'),
  282. ('monitor', '\u76d1\u63a7', 'monitor'),
  283. ]
  284. sec_no = 1
  285. for top_key, title, path_prefix in detail:
  286. items = list(by_top.get(top_key, []))
  287. if top_key == 'fastGpt':
  288. for extra in ('chat', 'aiob', 'FastGptExtUserTag'):
  289. items.extend(by_top.get(extra, []))
  290. if not items:
  291. continue
  292. w('### 4.%d %s (`%s/`) \u2014 %d \u9875' % (sec_no, title, top_key, len(items)))
  293. sec_no += 1
  294. w('')
  295. w('\u5efa\u8bae\u8def\u7531\u524d\u7f00\uff1a`/%s`' % path_prefix)
  296. w('')
  297. groups = defaultdict(list)
  298. for p in sorted(items, key=lambda x: x['component']):
  299. groups[guess_secondary(top_key, p['module'])].append(p)
  300. order = list(SECONDARY_HINTS.get(top_key, {}).keys()) + ['general']
  301. for g in order:
  302. if g not in groups:
  303. continue
  304. w('#### %s' % GROUP_TITLES.get(g, g))
  305. w('')
  306. w('| \u5efa\u8bae menu_name | component | \u6e90\u6587\u4ef6 |')
  307. w('|--------------|-----------|--------|')
  308. for p in groups[g]:
  309. w('| %s | `%s` | `%s` |' % (p['module'], p['component'], p['file']))
  310. w('')
  311. w('---')
  312. w('')
  313. w('## \u4e94\u3001\u91cd\u590d/\u9057\u7559\u6a21\u5757\u5408\u5e76\u5efa\u8bae')
  314. w('')
  315. w('| \u7ec4 | \u76ee\u5f55 | \u5efa\u8bae |')
  316. w('|------|------|------|')
  317. dupes = [
  318. ('\u5546\u57ce', '`store/` vs `hisStore/`', '\u4fdd\u7559 store\uff0chisStore \u653e\u5176\u4ed6'),
  319. ('\u5546\u57ce', '`store/` vs `his/store*`', 'his \u5185\u5d4c\u5957\u9875\u7559\u5728\u8bca\u6240\u6a21\u5757'),
  320. ('\u4f1a\u5458', '`user/` `users/` `member/`', '\u5408\u5e76\u4e3a member \u9876\u680f'),
  321. ('AI', '`fastGpt/` `chat/` `aiob/`', '\u5408\u5e76\u4e3a fastGpt'),
  322. ('\u5e7f\u544a', '`adv/` `ad/`', '\u7edf\u4e00 adv'),
  323. ('\u5fae\u4fe1', '`wx/` `gw/`', 'gwAccount \u5f52\u5165 wx'),
  324. ('\u7edf\u8ba1', '`statistics/` `taskStatistics/`', '\u5408\u5e76 statistics'),
  325. ('\u76d1\u63a7', '`monitor/` `watch/`', '\u5408\u5e76 watch'),
  326. ('\u7528\u6237', '`system/user` vs `sysUser/`', 'sysUser \u4ec5\u603b\u540e\u53f0'),
  327. ]
  328. for a, b, c in dupes:
  329. w('| %s | %s | %s |' % (a, b, c))
  330. w('')
  331. w('---')
  332. w('')
  333. w('## \u516d\u3001\u5b8c\u6574\u4e00\u7ea7\u76ee\u5f55\u626b\u63cf\u8868')
  334. w('')
  335. w('| views \u4e00\u7ea7\u76ee\u5f55 | \u53ef\u8def\u7531\u9875\u9762\u6570 | \u5f52\u7c7b |')
  336. w('|----------------|-------------|------|')
  337. for top in sorted(by_top.keys()):
  338. cnt = len(by_top[top])
  339. if top in ADMIN_ONLY_TOP or top == 'admin':
  340. cat = '\u5e73\u53f0/\u5176\u4ed6'
  341. elif top in TENANT_TOP:
  342. cat = '\u79df\u6237 \u2014 ' + TENANT_TOP[top][0]
  343. else:
  344. cat = '\u5f85\u786e\u8ba4'
  345. w('| `%s/` | %d | %s |' % (top, cnt, cat))
  346. w('')
  347. w('---')
  348. w('')
  349. w('## \u4e03\u3001\u672c\u6b21\u4e0d\u6267\u884c\u7684\u843d\u5730\u6b65\u9aa4')
  350. w('')
  351. w('1. \u8bc4\u5ba1\u672c\u6587\u6863\u4e8c\u7ea7\u5206\u7ec4')
  352. w('2. \u5bfc\u51fa component \u4e0e tenant_sys_menu diff')
  353. w('3. \u5408\u5e76\u91cd\u590d\u83dc\u5355')
  354. w('4. \u66f4\u65b0\u6a21\u677f\u5e76\u540c\u6b65\u79df\u6237\u5e93')
  355. w('5. saasadminui \u9a8c\u8bc1\u8def\u7531')
  356. w('')
  357. w('---')
  358. w('')
  359. w('*Generated by `sql/generate_adminUI_menu_doc.py`*')
  360. return '\n'.join(L)
  361. def main():
  362. pages, all_files = scan_views()
  363. md = build_markdown(pages, all_files)
  364. with open(OUT, 'w', encoding='utf-8') as f:
  365. f.write(md)
  366. print('Wrote', OUT)
  367. print('pages', len(pages), 'all vue', all_files)
  368. if __name__ == '__main__':
  369. main()