182 lines
6.6 KiB
Python
182 lines
6.6 KiB
Python
|
|
"""在容器内运行,渲染 scan 端点的实际 HTML 输出"""
|
|||
|
|
import sys
|
|||
|
|
sys.path.insert(0, '/app')
|
|||
|
|
|
|||
|
|
# 模拟 f-string 中的变量
|
|||
|
|
user_name = "测试用户"
|
|||
|
|
jsapi_signature = "test_sig_abc123"
|
|||
|
|
jsapi_timestamp = 1752105600
|
|||
|
|
jsapi_nonce = "test_nonce_xyz"
|
|||
|
|
jsapi_appid = "ww_test_corp_id"
|
|||
|
|
current_url = "https://itsupport.servyou.com.cn/api/auth_qrcode/scan?code=test&state=test"
|
|||
|
|
|
|||
|
|
html = f"""<!DOCTYPE html>
|
|||
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>登录成功 - IT智能服务台</title>
|
|||
|
|
<style>
|
|||
|
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|||
|
|
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #07C160 0%, #06AD56 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }}
|
|||
|
|
.card {{ background: rgba(255,255,255,0.95); border-radius: 20px; padding: 48px 32px; max-width: 360px; width: 100%; text-align: center; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }}
|
|||
|
|
.check {{ width: 64px; height: 64px; margin: 0 auto 16px; }}
|
|||
|
|
.title {{ color: #1f2937; font-size: 24px; font-weight: 600; margin-bottom: 8px; }}
|
|||
|
|
.subtitle {{ color: #6b7280; font-size: 14px; margin-bottom: 24px; }}
|
|||
|
|
.status {{ display: inline-flex; align-items: center; gap: 6px; background: #dcfce7; color: #166534; padding: 10px 20px; border-radius: 50px; font-size: 14px; font-weight: 500; }}
|
|||
|
|
.footer {{ margin-top: 20px; color: #9ca3af; font-size: 12px; }}
|
|||
|
|
.back-btn {{ display: none; margin-top: 20px; padding: 12px 32px; background: #07C160; color: white; border: none; border-radius: 50px; font-size: 16px; font-weight: 500; cursor: pointer; }}
|
|||
|
|
.debug {{ margin-top: 16px; color: #6b7280; font-size: 11px; line-height: 1.6; word-break: break-all; text-align: left; background: #f3f4f6; padding: 10px 12px; border-radius: 8px; }}
|
|||
|
|
.debug b {{ color: #07C160; }}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="card">
|
|||
|
|
<svg class="check" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|||
|
|
<circle cx="32" cy="32" r="30" fill="#07C160" stroke="#06AD56" stroke-width="4"/>
|
|||
|
|
<path d="M20 32l8 8 16-16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|||
|
|
</svg>
|
|||
|
|
<h1 class="title">登录成功</h1>
|
|||
|
|
<div class="status">已自动确认登录</div>
|
|||
|
|
<p class="subtitle">你好,{user_name}<br>请返回电脑端查看</p>
|
|||
|
|
<button class="back-btn" id="backBtn" onclick="manualClose()">点击返回企微</button>
|
|||
|
|
<div class="footer">页面即将自动关闭 · 税友集团</div>
|
|||
|
|
<div class="debug" id="debugInfo">
|
|||
|
|
<b>签名:</b> {'成功' if jsapi_signature else '未生成'}<br>
|
|||
|
|
<b>URL:</b> {current_url}<br>
|
|||
|
|
<b>状态:</b> <span id="jsStatus">JS加载中...</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<script>
|
|||
|
|
(function() {{
|
|||
|
|
var statusEl = document.getElementById('jsStatus');
|
|||
|
|
var btnEl = document.getElementById('backBtn');
|
|||
|
|
var startTime = Date.now();
|
|||
|
|
var tried = {{}};
|
|||
|
|
|
|||
|
|
function setStatus(msg) {{
|
|||
|
|
if (statusEl) statusEl.textContent = msg + ' (' + (Date.now() - startTime) + 'ms)';
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
function showBtn() {{
|
|||
|
|
if (btnEl) btnEl.style.display = 'inline-block';
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
function tryClose(forceShowBtn) {{
|
|||
|
|
setStatus('尝试关闭');
|
|||
|
|
|
|||
|
|
if (!tried.wxClose && typeof wx !== 'undefined' && wx.closeWindow) {{
|
|||
|
|
tried.wxClose = true;
|
|||
|
|
try {{
|
|||
|
|
setStatus('wx.closeWindow');
|
|||
|
|
wx.closeWindow();
|
|||
|
|
return true;
|
|||
|
|
}} catch(e) {{ setStatus('wx.closeWindow失败:' + (e.message || e)); }}
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (!tried.wxInvoke && typeof wx !== 'undefined' && wx.invoke) {{
|
|||
|
|
tried.wxInvoke = true;
|
|||
|
|
try {{
|
|||
|
|
setStatus('wx.invoke closeWindow');
|
|||
|
|
wx.invoke('closeWindow', {{}}, function(){{}});
|
|||
|
|
return true;
|
|||
|
|
}} catch(e) {{ setStatus('wx.invoke失败:' + (e.message || e)); }}
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (!tried.jsBridge && typeof WeixinJSBridge !== 'undefined' && WeixinJSBridge.call) {{
|
|||
|
|
tried.jsBridge = true;
|
|||
|
|
try {{
|
|||
|
|
setStatus('WeixinJSBridge.closeWindow');
|
|||
|
|
WeixinJSBridge.call('closeWindow');
|
|||
|
|
return true;
|
|||
|
|
}} catch(e) {{ setStatus('JSBridge失败:' + (e.message || e)); }}
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (!tried.windowClose) {{
|
|||
|
|
tried.windowClose = true;
|
|||
|
|
try {{
|
|||
|
|
setStatus('window.close');
|
|||
|
|
window.close();
|
|||
|
|
return true;
|
|||
|
|
}} catch(e) {{}}
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (!tried.historyBack) {{
|
|||
|
|
tried.historyBack = true;
|
|||
|
|
try {{
|
|||
|
|
setStatus('history.back');
|
|||
|
|
history.back();
|
|||
|
|
return true;
|
|||
|
|
}} catch(e) {{}}
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (forceShowBtn) {{
|
|||
|
|
setStatus('无法自动关闭,请手动返回');
|
|||
|
|
showBtn();
|
|||
|
|
}}
|
|||
|
|
return false;
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
function manualClose() {{
|
|||
|
|
tryClose(true);
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
var checkCount = 0;
|
|||
|
|
var maxChecks = 50;
|
|||
|
|
var interval = setInterval(function() {{
|
|||
|
|
checkCount++;
|
|||
|
|
var hasWx = typeof wx !== 'undefined';
|
|||
|
|
var hasBridge = typeof WeixinJSBridge !== 'undefined';
|
|||
|
|
setStatus('检测中 wx=' + hasWx + ' bridge=' + hasBridge + ' count=' + checkCount);
|
|||
|
|
|
|||
|
|
if (hasWx || hasBridge) {{
|
|||
|
|
clearInterval(interval);
|
|||
|
|
setStatus('已检测到关闭API,1秒后尝试关闭');
|
|||
|
|
setTimeout(function() {{
|
|||
|
|
tryClose(true);
|
|||
|
|
}}, 1000);
|
|||
|
|
return;
|
|||
|
|
}}
|
|||
|
|
|
|||
|
|
if (checkCount >= maxChecks) {{
|
|||
|
|
clearInterval(interval);
|
|||
|
|
setStatus('未检测到API,直接尝试关闭');
|
|||
|
|
tryClose(true);
|
|||
|
|
}}
|
|||
|
|
}}, 100);
|
|||
|
|
|
|||
|
|
setTimeout(function() {{
|
|||
|
|
showBtn();
|
|||
|
|
}}, 3000);
|
|||
|
|
}})();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>"""
|
|||
|
|
|
|||
|
|
# 保存渲染后的 HTML
|
|||
|
|
with open('/tmp/test_scan.html', 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(html)
|
|||
|
|
|
|||
|
|
print(f"HTML rendered: {len(html)} chars")
|
|||
|
|
print("Saved to /tmp/test_scan.html")
|
|||
|
|
|
|||
|
|
# 检查 script 部分
|
|||
|
|
import re
|
|||
|
|
script_match = re.search(r'<script>(.+?)</script>', html, re.DOTALL)
|
|||
|
|
if script_match:
|
|||
|
|
js = script_match.group(1)
|
|||
|
|
print(f"\nJS section: {len(js)} chars")
|
|||
|
|
# 检查是否有 {{ 残留(f-string 未正确渲染)
|
|||
|
|
if '{{' in js:
|
|||
|
|
print("ERROR: Found unrendered {{ in JS!")
|
|||
|
|
for i, line in enumerate(js.split('\n')):
|
|||
|
|
if '{{' in line:
|
|||
|
|
print(f" Line {i+1}: {line.strip()[:80]}")
|
|||
|
|
else:
|
|||
|
|
print("OK: No unrendered braces in JS")
|
|||
|
|
|
|||
|
|
# 打印前 20 行 JS
|
|||
|
|
print("\nFirst 20 lines of rendered JS:")
|
|||
|
|
for i, line in enumerate(js.split('\n')[:20]):
|
|||
|
|
print(f" {i+1}: {line}")
|