apply-list-toolbar-refresh.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env python3
  2. """Add ListToolbarEndActions (refresh-only) to list pages missing it."""
  3. from __future__ import annotations
  4. import re
  5. from pathlib import Path
  6. ROOT = Path(__file__).resolve().parents[1] / "src" / "views"
  7. REFRESH_HANDLERS = [
  8. "fetchData",
  9. "loadData",
  10. "getList",
  11. "handleQuery",
  12. "handleSearch",
  13. "loadList",
  14. "refresh",
  15. "loadWfDef",
  16. "refreshPending",
  17. ]
  18. TOOLBAR_SNIPPET = """
  19. <ListToolbarEndActions :loading="loading" @refresh="{handler}" />
  20. """
  21. IMPORT_COMPONENT = "import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'\n"
  22. TARGETS = [
  23. "system/role/index.vue",
  24. "system/post/index.vue",
  25. "system/tenant/index.vue",
  26. "system/site/index.vue",
  27. "system/dept/index.vue",
  28. "system/menu/index.vue",
  29. "system/operlog/index.vue",
  30. "system/leave/index.vue",
  31. "system/dict/index.vue",
  32. "system/workflow/process/index.vue",
  33. "system/workflow/task-pending/index.vue",
  34. "system/workflow/task-completed/index.vue",
  35. "system/workflow/my-applications/index.vue",
  36. "crm/product-category/index.vue",
  37. "crm/contract/approval/index.vue",
  38. "crm/contract/template/index.vue",
  39. "crm/contract/change/index.vue",
  40. "crm/customer/pool/RuleIndex.vue",
  41. "pl/vehicle-maintenance-apply/index.vue",
  42. "pl/vehicle-maintenance-process/index.vue",
  43. "pl/vehicle-recharge-apply/index.vue",
  44. "pl/vehicle-recharge-record/index.vue",
  45. "pl/vehicle-urea-apply/index.vue",
  46. "pl/vehicle-urea-disburse/index.vue",
  47. "pl/inventory/document-reminder.vue",
  48. "pl/inventory/ledger.vue",
  49. "bx/expense-item/index.vue",
  50. "bx/advance/apply/index.vue",
  51. "bx/reimburse/apply/index.vue",
  52. "bx/view/todo/index.vue",
  53. "pl/vehicle/index.vue",
  54. "pl/self-vehicle/index.vue",
  55. "pl/plan/index.vue",
  56. "pl/plan-archive/index.vue",
  57. "pl/plan-cert/index.vue",
  58. "demo/product/index.vue",
  59. "bx/advance/approve/index.vue",
  60. "bx/advance/issue/index.vue",
  61. "bx/reimburse/approve/index.vue",
  62. "bx/payment/index.vue",
  63. "bx/workflow/advance/index.vue",
  64. "bx/workflow/reimburse/index.vue",
  65. "pl/freight/components/UnsettledPlanPanel.vue",
  66. "pl/freight/components/SettlementBillPanel.vue",
  67. "pl/freight/components/DisbursePanel.vue",
  68. "pl/freight/components/ApprovalPendingPanel.vue",
  69. "pl/freight/components/ApprovalDonePanel.vue",
  70. "pl/freight/components/SettledPanel.vue",
  71. ]
  72. def detect_refresh_handler(content: str) -> str | None:
  73. for name in REFRESH_HANDLERS:
  74. if re.search(rf"\b(?:async\s+)?function\s+{name}\s*\(", content):
  75. return name
  76. if re.search(rf"\bconst\s+{name}\s*=", content):
  77. return name
  78. return None
  79. def has_loading_ref(content: str) -> bool:
  80. if re.search(r"\bconst\s+loading\s*=\s*ref\(", content):
  81. return True
  82. if re.search(r"\{[^}]*\bloading\b[^}]*\}\s*=", content):
  83. return True
  84. return False
  85. def find_toolbar_insert_point(content: str) -> re.Match | None:
  86. """Find end of first list-toolbar-row block before list-body."""
  87. row_start = re.search(r'<div class="list-toolbar-row[^"]*"', content)
  88. if not row_start:
  89. return None
  90. # From row start, find closing </div></div> before list-body
  91. rest = content[row_start.start() :]
  92. pattern = re.compile(
  93. r"(<div class=\"list-toolbar-row[^\"]*\"[\s\S]*?)"
  94. r"(\s*</div>\s*</div>\s*)"
  95. r"(?:<!--[\s\S]*?-->\s*)?"
  96. r"(<div class=\"list-body)",
  97. re.MULTILINE,
  98. )
  99. return pattern.search(rest)
  100. def insert_component_import(content: str) -> str | None:
  101. """Append ListToolbarEndActions import after existing import block."""
  102. if IMPORT_COMPONENT in content:
  103. return content
  104. script_match = re.search(r'(<script setup lang="ts"[^>]*>\n)', content)
  105. if not script_match:
  106. return None
  107. script_start = script_match.end()
  108. import_stmt = re.compile(
  109. r"^import[\s\S]*?from\s+['\"][^'\"]+['\"]\s*;?\s*$",
  110. re.MULTILINE,
  111. )
  112. insert_at = script_start
  113. for match in import_stmt.finditer(content, script_start):
  114. insert_at = match.end()
  115. if content[insert_at : insert_at + 1] == "\n":
  116. insert_at += 1
  117. return content[:insert_at] + IMPORT_COMPONENT + content[insert_at:]
  118. def apply_file(path: Path) -> str:
  119. content = path.read_text(encoding="utf-8")
  120. if "ListToolbarEndActions" in content:
  121. return "skip"
  122. if "list-toolbar-row" not in content:
  123. return "no-toolbar"
  124. if not has_loading_ref(content):
  125. return "no-loading"
  126. handler = detect_refresh_handler(content)
  127. if not handler:
  128. return "no-handler"
  129. match = find_toolbar_insert_point(content)
  130. if not match:
  131. return "no-match"
  132. row_start = re.search(r'<div class="list-toolbar-row[^"]*"', content)
  133. assert row_start
  134. abs_start = row_start.start() + match.start(1)
  135. abs_end = row_start.start() + match.end(1)
  136. snippet = TOOLBAR_SNIPPET.format(handler=handler)
  137. content = content[:abs_end] + snippet + content[abs_end:]
  138. updated = insert_component_import(content)
  139. if updated is None:
  140. return "no-script"
  141. content = updated
  142. path.write_text(content, encoding="utf-8")
  143. return "ok"
  144. def main() -> None:
  145. results: dict[str, list[str]] = {}
  146. for rel in TARGETS:
  147. path = ROOT / rel
  148. if not path.exists():
  149. results.setdefault("missing", []).append(rel)
  150. continue
  151. status = apply_file(path)
  152. results.setdefault(status, []).append(rel)
  153. for status, files in sorted(results.items()):
  154. print(f"[{status}] {len(files)}")
  155. for f in files:
  156. print(f" - {f}")
  157. if __name__ == "__main__":
  158. main()