Add Jintai visitor query page

main
wangqiyang 3 weeks ago
parent cd538abd9a
commit e9f527c96f

@ -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>
Loading…
Cancel
Save