Files
shanxia2.0/bak/mobile-ui/shanxia-mp-pl-plan-webview-demo.html
T
wuzilongandCursor da7476df5a docs: record miniprogram WebView template and transport plan design.
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>
2026-09-14 15:50:12 +08:00

564 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.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>