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.
112 lines
5.9 KiB
112 lines
5.9 KiB
# -*- 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)))
|
|
|