#!/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]*(?:
]*>\s*"
r"已选\s*[\s\S]*?\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*([\s\S]*?)\s*(项|条)",
block,
)
if not inner:
return ""
expr = inner.group(1).strip()
unit = inner.group(2)
return (
f' \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"]*)(/?)>", 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""
changed += 1
return f''
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 \n"
)
# Prefer insert after list-body/list-wrap block
m = re.search(
r'(
\s*\n)(\s*)',
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())