提交
This commit is contained in:
1 parent
80138c6388
commit
693e8a678e
682 files changed
+211375
-5306
No files matched your search
@@ -0,0 +1,720 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>关联审批单 · 选单卡片模式 Demo</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #10b981;
|
||||
--primary-dark: #059669;
|
||||
--primary-bg: #ecfdf5;
|
||||
--primary-border: #a7f3d0;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--text-faint: #94a3b8;
|
||||
--border: #e2e8f0;
|
||||
--border-soft: #f1f5f9;
|
||||
--bg: #f1f5f9;
|
||||
--surface: #ffffff;
|
||||
--warn: #d97706;
|
||||
--danger: #ef4444;
|
||||
--success: #059669;
|
||||
--info: #64748b;
|
||||
--shadow: 0 12px 40px rgba(15, 23, 42, 0.12);
|
||||
--radius: 10px;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: Inter, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(1200px 400px at 10% -10%, #d1fae5 0%, transparent 55%),
|
||||
radial-gradient(900px 360px at 100% 0%, #e0f2fe 0%, transparent 50%),
|
||||
var(--bg);
|
||||
}
|
||||
.page {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 20px 80px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
.lead {
|
||||
margin: 0 0 20px;
|
||||
color: var(--text-muted);
|
||||
max-width: 720px;
|
||||
}
|
||||
.notes {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 14px;
|
||||
border: 1px dashed var(--primary-border);
|
||||
border-radius: var(--radius);
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
}
|
||||
.notes strong { color: var(--primary-dark); }
|
||||
.notes ul { margin: 8px 0 0; padding-left: 18px; }
|
||||
.notes li { margin: 2px 0; }
|
||||
|
||||
/* Dialog shell */
|
||||
.dialog {
|
||||
background: var(--surface);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
.dialog__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.dialog__title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dialog__close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog__close:hover { background: var(--border-soft); }
|
||||
.dialog__body {
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.dialog__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 18px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: #f8fafc;
|
||||
}
|
||||
.dialog__footer-left {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.dialog__footer-left em {
|
||||
font-style: normal;
|
||||
color: var(--primary-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
.dialog__footer-right { display: flex; gap: 8px; }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn:hover { background: #f8fafc; }
|
||||
.btn--primary {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
.btn--primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
|
||||
.btn--primary:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 0 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #fff;
|
||||
}
|
||||
.tab {
|
||||
position: relative;
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab:hover { color: var(--text); }
|
||||
.tab.is-active {
|
||||
color: var(--primary-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
.tab.is-active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
background: var(--primary);
|
||||
}
|
||||
.tab .count {
|
||||
margin-left: 4px;
|
||||
color: var(--text-faint);
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Filters */
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
background: #fff;
|
||||
}
|
||||
.field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.field label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.input, .select {
|
||||
height: 34px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.input { min-width: 200px; }
|
||||
.input:focus, .select:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
.select { min-width: 120px; }
|
||||
|
||||
/* Card list */
|
||||
.card-list {
|
||||
background: #fff;
|
||||
}
|
||||
.card {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
cursor: pointer;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
.card:hover { background: #f8fafc; }
|
||||
.card.is-selected { background: var(--primary-bg); }
|
||||
.card.is-disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.card__check {
|
||||
flex-shrink: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.card__check input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.card__avatar {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.card__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.card__title {
|
||||
margin: 0 0 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.card__meta {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
color: var(--text-muted);
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.card__meta li {
|
||||
margin: 1px 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.card__time {
|
||||
margin-top: 6px;
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
.card__right {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
min-width: 100px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.card__actor {
|
||||
font-size: 12.5px;
|
||||
color: var(--warn);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.card__status {
|
||||
margin-top: 4px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.st-running { color: var(--warn); }
|
||||
.st-completed { color: var(--success); }
|
||||
.st-rejected { color: var(--danger); }
|
||||
.st-returned { color: var(--warn); }
|
||||
.st-cancelled { color: var(--info); }
|
||||
|
||||
.empty {
|
||||
padding: 48px 18px;
|
||||
text-align: center;
|
||||
color: var(--text-faint);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 18px 14px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.pager button {
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.pager button:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
|
||||
.legend {
|
||||
margin-top: 18px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>关联审批单 · 选单卡片模式</h1>
|
||||
<p class="lead">
|
||||
需求草案 Demo:去掉「仅已通过」限制;三 Tab(我发起的 / 我审批的 / 抄送我的);
|
||||
钉钉式卡片;展示部门与表单摘要;支持筛选。交互可点选,便于对齐产品预期。
|
||||
</p>
|
||||
<div class="notes">
|
||||
<strong>本 Demo 约定(待评审)</strong>
|
||||
<ul>
|
||||
<li>Tab 默认落在「我发起的」;切换 Tab 清空当前页勾选草稿(已确认关联保留在表单外)。</li>
|
||||
<li>状态不限制:处理中 / 已通过 / 已拒绝 / 已打回 / 已撤销均可出现;右侧展示当前处理人 + 状态文案。</li>
|
||||
<li>卡片主信息:标题、部门、前 2 个业务字段摘要、申请时间;左勾选 + 发起人头像字。</li>
|
||||
<li>筛选:仅关键词(流程名 / 标题)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dialog" role="dialog" aria-label="选择关联审批单">
|
||||
<div class="dialog__header">
|
||||
<h2 class="dialog__title">选择关联审批单</h2>
|
||||
<button class="dialog__close" type="button" aria-label="关闭">×</button>
|
||||
</div>
|
||||
|
||||
<div class="tabs" role="tablist">
|
||||
<button class="tab is-active" data-tab="mine" type="button">我发起的<span class="count" id="count-mine"></span></button>
|
||||
<button class="tab" data-tab="done" type="button">我审批的<span class="count" id="count-done"></span></button>
|
||||
<button class="tab" data-tab="cc" type="button">抄送我的<span class="count" id="count-cc"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<div class="field">
|
||||
<label>关键词</label>
|
||||
<input class="input" id="kw" type="search" placeholder="搜索流程名 / 标题" />
|
||||
</div>
|
||||
<button class="btn" type="button" id="btnSearch">查询</button>
|
||||
<button class="btn" type="button" id="btnReset">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="dialog__body">
|
||||
<div class="card-list" id="list"></div>
|
||||
<div class="pager">
|
||||
<span id="pageInfo">第 1 页</span>
|
||||
<button type="button" id="prevPage">上一页</button>
|
||||
<button type="button" id="nextPage">下一页</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog__footer">
|
||||
<div class="dialog__footer-left">已选 <em id="selCount">0</em> 项</div>
|
||||
<div class="dialog__footer-right">
|
||||
<button class="btn" type="button">取消</button>
|
||||
<button class="btn btn--primary" type="button" id="btnOk" disabled>确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="legend">
|
||||
静态 Mock · Fresh Mint 配色 · 打开本文件即可预览:
|
||||
<code>docs/mockups/oa-related-approval-picker-card-demo.html</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const AVATAR_COLORS = ["#3b82f6", "#10b981", "#8b5cf6", "#f59e0b", "#06b6d4", "#ef4444"];
|
||||
const STATUS_TEXT = {
|
||||
running: "处理中",
|
||||
completed: "已通过",
|
||||
rejected: "已拒绝",
|
||||
returned: "已打回",
|
||||
cancelled: "已撤销",
|
||||
};
|
||||
|
||||
/** 已关联(模拟表单已选,不可再选) */
|
||||
const alreadyLinked = new Set(["m2"]);
|
||||
|
||||
const DATA = {
|
||||
mine: [
|
||||
{
|
||||
id: "m1",
|
||||
appName: "人事审批",
|
||||
title: "吴自龙提交的人事审批",
|
||||
initiatorName: "吴自龙",
|
||||
deptName: "研发中心 / 平台组",
|
||||
fields: [
|
||||
{ label: "数字输入框", value: "123" },
|
||||
{ label: "单行输入框", value: "123" },
|
||||
],
|
||||
applyTime: "2026-07-23 13:50",
|
||||
status: "running",
|
||||
actor: "吴震 (审批人)",
|
||||
},
|
||||
{
|
||||
id: "m2",
|
||||
appName: "请假",
|
||||
title: "吴自龙提交的请假",
|
||||
initiatorName: "吴自龙",
|
||||
deptName: "研发中心 / 平台组",
|
||||
fields: [
|
||||
{ label: "请假类型", value: "年假" },
|
||||
{ label: "时长", value: "1.5 天" },
|
||||
],
|
||||
applyTime: "2026-07-20 09:12",
|
||||
status: "completed",
|
||||
actor: "",
|
||||
},
|
||||
{
|
||||
id: "m3",
|
||||
appName: "报销",
|
||||
title: "吴自龙提交的报销",
|
||||
initiatorName: "吴自龙",
|
||||
deptName: "研发中心 / 平台组",
|
||||
fields: [
|
||||
{ label: "报销金额", value: "¥1,280.00" },
|
||||
{ label: "事由", value: "客户拜访差旅" },
|
||||
],
|
||||
applyTime: "2026-07-18 16:40",
|
||||
status: "rejected",
|
||||
actor: "",
|
||||
},
|
||||
{
|
||||
id: "m4",
|
||||
appName: "采购",
|
||||
title: "吴自龙提交的采购",
|
||||
initiatorName: "吴自龙",
|
||||
deptName: "研发中心 / 平台组",
|
||||
fields: [
|
||||
{ label: "采购品类", value: "办公设备" },
|
||||
{ label: "预算", value: "¥8,000" },
|
||||
],
|
||||
applyTime: "2026-07-10 11:05",
|
||||
status: "returned",
|
||||
actor: "",
|
||||
},
|
||||
],
|
||||
done: [
|
||||
{
|
||||
id: "d1",
|
||||
appName: "请假",
|
||||
title: "张三提交的请假",
|
||||
initiatorName: "张三",
|
||||
deptName: "市场部 / 华东区",
|
||||
fields: [
|
||||
{ label: "请假类型", value: "事假" },
|
||||
{ label: "时长", value: "0.5 天" },
|
||||
],
|
||||
applyTime: "2026-07-22 10:20",
|
||||
status: "running",
|
||||
actor: "吴自龙 (审批人)",
|
||||
},
|
||||
{
|
||||
id: "d2",
|
||||
appName: "人事审批",
|
||||
title: "李四提交的人事审批",
|
||||
initiatorName: "李四",
|
||||
deptName: "人事行政部",
|
||||
fields: [
|
||||
{ label: "调整类型", value: "转正" },
|
||||
{ label: "生效日期", value: "2026-08-01" },
|
||||
],
|
||||
applyTime: "2026-07-15 14:33",
|
||||
status: "completed",
|
||||
actor: "",
|
||||
},
|
||||
{
|
||||
id: "d3",
|
||||
appName: "报销",
|
||||
title: "王五提交的报销",
|
||||
initiatorName: "王五",
|
||||
deptName: "财务部",
|
||||
fields: [
|
||||
{ label: "报销金额", value: "¥560.00" },
|
||||
{ label: "科目", value: "招待费" },
|
||||
],
|
||||
applyTime: "2026-07-12 08:50",
|
||||
status: "cancelled",
|
||||
actor: "",
|
||||
},
|
||||
],
|
||||
cc: [
|
||||
{
|
||||
id: "c1",
|
||||
appName: "采购",
|
||||
title: "赵六提交的采购",
|
||||
initiatorName: "赵六",
|
||||
deptName: "供应链 / 采购组",
|
||||
fields: [
|
||||
{ label: "供应商", value: "某某科技" },
|
||||
{ label: "金额", value: "¥12,000" },
|
||||
],
|
||||
applyTime: "2026-07-21 17:08",
|
||||
status: "running",
|
||||
actor: "陈七 (审批人)",
|
||||
},
|
||||
{
|
||||
id: "c2",
|
||||
appName: "人事审批",
|
||||
title: "钱八提交的人事审批",
|
||||
initiatorName: "钱八",
|
||||
deptName: "研发中心 / 测试组",
|
||||
fields: [
|
||||
{ label: "岗位", value: "高级测试工程师" },
|
||||
{ label: "职级", value: "P6" },
|
||||
],
|
||||
applyTime: "2026-07-08 09:40",
|
||||
status: "completed",
|
||||
actor: "",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const state = {
|
||||
tab: "mine",
|
||||
kw: "",
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
selected: new Set(),
|
||||
};
|
||||
|
||||
function shortName(name) {
|
||||
const s = String(name || "").trim();
|
||||
return s.length <= 2 ? s : s.slice(-2);
|
||||
}
|
||||
function avatarColor(name) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < name.length; i++) h = (h + name.charCodeAt(i) * 17) % AVATAR_COLORS.length;
|
||||
return AVATAR_COLORS[h];
|
||||
}
|
||||
function statusClass(st) {
|
||||
return "st-" + (st || "running");
|
||||
}
|
||||
|
||||
function filtered() {
|
||||
return (DATA[state.tab] || []).filter((row) => {
|
||||
if (state.kw) {
|
||||
const blob = [row.appName, row.title].join(" ").toLowerCase();
|
||||
if (!blob.includes(state.kw.toLowerCase())) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function renderCounts() {
|
||||
for (const k of ["mine", "done", "cc"]) {
|
||||
const el = document.getElementById("count-" + k);
|
||||
if (el) el.textContent = "(" + DATA[k].length + ")";
|
||||
}
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const all = filtered();
|
||||
const totalPages = Math.max(1, Math.ceil(all.length / state.pageSize));
|
||||
if (state.page > totalPages) state.page = totalPages;
|
||||
const start = (state.page - 1) * state.pageSize;
|
||||
const pageRows = all.slice(start, start + state.pageSize);
|
||||
const list = document.getElementById("list");
|
||||
|
||||
if (!pageRows.length) {
|
||||
list.innerHTML = '<div class="empty">暂无符合条件的审批单</div>';
|
||||
} else {
|
||||
list.innerHTML = pageRows
|
||||
.map((row) => {
|
||||
const disabled = alreadyLinked.has(row.id);
|
||||
const checked = state.selected.has(row.id);
|
||||
const meta = [
|
||||
row.deptName ? `<li>部门:${escapeHtml(row.deptName)}</li>` : "",
|
||||
...(row.fields || [])
|
||||
.slice(0, 2)
|
||||
.map((f) => `<li>${escapeHtml(f.label)}:${escapeHtml(f.value)}</li>`),
|
||||
].join("");
|
||||
const actor =
|
||||
row.status === "running" && row.actor
|
||||
? `<div class="card__actor">${escapeHtml(row.actor)}</div>`
|
||||
: "";
|
||||
return `
|
||||
<div class="card ${checked ? "is-selected" : ""} ${disabled ? "is-disabled" : ""}"
|
||||
data-id="${row.id}" data-disabled="${disabled ? "1" : "0"}">
|
||||
<div class="card__check">
|
||||
<input type="checkbox" ${checked ? "checked" : ""} ${disabled ? "disabled" : ""} />
|
||||
</div>
|
||||
<div class="card__avatar" style="background:${avatarColor(row.initiatorName)}">
|
||||
${escapeHtml(shortName(row.initiatorName))}
|
||||
</div>
|
||||
<div class="card__main">
|
||||
<h3 class="card__title">${escapeHtml(row.title)}</h3>
|
||||
<ul class="card__meta">${meta}</ul>
|
||||
<div class="card__time">${escapeHtml(row.applyTime)}</div>
|
||||
</div>
|
||||
<div class="card__right">
|
||||
${actor}
|
||||
<div class="card__status ${statusClass(row.status)}">${STATUS_TEXT[row.status] || row.status}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
document.getElementById("pageInfo").textContent =
|
||||
`共 ${all.length} 条 · 第 ${state.page}/${totalPages} 页`;
|
||||
document.getElementById("prevPage").disabled = state.page <= 1;
|
||||
document.getElementById("nextPage").disabled = state.page >= totalPages;
|
||||
document.getElementById("selCount").textContent = String(state.selected.size);
|
||||
document.getElementById("btnOk").disabled = state.selected.size === 0;
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
document.querySelectorAll(".tab").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
document.querySelectorAll(".tab").forEach((b) => b.classList.remove("is-active"));
|
||||
btn.classList.add("is-active");
|
||||
state.tab = btn.dataset.tab;
|
||||
state.page = 1;
|
||||
state.selected.clear();
|
||||
renderList();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("btnSearch").addEventListener("click", () => {
|
||||
state.kw = document.getElementById("kw").value.trim();
|
||||
state.page = 1;
|
||||
renderList();
|
||||
});
|
||||
document.getElementById("btnReset").addEventListener("click", () => {
|
||||
document.getElementById("kw").value = "";
|
||||
state.kw = "";
|
||||
state.page = 1;
|
||||
renderList();
|
||||
});
|
||||
document.getElementById("kw").addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") document.getElementById("btnSearch").click();
|
||||
});
|
||||
document.getElementById("prevPage").addEventListener("click", () => {
|
||||
if (state.page > 1) {
|
||||
state.page -= 1;
|
||||
renderList();
|
||||
}
|
||||
});
|
||||
document.getElementById("nextPage").addEventListener("click", () => {
|
||||
state.page += 1;
|
||||
renderList();
|
||||
});
|
||||
|
||||
document.getElementById("list").addEventListener("click", (e) => {
|
||||
const card = e.target.closest(".card");
|
||||
if (!card || card.dataset.disabled === "1") return;
|
||||
const id = card.dataset.id;
|
||||
if (state.selected.has(id)) state.selected.delete(id);
|
||||
else state.selected.add(id);
|
||||
renderList();
|
||||
});
|
||||
|
||||
renderCounts();
|
||||
renderList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user