721 lines
21 KiB
HTML
721 lines
21 KiB
HTML
<!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>
|