588 lines
14 KiB
HTML
588 lines
14 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</title>
|
||
<style>
|
||
:root {
|
||
--green: #059669;
|
||
--green-soft: #ecfdf5;
|
||
--green-border: #a7f3d0;
|
||
--text: #0f172a;
|
||
--muted: #64748b;
|
||
--border: #e2e8f0;
|
||
--page-bg: #f1f5f9;
|
||
--danger: #dc2626;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||
background: #cbd5e1;
|
||
color: var(--text);
|
||
}
|
||
|
||
.frame {
|
||
max-width: 390px;
|
||
margin: 24px auto;
|
||
background: var(--page-bg);
|
||
border-radius: 24px;
|
||
overflow: hidden;
|
||
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.18);
|
||
min-height: 720px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.note {
|
||
max-width: 390px;
|
||
margin: 0 auto 12px;
|
||
padding: 0 8px;
|
||
font-size: 13px;
|
||
color: #334155;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.note code {
|
||
font-size: 12px;
|
||
background: #e2e8f0;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.nav {
|
||
background: var(--green);
|
||
color: #fff;
|
||
height: 46px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.nav .back {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
text-decoration: none;
|
||
font-size: 22px;
|
||
line-height: 1;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.body {
|
||
padding: 12px 16px 24px;
|
||
flex: 1;
|
||
}
|
||
|
||
.calendar-card {
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 8px 8px 6px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.cal-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 4px;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.cal-title {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--text);
|
||
}
|
||
|
||
.cal-nav-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
color: var(--muted);
|
||
font-size: 15px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.cal-nav-btn:active {
|
||
background: var(--green-soft);
|
||
border-color: var(--green-border);
|
||
color: var(--green);
|
||
}
|
||
|
||
.weekday {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 0;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.weekday span {
|
||
text-align: center;
|
||
font-size: 11px;
|
||
color: var(--muted);
|
||
font-weight: 600;
|
||
padding: 2px 0;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.days {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 2px 0;
|
||
}
|
||
|
||
.day {
|
||
height: 28px;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.day.muted {
|
||
color: #cbd5e1;
|
||
cursor: default;
|
||
}
|
||
|
||
.day.today:not(.selected)::after {
|
||
content: "";
|
||
position: absolute;
|
||
bottom: 2px;
|
||
width: 3px;
|
||
height: 3px;
|
||
border-radius: 50%;
|
||
background: var(--green);
|
||
}
|
||
|
||
.day.selected {
|
||
background: var(--green);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.day:not(.muted):not(.selected):hover {
|
||
background: var(--green-soft);
|
||
}
|
||
|
||
.selected-hint {
|
||
margin-top: 6px;
|
||
text-align: center;
|
||
font-size: 11px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.selected-hint strong {
|
||
color: var(--green);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.record-card {
|
||
position: relative;
|
||
margin-bottom: 10px;
|
||
padding: 16px 14px 14px;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
.index-tag {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
min-width: 24px;
|
||
height: 22px;
|
||
padding: 0 8px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 12px 0 10px 0;
|
||
background: var(--green);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.record-top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
padding-left: 4px;
|
||
}
|
||
|
||
.plan-no {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tag {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
color: #fff;
|
||
background: var(--green);
|
||
}
|
||
|
||
.tag.danger { background: var(--danger); }
|
||
|
||
.meta {
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.meta + .meta { margin-top: 4px; }
|
||
|
||
.empty {
|
||
text-align: center;
|
||
padding: 40px 16px;
|
||
color: var(--muted);
|
||
font-size: 14px;
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.compare {
|
||
max-width: 820px;
|
||
margin: 16px auto 40px;
|
||
padding: 0 12px;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.compare { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.compare h2 {
|
||
grid-column: 1 / -1;
|
||
font-size: 15px;
|
||
margin: 8px 0 0;
|
||
color: #334155;
|
||
}
|
||
|
||
.mini-phone {
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
border: 1px solid #94a3b8;
|
||
overflow: hidden;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.mini-phone .bar {
|
||
background: var(--green);
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 8px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.mini-phone .pad { padding: 10px; background: var(--page-bg); }
|
||
|
||
.old-filters {
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 8px;
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: center;
|
||
}
|
||
|
||
.old-field {
|
||
flex: 1;
|
||
border: 1px solid #cbd5e1;
|
||
border-radius: 6px;
|
||
padding: 8px;
|
||
color: #1e293b;
|
||
background: #fff;
|
||
}
|
||
|
||
.chip {
|
||
height: 28px;
|
||
padding: 0 8px;
|
||
border-radius: 999px;
|
||
border: 1px solid #d1fae5;
|
||
background: #ecfdf5;
|
||
color: #047857;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.chip.on {
|
||
background: var(--green);
|
||
border-color: var(--green);
|
||
color: #fff;
|
||
}
|
||
|
||
.caption {
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
margin-top: 8px;
|
||
padding-bottom: 4px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<p class="note">
|
||
交互 Demo:顶部内嵌月历,默认当天,点日期即时过滤,左右切换月份。
|
||
文件路径:<code>mobile-ui/shanxia-mp-pl-transport-history-calendar-demo.html</code>
|
||
</p>
|
||
|
||
<div class="frame" id="app">
|
||
<div class="nav">
|
||
<a class="back" href="javascript:void(0)" aria-label="返回">‹</a>
|
||
运输记录
|
||
</div>
|
||
<div class="body">
|
||
<div class="calendar-card">
|
||
<div class="cal-head">
|
||
<button class="cal-nav-btn" type="button" id="prevMonth" aria-label="上一月">‹</button>
|
||
<div class="cal-title" id="calTitle">2026年8月</div>
|
||
<button class="cal-nav-btn" type="button" id="nextMonth" aria-label="下一月">›</button>
|
||
</div>
|
||
<div class="weekday">
|
||
<span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>
|
||
</div>
|
||
<div class="days" id="days"></div>
|
||
<div class="selected-hint">当前筛选:<strong id="selectedLabel">今天</strong></div>
|
||
</div>
|
||
|
||
<div id="list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="compare">
|
||
<h2>改前 / 改后对比(示意)</h2>
|
||
<div>
|
||
<div class="mini-phone">
|
||
<div class="bar">运输记录(改前)</div>
|
||
<div class="pad">
|
||
<div class="old-filters">
|
||
<div class="old-field">2026-08-21 ›</div>
|
||
<span class="chip on">今日</span>
|
||
<span class="chip">明日</span>
|
||
<span class="chip">后天</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="caption">日期输入框 + 今日/明日/后天快捷</div>
|
||
</div>
|
||
<div>
|
||
<div class="mini-phone">
|
||
<div class="bar">运输记录(改后)</div>
|
||
<div class="pad">
|
||
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:8px;text-align:center;color:#64748b">
|
||
顶部完整月历 · 可切月 · 点选即过滤
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="caption">去掉快捷 chips,日历常驻顶部</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
const MOCK = {
|
||
'2026-08-21': [
|
||
{
|
||
planNo: 'PL-20260821-01',
|
||
status: '进行中',
|
||
pickup: '2026-08-21',
|
||
delivery: '2026-08-21',
|
||
route: '张家港仓库 → 南通港区'
|
||
},
|
||
{
|
||
planNo: 'PL-20260820-18',
|
||
status: '进行中',
|
||
pickup: '2026-08-20',
|
||
delivery: '2026-08-21',
|
||
route: '上海石化 → 合肥仓'
|
||
}
|
||
],
|
||
'2026-08-20': [
|
||
{
|
||
planNo: 'PL-20260820-07',
|
||
status: '已归档',
|
||
pickup: '2026-08-20',
|
||
delivery: '2026-08-20',
|
||
route: '太仓码头 → 常州厂区'
|
||
}
|
||
],
|
||
'2026-08-22': [
|
||
{
|
||
planNo: 'PL-20260822-03',
|
||
status: '进行中',
|
||
pickup: '2026-08-22',
|
||
delivery: '2026-08-23',
|
||
route: '江阴仓 → 南京港'
|
||
}
|
||
],
|
||
'2026-07-15': [
|
||
{
|
||
planNo: 'PL-20260715-02',
|
||
status: '已作废',
|
||
pickup: '2026-07-15',
|
||
delivery: '2026-07-15',
|
||
route: '仪征 → 镇江'
|
||
}
|
||
]
|
||
}
|
||
|
||
function pad(n) { return String(n).padStart(2, '0') }
|
||
function ymd(d) {
|
||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||
}
|
||
function sameDay(a, b) {
|
||
return a.getFullYear() === b.getFullYear()
|
||
&& a.getMonth() === b.getMonth()
|
||
&& a.getDate() === b.getDate()
|
||
}
|
||
|
||
const today = new Date()
|
||
today.setHours(0, 0, 0, 0)
|
||
|
||
let viewYear = today.getFullYear()
|
||
let viewMonth = today.getMonth()
|
||
let selected = new Date(today)
|
||
|
||
const daysEl = document.getElementById('days')
|
||
const listEl = document.getElementById('list')
|
||
const calTitle = document.getElementById('calTitle')
|
||
const selectedLabel = document.getElementById('selectedLabel')
|
||
|
||
function renderCalendar() {
|
||
calTitle.textContent = viewYear + '年' + (viewMonth + 1) + '月'
|
||
daysEl.innerHTML = ''
|
||
|
||
const first = new Date(viewYear, viewMonth, 1)
|
||
const startWeekday = first.getDay()
|
||
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate()
|
||
const prevDays = new Date(viewYear, viewMonth, 0).getDate()
|
||
|
||
for (let i = 0; i < startWeekday; i++) {
|
||
const dayNum = prevDays - startWeekday + 1 + i
|
||
const btn = document.createElement('button')
|
||
btn.type = 'button'
|
||
btn.className = 'day muted'
|
||
btn.textContent = dayNum
|
||
btn.disabled = true
|
||
daysEl.appendChild(btn)
|
||
}
|
||
|
||
for (let d = 1; d <= daysInMonth; d++) {
|
||
const date = new Date(viewYear, viewMonth, d)
|
||
const btn = document.createElement('button')
|
||
btn.type = 'button'
|
||
btn.className = 'day'
|
||
if (sameDay(date, today)) btn.classList.add('today')
|
||
if (sameDay(date, selected)) btn.classList.add('selected')
|
||
btn.textContent = d
|
||
btn.addEventListener('click', () => {
|
||
selected = date
|
||
render()
|
||
})
|
||
daysEl.appendChild(btn)
|
||
}
|
||
|
||
const filled = startWeekday + daysInMonth
|
||
const trail = (7 - (filled % 7)) % 7
|
||
for (let i = 1; i <= trail; i++) {
|
||
const btn = document.createElement('button')
|
||
btn.type = 'button'
|
||
btn.className = 'day muted'
|
||
btn.textContent = i
|
||
btn.disabled = true
|
||
daysEl.appendChild(btn)
|
||
}
|
||
}
|
||
|
||
function renderList() {
|
||
const key = ymd(selected)
|
||
if (sameDay(selected, today)) {
|
||
selectedLabel.textContent = '今天(' + key + ')'
|
||
} else {
|
||
selectedLabel.textContent = key
|
||
}
|
||
|
||
const rows = MOCK[key] || []
|
||
if (!rows.length) {
|
||
listEl.innerHTML = '<div class="empty">暂无运输记录</div>'
|
||
return
|
||
}
|
||
|
||
listEl.innerHTML = rows.map((row, i) => {
|
||
const tagClass = row.status === '已作废' ? 'tag danger' : 'tag'
|
||
return (
|
||
'<div class="record-card">' +
|
||
'<span class="index-tag">' + (i + 1) + '</span>' +
|
||
'<div class="record-top">' +
|
||
'<span class="plan-no">' + row.planNo + '</span>' +
|
||
'<span class="' + tagClass + '">' + row.status + '</span>' +
|
||
'</div>' +
|
||
'<div class="meta">取货:' + row.pickup + ' · 送货:' + row.delivery + '</div>' +
|
||
'<div class="meta">线路:' + row.route + '</div>' +
|
||
'</div>'
|
||
)
|
||
}).join('')
|
||
}
|
||
|
||
function render() {
|
||
renderCalendar()
|
||
renderList()
|
||
}
|
||
|
||
document.getElementById('prevMonth').addEventListener('click', () => {
|
||
viewMonth -= 1
|
||
if (viewMonth < 0) {
|
||
viewMonth = 11
|
||
viewYear -= 1
|
||
}
|
||
renderCalendar()
|
||
})
|
||
|
||
document.getElementById('nextMonth').addEventListener('click', () => {
|
||
viewMonth += 1
|
||
if (viewMonth > 11) {
|
||
viewMonth = 0
|
||
viewYear += 1
|
||
}
|
||
renderCalendar()
|
||
})
|
||
|
||
render()
|
||
</script>
|
||
</body>
|
||
</html>
|