chore: sync local PL/CRM/BX and migration work to new Gogs host
Bundle pending backend/web-admin feature updates, data-migration scripts, and related docs so the V1.5 branch on the Tencent Cloud Gogs matches the current workspace (excluding local secrets). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
5a9abfdd5d
commit
10c3c5470c
269 files changed
+1035435
-92885
No files matched your search
@@ -0,0 +1,182 @@
|
||||
#!/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())
|
||||
Reference in new issue
Block a user