Capture the approved card-list, ticket-based login, and reusable shell so later modules can follow the same path. Co-authored-by: Cursor <cursoragent@cursor.com>
564 lines
18 KiB
HTML
564 lines
18 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>山峡化工 · 运输计划(WebView H5 Demo)</title>
|
||
<style>
|
||
:root {
|
||
--brand: #0a9f6d;
|
||
--brand-dark: #087f57;
|
||
--brand-soft: #e9f9f2;
|
||
--barley: #d6a343;
|
||
--barley-soft: #fff7e8;
|
||
--bg: #f5f8f7;
|
||
--surface: #ffffff;
|
||
--text: #1f2a26;
|
||
--muted: #6b7b74;
|
||
--placeholder: #9aa8a1;
|
||
--border: #dcebe4;
|
||
--danger: #d14343;
|
||
--warning: #cf8a19;
|
||
--success: #189b65;
|
||
--radius: 14px;
|
||
--shadow: 0 6px 16px rgba(10, 159, 109, 0.08);
|
||
--header: linear-gradient(145deg, #087f57 0%, #0a9f6d 72%, #d6a343 165%);
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
background: #dfe8e3;
|
||
color: var(--text);
|
||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.45;
|
||
}
|
||
.stage {
|
||
max-width: 980px;
|
||
margin: 0 auto;
|
||
padding: 20px 16px 40px;
|
||
}
|
||
.banner {
|
||
background: #1f2a26;
|
||
color: #fff;
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 16px;
|
||
font-size: 13px;
|
||
}
|
||
.banner strong { color: #9fe3c4; }
|
||
.banner code {
|
||
background: rgba(255,255,255,.12);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||
gap: 18px;
|
||
align-items: start;
|
||
}
|
||
.phone {
|
||
max-width: 390px;
|
||
margin: 0 auto;
|
||
background: var(--bg);
|
||
border-radius: 28px;
|
||
overflow: hidden;
|
||
box-shadow: 0 18px 40px rgba(0,0,0,.14);
|
||
border: 8px solid #24312c;
|
||
min-height: 760px;
|
||
position: relative;
|
||
}
|
||
.phone-label {
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: #4d5c56;
|
||
margin-bottom: 8px;
|
||
font-weight: 600;
|
||
}
|
||
.status-bar {
|
||
height: 28px;
|
||
background: var(--brand-dark);
|
||
color: rgba(255,255,255,.85);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 16px;
|
||
font-size: 11px;
|
||
}
|
||
.header {
|
||
background: var(--header);
|
||
color: #fff;
|
||
padding: 10px 14px 14px;
|
||
}
|
||
.header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.back { opacity: .92; font-size: 13px; }
|
||
.header h1 { font-size: 20px; font-weight: 700; margin-top: 8px; }
|
||
.header .hint {
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
opacity: .85;
|
||
}
|
||
.search {
|
||
margin-top: 12px;
|
||
background: rgba(255,255,255,.95);
|
||
color: var(--placeholder);
|
||
border-radius: 999px;
|
||
padding: 10px 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
}
|
||
.search .dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: var(--brand);
|
||
flex-shrink: 0;
|
||
}
|
||
.date-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
overflow-x: auto;
|
||
padding: 10px 12px 0;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.chip {
|
||
flex: 0 0 auto;
|
||
padding: 6px 12px;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
.chip.active {
|
||
background: var(--brand-soft);
|
||
border-color: #b7ebce;
|
||
color: var(--brand-dark);
|
||
font-weight: 600;
|
||
}
|
||
.tabs {
|
||
display: flex;
|
||
gap: 6px;
|
||
overflow-x: auto;
|
||
padding: 10px 12px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.tab {
|
||
flex: 0 0 auto;
|
||
padding: 7px 12px;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
.tab.active {
|
||
background: var(--brand);
|
||
border-color: var(--brand);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
.toolbar {
|
||
display: flex;
|
||
gap: 8px;
|
||
padding: 0 12px 10px;
|
||
overflow-x: auto;
|
||
}
|
||
.btn {
|
||
flex: 0 0 auto;
|
||
height: 34px;
|
||
padding: 0 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
color: var(--text);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.btn-primary {
|
||
background: var(--brand);
|
||
border-color: var(--brand);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
.btn-danger {
|
||
color: var(--danger);
|
||
border-color: #f0c2c2;
|
||
background: #fff7f7;
|
||
}
|
||
.list {
|
||
padding: 0 12px 88px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.card {
|
||
background: var(--surface);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
padding: 12px 12px 10px;
|
||
border: 1px solid rgba(220,235,228,.8);
|
||
cursor: pointer;
|
||
}
|
||
.card:active { transform: scale(.995); }
|
||
.card-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
}
|
||
.plan-no {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: var(--brand-dark);
|
||
}
|
||
.sub {
|
||
margin-top: 2px;
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
}
|
||
.tag {
|
||
flex-shrink: 0;
|
||
font-size: 11px;
|
||
padding: 3px 8px;
|
||
border-radius: 999px;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
}
|
||
.tag-warn { background: #d97706; }
|
||
.tag-ok { background: #059669; }
|
||
.tag-mute { background: #64748b; }
|
||
.tag-wait { background: #cf8a19; }
|
||
.meta {
|
||
margin-top: 8px;
|
||
display: grid;
|
||
gap: 4px;
|
||
font-size: 12px;
|
||
color: var(--muted);
|
||
}
|
||
.meta b { color: var(--text); font-weight: 500; }
|
||
.products {
|
||
margin-top: 8px;
|
||
background: var(--brand-soft);
|
||
border-radius: 10px;
|
||
padding: 8px 10px;
|
||
font-size: 12px;
|
||
color: var(--brand-dark);
|
||
}
|
||
.card-foot {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.fab-bar {
|
||
position: absolute;
|
||
left: 0; right: 0; bottom: 0;
|
||
padding: 10px 12px calc(12px + env(safe-area-inset-bottom, 0px));
|
||
background: linear-gradient(180deg, transparent, var(--bg) 28%);
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.fab-bar .btn { flex: 1; justify-content: center; height: 42px; font-size: 13px; }
|
||
.fab-bar .btn-primary { flex: 1.35; }
|
||
|
||
/* detail */
|
||
.detail-body { padding: 12px; padding-bottom: 96px; }
|
||
.section {
|
||
background: #fff;
|
||
border-radius: var(--radius);
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
.section h3 {
|
||
font-size: 13px;
|
||
margin-bottom: 8px;
|
||
color: var(--brand-dark);
|
||
}
|
||
.kv { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; font-size: 13px; border-bottom: 1px dashed var(--border); }
|
||
.kv:last-child { border-bottom: none; }
|
||
.kv span:first-child { color: var(--muted); flex-shrink: 0; }
|
||
.kv span:last-child { text-align: right; }
|
||
.actions-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
.actions-grid .btn { height: 40px; justify-content: center; }
|
||
.note {
|
||
margin-top: 8px;
|
||
font-size: 11px;
|
||
color: var(--muted);
|
||
background: var(--barley-soft);
|
||
border-radius: 8px;
|
||
padding: 8px 10px;
|
||
}
|
||
.hidden { display: none !important; }
|
||
.choice-hint {
|
||
margin-top: 18px;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
border: 1px solid #cfe3d9;
|
||
font-size: 13px;
|
||
color: #355248;
|
||
}
|
||
.choice-hint ol { margin: 8px 0 0 18px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="stage">
|
||
<div class="banner">
|
||
<strong>运输计划 · 小程序 WebView H5 Demo</strong><br />
|
||
对齐管理端 <code>/#/pl/plan</code> 核心能力;小程序侧仅壳页 + <code>web-view</code> 加载本 H5,业务改动发 H5 即可,无需反复提审。<br />
|
||
文件路径:<code>bak/mobile-ui/shanxia-mp-pl-plan-webview-demo.html</code>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<!-- LIST -->
|
||
<div>
|
||
<div class="phone-label">① 列表页(默认)</div>
|
||
<div class="phone" id="phone-list">
|
||
<div class="status-bar"><span>09:41</span><span>WebView</span></div>
|
||
<header class="header">
|
||
<div class="header-row">
|
||
<span class="back">← 工作台</span>
|
||
<button class="btn btn-primary" type="button" style="height:30px">+ 新增</button>
|
||
</div>
|
||
<h1>运输计划</h1>
|
||
<div class="hint">登录态由小程序注入 Token · H5 调同一套 /api</div>
|
||
<div class="search"><span class="dot"></span>计划编号 / 供方 / 需方 / 产品 / 车牌号</div>
|
||
</header>
|
||
|
||
<div class="date-row">
|
||
<button class="chip active" type="button">今天</button>
|
||
<button class="chip" type="button">明天</button>
|
||
<button class="chip" type="button">本周</button>
|
||
<button class="chip" type="button">本月</button>
|
||
<button class="chip" type="button">自定义</button>
|
||
</div>
|
||
|
||
<div class="tabs" id="list-tabs">
|
||
<button class="tab active" type="button" data-tab="pending">待安排</button>
|
||
<button class="tab" type="button" data-tab="arranged">已安排</button>
|
||
<button class="tab" type="button" data-tab="draft">草稿中</button>
|
||
<button class="tab" type="button" data-tab="all">全部</button>
|
||
<button class="tab" type="button" data-tab="inbound">入库单</button>
|
||
<button class="tab" type="button" data-tab="outbound">出库单</button>
|
||
<button class="tab" type="button" data-tab="direct">直销单</button>
|
||
</div>
|
||
|
||
<div class="toolbar">
|
||
<button class="btn" type="button">复制</button>
|
||
<button class="btn" type="button">安排运输</button>
|
||
<button class="btn" type="button">等通知</button>
|
||
<button class="btn" type="button">打印</button>
|
||
<button class="btn btn-danger" type="button">作废</button>
|
||
</div>
|
||
|
||
<div class="list" id="plan-list">
|
||
<!-- cards injected -->
|
||
</div>
|
||
|
||
<div class="fab-bar">
|
||
<button class="btn" type="button">三方人车</button>
|
||
<button class="btn btn-primary" type="button" onclick="showDetail()">查看选中计划</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DETAIL -->
|
||
<div>
|
||
<div class="phone-label">② 详情 / 操作(点卡片进入)</div>
|
||
<div class="phone" id="phone-detail">
|
||
<div class="status-bar"><span>09:41</span><span>WebView</span></div>
|
||
<header class="header">
|
||
<div class="header-row">
|
||
<span class="back" style="cursor:pointer" onclick="showList()">← 列表</span>
|
||
<span class="tag tag-warn">待安排</span>
|
||
</div>
|
||
<h1>JH20260914003</h1>
|
||
<div class="hint">出库单 · 计划日期 2026-09-14</div>
|
||
</header>
|
||
|
||
<div class="detail-body">
|
||
<div class="section">
|
||
<h3>基本信息</h3>
|
||
<div class="kv"><span>跟进状态</span><span><b>待安排</b></span></div>
|
||
<div class="kv"><span>送货时间</span><span>09-14 14:00</span></div>
|
||
<div class="kv"><span>车牌号</span><span>皖H·D1234 / 挂 皖H·G88</span></div>
|
||
<div class="kv"><span>人员</span><span>司机 张伟 · 押运 李强</span></div>
|
||
<div class="kv"><span>跟进人</span><span>王调度</span></div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>产品摘要</h3>
|
||
<div class="products" style="margin:0">
|
||
甲醇 32 吨 · 包装吨袋<br />
|
||
供方:安庆山峡 · 需方:合肥某化
|
||
</div>
|
||
<div class="note">移动端以卡片摘要为主;完整编辑走「编辑计划」全屏表单(同管理端字段,分步/分区展示)。</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>快捷操作(对齐管理端工具栏)</h3>
|
||
<div class="actions-grid">
|
||
<button class="btn btn-primary" type="button">安排运输</button>
|
||
<button class="btn" type="button">编辑计划</button>
|
||
<button class="btn" type="button">复制计划</button>
|
||
<button class="btn" type="button">等客户通知</button>
|
||
<button class="btn" type="button">打印单据</button>
|
||
<button class="btn btn-danger" type="button">作废</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fab-bar">
|
||
<button class="btn" type="button" onclick="showList()">返回列表</button>
|
||
<button class="btn btn-primary" type="button">安排运输</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="choice-hint">
|
||
<strong>请确认 Demo 方向(回复编号即可)</strong>
|
||
<ol>
|
||
<li><b>A</b> — 列表用「卡片流」如左图(推荐,适合手机一屏信息)</li>
|
||
<li><b>B</b> — 列表尽量仿桌面表格(横滑多列,信息更全但操作更重)</li>
|
||
<li><b>C</b> — 先只做列表+查看+安排运输,其余打印/作废/三方人车二期再上</li>
|
||
</ol>
|
||
也可直接说要改哪里(Tab、按钮优先级、字段展示等)。
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const plans = [
|
||
{
|
||
no: 'JH20260914003',
|
||
type: '出库单',
|
||
status: '待安排',
|
||
tag: 'tag-warn',
|
||
date: '2026-09-14',
|
||
plate: '未派车',
|
||
people: '—',
|
||
product: '甲醇 32 吨 · 安庆山峡 → 合肥某化',
|
||
tab: 'pending'
|
||
},
|
||
{
|
||
no: 'JH20260914001',
|
||
type: '入库单',
|
||
status: '已安排',
|
||
tag: 'tag-ok',
|
||
date: '2026-09-14',
|
||
plate: '皖H·D1234',
|
||
people: '张伟 / 李强',
|
||
product: '乙醇 28 吨 · 张家港 → 安庆仓',
|
||
tab: 'arranged'
|
||
},
|
||
{
|
||
no: 'JH20260913018',
|
||
type: '直销单',
|
||
status: '等通知',
|
||
tag: 'tag-wait',
|
||
date: '2026-09-13',
|
||
plate: '苏E·5M892',
|
||
people: '陈师傅',
|
||
product: '冰醋酸 20 吨 · 南通 → 芜湖',
|
||
tab: 'pending'
|
||
},
|
||
{
|
||
no: 'JH20260912007',
|
||
type: '出库单',
|
||
status: '草稿中',
|
||
tag: 'tag-mute',
|
||
date: '2026-09-12',
|
||
plate: '—',
|
||
people: '—',
|
||
product: '待完善产品行',
|
||
tab: 'draft'
|
||
}
|
||
]
|
||
|
||
const listEl = document.getElementById('plan-list')
|
||
let activeTab = 'pending'
|
||
|
||
function renderList() {
|
||
const rows = activeTab === 'all'
|
||
? plans
|
||
: plans.filter((p) => {
|
||
if (activeTab === 'pending') return p.status === '待安排' || p.status === '等通知'
|
||
if (activeTab === 'arranged') return p.status === '已安排'
|
||
if (activeTab === 'draft') return p.status === '草稿中'
|
||
if (activeTab === 'inbound') return p.type === '入库单'
|
||
if (activeTab === 'outbound') return p.type === '出库单'
|
||
if (activeTab === 'direct') return p.type === '直销单'
|
||
return true
|
||
})
|
||
|
||
listEl.innerHTML = rows.map((p) => `
|
||
<article class="card" onclick="showDetail()">
|
||
<div class="card-head">
|
||
<div>
|
||
<div class="plan-no">${p.no}</div>
|
||
<div class="sub">${p.type} · ${p.date}</div>
|
||
</div>
|
||
<span class="tag ${p.tag}">${p.status}</span>
|
||
</div>
|
||
<div class="meta">
|
||
<div>车牌:<b>${p.plate}</b></div>
|
||
<div>人员:<b>${p.people}</b></div>
|
||
</div>
|
||
<div class="products">${p.product}</div>
|
||
<div class="card-foot">
|
||
<button class="btn" type="button" onclick="event.stopPropagation()">复制</button>
|
||
<button class="btn btn-primary" type="button" onclick="event.stopPropagation()">安排运输</button>
|
||
</div>
|
||
</article>
|
||
`).join('') || '<div style="padding:24px;text-align:center;color:#6b7b74">该 Tab 暂无数据</div>'
|
||
}
|
||
|
||
document.getElementById('list-tabs').addEventListener('click', (e) => {
|
||
const btn = e.target.closest('.tab')
|
||
if (!btn) return
|
||
document.querySelectorAll('#list-tabs .tab').forEach((t) => t.classList.remove('active'))
|
||
btn.classList.add('active')
|
||
activeTab = btn.dataset.tab
|
||
renderList()
|
||
})
|
||
|
||
document.querySelectorAll('.date-row .chip').forEach((chip) => {
|
||
chip.addEventListener('click', () => {
|
||
document.querySelectorAll('.date-row .chip').forEach((c) => c.classList.remove('active'))
|
||
chip.classList.add('active')
|
||
})
|
||
})
|
||
|
||
function showDetail() {
|
||
document.getElementById('phone-list').classList.add('hidden')
|
||
document.getElementById('phone-detail').classList.remove('hidden')
|
||
// on dual layout keep both; on narrow, scroll to detail
|
||
document.getElementById('phone-detail').scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||
}
|
||
function showList() {
|
||
document.getElementById('phone-list').classList.remove('hidden')
|
||
document.getElementById('phone-list').scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||
}
|
||
|
||
renderList()
|
||
</script>
|
||
</body>
|
||
</html>
|