add-auto-search.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env python3
  2. """
  3. 批量给 web-admin 列表页添加 watchDebounced 自动搜索。
  4. watchDebounced 来自 @vueuse/core,已通过 auto-import 自动导入,无需手动 import。
  5. 用法: python3 scripts/add-auto-search.py [--dry-run]
  6. """
  7. import os
  8. import re
  9. import sys
  10. DRY_RUN = "--dry-run" in sys.argv
  11. BASE = os.path.join(os.path.dirname(__file__), "..", "web-admin", "src", "views")
  12. # 所有需要修改的文件(相对于 web-admin/src/views/)
  13. FILES = [
  14. # --- basic-info (6) ---
  15. "basic-info/collection-region/index.vue",
  16. "basic-info/customer/index.vue",
  17. "basic-info/collection-protocol-no/index.vue",
  18. "basic-info/insurance-company/index.vue",
  19. "basic-info/cell-sample-type/index.vue",
  20. "basic-info/sample-code/index.vue",
  21. # --- pilot-service (6) ---
  22. "pilot-service/cell-product-spec/index.vue",
  23. "pilot-service/project-price-system/index.vue",
  24. "pilot-service/cell-product-price/index.vue",
  25. "pilot-service/channel/index.vue",
  26. "pilot-service/checkup-institution/index.vue",
  27. "pilot-service/cell-product-type/index.vue",
  28. # --- quality-inspection (9) ---
  29. "quality-inspection/third-party-company/index.vue",
  30. "quality-inspection/qc-material-usage/index.vue",
  31. "quality-inspection/storage-release-review/index.vue",
  32. "quality-inspection/qc-task/index.vue",
  33. "quality-inspection/sample-reception/index.vue",
  34. "quality-inspection/qc-material/index.vue",
  35. "quality-inspection/lab-test-item/index.vue",
  36. "quality-inspection/sample-reception-check-item/index.vue",
  37. "quality-inspection/qc-work-order/index.vue",
  38. # --- cell-storage (9) ---
  39. "cell-storage/material-stock-log/index.vue",
  40. "cell-storage/storage-entry-info/index.vue",
  41. "cell-storage/fullmoon-todo/index.vue",
  42. "cell-storage/material/index.vue",
  43. "cell-storage/protocol-audit/index.vue",
  44. "cell-storage/storage-cert/index.vue",
  45. "cell-storage/payment-info/index.vue",
  46. "cell-storage/sample-reception-info/index.vue",
  47. "cell-storage/protocol/index.vue",
  48. # --- production (11) ---
  49. "production/process-category/index.vue",
  50. "production/process/index.vue",
  51. "production/consumable/index.vue",
  52. "production/consumable-pack/index.vue",
  53. "production/cell-product/index.vue",
  54. "production/output-type/index.vue",
  55. "production/production-work-order/index.vue",
  56. "production/reagent/index.vue",
  57. "production/production-task/index.vue",
  58. "production/sop/index.vue",
  59. "production/process-route/index.vue",
  60. # --- operations (2) ---
  61. "operations/exhibition-appointment/index.vue",
  62. "operations/clean-room/index.vue",
  63. # --- research-donation (2) ---
  64. "research-donation/list/index.vue",
  65. "research-donation/audit/index.vue",
  66. # --- system (17) ---
  67. "system/leave/index.vue",
  68. "system/role/index.vue",
  69. "system/dict/index.vue",
  70. "system/unit/index.vue",
  71. "system/dept/index.vue",
  72. "system/site/index.vue",
  73. "system/menu/index.vue",
  74. "system/order/index.vue",
  75. "system/product/index.vue",
  76. "system/user/index.vue",
  77. "system/post/index.vue",
  78. "system/express/index.vue",
  79. "system/category/index.vue",
  80. "system/workflow/process/index.vue",
  81. "system/workflow/task-completed/index.vue",
  82. "system/workflow/task-pending/index.vue",
  83. "system/workflow/my-applications/index.vue",
  84. ]
  85. # 特殊文件:有额外的 dateRange ref 需要监听
  86. DATE_RANGE_FILES = [
  87. "cell-storage/material-stock-log/index.vue",
  88. "quality-inspection/qc-material-usage/index.vue",
  89. ]
  90. # 特殊文件:使用 filterForm 而非 searchForm
  91. FILTER_FORM_FILES = {
  92. "system/leave/index.vue": "filterForm",
  93. }
  94. def detect_form_var(filepath: str, content: str) -> str:
  95. """检测搜索表单的变量名"""
  96. rel = os.path.relpath(filepath, BASE)
  97. if rel in FILTER_FORM_FILES:
  98. return FILTER_FORM_FILES[rel]
  99. # 查找 const xxxForm = reactive 或 const xxxFilter = reactive
  100. m = re.search(r"const\s+(\w*(?:Form|Filter|filter))\s*=\s*reactive", content)
  101. if m:
  102. return m.group(1)
  103. # 默认 searchForm
  104. return "searchForm"
  105. def detect_pagination_var(content: str) -> str:
  106. """检测分页变量名"""
  107. m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*\(\{[^}]*page\s*:", content)
  108. if not m:
  109. m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*<[^>]*>\s*\(\{[^}]*page\s*:", content)
  110. if not m:
  111. m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*\{[^}]*page\s*:", content)
  112. return m.group(1) if m else "pagination"
  113. def detect_load_fn(content: str, form_var: str) -> str:
  114. """检测数据加载函数名(在 handleSearch 中调用的函数)"""
  115. # 查找 handleSearch 函数体中调用的函数
  116. # 模式: function handleSearch() { ... xxx() } 或 const handleSearch = () => { ... xxx() }
  117. pattern = rf"(?:function\s+handleSearch|const\s+handleSearch\s*=\s*(?:\([^)]*\)\s*=>|\(\)\s*=>))\s*\{{[^}}]*?(\w+)\(\)"
  118. m = re.search(pattern, content)
  119. if m:
  120. return m.group(1)
  121. # 回退:查找 pagination.page = 1 后面紧跟的函数调用
  122. pattern2 = r"pagination\.page\s*=\s*1\s*\n?\s*(\w+)\(\)"
  123. m2 = re.search(pattern2, content)
  124. if m2:
  125. return m2.group(1)
  126. return "loadData"
  127. def build_watch_block(form_var: str, pagination_var: str, load_fn: str, has_date_range: bool = False) -> str:
  128. """生成 watchDebounced 代码块"""
  129. lines = [
  130. "",
  131. " // 输入检索条件时自动搜索(防抖 300ms)",
  132. " watchDebounced(",
  133. f" {form_var},",
  134. " () => {",
  135. f" {pagination_var}.page = 1",
  136. f" {load_fn}()",
  137. " },",
  138. " { debounce: 300, deep: true }",
  139. " )",
  140. ]
  141. if has_date_range:
  142. lines += [
  143. "",
  144. " // 日期范围变更时自动搜索",
  145. " watch(",
  146. " dateRange,",
  147. " () => {",
  148. f" {pagination_var}.page = 1",
  149. f" {load_fn}()",
  150. " }",
  151. " )",
  152. ]
  153. return "\n".join(lines)
  154. def process_file(rel_path: str) -> bool:
  155. """处理单个文件,返回是否修改"""
  156. filepath = os.path.join(BASE, rel_path)
  157. if not os.path.exists(filepath):
  158. print(f" SKIP (not found): {rel_path}")
  159. return False
  160. with open(filepath, "r", encoding="utf-8") as f:
  161. content = f.read()
  162. # 已经有 watchDebounced 的跳过
  163. if "watchDebounced" in content:
  164. print(f" SKIP (already has watchDebounced): {rel_path}")
  165. return False
  166. # 检测变量名
  167. form_var = detect_form_var(filepath, content)
  168. pagination_var = detect_pagination_var(content)
  169. load_fn = detect_load_fn(content, form_var)
  170. has_date_range = rel_path in DATE_RANGE_FILES
  171. # 生成 watch 代码块
  172. watch_block = build_watch_block(form_var, pagination_var, load_fn, has_date_range)
  173. # 查找插入点:onMounted 之前
  174. # 匹配多种 onMounted 写法
  175. on_mounted_patterns = [
  176. r"(\n)(\s*onMounted\s*\()", # onMounted(() => {
  177. r"(\n)(\s*onMounted\()", # onMounted(() => {
  178. ]
  179. inserted = False
  180. for pattern in on_mounted_patterns:
  181. match = re.search(pattern, content)
  182. if match:
  183. insert_pos = match.start(1)
  184. content = content[:insert_pos] + watch_block + content[insert_pos:]
  185. inserted = True
  186. break
  187. if not inserted:
  188. print(f" SKIP (no onMounted found): {rel_path}")
  189. return False
  190. if DRY_RUN:
  191. print(f" WOULD MODIFY: {rel_path} (form={form_var}, pagination={pagination_var}, load={load_fn})")
  192. else:
  193. with open(filepath, "w", encoding="utf-8") as f:
  194. f.write(content)
  195. print(f" DONE: {rel_path} (form={form_var}, pagination={pagination_var}, load={load_fn})")
  196. return True
  197. def main():
  198. mode = "DRY RUN" if DRY_RUN else "LIVE"
  199. print(f"=== Adding auto-search to list pages ({mode}) ===")
  200. print(f"Files to process: {len(FILES)}")
  201. print()
  202. modified = 0
  203. skipped = 0
  204. for f in FILES:
  205. if process_file(f):
  206. modified += 1
  207. else:
  208. skipped += 1
  209. print()
  210. print(f"=== Summary: {modified} modified, {skipped} skipped (total: {len(FILES)}) ===")
  211. if DRY_RUN:
  212. print("=== This was a dry run. Remove --dry-run to apply changes. ===")
  213. if __name__ == "__main__":
  214. main()