#!/usr/bin/env python3 """ 批量给 web-admin 列表页添加 watchDebounced 自动搜索。 watchDebounced 来自 @vueuse/core,已通过 auto-import 自动导入,无需手动 import。 用法: python3 scripts/add-auto-search.py [--dry-run] """ import os import re import sys DRY_RUN = "--dry-run" in sys.argv BASE = os.path.join(os.path.dirname(__file__), "..", "web-admin", "src", "views") # 所有需要修改的文件(相对于 web-admin/src/views/) FILES = [ # --- basic-info (6) --- "basic-info/collection-region/index.vue", "basic-info/customer/index.vue", "basic-info/collection-protocol-no/index.vue", "basic-info/insurance-company/index.vue", "basic-info/cell-sample-type/index.vue", "basic-info/sample-code/index.vue", # --- pilot-service (6) --- "pilot-service/cell-product-spec/index.vue", "pilot-service/project-price-system/index.vue", "pilot-service/cell-product-price/index.vue", "pilot-service/channel/index.vue", "pilot-service/checkup-institution/index.vue", "pilot-service/cell-product-type/index.vue", # --- quality-inspection (9) --- "quality-inspection/third-party-company/index.vue", "quality-inspection/qc-material-usage/index.vue", "quality-inspection/storage-release-review/index.vue", "quality-inspection/qc-task/index.vue", "quality-inspection/sample-reception/index.vue", "quality-inspection/qc-material/index.vue", "quality-inspection/lab-test-item/index.vue", "quality-inspection/sample-reception-check-item/index.vue", "quality-inspection/qc-work-order/index.vue", # --- cell-storage (9) --- "cell-storage/material-stock-log/index.vue", "cell-storage/storage-entry-info/index.vue", "cell-storage/fullmoon-todo/index.vue", "cell-storage/material/index.vue", "cell-storage/protocol-audit/index.vue", "cell-storage/storage-cert/index.vue", "cell-storage/payment-info/index.vue", "cell-storage/sample-reception-info/index.vue", "cell-storage/protocol/index.vue", # --- production (11) --- "production/process-category/index.vue", "production/process/index.vue", "production/consumable/index.vue", "production/consumable-pack/index.vue", "production/cell-product/index.vue", "production/output-type/index.vue", "production/production-work-order/index.vue", "production/reagent/index.vue", "production/production-task/index.vue", "production/sop/index.vue", "production/process-route/index.vue", # --- operations (2) --- "operations/exhibition-appointment/index.vue", "operations/clean-room/index.vue", # --- research-donation (2) --- "research-donation/list/index.vue", "research-donation/audit/index.vue", # --- system (17) --- "system/leave/index.vue", "system/role/index.vue", "system/dict/index.vue", "system/unit/index.vue", "system/dept/index.vue", "system/site/index.vue", "system/menu/index.vue", "system/order/index.vue", "system/product/index.vue", "system/user/index.vue", "system/post/index.vue", "system/express/index.vue", "system/category/index.vue", "system/workflow/process/index.vue", "system/workflow/task-completed/index.vue", "system/workflow/task-pending/index.vue", "system/workflow/my-applications/index.vue", ] # 特殊文件:有额外的 dateRange ref 需要监听 DATE_RANGE_FILES = [ "cell-storage/material-stock-log/index.vue", "quality-inspection/qc-material-usage/index.vue", ] # 特殊文件:使用 filterForm 而非 searchForm FILTER_FORM_FILES = { "system/leave/index.vue": "filterForm", } def detect_form_var(filepath: str, content: str) -> str: """检测搜索表单的变量名""" rel = os.path.relpath(filepath, BASE) if rel in FILTER_FORM_FILES: return FILTER_FORM_FILES[rel] # 查找 const xxxForm = reactive 或 const xxxFilter = reactive m = re.search(r"const\s+(\w*(?:Form|Filter|filter))\s*=\s*reactive", content) if m: return m.group(1) # 默认 searchForm return "searchForm" def detect_pagination_var(content: str) -> str: """检测分页变量名""" m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*\(\{[^}]*page\s*:", content) if not m: m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*<[^>]*>\s*\(\{[^}]*page\s*:", content) if not m: m = re.search(r"const\s+(\w+)\s*=\s*reactive\s*\{[^}]*page\s*:", content) return m.group(1) if m else "pagination" def detect_load_fn(content: str, form_var: str) -> str: """检测数据加载函数名(在 handleSearch 中调用的函数)""" # 查找 handleSearch 函数体中调用的函数 # 模式: function handleSearch() { ... xxx() } 或 const handleSearch = () => { ... xxx() } pattern = rf"(?:function\s+handleSearch|const\s+handleSearch\s*=\s*(?:\([^)]*\)\s*=>|\(\)\s*=>))\s*\{{[^}}]*?(\w+)\(\)" m = re.search(pattern, content) if m: return m.group(1) # 回退:查找 pagination.page = 1 后面紧跟的函数调用 pattern2 = r"pagination\.page\s*=\s*1\s*\n?\s*(\w+)\(\)" m2 = re.search(pattern2, content) if m2: return m2.group(1) return "loadData" def build_watch_block(form_var: str, pagination_var: str, load_fn: str, has_date_range: bool = False) -> str: """生成 watchDebounced 代码块""" lines = [ "", " // 输入检索条件时自动搜索(防抖 300ms)", " watchDebounced(", f" {form_var},", " () => {", f" {pagination_var}.page = 1", f" {load_fn}()", " },", " { debounce: 300, deep: true }", " )", ] if has_date_range: lines += [ "", " // 日期范围变更时自动搜索", " watch(", " dateRange,", " () => {", f" {pagination_var}.page = 1", f" {load_fn}()", " }", " )", ] return "\n".join(lines) def process_file(rel_path: str) -> bool: """处理单个文件,返回是否修改""" filepath = os.path.join(BASE, rel_path) if not os.path.exists(filepath): print(f" SKIP (not found): {rel_path}") return False with open(filepath, "r", encoding="utf-8") as f: content = f.read() # 已经有 watchDebounced 的跳过 if "watchDebounced" in content: print(f" SKIP (already has watchDebounced): {rel_path}") return False # 检测变量名 form_var = detect_form_var(filepath, content) pagination_var = detect_pagination_var(content) load_fn = detect_load_fn(content, form_var) has_date_range = rel_path in DATE_RANGE_FILES # 生成 watch 代码块 watch_block = build_watch_block(form_var, pagination_var, load_fn, has_date_range) # 查找插入点:onMounted 之前 # 匹配多种 onMounted 写法 on_mounted_patterns = [ r"(\n)(\s*onMounted\s*\()", # onMounted(() => { r"(\n)(\s*onMounted\()", # onMounted(() => { ] inserted = False for pattern in on_mounted_patterns: match = re.search(pattern, content) if match: insert_pos = match.start(1) content = content[:insert_pos] + watch_block + content[insert_pos:] inserted = True break if not inserted: print(f" SKIP (no onMounted found): {rel_path}") return False if DRY_RUN: print(f" WOULD MODIFY: {rel_path} (form={form_var}, pagination={pagination_var}, load={load_fn})") else: with open(filepath, "w", encoding="utf-8") as f: f.write(content) print(f" DONE: {rel_path} (form={form_var}, pagination={pagination_var}, load={load_fn})") return True def main(): mode = "DRY RUN" if DRY_RUN else "LIVE" print(f"=== Adding auto-search to list pages ({mode}) ===") print(f"Files to process: {len(FILES)}") print() modified = 0 skipped = 0 for f in FILES: if process_file(f): modified += 1 else: skipped += 1 print() print(f"=== Summary: {modified} modified, {skipped} skipped (total: {len(FILES)}) ===") if DRY_RUN: print("=== This was a dry run. Remove --dry-run to apply changes. ===") if __name__ == "__main__": main()