Files
zkxw/docs/demos/oa-start-history-demo.html
T
2026-08-28 10:56:24 +08:00

556 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OA 发起审批 · 历史记录 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--text: #334155;
--text-strong: #0f172a;
--text-muted: #94a3b8;
--border: #cbd5e1;
--bg: #f8fafc;
--danger: #ef4444;
--warning: #d97706;
--info: #64748b;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg);
}
.page { max-width: 1100px; margin: 0 auto; padding: 24px; }
.note {
margin-bottom: 16px;
padding: 12px 16px;
background: #fff;
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-muted);
font-size: 13px;
}
.note strong { color: var(--text-strong); }
.search-wrap { display: flex; justify-content: center; margin: 8px 0 20px; }
.search {
width: min(520px, 100%);
height: 42px;
padding: 0 16px 0 40px;
border: 1px solid var(--border);
border-radius: 999px;
background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85zm-5.242.656a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E") 14px center no-repeat;
box-shadow: 0 1px 2px rgb(15 23 42 / 6%);
outline: none;
}
.tabs { display: flex; gap: 28px; margin-bottom: 8px; }
.tab {
padding: 0 0 12px;
border: 0;
background: none;
font-size: 16px;
font-weight: 600;
color: var(--text);
cursor: pointer;
}
.tab.active {
color: var(--text-strong);
box-shadow: inset 0 -3px 0 var(--text-strong);
}
.hint { color: var(--text-muted); font-size: 13px; margin: 8px 0 20px; }
.section-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
}
.section-head h3 { margin: 0; font-size: 14px; font-weight: 600; }
.dot { width: 6px; height: 6px; border-radius: 50%; background: #f87171; }
.meta { margin-left: auto; color: var(--text-muted); font-size: 12px; }
.card-row { display: flex; flex-wrap: wrap; gap: 16px; }
.card {
display: flex;
flex-direction: column;
gap: 12px;
width: 280px;
min-height: 100px;
padding: 14px 16px 10px;
background: #fff;
border: 1px solid var(--border);
border-radius: 14px;
box-shadow: 0 2px 8px rgb(15 23 42 / 8%);
transition: border-color .15s, box-shadow .15s, transform .15s;
}
.card:hover {
border-color: #86efac;
box-shadow: 0 4px 14px rgb(16 185 129 / 16%);
transform: translateY(-1px);
}
.card-main { display: flex; gap: 14px; align-items: center; }
.icon {
width: 40px; height: 40px; border-radius: 10px;
background: linear-gradient(135deg, #34d399, #059669);
color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 14px;
flex-shrink: 0;
}
.icon.blue { background: linear-gradient(135deg, #60a5fa, #2563eb); }
.icon.amber { background: linear-gradient(135deg, #fbbf24, #d97706); }
.name { display: block; font-weight: 600; color: var(--text-strong); }
.desc { display: block; margin-top: 4px; font-size: 12px; color: var(--text-muted); }
.ops {
display: flex;
gap: 6px;
justify-content: flex-end;
padding-top: 8px;
border-top: 1px solid #f1f5f9;
}
.btn {
height: 26px;
padding: 0 10px;
border-radius: 5px;
border: 1px solid var(--border);
background: #fff;
color: var(--text);
font-size: 11px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 3px;
}
.btn:hover { border-color: #86efac; color: var(--primary-dark); }
.btn.primary {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.btn.primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
.btn.ghost-perm {
border-style: dashed;
color: var(--text-muted);
}
.legend {
margin-top: 28px;
padding: 14px 16px;
background: #fff;
border: 1px dashed #86efac;
border-radius: 10px;
font-size: 13px;
}
.legend code {
padding: 1px 6px;
background: var(--primary-bg);
border-radius: 4px;
color: var(--primary-dark);
font-size: 12px;
}
/* Dialog */
.mask {
position: fixed; inset: 0;
background: rgb(15 23 42 / 45%);
display: none;
align-items: flex-start;
justify-content: center;
padding: 40px 16px 24px;
z-index: 50;
overflow: auto;
}
.mask.open { display: flex; }
.dialog {
width: min(1080px, 100%);
max-height: calc(100vh - 80px);
background: #fff;
border-radius: 12px;
box-shadow: 0 20px 50px rgb(15 23 42 / 25%);
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-h {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #e2e8f0;
}
.dialog-h h2 {
margin: 0;
font-size: 16px;
color: var(--text-strong);
display: flex;
align-items: center;
gap: 8px;
}
.dialog-h h2::before {
content: "";
width: 3px; height: 16px;
background: var(--primary);
border-radius: 2px;
}
.close {
width: 28px; height: 28px; border: 0; background: transparent;
font-size: 18px; color: var(--text-muted); cursor: pointer; border-radius: 6px;
}
.close:hover { background: #f1f5f9; }
.dialog-b { padding: 16px 20px; overflow: auto; flex: 1; }
.filter {
padding: 14px 16px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 10px;
margin-bottom: 14px;
}
.filter-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 16px;
}
.field label {
display: block;
font-size: 11px;
color: var(--text-muted);
margin-bottom: 4px;
}
.field input, .field select {
width: 100%;
height: 36px;
padding: 0 10px;
border: 1px solid var(--border);
border-radius: 8px;
background: #fff;
font-size: 13px;
color: var(--text);
}
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
}
.btn-md {
height: 36px;
padding: 0 14px;
border-radius: 8px;
font-size: 13px;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-md.main {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th, td {
padding: 10px 8px;
text-align: center;
border-bottom: 1px solid #f1f5f9;
white-space: nowrap;
}
th {
color: var(--text-muted);
font-weight: 600;
background: #fafbfc;
font-size: 12px;
}
tr:nth-child(even) td { background: #fafbfc; }
tr:hover td { background: var(--primary-bg); }
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
line-height: 1.4;
}
.tag.ok { background: #ecfdf5; color: #059669; }
.tag.run { background: #ecfdf5; color: #047857; }
.tag.reject { background: #fef2f2; color: #dc2626; }
.tag.cancel { background: #f1f5f9; color: #64748b; }
.link {
color: var(--primary-dark);
background: none;
border: 0;
cursor: pointer;
font-size: 13px;
padding: 0 4px;
}
.pager {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #e2e8f0;
color: var(--text-muted);
font-size: 13px;
}
.pager .btn { height: 28px; }
.dyn-col { background: #f0fdf4 !important; color: #047857; }
.dyn-cell { color: var(--text-strong); }
@media (max-width: 900px) {
.filter-grid { grid-template-columns: 1fr 1fr; }
.card { width: 100%; }
}
</style>
</head>
<body>
<div class="page">
<div class="note">
<strong>Demo 说明:</strong>在「发起审批」流程卡片上,【发起】左侧增加【历史记录】(按钮始终显示;全员数据靠权限字符)。
点击有权限示意卡片上的「历史记录」可打开弹窗。下方虚线按钮仅示意「无全员权限时仍显示历史,但只看本人」。
</div>
<div class="search-wrap">
<input class="search" placeholder="请输入审批单名称" readonly />
</div>
<div class="tabs">
<button class="tab active" type="button">全部</button>
<button class="tab" type="button">人事</button>
<button class="tab" type="button">财务</button>
<button class="tab" type="button">行政</button>
</div>
<div class="hint">选择一个审批单即可发起 · 有权限时可查看该流程全员历史</div>
<div class="section-head">
<h3>人事</h3>
<span class="dot"></span>
<span class="meta">3 个表单</span>
</div>
<div class="card-row">
<!-- 有权限:历史记录 + 发起 -->
<div class="card">
<div class="card-main">
<div class="icon">假</div>
<div>
<span class="name">请假申请</span>
<span class="desc">事假 / 年假 / 病假等</span>
</div>
</div>
<div class="ops">
<button class="btn" type="button" onclick="openHistory('请假申请')">历史记录</button>
<button class="btn primary" type="button">发起</button>
</div>
</div>
<div class="card">
<div class="card-main">
<div class="icon blue">出</div>
<div>
<span class="name">出差申请</span>
<span class="desc">差旅事前审批</span>
</div>
</div>
<div class="ops">
<button class="btn" type="button" onclick="openHistory('出差申请')">历史记录</button>
<button class="btn primary" type="button">发起</button>
</div>
</div>
<!-- 无权限:仅发起 -->
<div class="card">
<div class="card-main">
<div class="icon amber">补</div>
<div>
<span class="name">补卡申请</span>
<span class="desc">无历史权限时不显示按钮</span>
</div>
</div>
<div class="ops">
<button class="btn ghost-perm" type="button" title="示意:无权限则不渲染此按钮" disabled>历史记录(隐藏)</button>
<button class="btn primary" type="button">发起</button>
</div>
</div>
</div>
<div class="legend">
<div><strong>交互点:</strong>点击「请假申请 / 出差申请」的 <code>历史记录</code> 打开弹窗</div>
<div style="margin-top:6px">弹窗内:筛选(流程名称只读、审批状态、审批 ID、发起/完成时间段;全员权限另有申请人)+ 列表(标准列 + 表单前 6 个业务字段)+ 分页;操作查看/打印</div>
</div>
</div>
<div class="mask" id="mask" onclick="onMask(event)">
<div class="dialog" role="dialog" aria-modal="true">
<div class="dialog-h">
<h2 id="dlgTitle">历史记录 · 请假申请</h2>
<button class="close" type="button" onclick="closeHistory()" aria-label="关闭">×</button>
</div>
<div class="dialog-b">
<div class="filter">
<div class="filter-grid">
<div class="field">
<label>流程名称</label>
<input id="fAppName" value="请假申请" />
</div>
<div class="field">
<label>审批状态</label>
<select>
<option value="">全部</option>
<option>进行中</option>
<option selected>已完成</option>
<option>已拒绝</option>
<option>已打回</option>
<option>已撤销</option>
</select>
</div>
<div class="field">
<label>审批 ID</label>
<input placeholder="请输入审批 ID" />
</div>
<div class="field">
<label>发起时间</label>
<input value="2026-08-01 至 2026-08-28" />
</div>
<div class="field">
<label>完成时间</label>
<input placeholder="YYYY-MM-DD 至 YYYY-MM-DD" />
</div>
</div>
<div class="filter-actions">
<button class="btn-md" type="button">重置</button>
<button class="btn-md main" type="button">搜索</button>
</div>
</div>
<div style="overflow:auto">
<table>
<thead>
<tr>
<th>序号</th>
<th>审批 ID</th>
<th>流程名称</th>
<th>申请人</th>
<th class="dyn-col">请假类型 ★</th>
<th class="dyn-col">开始日期 ★</th>
<th class="dyn-col">请假天数 ★</th>
<th class="dyn-col">结束日期 ★</th>
<th class="dyn-col">请假事由 ★</th>
<th class="dyn-col">附件说明 ★</th>
<th>发起时间</th>
<th>完成时间</th>
<th>审批状态</th>
<th>当前节点</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>a1b2c3d4-e5f6</td>
<td>请假申请</td>
<td>张三</td>
<td class="dyn-cell">年假</td>
<td class="dyn-cell">2026-09-01</td>
<td class="dyn-cell">2</td>
<td class="dyn-cell">2026-09-02</td>
<td class="dyn-cell">家庭事务</td>
<td class="dyn-cell">-</td>
<td>2026-08-28 09:12</td>
<td>2026-08-28 11:30</td>
<td><span class="tag ok">已完成</span></td>
<td>—</td>
<td><button class="link" type="button">查看</button></td>
</tr>
<tr>
<td>2</td>
<td>b2c3d4e5-f6a7</td>
<td>请假申请</td>
<td>李四</td>
<td class="dyn-cell">事假</td>
<td class="dyn-cell">2026-08-29</td>
<td class="dyn-cell">1</td>
<td class="dyn-cell">2026-08-29</td>
<td class="dyn-cell">外出办事</td>
<td class="dyn-cell">-</td>
<td>2026-08-27 14:05</td>
<td>—</td>
<td><span class="tag run">进行中</span></td>
<td>部门负责人</td>
<td><button class="link" type="button">查看</button></td>
</tr>
<tr>
<td>3</td>
<td>c3d4e5f6-a7b8</td>
<td>请假申请</td>
<td>王五</td>
<td class="dyn-cell">病假</td>
<td class="dyn-cell">2026-08-26</td>
<td class="dyn-cell">3</td>
<td class="dyn-cell">2026-08-28</td>
<td class="dyn-cell">就医</td>
<td class="dyn-cell">医院证明</td>
<td>2026-08-25 10:22</td>
<td>2026-08-25 16:40</td>
<td><span class="tag reject">已拒绝</span></td>
<td>—</td>
<td><button class="link" type="button">查看</button></td>
</tr>
<tr>
<td>4</td>
<td>d4e5f6a7-b8c9</td>
<td>请假申请</td>
<td>赵六</td>
<td class="dyn-cell">年假</td>
<td class="dyn-cell">2026-08-22</td>
<td class="dyn-cell">0.5</td>
<td class="dyn-cell">2026-08-22</td>
<td class="dyn-cell">休息</td>
<td class="dyn-cell">-</td>
<td>2026-08-20 08:50</td>
<td>2026-08-20 09:10</td>
<td><span class="tag cancel">已撤销</span></td>
<td>—</td>
<td><button class="link" type="button">查看</button></td>
</tr>
</tbody>
</table>
</div>
<div class="pager">
<span>共 28 条</span>
<button class="btn" type="button">上一页</button>
<span>第 1 / 3 页</span>
<button class="btn" type="button">下一页</button>
</div>
<p style="margin:14px 0 0;font-size:12px;color:var(--text-muted)">
★ 绿色表头为业务字段(表单前 6 列),插在「申请人」与「发起时间」之间;不同流程列名不同。审批 ID 为实例 id。
</p>
</div>
</div>
</div>
<script>
function openHistory(name) {
document.getElementById('dlgTitle').textContent = '历史记录 · ' + name;
document.getElementById('fAppName').value = name;
document.getElementById('mask').classList.add('open');
}
function closeHistory() {
document.getElementById('mask').classList.remove('open');
}
function onMask(e) {
if (e.target.id === 'mask') closeHistory();
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeHistory();
});
</script>
</body>
</html>