You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
5.9 KiB

5 hours ago
# -*- coding: utf-8 -*-
"""合并 A7 一期(12 tab) + 二期(3 tab) demo 为统一验收 demo(15 tab)。
用法: python _merge_demo.py
输出: demo/index.html.scratch/a7-acceptance/demo/
"""
import io, re, pathlib, sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
ROOT = pathlib.Path(__file__).resolve().parent
PAR = pathlib.Path(r'E:\code\crm-backend-matt\.scratch\a7-parity\demo\index.html')
HVY = pathlib.Path(r'E:\code\crm-backend-matt\.scratch\a7-heavy\demo\index.html')
OUT_DIR = ROOT / 'demo'
OUT = OUT_DIR / 'index.html'
par = PAR.read_text(encoding='utf-8')
hvy = HVY.read_text(encoding='utf-8')
assert par.count('<script>') == 1 and hvy.count('<script>') == 1
# ---------- 1. 标题与 h1 ----------
par = par.replace('<title>A7 后台管理 × 数据字典 E2E Demo(a7-parity)</title>',
'<title>A7 后台管理 E2E 验收 Demo(a7-acceptance · 15 页全分区)</title>')
par = par.replace('<h1>A7 后台管理 × 数据字典 E2E Demo</h1>',
'<h1>A7 后台管理 E2E 验收 Demo</h1>')
# ---------- 2. nav 按钮(按原型分区顺序插入) ----------
# A7-1 用户管理 后插「同步任务」
anchor = '<button data-tab="user" class="on" onclick="switchTab(\'user\')">用户管理</button>'
assert par.count(anchor) == 1
par = par.replace(anchor, anchor + '\n <button data-tab="sync" onclick="switchTab(\'sync\')">同步任务</button>')
# A7-3 线索池 后插「公海绑定数」+「全局日志」(A7-4 日志)
anchor2 = '<button data-tab="pool" onclick="switchTab(\'pool\')">线索池</button>'
assert par.count(anchor2) == 1
par = par.replace(anchor2, anchor2 +
'\n <button data-tab="poolrule" onclick="switchTab(\'poolrule\')">公海绑定数</button>' +
'\n <button data-tab="glog" onclick="switchTab(\'glog\')">全局日志</button>')
# ---------- 3. 二期三个 section 插入 </main> 前 ----------
hvy_sec_start = hvy.index('<!-- ========== Tab 同步任务')
hvy_sec_end = hvy.index('</main>')
hvy_sections = hvy[hvy_sec_start:hvy_sec_end].rstrip() + '\n'
assert 'id="tab-sync"' in hvy_sections and 'id="tab-glog"' in hvy_sections and 'id="tab-poolrule"' in hvy_sections
par = par.replace('</main>', hvy_sections + '</main>', 1)
# ---------- 4. JS 合并 ----------
# 4a. 一期 loginByUid 登录后补 syncPage()(同步任务页登录即加载)
old_login_tail = 'S_G.page = 1; loadGroups(); loadGroupOptions(); loadUsers();'
assert par.count(old_login_tail) == 1
par = par.replace(old_login_tail, old_login_tail + ' syncPage();')
# 4b. 一期 switchTab 加 sync 钩子
old_sw = " if (t === 'ditem') loadGroupOptions(); // 分组可能刚在分组页新建/启停,进字典项页刷新下拉\n" \
" if (t === 'dtree' && !$('tGroup').options.length) loadGroupOptions().then(loadTree);\n}"
assert par.count(old_sw) == 1
new_sw = old_sw.replace('}', " if (t === 'sync' && ME()) syncPage(); // 二期:同步任务页登录后进页即加载\n}", 1)
par = par.replace(old_sw, new_sw)
# 4c. 一期初始化补 syncPage()
old_init = "if (ME()) { loadUsers(); loadGroups().then(loadGroupOptions); }"
assert par.count(old_init) == 1
par = par.replace(old_init, "if (ME()) { loadUsers(); loadGroups().then(loadGroupOptions); syncPage(); }")
# 4d. 二期 JS 移植窗口:fetchBytes 注释起(共享工具已由一期底座提供),到「初始化」注释止
g_start = hvy.index('/* 二进制下载助手(对称一期 poolExport):带 token fetch → bytes */')
g_end = hvy.index('/* ================= 初始化 ================= */')
graft = hvy[g_start:g_end]
# 排除与一期底座冲突/需让位的函数(一期版本必须胜出)
def cut_fn(src, name):
"""删除 graft 中名为 name 的函数声明块(含前置注释行可选项不处理)。"""
pat = re.compile(r'(async )?function ' + name + r'\(.*?\n\}', re.S)
m = pat.search(src)
assert m, 'graft 中未找到 ' + name
return src[:m.start()] + src[m.end():]
for name in ('loginByUid', 'switchTab', 'paintWho'):
graft = cut_fn(graft, name)
# 与一期底座完全同源的输出盒函数:精确文本删除(单行函数,勿用正则 cut_fn——会连吞后续多行函数)
DUP_TEXTS = [
"function outBox(box, html) { document.getElementById(box).innerHTML = html; }\n",
"function okBox(box, msg) { outBox(box, '<div class=\"okbox\">' + msg + '</div>'); }\n",
"function failBox(box, e) { outBox(box, errHtml(e)); }\n",
"function logLine(box, line) {\n const b = document.getElementById(box);\n"
" b.textContent += '\\n[' + new Date().toLocaleTimeString() + '] ' + line;\n"
" b.scrollTop = b.scrollHeight;\n}\n",
]
for t in DUP_TEXTS:
assert graft.count(t) == 1, 'dup 文本未找到: ' + t[:40]
graft = graft.replace(t, '')
# STATUS_TXT 与一期字典映射冲突 → 二期同步状态映射改名
assert 'const STATUS_TXT' in graft
graft = graft.replace('STATUS_TXT', 'SY_STATUS_TXT')
# 插入位置:一期「初始化」注释之前
ins = '/* ================= 初始化 ================= */'
assert par.count(ins) == 1
par = par.replace(ins, '/* ================= 二期移植(a7-heavy 票 01-03:同步任务 / 全局日志 / 公海绑定数) ================= */\n'
+ graft.rstrip() + '\n\n' + ins, 1)
# ---------- 4e. 双击默认基址 = 测试服务(带 ?api= 或 localStorage 记忆仍可覆盖) ----------
old_fallback = "return location.protocol.startsWith('http') ? '' : 'http://localhost:8080';"
assert par.count(old_fallback) == 1
par = par.replace(old_fallback, "return location.protocol.startsWith('http') ? '' : 'https://ai.itc.vip/crm-api';")
# ---------- 5. 输出 ----------
OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT.write_text(par, encoding='utf-8')
print('OK ->', OUT)
print('total lines:', len(par.splitlines()))
print('tabs:', len(re.findall(r'<button data-tab=', par)))
print('sections:', len(re.findall(r'<section id="tab-', par)))