|
|
@@ -1,18 +1,236 @@
|
|
|
<template>
|
|
|
<MpWebviewShell>
|
|
|
- <p class="mp-wv-placeholder">运输计划加载中…</p>
|
|
|
+ <div v-if="forbidden" class="mp-wv-empty">无权限</div>
|
|
|
+ <template v-else>
|
|
|
+ <header class="mp-wv-header">
|
|
|
+ <div class="mp-wv-header__row">
|
|
|
+ <button class="mp-wv-header__back" type="button" @click="goBack">← 工作台</button>
|
|
|
+ <button class="mp-wv-btn mp-wv-btn--primary" type="button" @click="goNew">新增</button>
|
|
|
+ </div>
|
|
|
+ <h1>运输计划</h1>
|
|
|
+ <input
|
|
|
+ v-model="keyword"
|
|
|
+ class="mp-wv-search"
|
|
|
+ type="search"
|
|
|
+ placeholder="计划编号 / 供方 / 需方 / 产品 / 车牌号"
|
|
|
+ @keyup.enter="reload"
|
|
|
+ />
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <div class="mp-wv-chips">
|
|
|
+ <button
|
|
|
+ v-for="item in dateChips"
|
|
|
+ :key="item.value"
|
|
|
+ type="button"
|
|
|
+ class="mp-wv-chip"
|
|
|
+ :class="{ 'is-active': dateShortcut === item.value }"
|
|
|
+ @click="applyDate(item.value)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mp-wv-tabs">
|
|
|
+ <button
|
|
|
+ v-for="tab in PL_PLAN_LIST_TABS"
|
|
|
+ :key="tab.name"
|
|
|
+ type="button"
|
|
|
+ class="mp-wv-tab"
|
|
|
+ :class="{ 'is-active': activeTab === tab.name }"
|
|
|
+ @click="applyTab(tab.name)"
|
|
|
+ >
|
|
|
+ {{ tab.label }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mp-wv-toolbar">
|
|
|
+ <button class="mp-wv-btn" type="button" @click="goPaste">三方人车</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="loading && !cards.length" class="mp-wv-empty">加载中…</div>
|
|
|
+ <div v-else-if="!cards.length" class="mp-wv-empty">暂无计划</div>
|
|
|
+ <article
|
|
|
+ v-for="card in cards"
|
|
|
+ :key="card.id"
|
|
|
+ class="mp-wv-card"
|
|
|
+ @click="goDetail(card.id)"
|
|
|
+ >
|
|
|
+ <div class="mp-wv-card__head">
|
|
|
+ <div>
|
|
|
+ <div class="mp-wv-card__no">{{ card.planNo }}</div>
|
|
|
+ <div class="mp-wv-card__sub">{{ card.typeLabel }} · {{ card.planDate }}</div>
|
|
|
+ </div>
|
|
|
+ <el-tag :type="card.statusTagType" size="small">{{ card.statusLabel }}</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="mp-wv-card__meta">车牌:{{ card.plate }}</div>
|
|
|
+ <div class="mp-wv-card__meta">人员:{{ card.people }}</div>
|
|
|
+ <div class="mp-wv-card__product">{{ card.product }}</div>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <div v-if="hasMore" class="mp-wv-toolbar">
|
|
|
+ <button class="mp-wv-btn" type="button" :disabled="loading" @click="loadMore">
|
|
|
+ {{ loading ? '加载中…' : '加载更多' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</MpWebviewShell>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
+ import { computed, onMounted, ref } from 'vue'
|
|
|
+ import { useRouter } from 'vue-router'
|
|
|
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
|
|
|
-</script>
|
|
|
+ import { planApi, type Plan } from '@/api/pl'
|
|
|
+ import { buildPlanCard } from '@/utils/mp-pl-plan-card'
|
|
|
+ import { getWxMiniProgram } from '@/utils/mp-webview'
|
|
|
+ import {
|
|
|
+ PL_PLAN_LIST_TABS,
|
|
|
+ plPlanListTabToFilters,
|
|
|
+ type PlPlanListTab
|
|
|
+ } from '@/utils/pl-display'
|
|
|
+ import { HttpError } from '@/utils/http/error'
|
|
|
+ import { ApiStatus } from '@/utils/http/status'
|
|
|
+ import '@/styles/mp-webview.scss'
|
|
|
+
|
|
|
+ type DateChip = 'today' | 'tomorrow' | 'week' | 'month'
|
|
|
+
|
|
|
+ const router = useRouter()
|
|
|
+ const keyword = ref('')
|
|
|
+ const activeTab = ref<PlPlanListTab>('pending_arrange')
|
|
|
+ const dateShortcut = ref<DateChip>('today')
|
|
|
+ const dateRange = ref<string[]>([])
|
|
|
+ const loading = ref(false)
|
|
|
+ const forbidden = ref(false)
|
|
|
+ const page = ref(1)
|
|
|
+ const pageSize = 20
|
|
|
+ const total = ref(0)
|
|
|
+ const plans = ref<Plan[]>([])
|
|
|
+
|
|
|
+ const dateChips: { label: string; value: DateChip }[] = [
|
|
|
+ { label: '今天', value: 'today' },
|
|
|
+ { label: '明天', value: 'tomorrow' },
|
|
|
+ { label: '本周', value: 'week' },
|
|
|
+ { label: '本月', value: 'month' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const cards = computed(() => plans.value.map((row) => buildPlanCard(row)))
|
|
|
+ const hasMore = computed(() => plans.value.length < total.value)
|
|
|
+
|
|
|
+ function formatYmd(date: Date) {
|
|
|
+ const m = `${date.getMonth() + 1}`.padStart(2, '0')
|
|
|
+ const d = `${date.getDate()}`.padStart(2, '0')
|
|
|
+ return `${date.getFullYear()}-${m}-${d}`
|
|
|
+ }
|
|
|
+
|
|
|
+ function rangeFor(type: DateChip): string[] {
|
|
|
+ const today = new Date()
|
|
|
+ if (type === 'today') {
|
|
|
+ const d = formatYmd(today)
|
|
|
+ return [d, d]
|
|
|
+ }
|
|
|
+ if (type === 'tomorrow') {
|
|
|
+ const t = new Date(today)
|
|
|
+ t.setDate(t.getDate() + 1)
|
|
|
+ const d = formatYmd(t)
|
|
|
+ return [d, d]
|
|
|
+ }
|
|
|
+ if (type === 'week') {
|
|
|
+ const start = new Date(today)
|
|
|
+ const day = start.getDay() || 7
|
|
|
+ start.setDate(start.getDate() - day + 1)
|
|
|
+ const end = new Date(start)
|
|
|
+ end.setDate(start.getDate() + 6)
|
|
|
+ return [formatYmd(start), formatYmd(end)]
|
|
|
+ }
|
|
|
+ const start = new Date(today.getFullYear(), today.getMonth(), 1)
|
|
|
+ const end = new Date(today.getFullYear(), today.getMonth() + 1, 0)
|
|
|
+ return [formatYmd(start), formatYmd(end)]
|
|
|
+ }
|
|
|
+
|
|
|
+ function buildParams() {
|
|
|
+ const tabFilters = plPlanListTabToFilters(activeTab.value)
|
|
|
+ const params: Record<string, unknown> = {
|
|
|
+ page: page.value,
|
|
|
+ pageSize
|
|
|
+ }
|
|
|
+ const q = keyword.value.trim()
|
|
|
+ if (q) params.q = q
|
|
|
+ if (tabFilters.followStatus) params.followStatus = tabFilters.followStatus
|
|
|
+ if (tabFilters.planType) params.planType = tabFilters.planType
|
|
|
+ if (dateRange.value[0]) params.planDateFrom = dateRange.value[0]
|
|
|
+ if (dateRange.value[1]) params.planDateTo = dateRange.value[1]
|
|
|
+ return params
|
|
|
+ }
|
|
|
+
|
|
|
+ async function fetchPage(reset: boolean) {
|
|
|
+ if (reset) {
|
|
|
+ page.value = 1
|
|
|
+ plans.value = []
|
|
|
+ }
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await planApi.list(buildParams())
|
|
|
+ const data = (res as { data?: { list?: Plan[]; total?: number }; list?: Plan[]; total?: number })
|
|
|
+ .data ?? res
|
|
|
+ const list = (data as { list?: Plan[] }).list || []
|
|
|
+ total.value = (data as { total?: number }).total || 0
|
|
|
+ plans.value = reset ? list : plans.value.concat(list)
|
|
|
+ forbidden.value = false
|
|
|
+ } catch (error) {
|
|
|
+ if (error instanceof HttpError && error.code === ApiStatus.forbidden) {
|
|
|
+ forbidden.value = true
|
|
|
+ plans.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ throw error
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function reload() {
|
|
|
+ void fetchPage(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyDate(type: DateChip) {
|
|
|
+ dateShortcut.value = type
|
|
|
+ dateRange.value = rangeFor(type)
|
|
|
+ reload()
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyTab(tab: PlPlanListTab) {
|
|
|
+ activeTab.value = tab
|
|
|
+ reload()
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadMore() {
|
|
|
+ page.value += 1
|
|
|
+ void fetchPage(false)
|
|
|
+ }
|
|
|
|
|
|
-<style scoped>
|
|
|
- .mp-wv-placeholder {
|
|
|
- margin: 24px 0 0;
|
|
|
- font-size: 14px;
|
|
|
- color: #6b7b74;
|
|
|
- text-align: center;
|
|
|
+ function goBack() {
|
|
|
+ const mini = getWxMiniProgram()
|
|
|
+ if (mini?.navigateBack) {
|
|
|
+ mini.navigateBack({ delta: 1 })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ router.back()
|
|
|
}
|
|
|
-</style>
|
|
|
+
|
|
|
+ function goNew() {
|
|
|
+ void router.push('/m/pl/plan/new')
|
|
|
+ }
|
|
|
+
|
|
|
+ function goPaste() {
|
|
|
+ void router.push('/m/pl/plan/paste')
|
|
|
+ }
|
|
|
+
|
|
|
+ function goDetail(id: string) {
|
|
|
+ void router.push(`/m/pl/plan/${id}`)
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ dateRange.value = rangeFor('today')
|
|
|
+ reload()
|
|
|
+ })
|
|
|
+</script>
|