feat(web-admin): redesign mp transport plan detail sections.
Align mobile detail with demo IA including hazard cert slots and capped footer actions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8d1d5c19b5
commit
bf81d9b77c
1 file changed
+593
-78
@@ -9,33 +9,222 @@
|
||||
<el-tag :type="statusTagType" size="small">{{ statusLabel }}</el-tag>
|
||||
</div>
|
||||
<h1>{{ plan.planNo || '计划详情' }}</h1>
|
||||
<div class="mp-wv-card__sub" style="color: rgba(255, 255, 255, 0.9)">
|
||||
{{ typeLabel }} · 计划日期 {{ plan.planDate || '—' }}
|
||||
</div>
|
||||
<div class="mp-wv-header__sub">{{ typeLabel }} · 计划日期 {{ plan.planDate || '—' }}</div>
|
||||
</header>
|
||||
|
||||
<section class="mp-wv-card">
|
||||
<div class="mp-wv-card__meta">送货时间:{{ deliveryTimeText }}</div>
|
||||
<div class="mp-wv-card__meta">车牌:{{ plate }}</div>
|
||||
<div class="mp-wv-card__meta">人员:{{ people }}</div>
|
||||
<div class="mp-wv-card__meta">跟进人:{{ plan.followUserName || '—' }}</div>
|
||||
<div class="mp-wv-card__product">{{ product }}</div>
|
||||
</section>
|
||||
<div class="mp-wv-detail-body">
|
||||
<section class="mp-wv-section">
|
||||
<h3>基础信息</h3>
|
||||
<div class="mp-wv-kv"><span>计划编号</span><span>{{ plan.planNo || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>计划日期</span><span>{{ plan.planDate || '—' }}</span></div>
|
||||
<div class="mp-wv-kv">
|
||||
<span>归属公司</span><span>{{ getPlPlanCompanyLabel(plan.belongsCompany) }}</span>
|
||||
</div>
|
||||
<div class="mp-wv-kv"><span>单据类型</span><span>{{ typeLabel }}</span></div>
|
||||
<div class="mp-wv-kv"><span>跟进人</span><span>{{ plan.followUserName || '—' }}</span></div>
|
||||
</section>
|
||||
|
||||
<div class="mp-wv-toolbar" style="flex-wrap: wrap">
|
||||
<button v-if="canArrange" class="mp-wv-btn mp-wv-btn--primary" type="button" @click="goArrange">
|
||||
{{ arrangeLabel }}
|
||||
<section class="mp-wv-section">
|
||||
<h3>运输信息</h3>
|
||||
<template v-if="isArranged">
|
||||
<div v-if="arrangeDriverUrl" class="mp-wv-link-block">
|
||||
<div class="mp-wv-link-label">司机链接</div>
|
||||
<div class="mp-wv-link-row">
|
||||
<input class="mp-wv-link-input" :value="arrangeDriverUrl" readonly />
|
||||
<button class="mp-wv-btn" type="button" @click="copyText(arrangeDriverUrl, '司机链接')">
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteLinks.length" class="mp-wv-link-block">
|
||||
<div class="mp-wv-link-label">现场链接</div>
|
||||
<div v-for="link in siteLinks" :key="link.siteLinkId" class="mp-wv-site-link">
|
||||
<div class="mp-wv-card__meta">{{ link.productName || '现场链接' }}</div>
|
||||
<div class="mp-wv-link-row">
|
||||
<input class="mp-wv-link-input" :value="link.siteUrl || ''" readonly />
|
||||
<button
|
||||
class="mp-wv-btn"
|
||||
type="button"
|
||||
@click="copyText(link.siteUrl || '', link.productName || '现场链接')"
|
||||
>
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="hasVehicle" class="mp-wv-vehicle-box">
|
||||
<div><b>{{ plate }}</b></div>
|
||||
<div class="mp-wv-card__meta">挂车 {{ plan.trailerNo || '—' }}</div>
|
||||
<div class="mp-wv-card__meta">司机 {{ plan.driverName || '—' }} · 押运 {{ plan.escortName || '—' }}</div>
|
||||
<button class="mp-wv-btn" type="button" style="width: 100%; margin-top: 8px" @click="vehicleOpen = true">
|
||||
查看车辆详情
|
||||
</button>
|
||||
</div>
|
||||
<p v-else class="mp-wv-card__meta">暂无车辆信息</p>
|
||||
|
||||
<div v-if="feedbackLines.length" class="mp-wv-feedback">
|
||||
<div v-for="(line, idx) in feedbackLines" :key="idx" class="mp-wv-feedback-box">
|
||||
<h4>{{ line.title }}</h4>
|
||||
<div>{{ line.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mp-wv-section">
|
||||
<h3>装送货信息</h3>
|
||||
<div class="mp-wv-stop">
|
||||
<div class="mp-wv-stop-title">装货信息</div>
|
||||
<div class="mp-wv-kv"><span>装货客户</span><span>{{ load?.customerName || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>装货时间</span><span>{{ load?.loadTime || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>装货地址</span><span>{{ load?.address || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>联系方式</span><span>{{ load?.contact || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>注意事项</span><span>{{ load?.notes || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>装货合同</span><span>{{ loadContractText }}</span></div>
|
||||
<div class="mp-wv-kv"><span>地磅单</span><span>{{ dibangText(load) }}</span></div>
|
||||
<div class="mp-wv-prod-table">
|
||||
<div v-if="!(load?.products || []).length" class="mp-wv-prod-empty">暂无产品</div>
|
||||
<div v-for="(p, i) in load?.products || []" :key="p.id || i" class="mp-wv-prod-row">
|
||||
<div class="mp-wv-prod-name">{{ p.productName }}{{ p.model ? ` · ${p.model}` : '' }}</div>
|
||||
<div class="mp-wv-prod-grid">
|
||||
<div>单位 <b>{{ p.unit || '—' }}</b></div>
|
||||
<div>包装 <b>{{ p.packageSpec || '—' }}</b></div>
|
||||
<div>计划量 <b>{{ p.planQty ?? '—' }}</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mp-wv-stop">
|
||||
<div class="mp-wv-stop-title">送货信息</div>
|
||||
<div v-if="deliveries.length > 1" class="mp-wv-delivery-tabs">
|
||||
<button
|
||||
v-for="(d, idx) in deliveries"
|
||||
:key="d.id || idx"
|
||||
class="mp-wv-delivery-tab"
|
||||
:class="{ active: deliveryIndex === idx }"
|
||||
type="button"
|
||||
@click="deliveryIndex = idx"
|
||||
>
|
||||
送货段 {{ idx + 1 }}
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="activeDelivery">
|
||||
<div class="mp-wv-kv">
|
||||
<span>送货客户</span><span>{{ activeDelivery.customerName || '—' }}</span>
|
||||
</div>
|
||||
<div class="mp-wv-kv">
|
||||
<span>送货时间</span><span>{{ activeDelivery.deliveryTime || '—' }}</span>
|
||||
</div>
|
||||
<div class="mp-wv-kv"><span>送货地址</span><span>{{ activeDelivery.address || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>联系方式</span><span>{{ activeDelivery.contact || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>注意事项</span><span>{{ activeDelivery.notes || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>送货合同</span><span>{{ deliveryContractText }}</span></div>
|
||||
<div class="mp-wv-kv"><span>地磅单</span><span>{{ dibangText(activeDelivery) }}</span></div>
|
||||
<div class="mp-wv-prod-table">
|
||||
<div v-if="!(activeDelivery.products || []).length" class="mp-wv-prod-empty">暂无产品</div>
|
||||
<div
|
||||
v-for="(p, i) in activeDelivery.products || []"
|
||||
:key="p.id || i"
|
||||
class="mp-wv-prod-row"
|
||||
>
|
||||
<div class="mp-wv-prod-name">
|
||||
{{ p.productName }}{{ p.model ? ` · ${p.model}` : '' }}
|
||||
</div>
|
||||
<div class="mp-wv-prod-grid">
|
||||
<div>单位 <b>{{ p.unit || '—' }}</b></div>
|
||||
<div>包装 <b>{{ p.packageSpec || '—' }}</b></div>
|
||||
<div>计划量 <b>{{ p.planQty ?? '—' }}</b></div>
|
||||
<div v-if="p.deliveryMethod">送货方式 <b>{{ p.deliveryMethod }}</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="mp-wv-card__meta">暂无送货段</p>
|
||||
</div>
|
||||
|
||||
<div class="mp-wv-qty">
|
||||
<div>装货合计 <b>{{ loadQty }}</b></div>
|
||||
<div>送货合计 <b>{{ deliveryQty }}</b></div>
|
||||
<div :class="qtyBalanced ? 'is-ok' : ''">
|
||||
{{ qtyBalanced ? '✓ 数量平衡' : '⚠ 装送数量不一致' }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mp-wv-section">
|
||||
<h3>危化品证书信息</h3>
|
||||
<div class="mp-wv-cert-summary">
|
||||
<span>{{ certSummaryText }}</span>
|
||||
<el-tag :type="certReadyTagType" size="small">{{ certReadyLabel }}</el-tag>
|
||||
</div>
|
||||
<PlPlanHazardCertViewTable :products="hazardProducts" />
|
||||
<p class="mp-wv-cert-hint">上传与齐备确认在「危化品证上传」页操作。</p>
|
||||
</section>
|
||||
|
||||
<section class="mp-wv-section">
|
||||
<h3>运费信息</h3>
|
||||
<div class="mp-wv-kv">
|
||||
<span>是否有运输费用</span><span>{{ getPlYesNoLabel(plan.hasFreight) }}</span>
|
||||
</div>
|
||||
<div class="mp-wv-kv"><span>运费金额(元)</span><span>{{ freightAmountText }}</span></div>
|
||||
<div class="mp-wv-kv"><span>运费备注</span><span>{{ freightRemarkText }}</span></div>
|
||||
</section>
|
||||
|
||||
<section class="mp-wv-section">
|
||||
<h3>备注</h3>
|
||||
<p class="mp-wv-remark">{{ plan.remark?.trim() || '—' }}</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="mp-wv-fab-bar">
|
||||
<button
|
||||
v-if="actionSplit.more.length"
|
||||
class="mp-wv-btn mp-wv-btn-more"
|
||||
type="button"
|
||||
aria-label="更多"
|
||||
@click="moreOpen = true"
|
||||
>
|
||||
⋯
|
||||
</button>
|
||||
<button v-if="canEdit" class="mp-wv-btn" type="button" @click="goEdit">编辑计划</button>
|
||||
<button class="mp-wv-btn" type="button" @click="goCopy">复制计划</button>
|
||||
<button v-if="canWait" class="mp-wv-btn" type="button" :disabled="acting" @click="doWaitNotice">
|
||||
等客户通知
|
||||
</button>
|
||||
<button class="mp-wv-btn" type="button" @click="goPrint">打印单据</button>
|
||||
<button v-if="canVoid" class="mp-wv-btn" type="button" :disabled="acting" @click="doVoid">
|
||||
作废
|
||||
<button
|
||||
v-for="(action, idx) in actionSplit.primary"
|
||||
:key="action.kind"
|
||||
class="mp-wv-btn"
|
||||
:class="{
|
||||
'mp-wv-btn--primary': idx === 0 || actionSplit.primary.length === 1,
|
||||
'is-danger': action.danger
|
||||
}"
|
||||
type="button"
|
||||
:disabled="acting"
|
||||
@click="runAction(action.kind)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MpBottomSheet v-model="moreOpen" title="更多操作" half>
|
||||
<button
|
||||
v-for="action in actionSplit.more"
|
||||
:key="action.kind"
|
||||
class="mp-wv-filter-opt"
|
||||
:class="{ 'is-danger': action.danger }"
|
||||
type="button"
|
||||
:disabled="acting"
|
||||
@click="runAction(action.kind)"
|
||||
>
|
||||
{{ action.label }}
|
||||
</button>
|
||||
</MpBottomSheet>
|
||||
|
||||
<MpBottomSheet v-model="vehicleOpen" title="车辆详情">
|
||||
<div class="mp-wv-kv"><span>主车号</span><span>{{ plan.licensePlate || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>挂车号</span><span>{{ plan.trailerNo || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>司机</span><span>{{ plan.driverName || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>押运</span><span>{{ plan.escortName || '—' }}</span></div>
|
||||
<div class="mp-wv-kv"><span>人员摘要</span><span>{{ plan.personnelSummary || '—' }}</span></div>
|
||||
</MpBottomSheet>
|
||||
</template>
|
||||
</MpWebviewShell>
|
||||
</template>
|
||||
@@ -45,15 +234,37 @@
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import MpWebviewShell from '@/layouts/MpWebviewShell.vue'
|
||||
import { planApi, type Plan } from '@/api/pl'
|
||||
import MpBottomSheet from '@/components/mp/MpBottomSheet.vue'
|
||||
import PlPlanHazardCertViewTable from '@/components/business/pl-plan-hazard-cert-view-table/index.vue'
|
||||
import {
|
||||
planApi,
|
||||
type Plan,
|
||||
type PlanArrangeInfo,
|
||||
type PlanDelivery,
|
||||
type PlanLoad,
|
||||
type TransportSiteLink
|
||||
} from '@/api/pl'
|
||||
import { buildPlanCard } from '@/utils/mp-pl-plan-card'
|
||||
import {
|
||||
splitMpPlanDetailActions,
|
||||
type MpDetailActionKind
|
||||
} from '@/utils/mp-pl-plan-detail-actions'
|
||||
import {
|
||||
arrangeTransportLabel,
|
||||
canArrangeTransport,
|
||||
canCancelWaitNotice,
|
||||
canEditPlan,
|
||||
canVoidPlan,
|
||||
canWaitNotice
|
||||
} from '@/utils/pl-plan-action-guards'
|
||||
import {
|
||||
getPlPlanCompanyLabel,
|
||||
getPlYesNoLabel
|
||||
} from '@/utils/pl-display'
|
||||
import {
|
||||
isHazardCertReady,
|
||||
isHazardProduct
|
||||
} from '@/composables/usePlToxicProductCertCheck'
|
||||
import '@/styles/mp-webview.scss'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -62,26 +273,120 @@
|
||||
const acting = ref(false)
|
||||
const errorText = ref('')
|
||||
const plan = ref<Plan | null>(null)
|
||||
const arrangeInfo = ref<PlanArrangeInfo | null>(null)
|
||||
const deliveryIndex = ref(0)
|
||||
const moreOpen = ref(false)
|
||||
const vehicleOpen = ref(false)
|
||||
|
||||
const card = computed(() => (plan.value ? buildPlanCard(plan.value) : null))
|
||||
const statusLabel = computed(() => card.value?.statusLabel || '—')
|
||||
const statusTagType = computed(() => card.value?.statusTagType || 'info')
|
||||
const typeLabel = computed(() => card.value?.typeLabel || '—')
|
||||
const plate = computed(() => card.value?.plate || '—')
|
||||
const people = computed(() => card.value?.people || '—')
|
||||
const product = computed(() => card.value?.product || '—')
|
||||
const deliveryTimeText = computed(() => {
|
||||
const times = plan.value?.deliveryTimes || []
|
||||
if (times.length) return times.join('、')
|
||||
return plan.value?.deliveryTime || '—'
|
||||
const isArranged = computed(() => plan.value?.transportStatus === 'arranged')
|
||||
const hasVehicle = computed(() => {
|
||||
const p = plan.value
|
||||
return !!(p?.licensePlate || p?.vehicleId)
|
||||
})
|
||||
const canArrange = computed(() => (plan.value ? canArrangeTransport(plan.value) : false))
|
||||
const canEdit = computed(() => (plan.value ? canEditPlan(plan.value) : false))
|
||||
const canVoid = computed(() => (plan.value ? canVoidPlan(plan.value) : false))
|
||||
const canWait = computed(() => (plan.value ? canWaitNotice(plan.value) : false))
|
||||
const arrangeLabel = computed(() =>
|
||||
plan.value ? arrangeTransportLabel(plan.value) : '安排运输'
|
||||
const load = computed(() => plan.value?.load || null)
|
||||
const deliveries = computed(() => plan.value?.deliveries || [])
|
||||
const activeDelivery = computed(() => deliveries.value[deliveryIndex.value] || null)
|
||||
const arrangeDriverUrl = computed(() => arrangeInfo.value?.driverUrl || '')
|
||||
const siteLinks = computed<TransportSiteLink[]>(() => arrangeInfo.value?.siteRecords || [])
|
||||
|
||||
const loadContractText = computed(() => {
|
||||
const list = load.value?.contracts || []
|
||||
if (!list.length) return '—'
|
||||
return list.map((c) => c.contractNo || c.contractId).filter(Boolean).join('、') || '—'
|
||||
})
|
||||
const deliveryContractText = computed(() => {
|
||||
const list = activeDelivery.value?.contracts || []
|
||||
if (!list.length) return '—'
|
||||
return list.map((c) => c.contractNo || c.contractId).filter(Boolean).join('、') || '—'
|
||||
})
|
||||
|
||||
function sumProducts(products?: { planQty?: number }[]) {
|
||||
return (products || []).reduce((sum, row) => sum + Number(row.planQty || 0), 0)
|
||||
}
|
||||
const loadQty = computed(() => sumProducts(load.value?.products))
|
||||
const deliveryQty = computed(() =>
|
||||
deliveries.value.reduce((sum, d) => sum + sumProducts(d.products), 0)
|
||||
)
|
||||
const qtyBalanced = computed(() => Math.abs(loadQty.value - deliveryQty.value) < 1e-6)
|
||||
|
||||
const hazardProducts = computed(() => (plan.value?.products || []).filter((p) => isHazardProduct(p)))
|
||||
const certReady = computed(() => isHazardCertReady(plan.value))
|
||||
const certReadyLabel = computed(() => {
|
||||
if (!hazardProducts.value.length) return '无需证书'
|
||||
return certReady.value ? '已齐备' : '待确认'
|
||||
})
|
||||
const certReadyTagType = computed(() => {
|
||||
if (!hazardProducts.value.length) return 'info'
|
||||
return certReady.value ? 'success' : 'warning'
|
||||
})
|
||||
const certSummaryText = computed(() => {
|
||||
if (!hazardProducts.value.length) return '本计划无易制毒/易制爆产品'
|
||||
return `${hazardProducts.value.length} 个危化产品`
|
||||
})
|
||||
|
||||
const freightAmountText = computed(() => {
|
||||
if (plan.value?.hasFreight !== 1) return '—'
|
||||
const n = Number(plan.value?.freightAmount)
|
||||
return Number.isFinite(n) ? n.toFixed(2) : '—'
|
||||
})
|
||||
const freightRemarkText = computed(() => {
|
||||
if (plan.value?.hasFreight !== 1) return '—'
|
||||
return plan.value?.freightRemark?.trim() || '—'
|
||||
})
|
||||
|
||||
const feedbackLines = computed(() => {
|
||||
const lines: { title: string; text: string }[] = []
|
||||
for (const link of siteLinks.value) {
|
||||
const remark = String(link.siteFeedbackRemark || '').trim()
|
||||
if (remark) {
|
||||
lines.push({
|
||||
title: `现场回传 · ${link.productName || '现场'}`,
|
||||
text: remark
|
||||
})
|
||||
}
|
||||
}
|
||||
return lines
|
||||
})
|
||||
|
||||
const actionSplit = computed(() => {
|
||||
const p = plan.value
|
||||
if (!p) return { primary: [], more: [] }
|
||||
return splitMpPlanDetailActions({
|
||||
canEdit: canEditPlan(p),
|
||||
canArrange: canArrangeTransport(p),
|
||||
arrangeLabel: arrangeTransportLabel(p),
|
||||
canWait: canWaitNotice(p),
|
||||
canCancelWait: canCancelWaitNotice(p),
|
||||
canVoid: canVoidPlan(p)
|
||||
})
|
||||
})
|
||||
|
||||
function dibangText(stop: PlanLoad | PlanDelivery | null) {
|
||||
if (!stop) return '—'
|
||||
if (stop.generateDibang === 1) {
|
||||
return `生成${stop.dibangDirection ? `(${stop.dibangDirection})` : ''}`
|
||||
}
|
||||
return '未生成'
|
||||
}
|
||||
|
||||
async function copyText(text: string, label: string) {
|
||||
const value = text.trim()
|
||||
if (!value) {
|
||||
ElMessage.warning('无可复制内容')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
ElMessage.success(`已复制${label}`)
|
||||
} catch {
|
||||
ElMessage.error('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
const id = String(route.params.id || '')
|
||||
@@ -94,7 +399,19 @@
|
||||
try {
|
||||
const res = await planApi.get(id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
deliveryIndex.value = 0
|
||||
errorText.value = ''
|
||||
if (plan.value.transportStatus === 'arranged') {
|
||||
try {
|
||||
const infoRes = await planApi.getArrangeInfo(id)
|
||||
arrangeInfo.value = ((infoRes as { data?: PlanArrangeInfo }).data ??
|
||||
infoRes) as PlanArrangeInfo
|
||||
} catch {
|
||||
arrangeInfo.value = null
|
||||
}
|
||||
} else {
|
||||
arrangeInfo.value = null
|
||||
}
|
||||
} catch (error) {
|
||||
errorText.value = error instanceof Error ? error.message : '加载失败'
|
||||
} finally {
|
||||
@@ -106,51 +423,66 @@
|
||||
void router.push('/m/pl/plan')
|
||||
}
|
||||
|
||||
function goEdit() {
|
||||
if (!plan.value) return
|
||||
void router.push(`/m/pl/plan/${plan.value.id}/edit`)
|
||||
}
|
||||
|
||||
function goArrange() {
|
||||
if (!plan.value) return
|
||||
void router.push(`/m/pl/plan/${plan.value.id}/arrange`)
|
||||
}
|
||||
|
||||
function goCopy() {
|
||||
if (!plan.value) return
|
||||
void router.push({ path: '/m/pl/plan/new', query: { copyFrom: plan.value.id } })
|
||||
}
|
||||
|
||||
function goPrint() {
|
||||
if (!plan.value) return
|
||||
void router.push(`/m/pl/plan/${plan.value.id}/print`)
|
||||
}
|
||||
|
||||
async function doWaitNotice() {
|
||||
if (!plan.value) return
|
||||
await ElMessageBox.confirm('确认将该计划标记为等客户通知?', '等客户通知', {
|
||||
type: 'warning'
|
||||
})
|
||||
acting.value = true
|
||||
try {
|
||||
const res = await planApi.waitNotice(plan.value.id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
ElMessage.success('已标记为等客户通知')
|
||||
} finally {
|
||||
acting.value = false
|
||||
async function runAction(kind: MpDetailActionKind) {
|
||||
const p = plan.value
|
||||
if (!p) return
|
||||
moreOpen.value = false
|
||||
if (kind === 'edit') {
|
||||
void router.push(`/m/pl/plan/${p.id}/edit`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async function doVoid() {
|
||||
if (!plan.value) return
|
||||
await ElMessageBox.confirm('确认作废该运输计划?此操作不可恢复。', '作废', { type: 'warning' })
|
||||
acting.value = true
|
||||
try {
|
||||
const res = await planApi.cancel(plan.value.id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
ElMessage.success('已作废')
|
||||
} finally {
|
||||
acting.value = false
|
||||
if (kind === 'arrange') {
|
||||
void router.push(`/m/pl/plan/${p.id}/arrange`)
|
||||
return
|
||||
}
|
||||
if (kind === 'copy') {
|
||||
void router.push({ path: '/m/pl/plan/new', query: { copyFrom: p.id } })
|
||||
return
|
||||
}
|
||||
if (kind === 'print') {
|
||||
void router.push(`/m/pl/plan/${p.id}/print`)
|
||||
return
|
||||
}
|
||||
if (kind === 'wait') {
|
||||
await ElMessageBox.confirm('确认将该计划标记为等客户通知?', '等客户通知', {
|
||||
type: 'warning'
|
||||
})
|
||||
acting.value = true
|
||||
try {
|
||||
const res = await planApi.waitNotice(p.id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
ElMessage.success('已标记为等客户通知')
|
||||
} finally {
|
||||
acting.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
if (kind === 'cancel-wait') {
|
||||
await ElMessageBox.confirm('确认取消等通知,恢复为待安排?', '取消等通知', {
|
||||
type: 'warning'
|
||||
})
|
||||
acting.value = true
|
||||
try {
|
||||
const res = await planApi.cancelWaitNotice(p.id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
ElMessage.success('已取消等通知')
|
||||
} finally {
|
||||
acting.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
if (kind === 'void') {
|
||||
await ElMessageBox.confirm('确认作废该运输计划?此操作不可恢复。', '作废运输计划', {
|
||||
type: 'warning'
|
||||
})
|
||||
acting.value = true
|
||||
try {
|
||||
const res = await planApi.cancel(p.id)
|
||||
plan.value = ((res as { data?: Plan }).data ?? res) as Plan
|
||||
ElMessage.success('已作废')
|
||||
} finally {
|
||||
acting.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,3 +490,186 @@
|
||||
void loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mp-wv-header__sub {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.mp-wv-detail-body {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.mp-wv-link-block {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mp-wv-link-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: #6b7b74;
|
||||
}
|
||||
|
||||
.mp-wv-link-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mp-wv-link-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 36px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #dcebe4;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mp-wv-site-link + .mp-wv-site-link {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mp-wv-vehicle-box {
|
||||
padding: 10px;
|
||||
border: 1px solid #dcebe4;
|
||||
border-radius: 10px;
|
||||
background: #f7faf8;
|
||||
}
|
||||
|
||||
.mp-wv-feedback {
|
||||
margin-top: 10px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mp-wv-feedback-box {
|
||||
padding: 10px;
|
||||
border: 1px dashed #dcebe4;
|
||||
border-radius: 10px;
|
||||
background: #f7faf8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mp-wv-feedback-box h4 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 12px;
|
||||
color: #087f57;
|
||||
}
|
||||
|
||||
.mp-wv-stop {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mp-wv-stop-title {
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mp-wv-delivery-tabs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.mp-wv-delivery-tab {
|
||||
flex: 0 0 auto;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dcebe4;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
color: #6b7b74;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mp-wv-delivery-tab.active {
|
||||
color: #fff;
|
||||
background: #0a9f6d;
|
||||
border-color: #0a9f6d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mp-wv-prod-table {
|
||||
margin-top: 8px;
|
||||
border: 1px solid #dcebe4;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mp-wv-prod-empty,
|
||||
.mp-wv-prod-row {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #eef4f1;
|
||||
}
|
||||
|
||||
.mp-wv-prod-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.mp-wv-prod-name {
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #087f57;
|
||||
}
|
||||
|
||||
.mp-wv-prod-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px 10px;
|
||||
color: #6b7b74;
|
||||
}
|
||||
|
||||
.mp-wv-prod-grid b {
|
||||
color: #1f2a26;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mp-wv-qty {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: #f7faf8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mp-wv-qty .is-ok {
|
||||
color: #087f57;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mp-wv-cert-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
color: #6b7b74;
|
||||
}
|
||||
|
||||
.mp-wv-cert-hint {
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
color: #6b7b74;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.mp-wv-remark {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.is-danger {
|
||||
color: #d14343 !important;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user