| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- #!/usr/bin/env python3
- """Batch apply 运输计划 toolbar pattern across list pages:
- 1) Move「已选」from list-toolbar to list-pager-bar (left)
- 2) Ensure ListToolbarDateShortcuts uses variant=\"select\"
- """
- from __future__ import annotations
- import re
- import sys
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1] / "web-admin" / "src"
- SKIP_PARTS = {
- "components/bx/template/ListToolbar.vue", # handled separately
- "components/core/list-toolbar-date-shortcuts/index.vue",
- }
- # Dialog / selector toolbars are not top-level menu pages — still move selected if pager exists
- SELECTED_RE = re.compile(
- r"[ \t]*(?:<div|<span)\s+class=\"list-toolbar-selected\"[^>]*>\s*"
- r"已选\s*<span class=\"sel-num\">[\s\S]*?</span>\s*(?:项|条)\s*"
- r"</(?:div|span)>\s*\n?",
- re.MULTILINE,
- )
- PAGER_OPEN_RE = re.compile(
- r'(<(?:div|section)\s+class="[^"]*(?:pagination\s+list-pager-bar|list-pager-bar|list-pager)[^"]*"[^>]*>)',
- )
- LIST_PAGER_SELECTED_RE = re.compile(r'class="[^"]*list-pager-selected')
- def extract_selected_blocks(text: str) -> tuple[str, list[str]]:
- blocks = []
- def _keep(m: re.Match[str]) -> str:
- blocks.append(m.group(0).strip())
- return ""
- new_text = SELECTED_RE.sub(_keep, text)
- return new_text, blocks
- def selected_to_pager_html(block: str) -> str:
- # Normalize to list-pager-selected div
- inner = re.search(
- r"已选\s*<span class=\"sel-num\">([\s\S]*?)</span>\s*(项|条)",
- block,
- )
- if not inner:
- return ""
- expr = inner.group(1).strip()
- unit = inner.group(2)
- return (
- f' <div class="list-pager-selected">\n'
- f' 已选 <span class="sel-num">{expr}</span> {unit}\n'
- f" </div>\n"
- )
- def inject_into_pagers(text: str, selected_htmls: list[str]) -> tuple[str, int]:
- if not selected_htmls:
- return text, 0
- # Already has pager selected somewhere — just keep removals (caller already removed toolbar blocks)
- if LIST_PAGER_SELECTED_RE.search(text):
- return text, len(selected_htmls)
- idx = 0
- injected = 0
- def _repl(m: re.Match[str]) -> str:
- nonlocal idx, injected
- open_tag = m.group(1)
- html = selected_htmls[min(idx, len(selected_htmls) - 1)]
- idx += 1
- injected += 1
- return open_tag + "\n" + html
- new_text = PAGER_OPEN_RE.sub(_repl, text, count=len(selected_htmls))
- return new_text, injected
- def ensure_date_select_variant_safe(text: str) -> tuple[str, int]:
- changed = 0
- pattern = re.compile(r"<ListToolbarDateShortcuts\b([^>]*)(/?)>", re.DOTALL)
- def repl(m: re.Match[str]) -> str:
- nonlocal changed
- attrs, slash = m.group(1), m.group(2)
- if 'variant="select"' in attrs or "variant='select'" in attrs:
- return m.group(0)
- if re.search(r"variant\s*=", attrs):
- attrs2 = re.sub(r'''variant\s*=\s*["'][^"']*["']''', 'variant="select"', attrs)
- changed += 1
- return f"<ListToolbarDateShortcuts{attrs2}{slash}>"
- changed += 1
- return f'<ListToolbarDateShortcuts\n variant="select"{attrs}{slash}>'
- return pattern.sub(repl, text), changed
- def process_file(path: Path) -> list[str]:
- rel = str(path.relative_to(ROOT)).replace("\\", "/")
- if rel in SKIP_PARTS:
- return []
- text = path.read_text(encoding="utf-8")
- orig = text
- notes: list[str] = []
- text_wo, blocks = extract_selected_blocks(text)
- if blocks:
- selected_htmls = [h for h in (selected_to_pager_html(b) for b in blocks) if h]
- has_pager = bool(
- re.search(r"list-pager-bar|class=\"[^\"]*list-pager[^\"]*\"", text)
- )
- already_pager_selected = bool(LIST_PAGER_SELECTED_RE.search(text_wo))
- if already_pager_selected:
- text = text_wo
- notes.append(f"removed-toolbar-selected×{len(blocks)}")
- elif has_pager:
- text2, injected = inject_into_pagers(text_wo, selected_htmls)
- if injected:
- text = text2
- notes.append(f"moved-selected×{injected}")
- else:
- text = orig
- notes.append("selected-move-FAILED")
- else:
- # Tree / non-pager list: append a footer bar once before closing panel section
- footer = (
- '\n <div class="pagination list-pager-bar">\n'
- + selected_htmls[0]
- + " </div>\n"
- )
- # Prefer insert after list-body/list-wrap block
- m = re.search(
- r'(</div>\s*\n)(\s*</section>)',
- text_wo,
- )
- if m and selected_htmls:
- text = text_wo[: m.start(1)] + m.group(1) + footer + m.group(2) + text_wo[m.end() :]
- notes.append("selected-footer-no-pager")
- else:
- text = orig
- notes.append("selected-skip-no-pager")
- text, n_var = ensure_date_select_variant_safe(text)
- if n_var:
- notes.append(f"date-select×{n_var}")
- if text != orig:
- path.write_text(text, encoding="utf-8")
- return notes
- def main() -> int:
- targets = []
- for p in ROOT.rglob("*.vue"):
- rel = str(p.relative_to(ROOT)).replace("\\", "/")
- if "node_modules" in rel:
- continue
- t = p.read_text(encoding="utf-8")
- if "list-toolbar-selected" in t or "ListToolbarDateShortcuts" in t:
- targets.append(p)
- report = []
- for p in sorted(targets):
- notes = process_file(p)
- if notes:
- report.append(f"{p.relative_to(ROOT)}: {', '.join(notes)}")
- print(f"processed candidates: {len(targets)}")
- print(f"changed: {len(report)}")
- for line in report:
- print(line)
- return 0
- if __name__ == "__main__":
- sys.exit(main())
|