feat(web-admin): show brand qty on transport tab and highlight supplement actions
Expose driver per-product feedback on transport read, display site/driver brand summaries in the transport tab, move supplement buttons to section headers with primary highlight, and wire brand chips into IO/driver edit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b7236329c2
commit
5c323f32c2
7 files changed
+510
-118
No files matched your search
@@ -94,11 +94,12 @@ export function getPlanProductQuantity(product: {
|
||||
inboundEstimateQty?: number | string | null
|
||||
outboundEstimateQty?: number | string | null
|
||||
}) {
|
||||
const qty =
|
||||
product.actualQty ??
|
||||
product.quantity ??
|
||||
product.inboundEstimateQty ??
|
||||
product.outboundEstimateQty
|
||||
const actual = product.actualQty
|
||||
// 实际数量为空或为 0 时,展示合同/计划数量
|
||||
if (actual !== undefined && actual !== null && actual !== '' && Number(actual) !== 0) {
|
||||
return String(actual)
|
||||
}
|
||||
const qty = product.quantity ?? product.inboundEstimateQty ?? product.outboundEstimateQty
|
||||
if (qty === undefined || qty === null || qty === '') return '—'
|
||||
return String(qty)
|
||||
}
|
||||
@@ -146,6 +147,67 @@ export function formatPlanProductListText(
|
||||
return `${name}${specPart},${qtyUnit}`
|
||||
}
|
||||
|
||||
/** 入库单/出库单列表产品一行:名称 + 品牌/型号规格/单位/包装规格/送货方式 */
|
||||
export function formatIoOrderProductListText(item: {
|
||||
productName?: string | null
|
||||
brandSummary?: string | null
|
||||
brandName?: string | null
|
||||
specification?: string | null
|
||||
unit?: string | null
|
||||
unitLabel?: string | null
|
||||
packageSpec?: string | null
|
||||
deliveryMethod?: string | null
|
||||
}) {
|
||||
const name = (item.productName || '').trim() || '—'
|
||||
const bits: string[] = []
|
||||
const brand = (item.brandSummary || item.brandName || '').trim()
|
||||
if (brand) bits.push(`品牌 ${brand}`)
|
||||
const spec = (item.specification || '').trim()
|
||||
if (spec) bits.push(`型号/规格 ${spec}`)
|
||||
const unit = (item.unitLabel || item.unit || '').trim()
|
||||
if (unit) bits.push(`单位 ${unit}`)
|
||||
const packageSpec = (item.packageSpec || '').trim()
|
||||
if (packageSpec) bits.push(`包装规格 ${packageSpec}`)
|
||||
const delivery = (item.deliveryMethod || '').trim()
|
||||
if (delivery) {
|
||||
const deliveryLabels: Record<string, string> = {
|
||||
self_pick: '山峡自提',
|
||||
one_ticket: '一票制',
|
||||
customer_pick: '客户自提'
|
||||
}
|
||||
bits.push(`送货方式 ${deliveryLabels[delivery] || delivery}`)
|
||||
}
|
||||
return bits.length ? `${name},${bits.join(',')}` : name
|
||||
}
|
||||
|
||||
/** 品牌数量摘要,如 `君正 16 / 红四方 12` */
|
||||
export function formatBrandQtySummary(
|
||||
rows?: Array<{ brandName?: string | null; qty?: number | string | null }> | null
|
||||
): string {
|
||||
if (!rows?.length) return ''
|
||||
const parts = rows
|
||||
.map(row => {
|
||||
const qty = Number(row.qty ?? 0)
|
||||
if (!Number.isFinite(qty) || qty <= 0) return ''
|
||||
const name = (row.brandName || '').trim() || '未命名品牌'
|
||||
return `${name} ${qty}`
|
||||
})
|
||||
.filter(Boolean)
|
||||
return parts.join(' / ')
|
||||
}
|
||||
|
||||
/** 现场入出库明细品牌摘要(优先 brandQtys,其次 brandSummary) */
|
||||
export function resolveSiteBrandSummary(item: {
|
||||
brandQtys?: Array<{ brandName?: string | null; qty?: number | string | null }> | null
|
||||
brandSummary?: string | null
|
||||
brandName?: string | null
|
||||
}) {
|
||||
const fromQtys = formatBrandQtySummary(item.brandQtys)
|
||||
if (fromQtys) return fromQtys
|
||||
const summary = (item.brandSummary || item.brandName || '').trim()
|
||||
return summary || '—'
|
||||
}
|
||||
|
||||
/** 司机公开页产品一行展示(含运输计划产品备注) */
|
||||
export function formatPublicTransportProductLine(product: {
|
||||
productName?: string
|
||||
@@ -223,22 +285,38 @@ export function plPlanTypeFilterToTab(planType?: string): PlPlanTypeTab {
|
||||
return 'all'
|
||||
}
|
||||
|
||||
/** 运输计划跟进状态选项(筛选) */
|
||||
export const PL_PLAN_FOLLOW_STATUS_OPTIONS = [
|
||||
{ label: '等通知', value: 'wait_notice' },
|
||||
{ label: '待安排', value: 'pending_arrange' },
|
||||
{ label: '已安排', value: 'arranged' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '已取消', value: 'cancelled' }
|
||||
] as const
|
||||
|
||||
export function getPlPlanFollowStatusTagType(status?: string) {
|
||||
const map: Record<string, string> = {
|
||||
draft: 'info',
|
||||
processing: 'warning',
|
||||
wait_notice: 'warning',
|
||||
pending_arrange: 'danger',
|
||||
arranged: 'success',
|
||||
completed: 'success',
|
||||
cancelled: 'danger'
|
||||
cancelled: 'info',
|
||||
// 旧值兼容(迁移前)
|
||||
draft: 'danger',
|
||||
processing: 'warning'
|
||||
}
|
||||
return map[status || ''] || 'info'
|
||||
}
|
||||
|
||||
export function getPlPlanFollowStatusLabel(status?: string) {
|
||||
const map: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
processing: '进行中',
|
||||
wait_notice: '等通知',
|
||||
pending_arrange: '待安排',
|
||||
arranged: '已安排',
|
||||
completed: '已完成',
|
||||
cancelled: '已作废'
|
||||
cancelled: '已取消',
|
||||
draft: '待安排',
|
||||
processing: '进行中'
|
||||
}
|
||||
return status ? map[status] || status : '—'
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user