938 lines
40 KiB
HTML
938 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>智能调度终端 - 打印演示</title>
|
||
<style>
|
||
body { font-family: -apple-system, sans-serif; background: #f4f7f9; margin: 0; padding: 20px; color: #333; }
|
||
.header { text-align: center; padding: 20px 0; background: #004A8F; color: white; border-radius: 8px 8px 0 0; margin: -20px -20px 20px -20px; }
|
||
.card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; }
|
||
.info-box { background: #eef2f6; padding: 15px; border-radius: 8px; margin-bottom: 20px; font-size: 14px; }
|
||
.btn {
|
||
display: block; width: 100%; padding: 15px; margin: 12px 0;
|
||
background: #3B82F6; color: white; border: none; border-radius: 8px;
|
||
font-size: 16px; font-weight: bold; cursor: pointer; transition: background 0.3s;
|
||
}
|
||
.btn:active { background: #2563EB; }
|
||
.btn-green { background: #10B981; }
|
||
.btn-orange { background: #F59E0B; }
|
||
.btn-red { background: #ef4444; }
|
||
.status-tag { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-bottom: 10px; }
|
||
.status-online { background: #D1FAE5; color: #065F46; }
|
||
.status-offline { background: #FEE2E2; color: #991B1B; }
|
||
.bill-preview { border: 1px dashed #aaa; padding: 15px; background: #fff; margin-top: 20px; }
|
||
.footer { text-align: center; margin-top: 30px; font-size: 12px; color: #999; }
|
||
.exit-notice { color: #ef4444; font-weight: bold; font-size: 12px; margin-top: 5px; }
|
||
|
||
/* WebSocket Styles */
|
||
.ws-card { margin-top: 20px; border-top: 2px solid #eee; padding-top: 20px; }
|
||
.ws-status { font-size: 12px; padding: 4px 8px; border-radius: 4px; margin-left: 10px; }
|
||
.ws-connected { background: #dcfce7; color: #166534; }
|
||
.ws-disconnected { background: #fee2e2; color: #991b1b; }
|
||
.ws-input-group { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
|
||
.ws-input {
|
||
padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px;
|
||
font-size: 12px; font-family: monospace; width: 100%; box-sizing: border-box;
|
||
background: #f8fafc; color: #334155;
|
||
}
|
||
.log-container {
|
||
background: #1e293b; color: #f8fafc; padding: 10px;
|
||
border-radius: 6px; font-family: monospace; font-size: 11px;
|
||
max-height: 200px; overflow-y: auto; margin-top: 10px;
|
||
}
|
||
.log-item { margin-bottom: 4px; border-bottom: 1px solid #334155; padding-bottom: 2px; }
|
||
.log-time { color: #94a3b8; margin-right: 5px; }
|
||
.log-msg { word-break: break-all; }
|
||
|
||
/* PTT Module Styles */
|
||
.ptt-card { border-top: 2px solid #10B981 !important; margin-top: 20px; }
|
||
.ptt-label { font-size: 11px; color: #64748b; margin-top: 5px; display: block; }
|
||
.ptt-btn-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 10px; }
|
||
.ptt-btn { margin: 0 !important; padding: 8px !important; font-size: 12px !important; }
|
||
|
||
/* Custom Modal Styles */
|
||
.modal-overlay {
|
||
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||
background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;
|
||
}
|
||
.modal-content {
|
||
background: white; padding: 20px; border-radius: 12px; width: 85%; max-width: 400px;
|
||
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
||
}
|
||
.modal-header { font-weight: bold; font-size: 18px; margin-bottom: 15px; color: #004A8F; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
||
.modal-body { font-size: 14px; line-height: 1.8; color: #444; word-break: break-all; }
|
||
.modal-footer { margin-top: 20px; text-align: right; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="card">
|
||
<div class="header">
|
||
<h3>打印控制台</h3>
|
||
</div>
|
||
|
||
<div id="inject-status" class="status-tag status-offline">未检测到 App 接口</div>
|
||
|
||
<div class="info-box">
|
||
<strong>设备信息:</strong><br>
|
||
应用版本: <span id="app-version">Unknown</span><br>
|
||
环境检测: <span id="env-type">Browser</span><br>
|
||
打印机: <span id="printer-status" style="font-weight: bold; color: #ef4444;">未连接</span>
|
||
</div>
|
||
|
||
<!-- 打印功能网格 -->
|
||
<div style="margin-bottom: 20px;">
|
||
<div style="font-size: 13px; font-weight: bold; color: #64748b; margin-bottom: 8px;">基础打印测试</div>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||
<button class="btn btn-green" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintText()">文本打印</button>
|
||
<button class="btn btn-green" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintQR()">二维码打印</button>
|
||
<button class="btn btn-green" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintBarcode()">条形码打印</button>
|
||
<button class="btn btn-green" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintImg()">图片打印</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<div style="font-size: 13px; font-weight: bold; color: #64748b; margin-bottom: 8px;">高级业务样式与测试</div>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||
<button class="btn btn-orange" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintColumns()">分栏打印测试</button>
|
||
<button class="btn btn-orange" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintAlarm()">警情通知单</button>
|
||
<button class="btn btn-orange" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintDemo(false)">2寸演示单据</button>
|
||
<button class="btn btn-orange" style="margin:0; padding:10px; font-size:13px;" onclick="callNativePrintDemo(true)">3寸演示单据</button>
|
||
<button class="btn" style="margin:0; padding:10px; font-size:13px; background: #0ea5e9; grid-column: span 2;" onclick="testES6Features()">ES6 引擎特性测试</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<div style="font-size: 13px; font-weight: bold; color: #64748b; margin-bottom: 8px;">硬件控制与维护</div>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||
<button class="btn" style="margin:0; padding:10px; font-size:13px; background: #6366f1;" onclick="callNativeTestPage()">打印自检页</button>
|
||
<button class="btn" style="margin:0; padding:10px; font-size:13px; background: #8b5cf6;" onclick="callNativeCheckPaper()">查询纸张状态</button>
|
||
<button class="btn btn-red" style="margin:0; padding:10px; font-size:13px;" onclick="callNativeCutPaper(false)">执行半切</button>
|
||
<button class="btn btn-red" style="margin:0; padding:10px; font-size:13px;" onclick="callNativeCutPaper(true)">执行全切</button>
|
||
</div>
|
||
</div>
|
||
|
||
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
||
|
||
<button class="btn" style="background: #475569;" onclick="callSystemPrint()">1. 调用系统标准打印 (A4)</button>
|
||
<p style="font-size: 12px; color: #666;">(支持 PDF 导出、网络打印机、A4 适配)</p>
|
||
|
||
<button class="btn btn-green" onclick="callRawPrint()">2. 发送热敏指令打印</button>
|
||
<p style="font-size: 12px; color: #666;">(适用于蓝牙/USB 小票机,传输原始 JSON)</p>
|
||
|
||
<button class="btn btn-orange" onclick="callUpdate()">3. 检查系统更新</button>
|
||
<button class="btn" style="background: #10B981;" onclick="callUploadLogs()">4. 上传系统日志</button>
|
||
<button class="btn" style="background: #64748b;" onclick="callAbout()">5. 关于应用</button>
|
||
<button class="btn btn-red" onclick="callExit()">6. 退出应用</button>
|
||
|
||
<!-- WebSocket 控制区 -->
|
||
<div class="ws-card">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<strong>WebSocket 实时推送</strong>
|
||
<span id="ws-status-tag" class="ws-status ws-disconnected">未连接</span>
|
||
</div>
|
||
|
||
<div style="margin-top: 10px; font-size: 13px;">
|
||
ID: <span id="ws-client-id" style="color: #6366f1;">-</span>
|
||
</div>
|
||
|
||
<!-- WS 地址配置 -->
|
||
<div class="ws-input-group">
|
||
<label style="font-size: 12px; color: #64748b;">WebSocket 连接地址:</label>
|
||
<input type="text" id="ws-url-input" class="ws-input"
|
||
value="wss://template.gyouzhe.com/api/ws/{clientId}?Authorization=qiangfengsuiyuehen">
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap;">
|
||
<button class="btn btn-green" style="margin:0; flex:1; padding: 10px; min-width: 120px;" onclick="toggleWS()">连接/断开 WS</button>
|
||
<button class="btn" style="margin:0; flex:1; padding: 10px; min-width: 120px; background: #6366f1;" onclick="manualAddRecord()">手动新增记录</button>
|
||
<button class="btn btn-orange" style="margin:0; flex:1; padding: 10px; min-width: 120px;" onclick="loadHistory()">从终端同步</button>
|
||
<button class="btn btn-red" style="margin:0; flex:1; padding: 10px; min-width: 120px; background: #991b1b;" onclick="clearHistory()">清空终端存储</button>
|
||
</div>
|
||
|
||
<div id="log-list" class="log-container">
|
||
<div class="log-item">等待连接...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PTT 业务测试模块 -->
|
||
<div class="ws-card ptt-card">
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<strong style="color: #047857;">PTT 业务功能测试</strong>
|
||
<div style="display: flex; gap: 5px;">
|
||
<span id="ptt-talk-status" class="ws-status" style="background: #e2e8f0; color: #64748b; display: none;">通话中</span>
|
||
<span id="ptt-status-tag" class="ws-status ws-disconnected">PTT 未登录</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 8px; font-size: 13px;">
|
||
当前群组: <span id="ptt-current-group" style="color: #6366f1; font-weight: bold;">-</span>
|
||
<span id="ptt-current-group-id" style="color: #94a3b8; font-size: 11px; margin-left: 5px;"></span>
|
||
</div>
|
||
|
||
<div class="ws-input-group" style="margin-top: 10px;">
|
||
<div style="display: flex; gap: 8px;">
|
||
<div style="flex: 1;">
|
||
<span class="ptt-label">群组 ID (十六进制):</span>
|
||
<input type="text" id="ptt-gid" class="ws-input" value="00000001">
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<div style="flex: 4;">
|
||
<span class="ptt-label">AT 指令 (十六进制):</span>
|
||
<input type="text" id="ptt-at-cmd" class="ws-input" placeholder="e.g. 0D0000">
|
||
</div>
|
||
<div style="flex: 1; display: flex; align-items: flex-end;">
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #475569; height: 35px;" onclick="pttSendAtCmd()">发送</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PTT 核心对讲按钮 -->
|
||
<div style="margin-top: 15px;">
|
||
<button id="ptt-speak-btn" class="btn btn-red"
|
||
style="height: 80px; font-size: 20px; background: #94a3b8; cursor: not-allowed;"
|
||
disabled>长按对讲 (PTT)</button>
|
||
</div>
|
||
|
||
<div class="ptt-btn-grid">
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #6366f1;" onclick="pttGetGroups()" disabled>获取群组</button>
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #8b5cf6;" onclick="pttGetMembers()" disabled>获取成员</button>
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #3b82f6;" onclick="pttJumpGroup()" disabled>切换群组</button>
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #0ea5e9;" onclick="pttGetCurrentGroup()">当前群组</button>
|
||
<button class="btn ptt-btn ptt-action-btn btn-orange" onclick="pttForceRelease()" disabled>强制释放</button>
|
||
<button class="btn ptt-btn ptt-action-btn" style="background: #000; color: #fca5a5;" onclick="pttSimulateConflict()" disabled>模拟冲突</button>
|
||
</div>
|
||
|
||
<div id="ptt-result" class="log-container" style="background: #0f172a; margin-top: 10px; height: 120px;">
|
||
<div class="log-item">等待 PTT 登录成功后激活功能...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bill-preview" id="printArea">
|
||
<div style="text-align: center; font-weight: bold; font-size: 18px;">测试签收单</div>
|
||
<hr>
|
||
<p>单号: SN20260315888</p>
|
||
<div id="dynamic-items">
|
||
<p>物料: 工业级调度终端 x 5</p>
|
||
</div>
|
||
<p>时间: <span id="current-time"></span></p>
|
||
<div style="height: 40px; border: 1px solid #eee; text-align: center; line-height: 40px; color: #ccc;">
|
||
[ 此处为二维码/条码预览 ]
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
SDK Connectivity v1.0
|
||
</div>
|
||
|
||
<!-- 自定义“关于”弹窗 -->
|
||
<div id="aboutModal" class="modal-overlay" onclick="closeAbout()">
|
||
<div class="modal-content" onclick="event.stopPropagation()">
|
||
<div class="modal-header">关于应用信息</div>
|
||
<div id="aboutBody" class="modal-body">
|
||
正在获取信息...
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn" style="padding: 10px 20px; background: #64748b; margin: 0;" onclick="closeAbout()">确认</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 兼容性优化:全部使用 ES5 语法
|
||
|
||
// 初始化界面
|
||
document.getElementById('current-time').innerText = new Date().toLocaleString();
|
||
|
||
function checkApp() {
|
||
var tag = document.getElementById('inject-status');
|
||
var versionSpan = document.getElementById('app-version');
|
||
var envSpan = document.getElementById('env-type');
|
||
|
||
if (window.android) {
|
||
tag.innerText = "已连接至 App 原生接口";
|
||
tag.className = "status-tag status-online";
|
||
envSpan.innerText = "Android App Container";
|
||
|
||
if (window.android.getVersionName) {
|
||
versionSpan.innerText = window.android.getVersionName();
|
||
}
|
||
|
||
updatePrinterStatus();
|
||
// 每 3 秒同步一次打印机状态
|
||
setInterval(updatePrinterStatus, 3000);
|
||
// 确认接口可用后再加载历史
|
||
loadHistory();
|
||
// 通知 Android 页面加载完成
|
||
if (window.android.onPageFinished) {
|
||
window.android.onPageFinished();
|
||
}
|
||
} else {
|
||
// bridge 尚未注入,200ms 后重试
|
||
setTimeout(checkApp, 200);
|
||
}
|
||
}
|
||
|
||
function updatePrinterStatus() {
|
||
if (window.android && window.android.getPrinterStatus) {
|
||
var status = JSON.parse(window.android.getPrinterStatus());
|
||
var span = document.getElementById('printer-status');
|
||
var statusTxt = status.connected ? "已连接 (" + status.port + ")" : "未连接 (" + status.port + ")";
|
||
var paperTxt = status.hasPaper ? " [有纸]" : " [⚠️缺纸]";
|
||
|
||
span.innerText = statusTxt + paperTxt;
|
||
span.style.color = (status.connected && status.hasPaper) ? "#10B981" : "#ef4444";
|
||
}
|
||
}
|
||
|
||
function callNativePrintText() {
|
||
if (window.android && window.android.printText) {
|
||
window.android.printText("消防值守台 终端打印测试\n", 2, 1, 20);
|
||
window.android.printText("当前时间: " + new Date().toLocaleString() + "\n", 1, 1, 20);
|
||
}
|
||
}
|
||
|
||
function callNativePrintQR() {
|
||
if (window.android && window.android.printQR) {
|
||
window.android.printQR("https://www.gyouzhe.com", 6, 2);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativePrintBarcode() {
|
||
if (window.android && window.android.printBarcode) {
|
||
window.android.printBarcode("88888888", 3, 100, 5, 2, 2);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativePrintImg() {
|
||
if (window.android && window.android.printImage) {
|
||
window.android.printImage("1.jpg", 384, 1, 2);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativePrintColumns() {
|
||
if (window.android && window.android.printColumns) {
|
||
window.android.printColumns("项目", 0, "单价", 180, "数量", 250, "金额", 320);
|
||
window.android.printColumnsWithHeight("测试商品", 0, "10.0", 180, "1", 250, "10.0", 320, 70);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativePrintAlarm() {
|
||
if (window.android && window.android.printText) {
|
||
// 1. 标题
|
||
window.android.printText("警情通知单", 2, 2, 40);
|
||
window.android.cutPaper(false);
|
||
// 2. 二维码 (居中)
|
||
window.android.printQR("ALARM_20260304172432", 7, 2);
|
||
window.android.printText("\n", 1, 1, 20);
|
||
window.android.cutPaper(false);
|
||
// 3. 详情列表
|
||
window.android.printText("报警时间: 2026-03-04 17:24:32", 1, 1, 20);
|
||
window.android.printText("报警人: 张先生", 1, 1, 20);
|
||
window.android.printText("报警人电话: 18722727636", 1, 1, 20);
|
||
window.android.printText("灾害地址: 贵州省安顺市西秀区(赵家山村)宋旗镇赵家山村", 1, 1, 20);
|
||
window.android.printText("警情分类: 抢险救援", 1, 1, 20);
|
||
window.android.printText("报警描述: 车祸", 1, 1, 20);
|
||
window.android.printText("主管救援站联系电话: 0851-33410119", 1, 1, 20);
|
||
window.android.printText("出动车辆: ", 1, 1, 20);
|
||
|
||
// 4. 分割线
|
||
window.android.printText("--------------------------------", 1, 1, 10);
|
||
|
||
// 5. 注意事项
|
||
window.android.printText("注意事项:", 1, 1, 20);
|
||
window.android.printText("1、请专职消防队人员穿戴好防护装备,携带灭火、救援器材赶往现场科学处置。", 1, 1, 20);
|
||
window.android.printText("2、到达现场后及时上报情况,注意安全,做好个人防护和现场警戒。", 1, 1, 20);
|
||
window.android.printText("3、根据现场情况迅速组织人员疏散,并在确保安全的前提下利用灭火救援装备开展救援。", 1, 1, 20);
|
||
window.android.printText("4、待责任消防救援站力量到达现场后,主动与其汇报现场已知情况,并协助做好灭火救援工作。", 1, 1, 20);
|
||
window.android.printText("5、严格落实作战行动安全规定,防止触电、溺水、烧伤、坠落、中毒、砸伤、放射线等伤害。", 1, 1, 20);
|
||
|
||
// 6. 结尾
|
||
window.android.printText("--------------------------------", 1, 1, 10);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
|
||
// 7. 切纸
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativePrintDemo(is3Inch) {
|
||
if (window.android && window.android.printText) {
|
||
var width = is3Inch ? 400 : 300;
|
||
var right = is3Inch ? 120 : 100;
|
||
window.android.printText("\nStandAPP 业务测试单\n", 2, 1, 30);
|
||
window.android.printText("--------------------------------\n", 1, 1, 10);
|
||
window.android.printColumns("名称", 0, "单价", 180, "数量", 250, "金额", width);
|
||
window.android.printColumnsWithHeight("演示商品A", 0, "9.9", 180, "1", 250, "9.90", width, right);
|
||
window.android.printText("--------------------------------\n", 1, 1, 10);
|
||
window.android.printQR("http://www.standapp.com", 6, 2);
|
||
window.android.printText("\n\n\n", 1, 1, 10);
|
||
window.android.cutPaper(false);
|
||
}
|
||
}
|
||
|
||
function callNativeTestPage() {
|
||
if (window.android && window.android.printTestPage) {
|
||
window.android.printTestPage();
|
||
}
|
||
}
|
||
|
||
function callNativeCheckPaper() {
|
||
if (window.android && window.android.checkPaper) {
|
||
window.android.checkPaper();
|
||
}
|
||
}
|
||
|
||
function callNativeCutPaper(allCut) {
|
||
if (window.android && window.android.cutPaper) {
|
||
window.android.cutPaper(allCut);
|
||
}
|
||
}
|
||
|
||
// 调用系统打印
|
||
function callSystemPrint() {
|
||
if (window.android && window.android.printPage) {
|
||
window.android.printPage();
|
||
} else {
|
||
alert("当前环境不支持系统打印");
|
||
}
|
||
}
|
||
|
||
// 调用原始数据打印
|
||
function callRawPrint() {
|
||
if (window.android && window.android.printRawData) {
|
||
var data = {
|
||
type: "receipt",
|
||
title: "测试签收单",
|
||
sn: "SN20260315888",
|
||
items: ["调度终端 x 5"],
|
||
timestamp: Date.now()
|
||
};
|
||
window.android.printRawData(JSON.stringify(data));
|
||
} else {
|
||
alert("当前环境不支持原始指令打印");
|
||
}
|
||
}
|
||
|
||
// 检查更新
|
||
function callUpdate() {
|
||
if (window.android && window.android.startUpdate) {
|
||
window.android.startUpdate("https://example.com/latest.apk");
|
||
} else {
|
||
alert("更新接口未就绪");
|
||
}
|
||
}
|
||
|
||
// 上传日志
|
||
var isUploading = false;
|
||
function callUploadLogs() {
|
||
if (isUploading) {
|
||
alert("正在上传中,请耐心等待(30秒内请勿重复点击)...");
|
||
return;
|
||
}
|
||
if (window.android && window.android.uploadLogs) {
|
||
isUploading = true;
|
||
alert("开始上传日志,请耐心等待...");
|
||
window.android.uploadLogs();
|
||
setTimeout(function() {
|
||
isUploading = false;
|
||
}, 30000);
|
||
} else {
|
||
alert("上传日志接口不可用");
|
||
}
|
||
}
|
||
|
||
function callExit() {
|
||
if (window.android && window.android.exitApp) {
|
||
window.android.exitApp();
|
||
} else {
|
||
alert("App 退出接口不可用");
|
||
}
|
||
}
|
||
|
||
function callAbout() {
|
||
if (window.android && window.android.getAboutInfo) {
|
||
var infoStr = window.android.getAboutInfo();
|
||
try {
|
||
var info = JSON.parse(infoStr);
|
||
var html = '<strong>应用名称:</strong> ' + info.appName + '<br>' +
|
||
'<strong>版本号:</strong> <span style="color: #3b82f6;">' + info.versionName + '</span><br>' +
|
||
'<strong>设备型号:</strong> ' + info.model + '<br>' +
|
||
'<strong>系统版本:</strong> Android ' + info.osVersion + '<br>' +
|
||
'<strong>CPU 架构:</strong> ' + info.cpuAbi + '<br>' +
|
||
'<strong>运行模式:</strong> ' + (info.is64Bit ? "64位" : "32位兼容模式");
|
||
document.getElementById('aboutBody').innerHTML = html;
|
||
document.getElementById('aboutModal').style.display = 'flex';
|
||
} catch (e) {
|
||
document.getElementById('aboutBody').innerText = "获取信息解析失败: " + infoStr;
|
||
document.getElementById('aboutModal').style.display = 'flex';
|
||
}
|
||
} else {
|
||
alert("关于接口未就绪");
|
||
}
|
||
}
|
||
|
||
function closeAbout() {
|
||
document.getElementById('aboutModal').style.display = 'none';
|
||
}
|
||
|
||
// 打印机查纸回调
|
||
window.onPaperStateChanged = function(hasPaper) {
|
||
if (hasPaper) {
|
||
alert("打印机状态正常:当前有纸");
|
||
} else {
|
||
alert("⚠️ 打印机缺纸!请装入纸卷!");
|
||
}
|
||
updatePrinterStatus();
|
||
};
|
||
|
||
// PTT 各种事件回调
|
||
window.onPttEvent = function(eventType, payload) {
|
||
console.log("收到 PTT 事件: ", eventType, payload);
|
||
var msg = "";
|
||
switch(eventType) {
|
||
case 'GroupList':
|
||
msg = "获取群组成功,共 " + payload.length + " 个群组。第一组: " + (payload[0] ? payload[0].groupName : "无");
|
||
break;
|
||
case 'MemberList':
|
||
msg = "获取群成员成功,共 " + payload.length + " 个成员。";
|
||
break;
|
||
case 'SpeakerUpdate':
|
||
msg = "当前讲话人: " + payload.talkName + " (组:" + payload.groupName + ")";
|
||
break;
|
||
case 'PlayStatus':
|
||
msg = (payload.playStatus === 1 ? "开始播放:" : "结束播放:") + payload.talkName;
|
||
break;
|
||
case 'TempCall':
|
||
msg = "临时呼叫消息: " + payload.msg;
|
||
break;
|
||
case 'UpdateGroup':
|
||
msg = "进入群组: " + payload.groupName;
|
||
break;
|
||
case 'Alarm':
|
||
msg = "⚠️收到报警: " + payload.memberName + " (纬度:" + payload.latitude + ")";
|
||
break;
|
||
case 'StopAlarm':
|
||
msg = "群成员停止告警";
|
||
break;
|
||
case 'SpeakResult':
|
||
msg = payload.success ? "话权获取成功" : "话权获取失败";
|
||
break;
|
||
case 'SpeakEnd':
|
||
msg = payload.success ? "话权释放成功" : "话权释放失败";
|
||
break;
|
||
default:
|
||
msg = "收到事件: " + eventType;
|
||
}
|
||
showPttResult("[回调] " + msg);
|
||
};
|
||
|
||
// ES6 特性测试函数(通过 eval 避免阻塞旧版 JS 引擎整体解析)
|
||
function testES6Features() {
|
||
try {
|
||
var code = [
|
||
"const arr = [1, 2, 3];",
|
||
"const [a, ...rest] = arr;",
|
||
"let sum = rest.reduce((acc, val) => acc + val, 0);",
|
||
"const myMap = new Map();",
|
||
"myMap.set('key', `Sum is ${sum}`);",
|
||
"new Promise((resolve) => {",
|
||
" setTimeout(() => resolve(myMap.get('key')), 100);",
|
||
"}).then(res => {",
|
||
" alert(`ES6 引擎测试成功!\\n解构: a=${a}\\nPromise/Map 返回: ${res}`);",
|
||
"});"
|
||
].join("");
|
||
eval(code);
|
||
} catch(e) {
|
||
alert("ES6 测试失败 (当前环境可能不支持): " + e.message);
|
||
}
|
||
}
|
||
function addExitText() {
|
||
var tag = document.getElementById('inject-status');
|
||
if (tag) {
|
||
var notice = document.createElement('div');
|
||
notice.className = 'exit-notice';
|
||
notice.innerText = "⚠️ 检测到退出尝试 (" + new Date().toLocaleTimeString() + ")";
|
||
tag.parentNode.insertBefore(notice, tag.nextSibling);
|
||
}
|
||
}
|
||
|
||
// 延迟检测
|
||
setTimeout(checkApp, 300);
|
||
// 同步 PTT 登录状态
|
||
setTimeout(function() {
|
||
if (window.android && window.android.pttGetLoginStatus) {
|
||
var status = window.android.pttGetLoginStatus();
|
||
var loginId = window.android.pttGetLoginId ? window.android.pttGetLoginId() : "";
|
||
updatePttLoginStatus(status, loginId);
|
||
}
|
||
if (window.android && window.android.pttGetCurrentGroup) {
|
||
try {
|
||
var group = JSON.parse(window.android.pttGetCurrentGroup());
|
||
if (group.groupId) {
|
||
updatePttCurrentGroup(group.groupId, group.groupName);
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
}, 500);
|
||
|
||
// 告知 Android 页面加载完成(已移至 checkApp 中,等 bridge 注入后再调用)
|
||
window.onload = function() {};
|
||
|
||
// === WebSocket 逻辑 ===
|
||
var ws = null;
|
||
var clientId = "WEB_" + Math.random().toString(36).substr(2, 9);
|
||
var heartbeatTimer = null;
|
||
var reconnectTimer = null;
|
||
var isManualClose = false;
|
||
var logList = document.getElementById('log-list');
|
||
|
||
document.getElementById('ws-client-id').innerText = clientId;
|
||
|
||
function addLog(msg, save) {
|
||
if (save === undefined) save = true;
|
||
var time = new Date().toLocaleTimeString();
|
||
var item = document.createElement('div');
|
||
item.className = 'log-item';
|
||
item.innerHTML = '<span class="log-time">[' + time + ']</span><span class="log-msg">' + msg + '</span>';
|
||
logList.insertBefore(item, logList.firstChild);
|
||
|
||
if (logList.childNodes.length > 200) {
|
||
logList.removeChild(logList.lastChild);
|
||
}
|
||
|
||
if (save && window.android && window.android.saveMessage) {
|
||
window.android.saveMessage(time + ": " + msg);
|
||
}
|
||
}
|
||
|
||
function updateWSStatus(connected) {
|
||
var tag = document.getElementById('ws-status-tag');
|
||
if (connected) {
|
||
tag.innerText = "已连接";
|
||
tag.className = "ws-status ws-connected";
|
||
} else {
|
||
tag.innerText = "已断开";
|
||
tag.className = "ws-status ws-disconnected";
|
||
}
|
||
}
|
||
|
||
function connectWS() {
|
||
if (ws) {
|
||
ws.onclose = null;
|
||
ws.close();
|
||
}
|
||
|
||
var rawUrl = document.getElementById('ws-url-input').value;
|
||
var finalUrl = rawUrl.replace("{clientId}", clientId);
|
||
|
||
addLog("正在尝试连接: " + finalUrl, false);
|
||
isManualClose = false;
|
||
|
||
try {
|
||
ws = new WebSocket(finalUrl);
|
||
|
||
ws.onopen = function() {
|
||
updateWSStatus(true);
|
||
addLog("连接成功", false);
|
||
startHeartbeat();
|
||
clearTimeout(reconnectTimer);
|
||
};
|
||
|
||
ws.onmessage = function(evt) {
|
||
addLog("收到消息: " + evt.data);
|
||
if (evt.data.indexOf("print") !== -1) {
|
||
callRawPrint();
|
||
}
|
||
};
|
||
|
||
ws.onclose = function() {
|
||
updateWSStatus(false);
|
||
addLog("连接关闭", false);
|
||
stopHeartbeat();
|
||
|
||
if (!isManualClose) {
|
||
addLog("异常断开,5秒后自动重连...", false);
|
||
reconnectTimer = setTimeout(connectWS, 5000);
|
||
}
|
||
};
|
||
|
||
ws.onerror = function(err) {
|
||
addLog("连接错误或被拒绝", false);
|
||
};
|
||
} catch (e) {
|
||
addLog("连接失败: " + e.message, false);
|
||
}
|
||
}
|
||
|
||
function toggleWS() {
|
||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||
isManualClose = true;
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
ws.close();
|
||
} else {
|
||
connectWS();
|
||
}
|
||
}
|
||
|
||
function startHeartbeat() {
|
||
stopHeartbeat();
|
||
heartbeatTimer = setInterval(function() {
|
||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||
ws.send("ping");
|
||
}
|
||
}, 30000);
|
||
}
|
||
|
||
function stopHeartbeat() {
|
||
clearInterval(heartbeatTimer);
|
||
}
|
||
|
||
function clearHistory() {
|
||
if (confirm("确定要清空安卓端存储的所有消息吗?")) {
|
||
if (window.android && window.android.clearMessages) {
|
||
window.android.clearMessages();
|
||
}
|
||
logList.innerHTML = '<div class="log-item">记录已清空</div>';
|
||
}
|
||
}
|
||
|
||
function manualAddRecord() {
|
||
var text = prompt("请输入要保存的消息内容:", "手动测试记录");
|
||
if (text) {
|
||
addLog("用户新增: " + text);
|
||
}
|
||
}
|
||
|
||
function loadHistory() {
|
||
if (window.android && window.android.getMessages) {
|
||
var history = window.android.getMessages();
|
||
logList.innerHTML = "";
|
||
if (history) {
|
||
var msgs = history.split("|---|");
|
||
addLog("系统: 已从安卓端同步 " + msgs.length + " 条记录", false);
|
||
for (var i = 0; i < msgs.length; i++) {
|
||
var msg = msgs[i];
|
||
if (!msg || i > 500) continue;
|
||
var item = document.createElement('div');
|
||
item.className = 'log-item';
|
||
var separatorIndex = msg.indexOf(": ");
|
||
if (separatorIndex === -1) continue;
|
||
var time = msg.substring(0, separatorIndex);
|
||
var content = msg.substring(separatorIndex + 2);
|
||
item.innerHTML = '<span class="log-time">[' + time + ']</span><span class="log-msg">' + content + '</span>';
|
||
logList.appendChild(item);
|
||
}
|
||
} else {
|
||
addLog("系统: 终端暂无保存的记录", false);
|
||
}
|
||
} else {
|
||
// 不再弹窗,checkApp 会在 bridge 就绪后调用 loadHistory
|
||
}
|
||
}
|
||
|
||
|
||
// === PTT 业务测试逻辑 ===
|
||
var pttBtn = document.getElementById('ptt-speak-btn');
|
||
var pttStatusTag = document.getElementById('ptt-status-tag');
|
||
var pttResult = document.getElementById('ptt-result');
|
||
|
||
function showPttResult(msg) {
|
||
var time = new Date().toLocaleTimeString();
|
||
var item = document.createElement('div');
|
||
item.className = 'log-item';
|
||
item.innerHTML = '<span class="log-time">[' + time + ']</span><span class="log-msg">' + msg + '</span>';
|
||
pttResult.insertBefore(item, pttResult.firstChild);
|
||
}
|
||
|
||
// 更新 PTT 通话状态 (由 Android 原生同步调用)
|
||
function updatePttTalkStatus(isTalking) {
|
||
var statusNode = document.getElementById('ptt-talk-status');
|
||
if (isTalking) {
|
||
statusNode.style.display = 'inline-block';
|
||
statusNode.style.background = '#fee2e2';
|
||
statusNode.style.color = '#ef4444';
|
||
statusNode.innerText = '● 正在通话';
|
||
|
||
// 如果 UI 按钮还没更新,强制更新一次
|
||
pttBtn.style.background = "#b91c1c";
|
||
pttBtn.innerText = "正在通话...";
|
||
} else {
|
||
statusNode.style.display = 'none';
|
||
pttBtn.style.background = "#ef4444";
|
||
pttBtn.innerText = "长按对讲 (PTT)";
|
||
}
|
||
};
|
||
|
||
// 更新当前群组信息 (由 Android 实时推送)
|
||
function updatePttCurrentGroup(groupId, groupName) {
|
||
document.getElementById('ptt-current-group').innerText = groupName || '-';
|
||
document.getElementById('ptt-current-group-id').innerText = groupId ? '(' + groupId + ')' : '';
|
||
showPttResult("进入群组: " + groupName + " (" + groupId + ")");
|
||
};
|
||
|
||
// PTT 登录成功回调 (由 Android 调用)
|
||
function onPttLoginSuccess() {
|
||
updatePttLoginStatusUI("02");
|
||
showPttResult("PTT 业务初始化成功,对讲功能已就绪");
|
||
}
|
||
|
||
// 更新 PTT 登录状态 (由 Android 实时推送)
|
||
function updatePttLoginStatus(status, loginId) {
|
||
updatePttLoginStatusUI(status);
|
||
var statusMap = { "00": "未登录", "01": "登录中...", "02": "已登录" };
|
||
showPttResult("登录状态变更: " + (statusMap[status] || status) + " (ID:" + loginId + ")");
|
||
};
|
||
|
||
function updatePttLoginStatusUI(status) {
|
||
if (status === "02") {
|
||
pttStatusTag.innerText = "PTT 已登录";
|
||
pttStatusTag.className = "ws-status ws-connected";
|
||
pttBtn.disabled = false;
|
||
pttBtn.style.background = "#ef4444";
|
||
pttBtn.style.cursor = "pointer";
|
||
var actionBtns = document.querySelectorAll('.ptt-action-btn');
|
||
for (var i = 0; i < actionBtns.length; i++) {
|
||
actionBtns[i].disabled = false;
|
||
}
|
||
} else if (status === "01") {
|
||
pttStatusTag.innerText = "PTT 登录中...";
|
||
pttStatusTag.className = "ws-status ws-disconnected";
|
||
} else {
|
||
pttStatusTag.innerText = "PTT 未登录";
|
||
pttStatusTag.className = "ws-status ws-disconnected";
|
||
}
|
||
}
|
||
|
||
// 修复 PTT 按键逻辑:支持 TouchEvents 并处理异常中断
|
||
if (pttBtn) {
|
||
var isDown = false;
|
||
var lastTouchTime = 0;
|
||
|
||
var startSpeak = function(e) {
|
||
if (pttBtn.disabled) return;
|
||
e.preventDefault();
|
||
|
||
var now = Date.now();
|
||
if (now - lastTouchTime < 300) return; // 物理防抖:300ms 内禁止重复触发按下
|
||
|
||
if (!isDown) {
|
||
isDown = true;
|
||
lastTouchTime = now;
|
||
pttBtn.style.background = "#b91c1c";
|
||
pttBtn.innerText = "正在通话...";
|
||
if (window.android && window.android.pttSpeakPlay) {
|
||
window.android.pttSpeakPlay();
|
||
}
|
||
showPttResult("申请话权...");
|
||
}
|
||
};
|
||
|
||
var stopSpeak = function(e) {
|
||
if (isDown) {
|
||
// 延迟 50ms 再处理释放,确保即使在极速点击下,Down 指令也先到达原生层
|
||
setTimeout(function() {
|
||
if (!isDown) return;
|
||
isDown = false;
|
||
pttBtn.style.background = "#ef4444";
|
||
pttBtn.innerText = "长按对讲 (PTT)";
|
||
if (window.android && window.android.pttSpeakRelease) {
|
||
window.android.pttSpeakRelease();
|
||
}
|
||
showPttResult("释放话权");
|
||
}, 50);
|
||
}
|
||
};
|
||
|
||
// 绑定事件
|
||
pttBtn.addEventListener('touchstart', startSpeak);
|
||
pttBtn.addEventListener('mousedown', startSpeak);
|
||
|
||
// 关键:除了 end/up,还要处理 cancel 和 leave
|
||
pttBtn.addEventListener('touchend', stopSpeak);
|
||
pttBtn.addEventListener('touchcancel', stopSpeak);
|
||
pttBtn.addEventListener('mouseup', stopSpeak);
|
||
pttBtn.addEventListener('mouseleave', stopSpeak);
|
||
}
|
||
|
||
function pttGetGroups() {
|
||
if (window.android && window.android.pttGetGroupInfo) {
|
||
window.android.pttGetGroupInfo();
|
||
showPttResult("已请求群组列表");
|
||
}
|
||
}
|
||
|
||
function pttGetMembers() {
|
||
var gid = document.getElementById('ptt-gid').value;
|
||
if (window.android && window.android.pttGetGroupMemberInfo) {
|
||
window.android.pttGetGroupMemberInfo(gid);
|
||
showPttResult("已请求群组 [" + gid + "] 成员");
|
||
}
|
||
}
|
||
|
||
function pttJumpGroup() {
|
||
var gid = document.getElementById('ptt-gid').value;
|
||
if (window.android && window.android.pttJumpGroup) {
|
||
window.android.pttJumpGroup(gid);
|
||
showPttResult("尝试切换至群组: " + gid);
|
||
}
|
||
}
|
||
|
||
function pttForceRelease() {
|
||
if (window.android && window.android.pttForceRelease) {
|
||
window.android.pttForceRelease();
|
||
isDown = false;
|
||
pttBtn.style.background = "#ef4444";
|
||
pttBtn.innerText = "长按对讲 (PTT)";
|
||
showPttResult("执行强制释放");
|
||
}
|
||
}
|
||
|
||
function pttSimulateConflict() {
|
||
if (window.android && window.android.simulatePttConflict) {
|
||
window.android.simulatePttConflict();
|
||
showPttResult("已触发模拟账号冲突");
|
||
}
|
||
}
|
||
|
||
function pttGetCurrentGroup() {
|
||
if (window.android && window.android.pttGetCurrentGroup) {
|
||
try {
|
||
var group = JSON.parse(window.android.pttGetCurrentGroup());
|
||
if (group.groupId) {
|
||
updatePttCurrentGroup(group.groupId, group.groupName);
|
||
} else {
|
||
showPttResult("当前无群组信息");
|
||
}
|
||
} catch(e) {
|
||
showPttResult("获取群组信息失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
function pttSendAtCmd() {
|
||
var cmd = document.getElementById('ptt-at-cmd').value.trim();
|
||
if (!cmd) {
|
||
showPttResult("请输入 AT 指令");
|
||
return;
|
||
}
|
||
if (window.android && window.android.pttSendAtCmd) {
|
||
window.android.pttSendAtCmd(cmd);
|
||
showPttResult("已发送 AT 指令: " + cmd);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|