702 lines
20 KiB
HTML
702 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||
<title>报销申请 · 发票附件选择 Demo(对齐 Web)</title>
|
||
<style>
|
||
:root {
|
||
--primary: #00c98d;
|
||
--primary-dark: #00a874;
|
||
--primary-bg: #eafff6;
|
||
--bg: #f4f7f6;
|
||
--surface: #fff;
|
||
--text: #1e293b;
|
||
--text-sec: #64748b;
|
||
--text-muted: #94a3b8;
|
||
--border: #e2e8f0;
|
||
--danger: #ef4444;
|
||
--radius: 12px;
|
||
--phone-w: 375px;
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
min-height: 100vh;
|
||
background: #dfe8e4;
|
||
color: var(--text);
|
||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
padding: 24px 16px 48px;
|
||
}
|
||
|
||
.page-intro {
|
||
max-width: 920px;
|
||
margin: 0 auto 20px;
|
||
color: #334155;
|
||
}
|
||
.page-intro h1 { font-size: 18px; margin-bottom: 6px; }
|
||
.page-intro p { font-size: 13px; color: #64748b; }
|
||
.page-intro code {
|
||
font-size: 12px;
|
||
background: #fff;
|
||
border: 1px solid #cbd5e1;
|
||
border-radius: 4px;
|
||
padding: 1px 5px;
|
||
}
|
||
|
||
.stage {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 24px;
|
||
justify-content: center;
|
||
align-items: flex-start;
|
||
max-width: 920px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.phone {
|
||
width: var(--phone-w);
|
||
background: var(--bg);
|
||
border-radius: 28px;
|
||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.12);
|
||
overflow: hidden;
|
||
border: 8px solid #1e293b;
|
||
position: relative;
|
||
min-height: 720px;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(145deg, #008f62 0%, #00c98d 70%, #34d399 160%);
|
||
color: #fff;
|
||
padding: 16px 16px 20px;
|
||
border-radius: 0 0 20px 20px;
|
||
}
|
||
.header__back { font-size: 13px; opacity: 0.95; }
|
||
.header__title { margin-top: 10px; font-size: 20px; font-weight: 700; }
|
||
|
||
.body { padding: 12px 12px 88px; }
|
||
|
||
.card {
|
||
background: var(--surface);
|
||
border-radius: var(--radius);
|
||
padding: 14px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
|
||
}
|
||
.card h3 {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.tip {
|
||
font-size: 13px;
|
||
color: var(--text-sec);
|
||
margin-bottom: 10px;
|
||
line-height: 1.5;
|
||
}
|
||
.tip b { color: var(--primary-dark); }
|
||
.form-tip { margin-top: 8px; font-size: 12px; color: var(--text-muted); }
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
height: 36px;
|
||
padding: 0 14px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
.btn-block { width: 100%; height: 44px; }
|
||
.btn-ghost {
|
||
background: #f8fafc;
|
||
color: var(--text-sec);
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
/* 已选:附件卡片横向滚动(对齐 Web 缩略图语义) */
|
||
.picked-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
margin-top: 12px;
|
||
padding-bottom: 4px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.picked-card {
|
||
flex: 0 0 120px;
|
||
width: 120px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.picked-card__remove {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
z-index: 2;
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: rgba(15, 23, 42, 0.55);
|
||
color: #fff;
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
}
|
||
.picked-card__preview {
|
||
height: 80px;
|
||
background: #f1f5f9;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
.picked-card__preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
.picked-card__icon {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
text-align: center;
|
||
padding: 8px;
|
||
}
|
||
.picked-card__meta { padding: 8px; }
|
||
.picked-card__name {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.picked-card__amount {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: absolute;
|
||
left: 0; right: 0; bottom: 0;
|
||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||
background: #fff;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
/* Sheet */
|
||
.mask {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(15, 23, 42, 0.45);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s;
|
||
z-index: 20;
|
||
}
|
||
.mask.show { opacity: 1; pointer-events: auto; }
|
||
|
||
.sheet {
|
||
position: absolute;
|
||
left: 0; right: 0; bottom: 0;
|
||
height: 72%;
|
||
background: #fff;
|
||
border-radius: 16px 16px 0 0;
|
||
z-index: 30;
|
||
transform: translateY(105%);
|
||
transition: transform 0.25s ease;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.sheet.show { transform: translateY(0); }
|
||
|
||
.sheet__title {
|
||
padding: 14px 16px;
|
||
text-align: center;
|
||
font-weight: 600;
|
||
border-bottom: 1px solid var(--border);
|
||
position: relative;
|
||
}
|
||
.sheet__close {
|
||
position: absolute;
|
||
right: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 32px;
|
||
height: 32px;
|
||
color: var(--text-muted);
|
||
font-size: 20px;
|
||
}
|
||
|
||
.sheet__body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 12px 12px 8px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.month-block { margin-bottom: 18px; }
|
||
.month-block__title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
margin: 0 0 10px 4px;
|
||
}
|
||
|
||
.attach-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
.attach-card {
|
||
position: relative;
|
||
border: 1.5px solid var(--border);
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
padding: 0;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
overflow: hidden;
|
||
}
|
||
.attach-card.is-selected {
|
||
border-color: var(--primary);
|
||
box-shadow: 0 0 0 1px var(--primary);
|
||
}
|
||
.attach-card__index {
|
||
position: absolute;
|
||
top: 4px;
|
||
left: 4px;
|
||
z-index: 1;
|
||
min-width: 18px;
|
||
height: 18px;
|
||
padding: 0 4px;
|
||
border-radius: 9px;
|
||
background: rgba(15, 23, 42, 0.5);
|
||
color: #fff;
|
||
font-size: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.attach-card__check {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
z-index: 1;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: var(--primary);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.attach-card.is-selected .attach-card__check { display: flex; }
|
||
|
||
.attach-card__preview {
|
||
height: 72px;
|
||
background: #f1f5f9;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
.attach-card__preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
.attach-card__doc {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
padding: 6px;
|
||
text-align: center;
|
||
}
|
||
.attach-card__meta { padding: 6px 8px 8px; }
|
||
.attach-card__name {
|
||
display: block;
|
||
font-size: 11px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.attach-card__amount {
|
||
margin-top: 2px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.sheet__footer {
|
||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
/* 右侧说明面板 */
|
||
.side {
|
||
flex: 1;
|
||
min-width: 280px;
|
||
max-width: 420px;
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
padding: 16px;
|
||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||
}
|
||
.side h2 { font-size: 15px; margin-bottom: 8px; }
|
||
.side ol { padding-left: 18px; font-size: 13px; color: #475569; }
|
||
.side li { margin: 6px 0; }
|
||
.side pre {
|
||
margin-top: 12px;
|
||
background: #0f172a;
|
||
color: #e2e8f0;
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
font-size: 11px;
|
||
line-height: 1.55;
|
||
overflow: auto;
|
||
min-height: 160px;
|
||
}
|
||
.legend {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
margin: 12px 0 4px;
|
||
}
|
||
.pill {
|
||
font-size: 11px;
|
||
padding: 4px 8px;
|
||
border-radius: 999px;
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
border: 1px solid #a7f3d0;
|
||
}
|
||
.empty {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
padding: 8px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page-intro">
|
||
<h1>报销申请 · 从发票夹选择附件(移动端 Demo)</h1>
|
||
<p>
|
||
对齐 Web:选择粒度为 <code>invoiceId + attachmentId</code>;一张发票多附件可分别勾选。
|
||
打开文件:<code>mobile-ui/shanxia-mp-bx-reimburse-invoice-picker-demo.html</code>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="stage">
|
||
<div class="phone" id="phone">
|
||
<header class="header">
|
||
<div class="header__back">← 返回</div>
|
||
<h1 class="header__title">报销申请</h1>
|
||
</header>
|
||
|
||
<div class="body">
|
||
<div class="card">
|
||
<h3>报销明细(示意)</h3>
|
||
<p class="tip">差旅费 · 合计 <b>¥1,280.00</b></p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3>关联发票(可选)</h3>
|
||
<p class="tip">
|
||
发票余额 <b>¥3,500.00</b> · 本次合计 <b>¥1,280.00</b>
|
||
</p>
|
||
<button type="button" class="btn btn-primary" id="btnOpen">从发票夹选择</button>
|
||
|
||
<div id="picked" class="picked-row" hidden></div>
|
||
<p class="form-tip">请先在「我的发票夹」上传发票;提交时按合计扣减余额。选择对象为附件文件。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bottom-bar">
|
||
<button type="button" class="btn btn-primary btn-block" id="btnSave">保存</button>
|
||
</div>
|
||
|
||
<div class="mask" id="mask"></div>
|
||
<div class="sheet" id="sheet" role="dialog" aria-label="选择发票附件">
|
||
<div class="sheet__title">
|
||
选择发票附件
|
||
<button type="button" class="sheet__close" id="btnClose" aria-label="关闭">×</button>
|
||
</div>
|
||
<div class="sheet__body" id="sheetBody"></div>
|
||
<div class="sheet__footer">
|
||
<button type="button" class="btn btn-primary btn-block" id="btnConfirm">
|
||
确定(已选 <span id="selCount">0</span>)
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<aside class="side">
|
||
<h2>交互说明</h2>
|
||
<div class="legend">
|
||
<span class="pill">粒度 = 附件</span>
|
||
<span class="pill">key = invoiceId:attachmentId</span>
|
||
<span class="pill">金额 = 整票金额</span>
|
||
</div>
|
||
<ol>
|
||
<li>点「从发票夹选择」打开底部 sheet。</li>
|
||
<li>按月份分组,3 列附件缩略图(图片 / PDF)。</li>
|
||
<li>同一发票的多附件可独立勾选。</li>
|
||
<li>确定后表单区横向展示已选附件卡片。</li>
|
||
<li>提交 payload 见下方 JSON(实时更新)。</li>
|
||
</ol>
|
||
<h2 style="margin-top:16px">提交 payload 预览</h2>
|
||
<pre id="payload">{}</pre>
|
||
</aside>
|
||
</div>
|
||
|
||
<script>
|
||
/**
|
||
* Mock:一次上传 = 1 张发票(一个金额)+ N 个附件
|
||
* 与后端 InvoiceService.upload_with_files / out_rows_with_attachments 一致
|
||
*/
|
||
const invoices = [
|
||
{
|
||
id: 'inv-202608-01',
|
||
amount: 800,
|
||
remark: '出差住宿',
|
||
createTime: '2026-08-07 14:20:00',
|
||
attachments: [
|
||
{ id: 'att-a1', fileName: '酒店发票.jpg', fileType: 'image/jpeg', previewHue: 160 },
|
||
{ id: 'att-a2', fileName: '房费明细.pdf', fileType: 'application/pdf' }
|
||
]
|
||
},
|
||
{
|
||
id: 'inv-202608-02',
|
||
amount: 480,
|
||
remark: '高铁票',
|
||
createTime: '2026-08-05 09:10:00',
|
||
attachments: [
|
||
{ id: 'att-b1', fileName: '高铁电子客票.jpg', fileType: 'image/jpeg', previewHue: 200 }
|
||
]
|
||
},
|
||
{
|
||
id: 'inv-202607-01',
|
||
amount: 1200,
|
||
remark: '客户招待',
|
||
createTime: '2026-07-28 18:40:00',
|
||
attachments: [
|
||
{ id: 'att-c1', fileName: '餐费发票-1.jpg', fileType: 'image/jpeg', previewHue: 40 },
|
||
{ id: 'att-c2', fileName: '餐费发票-2.jpg', fileType: 'image/jpeg', previewHue: 25 },
|
||
{ id: 'att-c3', fileName: '菜单拍照.jpg', fileType: 'image/jpeg', previewHue: 10 }
|
||
]
|
||
}
|
||
]
|
||
|
||
function flattenAttachItems(rows) {
|
||
const items = []
|
||
rows.forEach((row) => {
|
||
;(row.attachments || []).forEach((file) => {
|
||
items.push({
|
||
key: `${row.id}:${file.id}`,
|
||
invoiceId: row.id,
|
||
attachmentId: file.id,
|
||
amount: row.amount,
|
||
createTime: row.createTime,
|
||
remark: row.remark || '',
|
||
file
|
||
})
|
||
})
|
||
})
|
||
return items
|
||
}
|
||
|
||
function monthKey(raw) {
|
||
const m = String(raw || '').match(/^(\d{4})-(\d{2})/)
|
||
return m ? `${m[1]}-${m[2]}` : 'unknown'
|
||
}
|
||
|
||
function monthLabel(key) {
|
||
if (key === 'unknown') return '未知月份'
|
||
const [y, m] = key.split('-')
|
||
return `${y}年${Number(m)}月`
|
||
}
|
||
|
||
function isImage(ft) {
|
||
return String(ft || '').startsWith('image/')
|
||
}
|
||
|
||
function previewBlock(file) {
|
||
if (isImage(file.fileType)) {
|
||
const hue = file.previewHue || 150
|
||
// 占位图:纯色渐变模拟发票缩略图
|
||
return `<div class="attach-card__preview" style="background:linear-gradient(135deg,hsl(${hue} 55% 70%),hsl(${hue} 40% 45%))"></div>`
|
||
}
|
||
return `<div class="attach-card__preview"><div class="attach-card__doc">PDF<br/>${escapeHtml(file.fileName)}</div></div>`
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
}
|
||
|
||
const allItems = flattenAttachItems(invoices)
|
||
/** @type {Set<string>} */
|
||
let draftKeys = new Set()
|
||
/** @type {Set<string>} */
|
||
let selectedKeys = new Set()
|
||
|
||
const sheet = document.getElementById('sheet')
|
||
const mask = document.getElementById('mask')
|
||
const sheetBody = document.getElementById('sheetBody')
|
||
const selCount = document.getElementById('selCount')
|
||
const picked = document.getElementById('picked')
|
||
const payloadEl = document.getElementById('payload')
|
||
|
||
function openSheet() {
|
||
draftKeys = new Set(selectedKeys)
|
||
renderSheet()
|
||
mask.classList.add('show')
|
||
sheet.classList.add('show')
|
||
}
|
||
|
||
function closeSheet() {
|
||
mask.classList.remove('show')
|
||
sheet.classList.remove('show')
|
||
}
|
||
|
||
function groupByMonth(items) {
|
||
const map = new Map()
|
||
items.forEach((item) => {
|
||
const k = monthKey(item.createTime)
|
||
if (!map.has(k)) map.set(k, [])
|
||
map.get(k).push(item)
|
||
})
|
||
return Array.from(map.entries())
|
||
.sort((a, b) => b[0].localeCompare(a[0]))
|
||
.map(([k, list]) => ({
|
||
key: k,
|
||
label: monthLabel(k),
|
||
items: list
|
||
.slice()
|
||
.sort((a, b) => String(b.createTime).localeCompare(String(a.createTime)))
|
||
}))
|
||
}
|
||
|
||
function renderSheet() {
|
||
const groups = groupByMonth(allItems)
|
||
let html = ''
|
||
groups.forEach((g) => {
|
||
html += `<section class="month-block"><h4 class="month-block__title">${g.label}</h4><div class="attach-grid">`
|
||
g.items.forEach((item, idx) => {
|
||
const selected = draftKeys.has(item.key) ? ' is-selected' : ''
|
||
html += `
|
||
<button type="button" class="attach-card${selected}" data-key="${item.key}">
|
||
<span class="attach-card__index">${idx + 1}</span>
|
||
<span class="attach-card__check">✓</span>
|
||
${previewBlock(item.file)}
|
||
<div class="attach-card__meta">
|
||
<span class="attach-card__name" title="${escapeHtml(item.file.fileName)}">${escapeHtml(item.file.fileName)}</span>
|
||
<div class="attach-card__amount">¥${Number(item.amount).toFixed(2)}</div>
|
||
</div>
|
||
</button>`
|
||
})
|
||
html += `</div></section>`
|
||
})
|
||
sheetBody.innerHTML = html || '<p class="empty">暂无发票附件</p>'
|
||
selCount.textContent = String(draftKeys.size)
|
||
|
||
sheetBody.querySelectorAll('.attach-card').forEach((el) => {
|
||
el.addEventListener('click', () => {
|
||
const key = el.getAttribute('data-key')
|
||
if (draftKeys.has(key)) draftKeys.delete(key)
|
||
else draftKeys.add(key)
|
||
renderSheet()
|
||
})
|
||
})
|
||
}
|
||
|
||
function buildPayload() {
|
||
const picks = allItems.filter((i) => selectedKeys.has(i.key))
|
||
const invoiceLinks = picks.map((p) => ({
|
||
invoiceId: p.invoiceId,
|
||
attachmentId: p.attachmentId
|
||
}))
|
||
const invoiceIds = Array.from(new Set(invoiceLinks.map((p) => p.invoiceId)))
|
||
return { invoiceIds, invoiceLinks }
|
||
}
|
||
|
||
function renderPicked() {
|
||
const picks = allItems
|
||
.filter((i) => selectedKeys.has(i.key))
|
||
.sort((a, b) => String(b.createTime).localeCompare(String(a.createTime)))
|
||
|
||
if (!picks.length) {
|
||
picked.hidden = true
|
||
picked.innerHTML = ''
|
||
} else {
|
||
picked.hidden = false
|
||
picked.innerHTML = picks
|
||
.map((item) => {
|
||
const preview = isImage(item.file.fileType)
|
||
? `<div class="picked-card__preview" style="background:linear-gradient(135deg,hsl(${item.file.previewHue || 150} 55% 70%),hsl(${item.file.previewHue || 150} 40% 45%))"></div>`
|
||
: `<div class="picked-card__preview"><div class="picked-card__icon">PDF</div></div>`
|
||
return `
|
||
<div class="picked-card" data-key="${item.key}">
|
||
<button type="button" class="picked-card__remove" data-key="${item.key}" aria-label="移除">×</button>
|
||
${preview}
|
||
<div class="picked-card__meta">
|
||
<span class="picked-card__name">${escapeHtml(item.file.fileName)}</span>
|
||
<div class="picked-card__amount">¥${Number(item.amount).toFixed(2)}</div>
|
||
</div>
|
||
</div>`
|
||
})
|
||
.join('')
|
||
picked.querySelectorAll('.picked-card__remove').forEach((btn) => {
|
||
btn.addEventListener('click', () => {
|
||
selectedKeys.delete(btn.getAttribute('data-key'))
|
||
renderPicked()
|
||
renderPayload()
|
||
})
|
||
})
|
||
}
|
||
renderPayload()
|
||
}
|
||
|
||
function renderPayload() {
|
||
payloadEl.textContent = JSON.stringify(buildPayload(), null, 2)
|
||
}
|
||
|
||
document.getElementById('btnOpen').addEventListener('click', openSheet)
|
||
document.getElementById('btnClose').addEventListener('click', closeSheet)
|
||
mask.addEventListener('click', closeSheet)
|
||
document.getElementById('btnConfirm').addEventListener('click', () => {
|
||
selectedKeys = new Set(draftKeys)
|
||
closeSheet()
|
||
renderPicked()
|
||
})
|
||
document.getElementById('btnSave').addEventListener('click', () => {
|
||
alert('Demo:将提交右侧 payload\n\n' + JSON.stringify(buildPayload(), null, 2))
|
||
})
|
||
|
||
renderPayload()
|
||
</script>
|
||
</body>
|
||
</html>
|