Files
shanxia2.0/scripts/add-auto-search.py
T
2026-05-23 21:48:49 +08:00

247 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()