Files
zkxw/docs/mockups/oa-related-approval-nested-demo.html
T
zlwuandCursor 51ce9d6059 docs(oa): add nested relatedApproval print design, plan, and demos
Records screen count+jump vs print multi-line recursion (max 3 levels)
and links the V1 related-approval spec to the revision.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 23:04:41 +08:00

418 lines
16 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>关联审批单 · 嵌套展示方案 Demo</title>
<style>
:root {
--primary: #10b981;
--primary-dark: #059669;
--primary-bg: #ecfdf5;
--primary-border: #a7f3d0;
--text: #334155;
--text-muted: #94a3b8;
--text-strong: #0f172a;
--border: #e2e8f0;
--bg: #f8fafc;
--panel: #ffffff;
}
* { box-sizing: border-box; }
body {
margin: 0;
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: var(--bg);
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 28px 24px 64px;
}
h1 {
margin: 0 0 8px;
font-size: 22px;
color: var(--text-strong);
}
.lead {
margin: 0 0 24px;
color: var(--text-muted);
max-width: 720px;
}
.scene {
margin-bottom: 12px;
padding: 12px 14px;
border: 1px dashed var(--primary-border);
border-radius: 10px;
background: #fff;
color: var(--text);
font-size: 13px;
}
.scene strong { color: var(--primary-dark); }
.options {
display: grid;
gap: 16px;
}
@media (min-width: 900px) {
.options { grid-template-columns: 1fr 1fr; }
}
.option {
border: 1px solid var(--border);
border-radius: 12px;
background: var(--panel);
overflow: hidden;
box-shadow: 0 1px 3px rgb(15 23 42 / 6%);
cursor: pointer;
transition: border-color .15s, box-shadow .15s;
}
.option:hover { border-color: var(--primary-border); }
.option.is-selected {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgb(16 185 129 / 25%);
}
.option-head {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 14px 16px 10px;
border-bottom: 1px solid var(--border);
background: #fafafa;
}
.badge {
flex-shrink: 0;
width: 28px;
height: 28px;
border-radius: 8px;
background: var(--primary-bg);
color: var(--primary-dark);
font-weight: 700;
font-size: 13px;
display: grid;
place-items: center;
}
.option.is-current .badge {
background: #fef3c7;
color: #b45309;
}
.option-meta h3 {
margin: 0 0 4px;
font-size: 15px;
color: var(--text-strong);
}
.option-meta p {
margin: 0;
font-size: 12.5px;
color: var(--text-muted);
}
.tag-now {
display: inline-block;
margin-left: 6px;
padding: 1px 6px;
border-radius: 4px;
background: #fef3c7;
color: #b45309;
font-size: 11px;
font-weight: 600;
vertical-align: middle;
}
.mock-body { padding: 14px 16px 16px; }
.field-label {
margin-bottom: 8px;
font-size: 12px;
color: var(--text-muted);
}
/* Related approval card (shared) */
.ra-card {
padding: 10px 12px;
border: 1px solid var(--primary-border);
border-radius: 8px;
background: var(--primary-bg);
}
.ra-head {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
}
.ra-title {
padding: 0;
border: 0;
background: transparent;
color: var(--primary);
font-weight: 600;
font-size: 13px;
text-decoration: underline;
cursor: pointer;
text-align: left;
}
.ra-toggle {
padding: 0;
border: 0;
background: transparent;
color: var(--primary);
font-size: 13px;
cursor: pointer;
}
.ra-toggle:hover, .ra-title:hover { color: var(--primary-dark); }
.ra-fields {
display: flex;
flex-wrap: wrap;
gap: 6px 16px;
margin-top: 8px;
font-size: 12.5px;
}
.ra-fields.collapsed .ra-f:nth-child(n+4) { display: none; }
.ra-f { display: inline-flex; gap: 6px; max-width: 100%; }
.ra-f-label { color: var(--text-muted); }
.ra-f-value { color: var(--text); word-break: break-all; }
.ra-f.muted .ra-f-value { color: var(--text-muted); font-style: italic; }
.ra-nested {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--primary-border);
display: none;
flex-direction: column;
gap: 8px;
}
.ra-nested.open { display: flex; }
.ra-card--nested {
background: #fff;
border-color: #d1fae5;
}
.ra-hint {
margin-top: 8px;
font-size: 12px;
color: var(--text-muted);
}
.pros {
margin: 12px 16px 16px;
padding: 10px 12px;
border-radius: 8px;
background: #f1f5f9;
font-size: 12.5px;
color: var(--text);
}
.pros strong { color: var(--primary-dark); }
.footer-note {
margin-top: 28px;
padding: 14px 16px;
border-radius: 10px;
background: #fff;
border: 1px solid var(--border);
font-size: 13px;
color: var(--text-muted);
}
.pick-bar {
position: sticky;
bottom: 0;
margin: 24px -24px -64px;
padding: 14px 24px;
background: rgb(255 255 255 / 92%);
backdrop-filter: blur(8px);
border-top: 1px solid var(--border);
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
}
.pick-bar span { color: var(--text-muted); font-size: 13px; }
.pick-bar em { color: var(--primary-dark); font-style: normal; font-weight: 600; }
</style>
</head>
<body>
<div class="page">
<h1>关联审批单 · 嵌套展示方案</h1>
<p class="lead">
场景:发起/详情里选中一张历史审批单 A;A 自己的表单里也有「关联审批单」字段(指向 B)。
当前 V1 规格把嵌套字段压成「已关联 M 条」,导致看不到缩略信息与展开。请点选你更倾向的方案(可点卡片内「展开」试交互)。
</p>
<div class="scene">
<strong>数据链:</strong>当前单 → 关联 <u>差旅报销 · 07-18</u>(单 A)→ 单 A 内又关联了 <u>外出申请 · 07-10</u>(单 B)
</div>
<div class="options" id="options">
<!-- A: current -->
<article class="option is-current" data-choice="a-status-quo" onclick="selectOption(this)">
<div class="option-head">
<div class="badge">A</div>
<div class="option-meta">
<h3>现状(V1 已定)<span class="tag-now">当前实现</span></h3>
<p>嵌套 relatedApproval 只显示「已关联 M 条」,不递归、无子卡片缩略</p>
</div>
</div>
<div class="mock-body">
<div class="field-label">关联审批单</div>
<div class="ra-card" data-stop>
<div class="ra-head">
<button type="button" class="ra-title">差旅报销 · 07-18</button>
<button type="button" class="ra-toggle" data-expand="a1">展开</button>
</div>
<div class="ra-fields collapsed" id="a1">
<span class="ra-f"><span class="ra-f-label">事由</span><span class="ra-f-value">客户拜访</span></span>
<span class="ra-f"><span class="ra-f-label">金额</span><span class="ra-f-value">¥1,280.00</span></span>
<span class="ra-f muted"><span class="ra-f-label">关联审批单</span><span class="ra-f-value">已关联 1 条</span></span>
<span class="ra-f"><span class="ra-f-label">备注</span><span class="ra-f-value">含打车费</span></span>
</div>
<div class="ra-hint">↑ 嵌套字段只有计数文案;即使点「展开」也看不到单 B 的业务字段</div>
</div>
</div>
<div class="pros"><strong>优点</strong>:实现简单、无递归性能风险。<strong>缺点</strong>:信息不足,正是你遇到的问题。</div>
</article>
<!-- B: jump only -->
<article class="option" data-choice="b-jump" onclick="selectOption(this)">
<div class="option-head">
<div class="badge">B</div>
<div class="option-meta">
<h3>计数 + 可跳转详情</h3>
<p>仍显示「已关联 M 条」,但做成可点链接,跳进被关联单详情查看</p>
</div>
</div>
<div class="mock-body">
<div class="field-label">关联审批单</div>
<div class="ra-card" data-stop>
<div class="ra-head">
<button type="button" class="ra-title">差旅报销 · 07-18</button>
<button type="button" class="ra-toggle" data-expand="b1">展开</button>
</div>
<div class="ra-fields collapsed" id="b1">
<span class="ra-f"><span class="ra-f-label">事由</span><span class="ra-f-value">客户拜访</span></span>
<span class="ra-f"><span class="ra-f-label">金额</span><span class="ra-f-value">¥1,280.00</span></span>
<span class="ra-f"><span class="ra-f-label">关联审批单</span><span class="ra-f-value"><a href="#" style="color:var(--primary)" onclick="event.preventDefault();alert('跳转到:外出申请详情')">已关联 1 条 → 查看</a></span></span>
<span class="ra-f"><span class="ra-f-label">备注</span><span class="ra-f-value">含打车费</span></span>
</div>
</div>
</div>
<div class="pros"><strong>优点</strong>:改动小、无嵌套 UI。<strong>缺点</strong>:仍无缩略预览,要跳页才能看清。</div>
</article>
<!-- C: one-level nested cards -->
<article class="option" data-choice="c-one-level" onclick="selectOption(this)">
<div class="option-head">
<div class="badge">C</div>
<div class="option-meta">
<h3>一层嵌套卡片(推荐倾向)</h3>
<p>展开单 A 后,嵌套关联展示为子卡片(应用名·时间 + 前 3 字段);再往下仍用计数,不无限递归</p>
</div>
</div>
<div class="mock-body">
<div class="field-label">关联审批单</div>
<div class="ra-card" data-stop>
<div class="ra-head">
<button type="button" class="ra-title">差旅报销 · 07-18</button>
<button type="button" class="ra-toggle" data-expand="c1" data-nested="c1n">展开</button>
</div>
<div class="ra-fields collapsed" id="c1">
<span class="ra-f"><span class="ra-f-label">事由</span><span class="ra-f-value">客户拜访</span></span>
<span class="ra-f"><span class="ra-f-label">金额</span><span class="ra-f-value">¥1,280.00</span></span>
<span class="ra-f"><span class="ra-f-label">备注</span><span class="ra-f-value">含打车费</span></span>
</div>
<div class="ra-nested" id="c1n">
<div class="field-label" style="margin:0">内含关联审批单 · 1 条</div>
<div class="ra-card ra-card--nested">
<div class="ra-head">
<button type="button" class="ra-title">外出申请 · 07-10</button>
<button type="button" class="ra-toggle" data-expand="c1b">展开</button>
</div>
<div class="ra-fields collapsed" id="c1b">
<span class="ra-f"><span class="ra-f-label">目的地</span><span class="ra-f-value">上海</span></span>
<span class="ra-f"><span class="ra-f-label">起止</span><span class="ra-f-value">07-10 ~ 07-12</span></span>
<span class="ra-f"><span class="ra-f-label">同行人</span><span class="ra-f-value">张三</span></span>
<span class="ra-f muted"><span class="ra-f-label">关联审批单</span><span class="ra-f-value">已关联 0 条</span></span>
</div>
</div>
</div>
</div>
</div>
<div class="pros"><strong>优点</strong>:有缩略与展开,信息完整可控。<strong>缺点</strong>:enrichment 需多取一层;UI 略复杂。</div>
</article>
<!-- D: deep recursive -->
<article class="option" data-choice="d-recursive" onclick="selectOption(this)">
<div class="option-head">
<div class="badge">D</div>
<div class="option-meta">
<h3>无限递归嵌套</h3>
<p>每一层关联都可以再展开子关联,形成树状</p>
</div>
</div>
<div class="mock-body">
<div class="field-label">关联审批单</div>
<div class="ra-card" data-stop>
<div class="ra-head">
<button type="button" class="ra-title">差旅报销 · 07-18</button>
<span class="ra-toggle" style="cursor:default;color:var(--text-muted)">已展开</span>
</div>
<div class="ra-fields">
<span class="ra-f"><span class="ra-f-label">事由</span><span class="ra-f-value">客户拜访</span></span>
</div>
<div class="ra-nested open">
<div class="ra-card ra-card--nested">
<div class="ra-head">
<button type="button" class="ra-title">外出申请 · 07-10</button>
</div>
<div class="ra-nested open" style="border-top:1px dashed #d1fae5;margin-top:8px;padding-top:8px">
<div class="ra-card ra-card--nested" style="opacity:.85">
<div class="ra-head">
<button type="button" class="ra-title">立项申请 · 06-01</button>
</div>
<div class="ra-hint">…可能继续嵌套</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="pros"><strong>优点</strong>:最完整。<strong>缺点</strong>:性能、权限、循环引用、UI 噪声都更大,一般不建议 V1.x。</div>
</article>
</div>
<div class="footer-note">
说明:V1 设计文档 §5 明确写了「嵌套 relatedApproval → 已关联 M 条(不递归展开)」。
你遇到的现象符合现规格,不是前端展开按钮单独坏了。接下来要决定是否修订规格(例如采用方案 C)。
文件路径:<code>docs/mockups/oa-related-approval-nested-demo.html</code>
</div>
<div class="pick-bar">
<span>当前选择:<em id="picked">尚未选择</em></span>
<span>也可直接在对话里回复 A / B / C / D</span>
</div>
</div>
<script>
function selectOption(el) {
document.querySelectorAll('.option').forEach((n) => n.classList.remove('is-selected'))
el.classList.add('is-selected')
const choice = el.getAttribute('data-choice')
const title = el.querySelector('h3')?.childNodes[0]?.textContent?.trim() || choice
document.getElementById('picked').textContent = title
window.__nestedChoice = choice
console.log('[demo] selected', choice)
}
document.querySelectorAll('[data-stop]').forEach((el) => {
el.addEventListener('click', (e) => e.stopPropagation())
})
document.querySelectorAll('[data-expand]').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation()
const id = btn.getAttribute('data-expand')
const fields = document.getElementById(id)
if (!fields) return
const collapsed = fields.classList.toggle('collapsed')
btn.textContent = collapsed ? '展开' : '收起'
const nestedId = btn.getAttribute('data-nested')
if (nestedId) {
document.getElementById(nestedId)?.classList.toggle('open', !collapsed)
}
})
})
</script>
</body>
</html>