This commit is contained in:
zlwu committed 2026-08-22 09:08:05 +08:00
1 parent 833544def3
commit 16be6fa22d
204 files changed
+455089 -46491

No files matched your search

@@ -10,48 +10,29 @@
/>
<div class="filters">
<div class="filter-row">
<van-field
class="date-field"
readonly
is-link
clickable
:model-value="pickDate"
placeholder="选择日期"
@click="openDatePicker"
/>
<input
v-model="keyword"
type="search"
class="search-input"
placeholder="供方/需方"
enterkeyhint="search"
/>
</div>
<div class="chips">
<button
v-for="q in quickOptions"
:key="q.offset"
type="button"
class="chip"
:class="{ on: pickDate === q.ymd }"
@click="setQuick(q.offset)"
>
{{ q.label }}
</button>
</div>
</div>
<van-popup v-model:show="showDatePicker" position="bottom" round>
<van-date-picker
v-model="datePickerValue"
title="选择日期"
<van-calendar
class="history-calendar"
:poppable="false"
:show-confirm="false"
:show-title="false"
:show-mark="false"
:safe-area-inset-bottom="false"
switch-mode="month"
color="#059669"
:row-height="28"
:default-date="calendarDate"
:min-date="minDate"
:max-date="maxDate"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
@select="onCalendarSelect"
/>
</van-popup>
<input
v-model="keyword"
type="search"
class="search-input"
placeholder="车牌号/产品名称"
enterkeyhint="search"
/>
</div>
<van-empty v-if="errorMessage" :description="errorMessage" />
<van-list
@@ -72,16 +53,33 @@
>
<span class="index-tag">{{ index + 1 }}</span>
<div class="record-top">
<span class="plan-no">{{ row.orderNo || row.planNo || '—' }}</span>
<span class="card-title">{{ cardTitle(row) }}</span>
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
</div>
<div class="meta">类型:{{ kindLabel(row.kind) }} · 计划:{{ row.planNo || '—' }}</div>
<div class="meta">
往来:{{ row.partnerCompany || '—' }} · 车牌:{{ row.vehicleNo || '—' }}
车牌:<span class="plate-hl">{{ row.vehicleNo || '—' }}</span>
</div>
<div class="meta">
供方:{{ row.supplierName || '—' }} · 需方:{{ row.customerName || '—' }}
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
</div>
<div class="meta">
路线:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
</div>
<template v-if="!(row.products || []).length">
<div class="meta">产品:—</div>
<div class="meta">规格型号:—</div>
<div class="meta">包装规格:—</div>
<div class="meta">数量:— · 单位:—</div>
</template>
<template v-for="(prod, pIndex) in row.products || []" :key="pIndex">
<div class="meta">产品:{{ prod.productName || '—' }}</div>
<div class="meta">规格型号:{{ prod.model || '—' }}</div>
<div class="meta">包装规格:{{ prod.packageSpec || '—' }}</div>
<div class="meta">
数量:{{ formatQty(prod.quantity) }} · 单位:{{ prod.unit || '—' }}
</div>
</template>
<div class="meta">运输计划号:{{ row.planNo || '—' }}</div>
</div>
</van-list>
</div>
@@ -99,9 +97,7 @@
import 'vant/es/toast/style'
import 'vant/es/list/style'
import 'vant/es/field/style'
import 'vant/es/popup/style'
import 'vant/es/date-picker/style'
import 'vant/es/calendar/style'
import '@/styles/public-mobile-vant.scss'
const PAGE_SIZE = 20
@@ -118,19 +114,18 @@
const records = ref<PublicIoOrderHistoryRecord[]>([])
const total = ref(0)
const page = ref(1)
const pickDate = ref(addDays(0))
const pickDate = ref(formatYmd(startOfToday()))
const calendarDate = ref(startOfToday())
const keyword = ref('')
const showDatePicker = ref(false)
const datePickerValue = ref<string[]>(ymdToParts(pickDate.value))
const minDate = new Date(new Date().getFullYear() - 2, 0, 1)
const maxDate = new Date(new Date().getFullYear() + 2, 11, 31)
let requestSeq = 0
const quickOptions = [
{ label: '今日', offset: 0, ymd: addDays(0) },
{ label: '明日', offset: 1, ymd: addDays(1) },
{ label: '后天', offset: 2, ymd: addDays(2) }
]
function startOfToday() {
const d = new Date()
d.setHours(0, 0, 0, 0)
return d
}
function formatYmd(d: Date) {
const y = d.getFullYear()
@@ -139,34 +134,33 @@
return `${y}-${m}-${day}`
}
function addDays(n: number) {
const d = new Date()
d.setHours(0, 0, 0, 0)
d.setDate(d.getDate() + n)
return formatYmd(d)
function formatDay(value?: string | null) {
if (!value) return '—'
const normalized = String(value).replace('T', ' ')
return normalized.length >= 10 ? normalized.slice(0, 10) : normalized
}
function ymdToParts(ymd: string): string[] {
const [y, m, d] = String(ymd || '').split('-')
if (!y || !m || !d) return ymdToParts(addDays(0))
return [y, m, d]
function formatQty(value?: number | null) {
if (value == null) return '—'
return String(value)
}
function openDatePicker() {
datePickerValue.value = ymdToParts(pickDate.value)
showDatePicker.value = true
function cardTitleDate(row: PublicIoOrderHistoryRecord) {
const fromPlan = formatDay(row.planDate)
if (fromPlan !== '—') return fromPlan
const fromPickup = formatDay(row.pickupTime)
if (fromPickup !== '—') return fromPickup
const fromDelivery = formatDay(row.deliveryTime)
if (fromDelivery !== '—') return fromDelivery
return pickDate.value
}
function onDateConfirm({ selectedValues }: { selectedValues: string[] }) {
pickDate.value = selectedValues.join('-')
showDatePicker.value = false
reload()
}
function kindLabel(kind?: string) {
if (kind === 'inbound') return '入库'
if (kind === 'outbound') return '出库'
return '—'
function cardTitle(row: PublicIoOrderHistoryRecord) {
const names = (row.products || [])
.map((p) => (p.productName || '').trim())
.filter(Boolean)
const productLabel = names.length ? names.join('、') : '—'
return `${cardTitleDate(row)} ${productLabel}`
}
function tagClass(row: PublicIoOrderHistoryRecord) {
@@ -183,9 +177,11 @@
router.push({ name: detailRouteName(), params: { token: token.value } })
}
function setQuick(offset: number) {
pickDate.value = addDays(offset)
datePickerValue.value = ymdToParts(pickDate.value)
function onCalendarSelect(date: Date) {
const next = formatYmd(date)
if (next === pickDate.value) return
pickDate.value = next
calendarDate.value = date
reload()
}
@@ -283,7 +279,7 @@
.filters {
margin-bottom: 12px;
padding: 12px 14px;
padding: 4px 4px 10px;
border-radius: 12px;
background: #fff;
border: 1px solid #e2e8f0;
@@ -292,78 +288,45 @@
box-sizing: border-box;
}
.filter-row {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
gap: 8px;
max-width: 100%;
min-width: 0;
}
.history-calendar {
height: auto;
--van-calendar-background: #fff;
--van-calendar-header-shadow: none;
--van-calendar-header-title-height: 32px;
--van-calendar-header-subtitle-font-size: 13px;
--van-calendar-weekdays-height: 24px;
--van-calendar-weekdays-font-size: 11px;
--van-calendar-day-height: 28px;
--van-calendar-day-font-size: 12px;
--van-calendar-day-margin-bottom: 0;
--van-calendar-selected-day-size: 26px;
--van-primary-color: #059669;
.date-field {
padding: 0 12px;
min-height: 40px;
min-width: 0;
width: 100%;
border: 1px solid #cbd5e1;
border-radius: 8px;
align-items: center;
background: #fff;
box-sizing: border-box;
:deep(.van-field__control) {
font-size: 14px;
color: #1e293b;
height: 40px;
line-height: 40px;
:deep(.van-calendar__footer) {
display: none;
}
:deep(.van-field__right-icon) {
margin-right: 0;
color: #64748b;
:deep(.van-calendar__body) {
overflow: hidden;
}
}
.search-input {
display: block;
width: 100%;
width: calc(100% - 8px);
max-width: 100%;
min-width: 0;
height: 40px;
height: 36px;
margin: 8px 4px 0;
padding: 0 12px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font-size: 14px;
color: #1e293b;
color: #0f172a;
background: #fff;
box-sizing: border-box;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.chip {
height: 30px;
padding: 0 12px;
border-radius: 999px;
border: 1px solid #d1fae5;
background: #ecfdf5;
color: #047857;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.chip.on {
background: #059669;
border-color: #059669;
color: #fff;
}
.record-card {
position: relative;
margin-bottom: 10px;
@@ -402,7 +365,7 @@
padding-left: 4px;
}
.plan-no {
.card-title {
font-size: 15px;
font-weight: 700;
color: #0f172a;
@@ -435,7 +398,7 @@
.meta {
font-size: 13px;
color: #64748b;
color: #0f172a;
line-height: 1.45;
word-break: break-all;
@@ -443,4 +406,10 @@
margin-top: 4px;
}
}
.plate-hl {
color: #059669;
font-weight: 700;
font-size: 15px;
}
</style>
@@ -9,66 +9,68 @@
@click-left="goBack"
/>
<van-loading v-if="loading" vertical class="page-loading">加载中...</van-loading>
<van-empty v-else-if="errorMessage" :description="errorMessage" />
<van-loading v-if="bootLoading" vertical class="page-loading">加载中...</van-loading>
<van-empty v-else-if="fatalError" :description="fatalError" />
<template v-else>
<div class="filters">
<div class="filter-row">
<van-field
class="date-field"
readonly
is-link
clickable
:model-value="pickDate"
placeholder="选择日期"
@click="openDatePicker"
/>
<div class="chips">
<button
v-for="q in quickOptions"
:key="q.offset"
type="button"
class="chip"
:class="{ on: pickDate === q.ymd }"
@click="setQuick(q.offset)"
>
{{ q.label }}
</button>
</div>
</div>
</div>
<van-popup v-model:show="showDatePicker" position="bottom" round>
<van-date-picker
v-model="datePickerValue"
title="选择日期"
<van-calendar
class="history-calendar"
:poppable="false"
:show-confirm="false"
:show-title="false"
:show-mark="false"
:safe-area-inset-bottom="false"
switch-mode="month"
color="#059669"
:row-height="28"
:default-date="calendarDate"
:min-date="minDate"
:max-date="maxDate"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
@select="onCalendarSelect"
/>
</van-popup>
<van-empty v-if="!records.length" description="暂无运输记录" />
<div
v-for="(row, index) in records"
:key="row.planId"
class="record-card"
role="button"
@click="openRecord(row)"
>
<span class="index-tag">{{ index + 1 }}</span>
<div class="record-top">
<span class="plan-no">{{ row.planNo }}</span>
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
</div>
<div class="meta">
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
</div>
<div class="meta">
线路:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
</div>
</div>
<van-loading v-if="loading" vertical class="list-loading">加载中...</van-loading>
<van-empty v-else-if="!records.length" description="暂无运输记录" />
<template v-else>
<div
v-for="(row, index) in records"
:key="row.planId"
class="record-card"
role="button"
@click="openRecord(row)"
>
<span class="index-tag">{{ index + 1 }}</span>
<div class="record-top">
<span class="card-title">{{ cardTitle(row) }}</span>
<span class="tag" :class="tagClass(row)">{{ row.statusLabel || '进行中' }}</span>
</div>
<div class="meta">
车牌:<span class="plate-hl">{{ row.licensePlate || '—' }}</span>
</div>
<div class="meta">
取货:{{ formatDay(row.pickupTime) }} · 送货:{{ formatDay(row.deliveryTime) }}
</div>
<div class="meta">
路线:{{ row.pickupAddress || '—' }} → {{ row.destinationAddress || '—' }}
</div>
<template v-if="!(row.products || []).length">
<div class="meta">产品:—</div>
<div class="meta">规格型号:—</div>
<div class="meta">包装规格:—</div>
<div class="meta">数量:— · 单位:—</div>
</template>
<template v-for="(prod, pIndex) in row.products || []" :key="pIndex">
<div class="meta">产品:{{ prod.productName || '—' }}</div>
<div class="meta">规格型号:{{ prod.model || '—' }}</div>
<div class="meta">包装规格:{{ prod.packageSpec || '—' }}</div>
<div class="meta">
数量:{{ formatQty(prod.quantity) }} · 单位:{{ prod.unit || '—' }}
</div>
</template>
<div class="meta">运输计划号:{{ row.planNo || '—' }}</div>
</div>
</template>
</template>
</div>
</PublicMobileShell>
@@ -83,29 +85,27 @@
import PublicMobileShell from '@/layouts/PublicMobileShell.vue'
import 'vant/es/toast/style'
import 'vant/es/field/style'
import 'vant/es/popup/style'
import 'vant/es/date-picker/style'
import 'vant/es/calendar/style'
import '@/styles/public-mobile-vant.scss'
const route = useRoute()
const router = useRouter()
const token = computed(() => String(route.params.token || ''))
const loading = ref(true)
const errorMessage = ref('')
const bootLoading = ref(true)
const loading = ref(false)
const fatalError = ref('')
const records = ref<PublicTransportHistoryRecord[]>([])
const pickDate = ref(addDays(0))
const showDatePicker = ref(false)
const datePickerValue = ref<string[]>(ymdToParts(pickDate.value))
const pickDate = ref(formatYmd(startOfToday()))
const calendarDate = ref(startOfToday())
const minDate = new Date(new Date().getFullYear() - 2, 0, 1)
const maxDate = new Date(new Date().getFullYear() + 2, 11, 31)
const quickOptions = [
{ label: '今日', offset: 0, ymd: addDays(0) },
{ label: '明日', offset: 1, ymd: addDays(1) },
{ label: '后天', offset: 2, ymd: addDays(2) }
]
function startOfToday() {
const d = new Date()
d.setHours(0, 0, 0, 0)
return d
}
function formatYmd(d: Date) {
const y = d.getFullYear()
@@ -114,36 +114,33 @@
return `${y}-${m}-${day}`
}
function addDays(n: number) {
const d = new Date()
d.setHours(0, 0, 0, 0)
d.setDate(d.getDate() + n)
return formatYmd(d)
}
function ymdToParts(ymd: string): string[] {
const [y, m, d] = String(ymd || '').split('-')
if (!y || !m || !d) return ymdToParts(addDays(0))
return [y, m, d]
}
function openDatePicker() {
datePickerValue.value = ymdToParts(pickDate.value)
showDatePicker.value = true
}
function onDateConfirm({ selectedValues }: { selectedValues: string[] }) {
pickDate.value = selectedValues.join('-')
showDatePicker.value = false
load()
}
function formatDay(value?: string) {
if (!value) return '—'
const normalized = value.replace('T', ' ')
return normalized.length >= 10 ? normalized.slice(0, 10) : normalized
}
function formatQty(value?: number | null) {
if (value == null) return '—'
return String(value)
}
function cardTitleDate(row: PublicTransportHistoryRecord) {
const fromPickup = formatDay(row.pickupTime)
if (fromPickup !== '—') return fromPickup
const fromDelivery = formatDay(row.deliveryTime)
if (fromDelivery !== '—') return fromDelivery
return pickDate.value
}
function cardTitle(row: PublicTransportHistoryRecord) {
const names = (row.products || [])
.map((p) => (p.productName || '').trim())
.filter(Boolean)
const productLabel = names.length ? names.join('、') : '—'
return `${cardTitleDate(row)} ${productLabel}`
}
function tagClass(row: PublicTransportHistoryRecord) {
if (row.planVoided || row.statusLabel === '已作废') return 'tag--danger'
if (row.planArchived || row.statusLabel === '已归档') return 'tag--muted'
@@ -154,10 +151,12 @@
router.push({ name: 'PublicPlTransportDriver', params: { token: token.value } })
}
function setQuick(offset: number) {
pickDate.value = addDays(offset)
datePickerValue.value = ymdToParts(pickDate.value)
load()
function onCalendarSelect(date: Date) {
const next = formatYmd(date)
if (next === pickDate.value) return
pickDate.value = next
calendarDate.value = date
load({ silentBoot: true })
}
function openRecord(row: PublicTransportHistoryRecord) {
@@ -169,30 +168,44 @@
router.push({ name: 'PublicPlTransportDriver', params: { token: accessToken } })
}
async function load() {
async function load(options?: { silentBoot?: boolean }) {
loading.value = true
errorMessage.value = ''
if (!options?.silentBoot) {
bootLoading.value = true
fatalError.value = ''
}
try {
const data = await publicTransportApi.getHistory(token.value, pickDate.value)
records.value = data?.records || []
fatalError.value = ''
} catch (e: any) {
errorMessage.value = e?.message || '链接无效或已失效'
records.value = []
if (!options?.silentBoot) {
fatalError.value = e?.message || '链接无效或已失效'
} else {
showFailToast(e?.message || '加载失败')
}
} finally {
loading.value = false
bootLoading.value = false
}
}
onMounted(load)
onMounted(() => load())
</script>
<style scoped lang="scss">
.page-loading {
.page-loading,
.list-loading {
display: flex;
justify-content: center;
padding: 48px 0;
}
.list-loading {
padding: 32px 0;
}
.transport-nav-bar {
margin: -12px -16px 12px;
border-radius: 0 0 12px 12px;
@@ -214,7 +227,7 @@
.filters {
margin-bottom: 12px;
padding: 12px 14px;
padding: 4px 4px 2px;
border-radius: 12px;
background: #fff;
border: 1px solid #e2e8f0;
@@ -223,69 +236,29 @@
box-sizing: border-box;
}
.filter-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
max-width: 100%;
}
.history-calendar {
height: auto;
--van-calendar-background: #fff;
--van-calendar-header-shadow: none;
--van-calendar-header-title-height: 32px;
--van-calendar-header-subtitle-font-size: 13px;
--van-calendar-weekdays-height: 24px;
--van-calendar-weekdays-font-size: 11px;
--van-calendar-day-height: 28px;
--van-calendar-day-font-size: 12px;
--van-calendar-day-margin-bottom: 0;
--van-calendar-selected-day-size: 26px;
--van-primary-color: #059669;
.date-field {
flex: 0 1 11.5em;
min-width: 0;
padding: 0 10px;
min-height: 40px;
border: 1px solid #cbd5e1;
border-radius: 8px;
align-items: center;
background: #fff;
box-sizing: border-box;
:deep(.van-field__control) {
font-size: 14px;
color: #1e293b;
height: 40px;
line-height: 40px;
:deep(.van-calendar__footer) {
display: none;
}
:deep(.van-field__right-icon) {
margin-right: 0;
color: #64748b;
:deep(.van-calendar__body) {
overflow: hidden;
}
}
.chips {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 6px;
margin-top: 0;
flex: 1 1 auto;
min-width: 0;
justify-content: flex-end;
}
.chip {
height: 30px;
padding: 0 10px;
border-radius: 999px;
border: 1px solid #d1fae5;
background: #ecfdf5;
color: #047857;
font-size: 12px;
font-weight: 600;
cursor: pointer;
flex-shrink: 0;
white-space: nowrap;
}
.chip.on {
background: #059669;
border-color: #059669;
color: #fff;
}
.record-card {
position: relative;
margin-bottom: 10px;
@@ -324,7 +297,7 @@
padding-left: 4px;
}
.plan-no {
.card-title {
font-size: 15px;
font-weight: 700;
color: #0f172a;
@@ -357,7 +330,7 @@
.meta {
font-size: 13px;
color: #64748b;
color: #0f172a;
line-height: 1.45;
word-break: break-all;
@@ -365,4 +338,10 @@
margin-top: 4px;
}
}
.plate-hl {
color: #059669;
font-weight: 700;
font-size: 15px;
}
</style>