| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- <!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>OA 审批任务中心 · 检索区 Demo</title>
- <link rel="preconnect" href="https://fonts.googleapis.com" />
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
- <style>
- :root {
- --primary: #10b981;
- --primary-dark: #059669;
- --primary-bg: #ecfdf5;
- --text: #1e293b;
- --text-muted: #64748b;
- --border: #d9e8e2;
- --bg-page: #fefefe;
- --surface: #ffffff;
- --table-head: #f8fafc;
- --row-alt: #fafafa;
- --row-hover: #f1f5f9;
- --shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- --radius: 10px;
- --placeholder: #94a3b8;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- min-height: 100vh;
- font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
- font-size: 14px;
- line-height: 1.5;
- color: var(--text);
- background: #f1f5f9;
- }
- .page {
- max-width: 1280px;
- margin: 0 auto;
- padding: 24px;
- }
- .demo-banner {
- margin-bottom: 16px;
- padding: 12px 16px;
- border: 1px solid var(--border);
- border-radius: 8px;
- background: var(--primary-bg);
- color: var(--primary-dark);
- font-size: 13px;
- }
- .demo-banner strong { font-weight: 600; }
- .live-toast {
- position: fixed;
- right: 24px;
- bottom: 24px;
- z-index: 50;
- min-width: 220px;
- padding: 10px 14px;
- border-radius: 8px;
- border: 1px solid #a7f3d0;
- background: #fff;
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
- color: var(--text);
- font-size: 13px;
- opacity: 0;
- transform: translateY(8px);
- pointer-events: none;
- transition: opacity 0.2s ease, transform 0.2s ease;
- }
- .live-toast.show {
- opacity: 1;
- transform: translateY(0);
- }
- .live-toast .muted { color: var(--text-muted); font-size: 12px; margin-top: 2px; }
- .page-tabs {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 16px;
- }
- .page-tab {
- height: 36px;
- padding: 0 14px;
- border: 1px solid var(--border);
- border-radius: 8px;
- background: var(--surface);
- color: var(--text-muted);
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- font-family: inherit;
- }
- .page-tab:hover { color: var(--primary-dark); border-color: #a7f3d0; }
- .page-tab.active {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- font-weight: 600;
- }
- .panel {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- box-shadow: var(--shadow);
- overflow: hidden;
- }
- .panel-top {
- display: flex;
- align-items: center;
- padding: 14px 16px;
- border-bottom: 1px solid var(--border);
- }
- .panel-top h2 {
- margin: 0;
- font-size: 16px;
- font-weight: 600;
- color: var(--text);
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .panel-top h2::before {
- content: "";
- width: 3px;
- height: 16px;
- border-radius: 2px;
- background: var(--primary);
- flex-shrink: 0;
- }
- /* —— 筛选区(对齐 Fresh Mint §5 / unit / order / 抄送我的) —— */
- .filter-block {
- padding: 12px 16px 10px;
- border-bottom: 1px solid var(--border);
- background: var(--surface);
- }
- .filter-form { margin: 0; }
- .filter-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 10px 14px;
- margin-bottom: 10px;
- }
- @media (max-width: 1100px) {
- .filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
- }
- @media (max-width: 560px) {
- .filter-grid { grid-template-columns: 1fr; }
- }
- .filter-field label {
- display: block;
- font-size: 11px;
- font-weight: 500;
- color: var(--text-muted);
- margin-bottom: 4px;
- }
- .input, .select {
- width: 100%;
- height: 36px;
- padding: 0 12px;
- border: 1px solid var(--border);
- border-radius: 8px;
- font-size: 14px;
- color: var(--text);
- background: var(--surface);
- font-family: inherit;
- outline: none;
- }
- .input::placeholder {
- color: var(--placeholder);
- font-size: 13px;
- opacity: 1;
- }
- .input:focus, .select:focus {
- border-color: var(--primary);
- box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
- }
- .select {
- appearance: none;
- padding-right: 32px;
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M3 4.5L6 8l3-3.5'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 12px center;
- }
- .date-range {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- }
- .date-range .input { flex: 1; min-width: 0; }
- .date-range .sep {
- color: var(--text-muted);
- font-size: 12px;
- flex-shrink: 0;
- }
- .filter-actions-row {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding-top: 10px;
- margin-top: 0;
- }
- .filter-actions-end {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- height: 36px;
- padding: 0 14px;
- border-radius: 8px;
- font-size: 14px;
- font-weight: 600;
- font-family: inherit;
- cursor: pointer;
- border: 1px solid transparent;
- }
- .btn-ghost {
- background: var(--surface);
- border-color: var(--border);
- color: var(--text);
- }
- .btn-ghost:hover { background: var(--row-hover); }
- .btn-main {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .btn-main:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
- .btn svg { width: 14px; height: 14px; flex-shrink: 0; }
- .list-body {
- border-top: none;
- }
- .list-wrap { padding: 0; }
- table.list-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 13px;
- }
- .list-table th,
- .list-table td {
- padding: 12px 10px;
- text-align: center;
- border-bottom: 1px solid #eef2f7;
- color: var(--text);
- }
- .list-table th {
- background: var(--table-head);
- color: var(--text-muted);
- font-weight: 600;
- font-size: 12px;
- }
- .list-table tbody tr:nth-child(even) { background: var(--row-alt); }
- .list-table tbody tr:hover { background: var(--row-hover); }
- .tag {
- display: inline-flex;
- align-items: center;
- height: 22px;
- padding: 0 8px;
- border-radius: 4px;
- font-size: 12px;
- font-weight: 500;
- }
- .tag.warning { background: #fff7ed; color: #c2410c; }
- .tag.success { background: var(--primary-bg); color: var(--primary-dark); }
- .tag.danger { background: #fef2f2; color: #dc2626; }
- .tag.info { background: #f1f5f9; color: #64748b; }
- .btn-op {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- height: 28px;
- padding: 0 10px;
- border-radius: 6px;
- border: 1px solid transparent;
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- font-family: inherit;
- }
- .btn-op.primary {
- background: var(--primary-bg);
- color: var(--primary-dark);
- border-color: #a7f3d0;
- }
- .btn-op.neutral {
- background: var(--surface);
- color: var(--text);
- border-color: var(--border);
- }
- .pagination {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- gap: 8px;
- padding: 12px 16px;
- border-top: 1px solid var(--border);
- color: var(--text-muted);
- font-size: 13px;
- }
- .pager-btn {
- height: 28px;
- min-width: 28px;
- padding: 0 8px;
- border: 1px solid var(--border);
- border-radius: 6px;
- background: var(--surface);
- color: var(--text);
- cursor: pointer;
- font-family: inherit;
- }
- .pager-btn.active {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .field-note {
- margin-top: 12px;
- padding: 10px 12px;
- border-radius: 8px;
- background: #f8fafc;
- border: 1px dashed var(--border);
- font-size: 12px;
- color: var(--text-muted);
- }
- .field-note code {
- font-size: 11px;
- background: #e2e8f0;
- padding: 1px 5px;
- border-radius: 4px;
- color: var(--text);
- }
- [data-only] { display: none; }
- </style>
- </head>
- <body>
- <div class="page">
- <div class="demo-banner">
- <strong>OA 审批 · 任务中心检索区 Demo</strong>
- —— 样式对齐 Fresh Mint;交互为<strong>即时检索</strong>:输入/选择条件后防抖约 300ms 自动请求,无需点「搜索」。试着改下方任一条件即可看到触发提示。
- </div>
- <div class="page-tabs" role="tablist">
- <button type="button" class="page-tab active" data-tab="pending">待办中心</button>
- <button type="button" class="page-tab" data-tab="done">已办中心</button>
- <button type="button" class="page-tab" data-tab="mine">我发起的</button>
- <button type="button" class="page-tab" data-tab="cc">抄送我的</button>
- </div>
- <section class="panel">
- <div class="panel-top">
- <h2 id="panelTitle">待办中心</h2>
- </div>
- <div class="filter-block">
- <form class="filter-form" onsubmit="return false;">
- <div class="filter-grid">
- <div class="filter-field">
- <label for="appName">流程名称</label>
- <input id="appName" class="input" type="text" placeholder="请输入流程名称" />
- </div>
- <div class="filter-field" data-only="pending,done,cc">
- <label for="initiator">申请人</label>
- <input id="initiator" class="input" type="text" placeholder="请输入申请人" />
- </div>
- <div class="filter-field">
- <label>申请时间</label>
- <div class="date-range">
- <input class="input" type="date" aria-label="申请开始日期" />
- <span class="sep">至</span>
- <input class="input" type="date" aria-label="申请结束日期" />
- </div>
- </div>
- <div class="filter-field">
- <label for="status">流程状态</label>
- <select id="status" class="select">
- <option value="">全部</option>
- <option value="running">进行中</option>
- <option value="approved">已同意</option>
- <option value="rejected">已拒绝</option>
- <option value="returned">已打回</option>
- <option value="cancelled">已撤销</option>
- <option value="completed">已完成</option>
- </select>
- </div>
- <div class="filter-field" data-only="cc">
- <label for="readStatus">阅读状态</label>
- <select id="readStatus" class="select">
- <option value="unread">未读</option>
- <option value="read">已读</option>
- <option value="all">全部</option>
- </select>
- </div>
- </div>
- <div class="filter-actions-row">
- <div class="filter-actions-end">
- <button type="button" class="btn btn-ghost" id="btnReset">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9"/><path d="M3 4v5h5"/></svg>
- 重置
- </button>
- <button type="button" class="btn btn-main" id="btnSearch">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
- 搜索
- </button>
- </div>
- </div>
- </form>
- <div class="field-note" id="fieldNote">
- 交互:条件变更 → 防抖 300ms → 自动检索(页码回第 1 页)。本页字段:<code>流程名称</code>· <code>申请人</code>· <code>申请时间</code>· <code>流程状态</code>
- </div>
- </div>
- <div class="list-body list-wrap">
- <table class="list-table">
- <thead>
- <tr>
- <th style="width:68px">序号</th>
- <th>流程名称</th>
- <th>申请人</th>
- <th>申请时间</th>
- <th>当前节点</th>
- <th>当前审批人</th>
- <th>流程状态</th>
- <th data-only="cc">阅读状态</th>
- <th style="min-width:120px">操作</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>1</td>
- <td>请假审批</td>
- <td>张三</td>
- <td>2026-08-10 09:20:00</td>
- <td>部门负责人</td>
- <td>李四</td>
- <td><span class="tag warning">进行中</span></td>
- <td data-only="cc"><span class="tag danger">未读</span></td>
- <td><button type="button" class="btn-op primary">查看</button></td>
- </tr>
- <tr>
- <td>2</td>
- <td>加班申请</td>
- <td>王五</td>
- <td>2026-08-09 18:05:00</td>
- <td>人事复核</td>
- <td>赵六</td>
- <td><span class="tag warning">进行中</span></td>
- <td data-only="cc"><span class="tag info">已读</span></td>
- <td><button type="button" class="btn-op primary">查看</button></td>
- </tr>
- <tr>
- <td>3</td>
- <td>差旅报销</td>
- <td>陈七</td>
- <td>2026-08-08 11:30:00</td>
- <td>—</td>
- <td>—</td>
- <td><span class="tag success">已同意</span></td>
- <td data-only="cc"><span class="tag info">已读</span></td>
- <td>
- <button type="button" class="btn-op primary">查看</button>
- <button type="button" class="btn-op neutral" style="margin-left:6px">打印</button>
- </td>
- </tr>
- </tbody>
- </table>
- <div class="pagination">
- <span>共 3 条</span>
- <button type="button" class="pager-btn">10条/页</button>
- <button type="button" class="pager-btn">‹</button>
- <button type="button" class="pager-btn active">1</button>
- <button type="button" class="pager-btn">›</button>
- </div>
- </div>
- </section>
- </div>
- <div class="live-toast" id="liveToast" aria-live="polite">
- <div><strong>已触发即时检索</strong></div>
- <div class="muted" id="liveToastDetail">防抖 300ms · 页码重置为 1</div>
- </div>
- <script>
- const titles = {
- pending: '待办中心',
- done: '已办中心',
- mine: '我发起的',
- cc: '抄送我的'
- };
- const notes = {
- pending: '交互:条件变更 → 防抖 300ms → 自动检索。字段:<code>流程名称</code>· <code>申请人</code>· <code>申请时间</code>· <code>流程状态</code>',
- done: '交互:条件变更 → 防抖 300ms → 自动检索。字段:<code>流程名称</code>· <code>申请人</code>· <code>申请时间</code>· <code>流程状态</code>',
- mine: '交互:条件变更 → 防抖 300ms → 自动检索。字段:<code>流程名称</code>· <code>申请时间</code>· <code>流程状态</code>(不放申请人)',
- cc: '交互:条件变更 → 防抖 300ms → 自动检索。字段:现有 <code>阅读状态</code> + <code>流程名称</code>· <code>申请人</code>· <code>申请时间</code>· <code>流程状态</code>'
- };
- let toastTimer = null;
- let debounceTimer = null;
- let searchSeq = 0;
- function showLiveSearch(reason) {
- searchSeq += 1;
- const el = document.getElementById('liveToast');
- const detail = document.getElementById('liveToastDetail');
- detail.textContent = reason + ' · 防抖 300ms · 页码重置为 1 · #' + searchSeq;
- el.classList.add('show');
- clearTimeout(toastTimer);
- toastTimer = setTimeout(() => el.classList.remove('show'), 1600);
- }
- function scheduleLiveSearch(reason) {
- clearTimeout(debounceTimer);
- debounceTimer = setTimeout(() => showLiveSearch(reason), 300);
- }
- function applyTab(tab) {
- document.getElementById('panelTitle').textContent = titles[tab];
- document.getElementById('fieldNote').innerHTML = notes[tab];
- document.querySelectorAll('[data-only]').forEach((el) => {
- const keys = el.getAttribute('data-only').split(',');
- const show = keys.includes(tab);
- if (el.tagName === 'TH' || el.tagName === 'TD') {
- el.style.display = show ? 'table-cell' : 'none';
- } else {
- el.style.display = show ? '' : 'none';
- }
- });
- }
- document.querySelectorAll('.page-tab').forEach((btn) => {
- btn.addEventListener('click', () => {
- document.querySelectorAll('.page-tab').forEach((b) => b.classList.remove('active'));
- btn.classList.add('active');
- applyTab(btn.dataset.tab);
- });
- });
- document.querySelectorAll('.filter-form .input, .filter-form .select').forEach((el) => {
- const evt = el.tagName === 'SELECT' || el.type === 'date' ? 'change' : 'input';
- el.addEventListener(evt, () => {
- const label = el.closest('.filter-field')?.querySelector('label')?.textContent || '条件';
- scheduleLiveSearch('变更「' + label + '」');
- });
- });
- document.getElementById('btnReset').addEventListener('click', () => {
- document.querySelectorAll('.filter-form .input').forEach((el) => { el.value = ''; });
- document.querySelectorAll('.filter-form .select').forEach((el) => {
- el.selectedIndex = 0;
- });
- scheduleLiveSearch('重置条件');
- });
- document.getElementById('btnSearch').addEventListener('click', () => {
- clearTimeout(debounceTimer);
- showLiveSearch('点击「搜索」立即检索');
- });
- applyTab('pending');
- </script>
- </body>
- </html>
|