| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- #!/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()
|