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.
tmp/jintai_visitor_query.html

422 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>