|
|
|
|
@ -0,0 +1,421 @@
|
|
|
|
|
<!doctype html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<title>金泰访客查询</title>
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
color-scheme: light;
|
|
|
|
|
--bg: #f5f7fb;
|
|
|
|
|
--panel: #ffffff;
|
|
|
|
|
--text: #1f2937;
|
|
|
|
|
--muted: #6b7280;
|
|
|
|
|
--line: #d9e2ef;
|
|
|
|
|
--primary: #0877d8;
|
|
|
|
|
--primary-dark: #075da9;
|
|
|
|
|
--danger: #b91c1c;
|
|
|
|
|
--ok: #047857;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
margin: 0;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(8, 119, 216, 0.12), rgba(8, 119, 216, 0) 280px),
|
|
|
|
|
var(--bg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.shell {
|
|
|
|
|
width: min(560px, calc(100% - 32px));
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
padding: 28px 0 40px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brand {
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
margin: 0 0 6px;
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
line-height: 1.25;
|
|
|
|
|
letter-spacing: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
background: var(--panel);
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 16px 45px rgba(31, 41, 55, 0.08);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.row {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.row:last-child {
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
label {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 44px;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
color: var(--text);
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
outline: none;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
input:focus {
|
|
|
|
|
border-color: var(--primary);
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(8, 119, 216, 0.14);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.captcha-line {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 132px;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.captcha-box {
|
|
|
|
|
height: 44px;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background: #fbfdff;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.captcha-box img {
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
max-height: 42px;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.actions {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
height: 44px;
|
|
|
|
|
border: 0;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: var(--primary);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button:hover {
|
|
|
|
|
background: var(--primary-dark);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button.secondary {
|
|
|
|
|
color: var(--primary);
|
|
|
|
|
background: #eaf4ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button.secondary:hover {
|
|
|
|
|
background: #d8ebff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button:disabled {
|
|
|
|
|
opacity: 0.65;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status {
|
|
|
|
|
min-height: 22px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status.ok {
|
|
|
|
|
color: var(--ok);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status.err {
|
|
|
|
|
color: var(--danger);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pre {
|
|
|
|
|
margin: 0;
|
|
|
|
|
min-height: 120px;
|
|
|
|
|
max-height: 360px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.55;
|
|
|
|
|
color: #111827;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 460px) {
|
|
|
|
|
.captcha-line,
|
|
|
|
|
.actions {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<main class="shell">
|
|
|
|
|
<header class="brand">
|
|
|
|
|
<h1>金泰访客查询</h1>
|
|
|
|
|
<p class="sub">手动验证码单次查询。把页面源码里的 ID_CARD 改成你自己的身份证号。</p>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<section class="panel">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<label for="idCard">身份证号</label>
|
|
|
|
|
<input id="idCard" autocomplete="off">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<label for="visitDate">来访日期</label>
|
|
|
|
|
<input id="visitDate" placeholder="YYYY-MM-DD" autocomplete="off">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<label for="captcha">验证码</label>
|
|
|
|
|
<div class="captcha-line">
|
|
|
|
|
<input id="captcha" placeholder="请输入右侧验证码" autocomplete="off">
|
|
|
|
|
<button class="captcha-box" id="captchaBox" type="button" title="点击刷新验证码">
|
|
|
|
|
<img id="captchaImg" alt="验证码">
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="actions">
|
|
|
|
|
<button class="secondary" id="refreshBtn" type="button">刷新验证码</button>
|
|
|
|
|
<button id="queryBtn" type="button">查询</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="status" id="status">页面已就绪。</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
<pre id="result">等待查询结果...</pre>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
const BASE_URL = "https://www.watvt.com";
|
|
|
|
|
|
|
|
|
|
// 在这里写死你自己的身份证号。
|
|
|
|
|
const ID_CARD = "请改成你的身份证号";
|
|
|
|
|
|
|
|
|
|
const idCardEl = document.getElementById("idCard");
|
|
|
|
|
const visitDateEl = document.getElementById("visitDate");
|
|
|
|
|
const captchaEl = document.getElementById("captcha");
|
|
|
|
|
const captchaImgEl = document.getElementById("captchaImg");
|
|
|
|
|
const statusEl = document.getElementById("status");
|
|
|
|
|
const resultEl = document.getElementById("result");
|
|
|
|
|
const refreshBtn = document.getElementById("refreshBtn");
|
|
|
|
|
const queryBtn = document.getElementById("queryBtn");
|
|
|
|
|
const captchaBox = document.getElementById("captchaBox");
|
|
|
|
|
|
|
|
|
|
function setStatus(text, kind) {
|
|
|
|
|
statusEl.textContent = text;
|
|
|
|
|
statusEl.className = "status" + (kind ? " " + kind : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setBusy(isBusy) {
|
|
|
|
|
refreshBtn.disabled = isBusy;
|
|
|
|
|
queryBtn.disabled = isBusy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function todayText() {
|
|
|
|
|
const d = new Date();
|
|
|
|
|
const y = d.getFullYear();
|
|
|
|
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
const day = String(d.getDate()).padStart(2, "0");
|
|
|
|
|
return `${y}-${m}-${day}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function encodeStr(payload) {
|
|
|
|
|
return btoa(encodeURIComponent(JSON.stringify(payload)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function decodeStr(value) {
|
|
|
|
|
return decodeURIComponent(atob(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function warmup() {
|
|
|
|
|
try {
|
|
|
|
|
await fetch(BASE_URL + "/app/#/user/visitorSearch", {
|
|
|
|
|
method: "GET",
|
|
|
|
|
credentials: "include",
|
|
|
|
|
mode: "cors"
|
|
|
|
|
});
|
|
|
|
|
} catch (_) {
|
|
|
|
|
// The API call below is the useful signal. Some browsers block this warmup from file://.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function postEncoded(path, payload) {
|
|
|
|
|
const body = JSON.stringify({ encodeStr: encodeStr(payload) });
|
|
|
|
|
const resp = await fetch(BASE_URL + path, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
credentials: "include",
|
|
|
|
|
mode: "cors",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
|
|
|
"X-Access-Type": "app"
|
|
|
|
|
},
|
|
|
|
|
body
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const text = await resp.text();
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
throw new Error(`HTTP ${resp.status}: ${text.slice(0, 500)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let data;
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(text);
|
|
|
|
|
} catch (_) {
|
|
|
|
|
throw new Error(text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data && data.responseEncodeStr) {
|
|
|
|
|
return JSON.parse(decodeStr(data.responseEncodeStr));
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function validateIdCard(value) {
|
|
|
|
|
return /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/.test(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadCaptcha() {
|
|
|
|
|
const idCard = idCardEl.value.trim();
|
|
|
|
|
if (!validateIdCard(idCard)) {
|
|
|
|
|
setStatus("先把源码里的 ID_CARD 改成你的身份证号,或在输入框里填入身份证号。", "err");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setBusy(true);
|
|
|
|
|
setStatus("正在获取验证码...");
|
|
|
|
|
resultEl.textContent = "等待输入验证码...";
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await warmup();
|
|
|
|
|
const data = await postEncoded("/boot/system/getCaptcha", { username: idCard });
|
|
|
|
|
|
|
|
|
|
if (!data.success) {
|
|
|
|
|
setStatus(data.message || "验证码接口返回失败。", "err");
|
|
|
|
|
resultEl.textContent = JSON.stringify(data, null, 2);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
captchaImgEl.src = "data:image/png;base64," + data.message;
|
|
|
|
|
captchaEl.value = "";
|
|
|
|
|
captchaEl.focus();
|
|
|
|
|
setStatus("验证码已加载。", "ok");
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setStatus("获取验证码失败。", "err");
|
|
|
|
|
resultEl.textContent =
|
|
|
|
|
String(err.message || err) +
|
|
|
|
|
"\n\n如果这里显示 Failed to fetch / CORS,本地 HTML 被浏览器跨域策略拦截了,需要放到同源环境或改用 Go 脚本。";
|
|
|
|
|
} finally {
|
|
|
|
|
setBusy(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function queryVisitor() {
|
|
|
|
|
const idCard = idCardEl.value.trim();
|
|
|
|
|
const visitDate = visitDateEl.value.trim();
|
|
|
|
|
const captcha = captchaEl.value.trim();
|
|
|
|
|
|
|
|
|
|
if (!validateIdCard(idCard)) {
|
|
|
|
|
setStatus("身份证号格式不对。", "err");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(visitDate)) {
|
|
|
|
|
setStatus("来访日期格式应为 YYYY-MM-DD。", "err");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!captcha) {
|
|
|
|
|
setStatus("请输入验证码。", "err");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setBusy(true);
|
|
|
|
|
setStatus("正在查询...");
|
|
|
|
|
resultEl.textContent = "查询中...";
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const data = await postEncoded("/boot/system/visitor/getVisitorHisByCard", {
|
|
|
|
|
"身份证号": idCard,
|
|
|
|
|
"随行人员": visitDate,
|
|
|
|
|
"备注": captcha
|
|
|
|
|
});
|
|
|
|
|
setStatus(data.success ? "查询完成。" : (data.message || "查询返回失败。"), data.success ? "ok" : "err");
|
|
|
|
|
resultEl.textContent = JSON.stringify(data, null, 2);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setStatus("查询失败。", "err");
|
|
|
|
|
resultEl.textContent = String(err.message || err);
|
|
|
|
|
} finally {
|
|
|
|
|
setBusy(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idCardEl.value = ID_CARD === "请改成你的身份证号" ? "" : ID_CARD;
|
|
|
|
|
visitDateEl.value = todayText();
|
|
|
|
|
|
|
|
|
|
refreshBtn.addEventListener("click", loadCaptcha);
|
|
|
|
|
queryBtn.addEventListener("click", queryVisitor);
|
|
|
|
|
captchaBox.addEventListener("click", loadCaptcha);
|
|
|
|
|
|
|
|
|
|
window.addEventListener("DOMContentLoaded", () => {
|
|
|
|
|
if (idCardEl.value) {
|
|
|
|
|
loadCaptcha();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|