Files
cmautobuy/docs/prototypes/admin-inner-code.html
T

806 lines
27 KiB
HTML
Raw Normal View History

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>档口入库码 · Admin 交互原型</title>
<style>
:root {
color-scheme: light;
--nav: #2b3440;
--nav-hover: #3a4552;
--primary: #1f6feb;
--border: #e1e4e8;
--border-strong: #ccd1d6;
--page: #f5f6f8;
--muted: #667085;
--danger: #b42318;
--success: #176b36;
--warning: #7a4b00;
}
* { box-sizing: border-box; }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
body {
height: 100vh;
height: 100dvh;
margin: 0;
overflow: hidden;
display: flex;
flex-direction: column;
font-family: "Microsoft YaHei", "PingFang SC", system-ui, sans-serif;
font-size: 14px;
color: #222;
background: var(--page);
}
button,
input,
select { font: inherit; }
button:focus-visible,
input:focus-visible,
select:focus-visible,
a:focus-visible {
outline: 3px solid rgba(31, 111, 235, .28);
outline-offset: 2px;
}
[hidden] { display: none !important; }
.nav {
flex: 0 0 auto;
min-height: 44px;
display: flex;
align-items: center;
gap: 2px;
padding: 0 12px;
background: var(--nav);
white-space: nowrap;
}
.nav-brand {
margin-right: 14px;
color: #fff;
font-weight: 600;
font-size: 14px;
}
.nav a {
padding: 13px 10px;
color: #c8cdd4;
text-decoration: none;
font-size: 13px;
}
.nav a:hover { color: #fff; background: var(--nav-hover); }
.nav a.active { color: #fff; background: var(--primary); }
.nav-spacer { flex: 1 1 auto; min-width: 8px; }
.nav-user { padding: 0 6px; color: #fff; font-size: 13px; }
.nav-button {
min-height: 30px;
padding: 4px 8px;
border: 1px solid #56616d;
border-radius: 3px;
color: #c8cdd4;
background: transparent;
}
.page {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px 16px 8px;
}
.prototype-notice {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 8px;
min-height: 34px;
margin: 0;
padding: 7px 10px;
border: 1px solid #b8d4fa;
border-left: 4px solid var(--primary);
border-radius: 4px;
color: #174ea6;
background: #eef6ff;
}
.prototype-notice strong { white-space: nowrap; }
.toolbar {
flex: 0 0 auto;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 9px 10px;
border: 1px solid var(--border);
border-radius: 4px;
background: #fff;
}
.toolbar-group {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.toolbar-separator {
width: 1px;
height: 28px;
background: var(--border);
}
label { color: #555; white-space: nowrap; }
input[type="date"],
input[type="search"],
select {
height: 30px;
padding: 4px 8px;
border: 1px solid var(--border-strong);
border-radius: 3px;
background: #fff;
}
input[type="search"] { width: 190px; }
select { min-width: 112px; }
button {
min-height: 30px;
padding: 5px 12px;
border: 1px solid var(--border-strong);
border-radius: 3px;
color: #222;
background: #fff;
cursor: pointer;
white-space: nowrap;
}
button:hover { background: #f0f2f4; }
button:disabled { opacity: .48; cursor: not-allowed; }
button.primary { color: #fff; border-color: var(--primary); background: var(--primary); }
button.primary:hover { background: #195fc9; }
button.danger { color: var(--danger); border-color: #efb2ac; }
button.link-button { min-height: auto; padding: 0; border: 0; color: var(--primary); background: transparent; }
.file-field {
max-width: 228px;
overflow: hidden;
color: #444;
text-overflow: ellipsis;
white-space: nowrap;
}
.toolbar-actions { margin-left: auto; }
.table-wrap {
flex: 1 1 auto;
min-height: 180px;
overflow: auto;
scrollbar-gutter: stable;
border: 1px solid var(--border);
border-radius: 4px;
background: #fff;
}
table { width: 100%; min-width: 1490px; border-collapse: collapse; }
th,
td {
padding: 7px 9px;
border-bottom: 1px solid #eef0f2;
text-align: left;
white-space: nowrap;
}
th {
position: sticky;
top: 0;
z-index: 2;
color: #444;
background: #fafbfc;
font-weight: 600;
}
tbody tr:hover { background: #f7f9fb; }
tbody tr.selected { background: #eef6ff; }
tbody tr.selected:hover { background: #e5f1ff; }
tbody tr.row-warning { background: #fffbe6; }
tbody tr.row-error { background: #fff5f4; }
.col-check { width: 38px; text-align: center; }
.col-order { min-width: 145px; }
.col-stall { min-width: 140px; }
.col-spec { min-width: 118px; }
.col-code { min-width: 162px; }
.col-state { min-width: 108px; }
.col-message { min-width: 260px; max-width: 340px; }
.col-time { min-width: 142px; }
.truncate {
display: block;
overflow: hidden;
color: #555;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
color: #555;
background: #eceff2;
font-size: 12px;
}
.status-pending { color: #555; background: #eceff2; }
.status-ready { color: #174ea6; background: #e8f1ff; }
.status-applying { color: var(--warning); background: #fff3cd; }
.status-updated,
.status-already_filled { color: var(--success); background: #dff3e5; }
.status-skipped { color: var(--warning); background: #fff3cd; }
.status-failed { color: var(--danger); background: #fde8e8; }
.status-needs_check { color: #7c2d12; background: #ffedd5; }
.statusbar {
flex: 0 0 auto;
min-height: 38px;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 14px;
padding: 8px 16px;
color: #c8cdd4;
background: var(--nav);
font-size: 13px;
}
.status-message { margin-right: auto; color: #fff; }
.pagination { display: flex; gap: 10px; }
.pagination span { color: #7f8a97; }
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 20;
display: grid;
place-items: center;
padding: 24px;
background: rgba(28, 36, 46, .48);
}
.modal {
width: min(520px, 100%);
overflow: hidden;
border: 1px solid var(--border);
border-radius: 6px;
background: #fff;
box-shadow: 0 18px 50px rgba(0, 0, 0, .22);
}
.modal-head,
.modal-foot {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
}
.modal-head { border-bottom: 1px solid var(--border); }
.modal-head h2 { margin: 0; font-size: 18px; }
.modal-x { margin-left: auto; min-width: 30px; padding: 2px 8px; font-size: 20px; }
.modal-body { padding: 16px; line-height: 1.65; }
.modal-body p { margin: 0 0 10px; }
.modal-body ul { margin: 8px 0; padding-left: 22px; }
.modal-foot { justify-content: flex-end; border-top: 1px solid var(--border); background: #fafbfc; }
.confirm-box {
padding: 10px 12px;
border-left: 4px solid var(--primary);
background: #f5f9ff;
}
.danger-note { color: var(--danger); }
.toast {
position: fixed;
right: 20px;
bottom: 54px;
z-index: 30;
max-width: 420px;
padding: 10px 14px;
border: 1px solid #9bc2f7;
border-radius: 4px;
color: #174ea6;
background: #eef6ff;
box-shadow: 0 8px 24px rgba(0, 0, 0, .14);
}
@media (max-width: 1100px) {
.nav { overflow-x: auto; }
}
</style>
</head>
<body>
<nav class="nav" aria-label="Admin 主导航">
<span class="nav-brand">采集采购管理端</span>
<a href="#">蝦皮数据</a>
<a href="#">PDD 商品</a>
<a href="#">顺运宝数据</a>
<a href="#">采集采购</a>
<a href="#">客户端列表</a>
<a href="#" class="active" aria-current="page">档口入库码</a>
<a href="#">店铺管理</a>
<a href="#">用户管理</a>
<a href="#">AI 配置</a>
<span class="nav-spacer"></span>
<span class="nav-user">admin</span>
<button type="button" class="nav-button">退出登录</button>
</nav>
<main class="page">
<p class="prototype-notice" role="status">
<strong>交互原型</strong>
<span>当前页面只使用虚构演示数据,不会上传 Excel、连接 Admin 或回写顺运宝。</span>
</p>
<section class="toolbar" aria-label="导入、匹配和回写操作">
<div class="toolbar-group">
<label for="biz-date">业务日期</label>
<input id="biz-date" type="date" value="2026-08-14">
</div>
<div class="toolbar-separator" aria-hidden="true"></div>
<div class="toolbar-group">
<button type="button" id="choose-file">选择 Excel</button>
<input id="file-input" type="file" accept=".xlsx" hidden>
<span class="file-field" id="file-name" title="档口入库码_2026-08-14.xlsx(演示文件)">档口入库码_2026-08-14.xlsx(演示)</span>
<button type="button" id="import-demo">重新导入演示数据</button>
</div>
<div class="toolbar-separator" aria-hidden="true"></div>
<div class="toolbar-group">
<label for="status-filter">状态</label>
<select id="status-filter">
<option value="all">全部状态</option>
<option value="pending">待匹配</option>
<option value="ready">可回写</option>
<option value="updated">已回写</option>
<option value="already_filled">已存在</option>
<option value="skipped">已跳过</option>
<option value="failed">失败</option>
<option value="needs_check">需核对</option>
</select>
<label for="keyword" class="visually-hidden">搜索订单号、档口、规格或入库码</label>
<input id="keyword" type="search" placeholder="订单号 / 档口 / 规格 / 入库码">
<button type="button" id="clear-filter">清除</button>
</div>
<div class="toolbar-group toolbar-actions">
<button type="button" id="match-button" class="primary">匹配顺运宝商品</button>
<button type="button" id="write-button" disabled>回写已选 <span id="selected-count">0</span> 条</button>
</div>
</section>
<section class="table-wrap" role="region" aria-label="档口入库码记录列表" tabindex="0">
<table>
<thead>
<tr>
<th class="col-check"><input id="check-all" type="checkbox" aria-label="全选当前页可回写记录"></th>
<th>业务日期</th>
<th class="col-order">订单号</th>
<th class="col-stall">档口及货号</th>
<th class="col-spec">Excel 颜色尺码</th>
<th class="col-spec">顺运宝规格</th>
<th class="col-code">档口入库码</th>
<th class="col-code">远端原值</th>
<th class="col-state">状态</th>
<th class="col-message">结果 / 原因</th>
<th class="col-time">更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="records-body"></tbody>
</table>
</section>
</main>
<footer class="statusbar">
<span id="status-message" class="status-message" role="status" aria-live="polite">已加载演示数据,可以先点击“匹配顺运宝商品”。</span>
<span id="summary-counts">共 0 条,可回写 0 条,需核对 0 条</span>
<span id="visible-count">当前显示 0 条</span>
<span>第 1 / 1 页</span>
<nav class="pagination" aria-label="分页"><span>首页</span><span>上一页</span><span>下一页</span><span>末页</span></nav>
</footer>
<div class="modal-backdrop" id="write-modal" hidden>
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="write-title">
<header class="modal-head">
<h2 id="write-title">确认回写档口入库码</h2>
<button type="button" class="modal-x" data-close-modal aria-label="关闭">×</button>
</header>
<div class="modal-body">
<div class="confirm-box">
将回写 <strong id="confirm-count">0</strong> 条记录,其中 <strong id="replace-count">0</strong> 条存在旧快递单号,需要先删除旧值再写入。
</div>
<ul>
<li>系统会在每条写入前重新读取顺运宝详情并核验商品。</li>
<li>写入后会再次查询;只有远端值一致才标记“已回写”。</li>
<li class="danger-note">超时或结果不确定时只标记“需核对”,不会自动重复写入。</li>
</ul>
</div>
<footer class="modal-foot">
<button type="button" data-close-modal>取消</button>
<button type="button" id="confirm-write" class="primary">确认回写</button>
</footer>
</section>
</div>
<div class="modal-backdrop" id="check-modal" hidden>
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="check-title">
<header class="modal-head">
<h2 id="check-title">重新核对远端结果</h2>
<button type="button" class="modal-x" data-close-modal aria-label="关闭">×</button>
</header>
<div class="modal-body">
<p>这里只会重新读取顺运宝详情,不会再次写入。</p>
<p>演示结果:远端快递单号已经等于 <strong id="check-code"></strong>,可以安全标记为“已回写”。</p>
</div>
<footer class="modal-foot">
<button type="button" data-close-modal>取消</button>
<button type="button" id="confirm-check" class="primary">读取并确认结果</button>
</footer>
</section>
</div>
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
<script>
(() => {
"use strict";
const initialRecords = () => [
{
id: 1, date: "2026-08-14", order: "260814DEMO001", stall: "A12-101",
rawSpec: "黑色,M", sybSpec: "—", code: "DK260814A010101", oldCode: "—",
status: "pending", message: "等待匹配顺运宝货运单商品", updated: "08-14 09:20"
},
{
id: 2, date: "2026-08-14", order: "260814DEMO002", stall: "B07-305",
rawSpec: "杏色,XL", sybSpec: "—", code: "DK260814A010102", oldCode: "OLD-DEMO-002",
status: "pending", message: "等待匹配顺运宝货运单商品", updated: "08-14 09:20"
},
{
id: 3, date: "2026-08-14", order: "260814DEMO003", stall: "C03-118",
rawSpec: "白色,均码", sybSpec: "—", code: "DK260814A010103", oldCode: "—",
status: "pending", message: "等待匹配顺运宝货运单商品", updated: "08-14 09:20"
},
{
id: 4, date: "2026-08-14", order: "260814DEMO004", stall: "D21-006",
rawSpec: "卡其色,L", sybSpec: "卡其色,L", code: "DK260814A010104", oldCode: "—",
status: "needs_check", message: "上次写入响应超时,禁止自动重试", updated: "08-14 09:18"
},
{
id: 5, date: "2026-08-14", order: "260814DEMO005", stall: "A16-208",
rawSpec: "蓝色,2XL", sybSpec: "蓝色,2XL", code: "DK260814A010105", oldCode: "DK260814A010105",
status: "already_filled", message: "远端已是相同入库码,无需重复写入", updated: "08-14 09:16"
},
{
id: 6, date: "2026-08-14", order: "260814DEMO006", stall: "F09-511",
rawSpec: "灰色,S", sybSpec: "灰色,S", code: "DK260814A010106", oldCode: "—",
status: "skipped", message: "商品已有采购平台或采购单号,不允许覆盖", updated: "08-14 09:15"
},
{
id: 7, date: "2026-08-14", order: "260814DEMO007", stall: "B18-022",
rawSpec: "粉色,M", sybSpec: "—", code: "DK260814A010107", oldCode: "—",
status: "failed", message: "本地未找到对应的顺运宝货运单", updated: "08-14 09:14"
}
];
const statusLabels = {
pending: "待匹配",
ready: "可回写",
applying: "回写中",
updated: "已回写",
already_filled: "已存在",
skipped: "已跳过",
failed: "失败",
needs_check: "需核对"
};
let records = initialRecords();
let selected = new Set();
let checkTarget = null;
const body = document.querySelector("#records-body");
const statusFilter = document.querySelector("#status-filter");
const keyword = document.querySelector("#keyword");
const checkAll = document.querySelector("#check-all");
const writeButton = document.querySelector("#write-button");
const selectedCount = document.querySelector("#selected-count");
const statusMessage = document.querySelector("#status-message");
const writeModal = document.querySelector("#write-modal");
const checkModal = document.querySelector("#check-modal");
const toast = document.querySelector("#toast");
function escapeHTML(value) {
return String(value).replace(/[&<>'"]/g, char => ({
"&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;"
})[char]);
}
function visibleRecords() {
const state = statusFilter.value;
const search = keyword.value.trim().toLowerCase();
return records.filter(record => {
const stateMatched = state === "all"
|| record.status === state
|| (state === "problem" && ["skipped", "failed"].includes(record.status));
const haystack = [record.order, record.stall, record.rawSpec, record.sybSpec, record.code].join(" ").toLowerCase();
return stateMatched && (!search || haystack.includes(search));
});
}
function rowClass(record) {
const classes = [];
if (selected.has(record.id)) classes.push("selected");
if (["skipped", "needs_check"].includes(record.status)) classes.push("row-warning");
if (record.status === "failed") classes.push("row-error");
return classes.join(" ");
}
function rowAction(record) {
if (record.status === "needs_check") {
return `<button type="button" class="link-button" data-recheck="${record.id}">重新核对</button>`;
}
if (record.status === "failed") {
return `<button type="button" class="link-button" data-detail="${record.id}">查看原因</button>`;
}
return "—";
}
function render() {
const rows = visibleRecords();
body.innerHTML = rows.length ? rows.map(record => `
<tr class="${rowClass(record)}">
<td class="col-check"><input type="checkbox" data-row-check="${record.id}"
aria-label="选择订单 ${escapeHTML(record.order)}" ${selected.has(record.id) ? "checked" : ""}
${record.status === "ready" ? "" : "disabled"}></td>
<td>${escapeHTML(record.date)}</td>
<td>${escapeHTML(record.order)}</td>
<td>${escapeHTML(record.stall)}</td>
<td>${escapeHTML(record.rawSpec)}</td>
<td>${escapeHTML(record.sybSpec)}</td>
<td>${escapeHTML(record.code)}</td>
<td>${escapeHTML(record.oldCode)}</td>
<td><span class="status-pill status-${record.status}">${statusLabels[record.status]}</span></td>
<td class="col-message"><span class="truncate" title="${escapeHTML(record.message)}">${escapeHTML(record.message)}</span></td>
<td>${escapeHTML(record.updated)}</td>
<td>${rowAction(record)}</td>
</tr>`).join("") : `
<tr><td colspan="12" style="padding: 44px; text-align: center; color: #777;">当前筛选条件下没有记录。请清除搜索条件后再试。</td></tr>`;
document.querySelector("#visible-count").textContent = `当前显示 ${rows.length} 条`;
updateCounts();
updateSelection();
}
function count(state) {
return records.filter(record => record.status === state).length;
}
function updateCounts() {
document.querySelector("#summary-counts").textContent =
`共 ${records.length} 条,可回写 ${count("ready")} 条,需核对 ${count("needs_check")} 条`;
}
function updateSelection() {
[...selected].forEach(id => {
const record = records.find(item => item.id === id);
if (!record || record.status !== "ready") selected.delete(id);
});
selectedCount.textContent = selected.size;
writeButton.disabled = selected.size === 0;
const eligible = visibleRecords().filter(record => record.status === "ready");
const checkedEligible = eligible.filter(record => selected.has(record.id));
checkAll.disabled = eligible.length === 0;
checkAll.checked = eligible.length > 0 && checkedEligible.length === eligible.length;
checkAll.indeterminate = checkedEligible.length > 0 && checkedEligible.length < eligible.length;
}
function setMessage(message) {
statusMessage.textContent = message;
}
function showToast(message) {
toast.textContent = message;
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => { toast.hidden = true; }, 3200);
}
function setFilter(state) {
statusFilter.value = state;
render();
}
document.querySelector("#choose-file").addEventListener("click", () => {
document.querySelector("#file-input").click();
});
document.querySelector("#file-input").addEventListener("change", event => {
const file = event.target.files[0];
if (!file) return;
document.querySelector("#file-name").textContent = `${file.name}(原型不读取)`;
showToast("已选择文件。原型不会读取或上传该文件,请使用“重新导入演示数据”体验流程。");
});
document.querySelector("#import-demo").addEventListener("click", () => {
records = initialRecords();
selected.clear();
statusFilter.value = "all";
keyword.value = "";
document.querySelector("#biz-date").value = "2026-08-14";
document.querySelector("#file-name").textContent = "档口入库码_2026-08-14.xlsx(演示)";
setFilter("all");
setMessage("演示导入完成:7 条记录,新增 7 条,重复 0 条,Excel 临时文件已删除。");
showToast("演示导入完成。下一步请点击“匹配顺运宝商品”。");
});
document.querySelector("#match-button").addEventListener("click", () => {
const pending = records.filter(record => record.status === "pending");
if (!pending.length) {
showToast("没有待匹配记录。可重新导入演示数据再次体验。");
return;
}
pending.forEach(record => {
if (record.id === 1) {
Object.assign(record, { status: "ready", sybSpec: "黑色,M", message: "唯一匹配:货运单商品和档口均一致", updated: "08-14 09:22" });
} else if (record.id === 2) {
Object.assign(record, { status: "ready", sybSpec: "杏色,XL", message: "唯一匹配;回写前需处理旧快递单号", updated: "08-14 09:22" });
} else {
Object.assign(record, { status: "skipped", sybSpec: "白色,均码", message: "同订单同规格匹配到 2 条商品,不能自动选择", updated: "08-14 09:22" });
}
});
setFilter("ready");
setMessage("匹配完成:可回写 2 条,已跳过 1 条。歧义记录没有自动选择。");
showToast("匹配完成。请勾选“可回写”记录后继续。");
});
body.addEventListener("change", event => {
const checkbox = event.target.closest("[data-row-check]");
if (!checkbox) return;
const id = Number(checkbox.dataset.rowCheck);
if (checkbox.checked) selected.add(id); else selected.delete(id);
render();
});
checkAll.addEventListener("change", () => {
visibleRecords().filter(record => record.status === "ready").forEach(record => {
if (checkAll.checked) selected.add(record.id); else selected.delete(record.id);
});
render();
});
body.addEventListener("click", event => {
const recheck = event.target.closest("[data-recheck]");
if (recheck) {
checkTarget = Number(recheck.dataset.recheck);
const record = records.find(item => item.id === checkTarget);
document.querySelector("#check-code").textContent = record.code;
checkModal.hidden = false;
return;
}
const detail = event.target.closest("[data-detail]");
if (detail) showToast("失败原因已完整显示在“结果 / 原因”列;正式页面会保留该行供筛选和重试匹配。");
});
writeButton.addEventListener("click", () => {
const chosen = records.filter(record => selected.has(record.id) && record.status === "ready");
document.querySelector("#confirm-count").textContent = chosen.length;
document.querySelector("#replace-count").textContent = chosen.filter(record => record.oldCode !== "—").length;
writeModal.hidden = false;
});
document.querySelector("#confirm-write").addEventListener("click", () => {
writeModal.hidden = true;
const chosen = records.filter(record => selected.has(record.id) && record.status === "ready");
chosen.forEach(record => {
record.status = "applying";
record.message = "正在重新核验并回写,请勿重复操作";
record.updated = "08-14 09:24";
});
selected.clear();
render();
setMessage(`正在逐条回写 ${chosen.length} 条记录,页面可继续查看进度。`);
window.setTimeout(() => {
chosen.forEach(record => {
record.status = "updated";
record.oldCode = record.code;
record.message = "写入完成,远端再次查询结果一致";
record.updated = "08-14 09:25";
});
setFilter("updated");
setMessage(`回写完成:成功 ${chosen.length} 条,失败 0 条,需核对 0 条。`);
showToast("演示回写完成,已切换到“已回写”记录。");
}, 900);
});
document.querySelector("#confirm-check").addEventListener("click", () => {
const record = records.find(item => item.id === checkTarget);
if (record) {
record.status = "updated";
record.oldCode = record.code;
record.message = "重新读取远端后确认写入成功;没有重复写入";
record.updated = "08-14 09:26";
}
checkModal.hidden = true;
checkTarget = null;
setFilter("updated");
setMessage("重新核对完成:远端值一致,已把记录标记为“已回写”。");
});
document.querySelectorAll("[data-close-modal]").forEach(button => {
button.addEventListener("click", () => {
button.closest(".modal-backdrop").hidden = true;
});
});
document.querySelectorAll(".modal-backdrop").forEach(backdrop => {
backdrop.addEventListener("click", event => {
if (event.target === backdrop) backdrop.hidden = true;
});
});
document.addEventListener("keydown", event => {
if (event.key === "Escape") {
writeModal.hidden = true;
checkModal.hidden = true;
}
});
statusFilter.addEventListener("change", () => setFilter(statusFilter.value));
keyword.addEventListener("input", render);
document.querySelector("#clear-filter").addEventListener("click", () => {
keyword.value = "";
setFilter("all");
keyword.focus();
});
document.querySelectorAll(".nav a").forEach(link => {
if (!link.classList.contains("active")) {
link.addEventListener("click", event => {
event.preventDefault();
showToast("这是独立原型,其他模块链接未接入。");
});
}
});
render();
})();
</script>
</body>
</html>