242 lines
26 KiB
Vue
242 lines
26 KiB
Vue
<script setup lang="ts">
|
||
import { computed, reactive, ref, watch } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { ClipboardCheck, Download, Eye, PackageCheck, Pencil, Plus, RotateCcw, Search, Ship, UserRoundCheck } from '@lucide/vue'
|
||
import { FIRST_LEG_ORDER_STATUSES } from '@chongchong/shared'
|
||
import MetricStrip from '@/components/MetricStrip.vue'
|
||
import StatusBadge from '@/components/StatusBadge.vue'
|
||
import { useUnsavedWorkRegistration } from '@/composables/useUnsavedWorkRegistration'
|
||
import { useAccessPreviewStore } from '@/stores/accessPreview'
|
||
import { useAdminDataStore } from '@/stores/adminData'
|
||
import type { FirstLegOrder, OrderStatus } from '@/types/admin'
|
||
|
||
type OrderForm = Omit<FirstLegOrder, 'no' | 'createdAt' | 'timeline'>
|
||
|
||
const store = useAdminDataStore()
|
||
const access = useAccessPreviewStore()
|
||
const page = ref(1)
|
||
const pageSize = 10
|
||
const editorOpen = ref(false)
|
||
const detailOpen = ref(false)
|
||
const statusDialogOpen = ref(false)
|
||
const deliveryDialogOpen = ref(false)
|
||
const currentOrder = ref<FirstLegOrder | null>(null)
|
||
const selectedRows = ref<FirstLegOrder[]>([])
|
||
const statusTargetIds = ref<string[]>([])
|
||
const dateRange = ref<[string, string] | []>([])
|
||
const filters = reactive({ keyword: '', status: '', mode: '', customerId: '' })
|
||
const statusForm = reactive<{ status: OrderStatus; operator: string; note: string }>({ status: '已受理', operator: '', note: '' })
|
||
const deliveryForm = reactive({ orderId: '', expressCompanyId: '', trackingNo: '', operator: '' })
|
||
|
||
const emptyForm = (): OrderForm => ({
|
||
id: '', customerId: '', customerName: '', referenceNo: '', transportMode: '海运整箱 FCL', businessType: '出口',
|
||
origin: '', destination: '', receivingWarehouseId: '', receivingWarehouseSnapshot: null, container: '40HQ x 1', etd: '', eta: '', amount: 0, currency: 'CNY', status: '草稿',
|
||
priority: '普通', operator: '未分配', shipper: '', consignee: '', cargoName: '', pieces: 1, weight: 0, volume: 0, remark: '',
|
||
})
|
||
const form = reactive(emptyForm())
|
||
const editorLabel = computed(() => form.id ? '编辑头程订单' : '创建头程订单')
|
||
const editorRequiredPermission = computed(() => form.id ? null : 'first-leg:create')
|
||
const { captureBaseline } = useUnsavedWorkRegistration({
|
||
key: 'first-leg-order-editor',
|
||
label: editorLabel,
|
||
isOpen: editorOpen,
|
||
model: form,
|
||
requiredPermission: editorRequiredPermission,
|
||
})
|
||
const statusOptions: OrderStatus[] = FIRST_LEG_ORDER_STATUSES.map((item) => item.label)
|
||
const statusTransitions: Record<OrderStatus, OrderStatus[]> = { 草稿: ['待审核', '已取消'], 待审核: ['已受理', '已取消'], 已受理: ['已订舱', '已取消'], 已订舱: ['运输中', '已取消'], 运输中: ['已完成', '已取消'], 已完成: [], 已取消: [] }
|
||
const availableStatusOptions = computed(() => {
|
||
if (statusTargetIds.value.length !== 1) return ['已取消'] as OrderStatus[]
|
||
const order = store.orders.find((item) => item.id === statusTargetIds.value[0])
|
||
return order ? [order.status, ...statusTransitions[order.status]] : []
|
||
})
|
||
const operatorOptions = computed(() => store.users.filter((item) => item.status === '正常' && ['运营部', '操作部', '平台管理'].includes(item.department)).map((item) => item.name))
|
||
const selectableWarehouses = computed(() => store.warehouses.filter((item) => item.status === 'ACTIVE' || item.id === form.receivingWarehouseId))
|
||
|
||
const filteredRows = computed(() => store.orders.filter((item) => {
|
||
const keyword = filters.keyword.trim().toLowerCase()
|
||
const keywordMatch = !keyword || [item.no, item.customerName, item.referenceNo, item.origin, item.destination].some((value) => value.toLowerCase().includes(keyword))
|
||
const dateMatch = dateRange.value.length !== 2 || (item.createdAt.slice(0, 10) >= dateRange.value[0] && item.createdAt.slice(0, 10) <= dateRange.value[1])
|
||
return keywordMatch && dateMatch && (!filters.status || item.status === filters.status) && (!filters.mode || item.transportMode === filters.mode) && (!filters.customerId || item.customerId === filters.customerId)
|
||
}))
|
||
const pagedRows = computed(() => filteredRows.value.slice((page.value - 1) * pageSize, page.value * pageSize))
|
||
const metrics = computed(() => [
|
||
{ label: '全部订单', value: store.orders.length, note: '当前数据范围' },
|
||
{ label: '待审核/受理', value: store.pendingOrders, note: '需要运营处理' },
|
||
{ label: '运输中', value: store.orders.filter((item) => item.status === '运输中').length, note: '正在履约' },
|
||
{ label: '应收金额', value: `¥${store.orders.reduce((total, item) => total + (item.currency === 'CNY' ? item.amount : 0), 0).toLocaleString()}`, note: '人民币订单合计' },
|
||
])
|
||
|
||
watch(filters, () => { page.value = 1 })
|
||
watch(dateRange, () => { page.value = 1 })
|
||
watch(
|
||
() => access.can('first-leg:create'),
|
||
(canCreate) => {
|
||
if (canCreate || !editorOpen.value || form.id) return
|
||
editorOpen.value = false
|
||
Object.assign(form, emptyForm())
|
||
},
|
||
{ flush: 'sync' },
|
||
)
|
||
|
||
function resetFilters() { Object.assign(filters, { keyword: '', status: '', mode: '', customerId: '' }); dateRange.value = [] }
|
||
function openCreate() { Object.assign(form, emptyForm()); captureBaseline(); editorOpen.value = true }
|
||
function openEdit(row: FirstLegOrder) { Object.assign(form, row); captureBaseline(); detailOpen.value = false; editorOpen.value = true }
|
||
function openDetail(row: FirstLegOrder) { currentOrder.value = row; detailOpen.value = true }
|
||
function handleCustomerChange(customerId: string) {
|
||
const customer = store.customers.find((item) => item.id === customerId)
|
||
if (!customer) return
|
||
form.customerName = customer.name
|
||
if (!form.shipper) form.shipper = customer.name
|
||
}
|
||
|
||
function saveOrder(status: '草稿' | '待审核') {
|
||
if (!form.customerId || !form.origin.trim() || !form.destination.trim() || !form.receivingWarehouseId || !form.cargoName.trim()) {
|
||
ElMessage.warning('请填写客户、起运地、目的地、平台收货仓和货物名称')
|
||
return
|
||
}
|
||
const customer = store.customers.find((item) => item.id === form.customerId)
|
||
try {
|
||
store.saveOrder({ ...form, id: form.id || undefined, customerName: customer?.name || form.customerName, status: form.id ? form.status : status })
|
||
editorOpen.value = false
|
||
ElMessage.success(status === '草稿' ? '订单草稿已保存' : '头程订单已创建并提交审核')
|
||
} catch (error) {
|
||
ElMessage.error(error instanceof Error ? error.message : '订单保存失败')
|
||
}
|
||
}
|
||
|
||
function openStatusDialog(rows: FirstLegOrder[]) {
|
||
if (rows.length === 0) return
|
||
statusTargetIds.value = rows.map((item) => item.id)
|
||
statusForm.status = rows.length === 1 ? rows[0].status : '已受理'
|
||
statusForm.operator = rows.length === 1 && rows[0].operator !== '未分配' ? rows[0].operator : ''
|
||
statusForm.note = ''
|
||
statusDialogOpen.value = true
|
||
}
|
||
|
||
function applyStatus() {
|
||
if (!statusForm.operator && !['草稿', '待审核', '已取消'].includes(statusForm.status)) {
|
||
ElMessage.warning('请指定订单负责人')
|
||
return
|
||
}
|
||
try { statusTargetIds.value.forEach((id) => store.updateOrderStatus(id, statusForm.status, statusForm.operator, statusForm.note)) } catch (error) { ElMessage.error(error instanceof Error ? error.message : '订单状态更新失败'); return }
|
||
statusDialogOpen.value = false
|
||
selectedRows.value = []
|
||
if (currentOrder.value) currentOrder.value = store.orders.find((item) => item.id === currentOrder.value?.id) || null
|
||
ElMessage.success(statusTargetIds.value.length > 1 ? `已更新 ${statusTargetIds.value.length} 笔订单` : '订单状态已更新')
|
||
}
|
||
|
||
function openDeliveryDialog(order: FirstLegOrder) {
|
||
currentOrder.value = order
|
||
Object.assign(deliveryForm, {
|
||
orderId: order.id,
|
||
expressCompanyId: order.deliveryInfo?.expressCompanyId || '',
|
||
trackingNo: order.deliveryInfo?.trackingNo || '',
|
||
operator: order.deliveryInfo?.operator || (order.operator === '未分配' ? '' : order.operator),
|
||
})
|
||
deliveryDialogOpen.value = true
|
||
}
|
||
|
||
function saveDeliveryInfo() {
|
||
if (!deliveryForm.expressCompanyId || !deliveryForm.trackingNo.trim() || !deliveryForm.operator) {
|
||
ElMessage.warning('请选择快递公司,并填写快递单号和操作人')
|
||
return
|
||
}
|
||
try {
|
||
store.saveOrderDeliveryInfo(deliveryForm.orderId, deliveryForm.expressCompanyId, deliveryForm.trackingNo, deliveryForm.operator)
|
||
currentOrder.value = store.orders.find((item) => item.id === deliveryForm.orderId) || null
|
||
deliveryDialogOpen.value = false
|
||
ElMessage.success('履约快递信息已保存')
|
||
} catch (error) {
|
||
ElMessage.error(error instanceof Error ? error.message : '履约快递信息保存失败')
|
||
}
|
||
}
|
||
|
||
function deliveryCompanies(order: FirstLegOrder | null) {
|
||
return store.expressCompanies.filter((item) => item.status === '合作中' || item.id === order?.deliveryInfo?.expressCompanyId)
|
||
}
|
||
|
||
function statusStep(status: OrderStatus) {
|
||
const index = ['待审核', '已受理', '已订舱', '运输中', '已完成'].indexOf(status)
|
||
return index < 0 ? 0 : index + 1
|
||
}
|
||
|
||
function contactCustomerForModification(order: FirstLegOrder) {
|
||
try { store.recordCustomerModificationContact(order.id, order.operator === '未分配' ? '平台管理员' : order.operator); currentOrder.value = store.orders.find((item) => item.id === order.id) || null; ElMessage.success('已记录联系客户修改,订单继续保持待审核') }
|
||
catch (error) { ElMessage.error(error instanceof Error ? error.message : '记录失败') }
|
||
}
|
||
|
||
function canEditOrder(order: FirstLegOrder) { return access.can('first-leg:status') || (order.status === '草稿' && access.can('first-leg:create')) }
|
||
|
||
function exportOrders() { ElMessage.success(`已生成 ${filteredRows.value.length} 笔订单的导出任务`) }
|
||
</script>
|
||
|
||
<template>
|
||
<div class="business-page">
|
||
<MetricStrip :items="metrics" />
|
||
<section class="surface-panel filter-panel order-filter" aria-label="头程订单筛选">
|
||
<div class="filter-fields">
|
||
<el-input v-model="filters.keyword" clearable placeholder="订单号、客户、参考号或港口" aria-label="订单关键词"><template #prefix><Search :size="16" /></template></el-input>
|
||
<el-select v-model="filters.customerId" clearable filterable placeholder="全部客户" aria-label="订单客户"><el-option v-for="item in store.customers" :key="item.id" :label="item.name" :value="item.id" /></el-select>
|
||
<el-select v-model="filters.status" clearable placeholder="全部状态" aria-label="订单状态"><el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" /></el-select>
|
||
<el-select v-model="filters.mode" clearable placeholder="全部运输方式" aria-label="运输方式"><el-option v-for="item in ['海运整箱 FCL','海运拼箱 LCL','空运','陆运']" :key="item" :label="item" :value="item" /></el-select>
|
||
<el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="创建开始" end-placeholder="创建结束" aria-label="订单创建日期" />
|
||
</div>
|
||
<div class="filter-actions"><el-button @click="resetFilters"><RotateCcw :size="16" />重置</el-button><el-button v-if="access.can('first-leg:status')" :disabled="selectedRows.length === 0" @click="openStatusDialog(selectedRows)"><ClipboardCheck :size="16" />批量更新</el-button><el-button v-if="!access.usesServerPermissions" @click="exportOrders"><Download :size="16" />导出</el-button><el-button v-if="access.can('first-leg:create')" type="primary" @click="openCreate"><Plus :size="16" />创建订单</el-button></div>
|
||
</section>
|
||
|
||
<section class="surface-panel table-panel">
|
||
<header class="panel-heading"><div><h2>头程订单</h2><p>从审核、受理、订舱到运输完成,统一跟进订单状态与负责人。</p></div><span v-if="selectedRows.length" class="selection-note">已选择 {{ selectedRows.length }} 笔</span></header>
|
||
<el-table :data="pagedRows" row-key="id" empty-text="没有符合条件的头程订单" @selection-change="selectedRows = $event">
|
||
<el-table-column type="selection" width="44" />
|
||
<el-table-column label="订单信息" min-width="210"><template #default="{ row }"><div class="primary-cell"><strong class="order-no">{{ row.no }}</strong><span>{{ row.referenceNo || '无客户参考号' }} · {{ row.createdAt }}</span></div></template></el-table-column>
|
||
<el-table-column label="客户" min-width="185" show-overflow-tooltip><template #default="{ row }"><div class="primary-cell"><strong>{{ row.customerName }}</strong><span>{{ row.businessType }} · {{ row.priority }}</span></div></template></el-table-column>
|
||
<el-table-column label="运输方案" min-width="190"><template #default="{ row }"><div class="primary-cell"><strong>{{ row.transportMode }}</strong><span>{{ row.container }}</span></div></template></el-table-column>
|
||
<el-table-column label="路线" min-width="160"><template #default="{ row }"><div class="route-cell"><span>{{ row.origin }}</span><i>→</i><span>{{ row.destination }}</span></div></template></el-table-column>
|
||
<el-table-column label="平台收货仓" min-width="150"><template #default="{ row }"><div class="primary-cell"><strong>{{ row.receivingWarehouseSnapshot?.name || '-' }}</strong><span>{{ row.receivingWarehouseSnapshot?.code || '-' }}</span></div></template></el-table-column>
|
||
<el-table-column label="履约快递" min-width="160"><template #default="{ row }"><div class="primary-cell"><strong>{{ row.deliveryInfo?.expressCompanyName || '未维护' }}</strong><span>{{ row.deliveryInfo?.trackingNo || '—' }}</span></div></template></el-table-column>
|
||
<el-table-column label="应收" width="118" align="right"><template #default="{ row }"><strong>{{ row.currency === 'CNY' ? '¥' : '$' }}{{ row.amount.toLocaleString() }}</strong></template></el-table-column>
|
||
<el-table-column label="负责人" width="100"><template #default="{ row }"><span :class="{ 'muted-value': row.operator === '未分配' }">{{ row.operator }}</span></template></el-table-column>
|
||
<el-table-column label="状态" width="96"><template #default="{ row }"><StatusBadge :status="row.status" /></template></el-table-column>
|
||
<el-table-column label="操作" width="290" fixed="right"><template #default="{ row }"><div class="table-actions"><el-button link type="primary" @click="openDetail(row as FirstLegOrder)"><Eye :size="15" />详情</el-button><el-button v-if="canEditOrder(row as FirstLegOrder)" link type="primary" @click="openEdit(row as FirstLegOrder)"><Pencil :size="15" />编辑</el-button><el-button v-if="access.can('first-leg:delivery')" link type="primary" @click="openDeliveryDialog(row as FirstLegOrder)"><PackageCheck :size="15" />快递</el-button><el-button v-if="access.can('first-leg:status')" link type="success" @click="openStatusDialog([row as FirstLegOrder])"><UserRoundCheck :size="15" />处理</el-button></div></template></el-table-column>
|
||
</el-table>
|
||
<footer class="table-footer"><span>显示 {{ pagedRows.length }} 条,共 {{ filteredRows.length }} 条</span><el-pagination v-model:current-page="page" :page-size="pageSize" :total="filteredRows.length" layout="prev, pager, next" /></footer>
|
||
</section>
|
||
|
||
<el-drawer v-model="editorOpen" :title="form.id ? '编辑头程订单' : '创建头程订单'" size="780px" class="business-drawer editor-drawer order-editor" :modal="false" :close-on-click-modal="false">
|
||
<el-form label-position="top" class="drawer-form">
|
||
<div class="form-section"><h3>业务信息</h3><div class="form-grid three-cols"><el-form-item label="客户" required><el-select v-model="form.customerId" filterable placeholder="请选择客户" @change="handleCustomerChange"><el-option v-for="item in store.customers.filter((customer) => customer.status === '正常')" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item><el-form-item label="运输方式"><el-select v-model="form.transportMode"><el-option v-for="item in ['海运整箱 FCL','海运拼箱 LCL','空运','陆运']" :key="item" :label="item" :value="item" /></el-select></el-form-item><el-form-item label="业务类型"><el-radio-group v-model="form.businessType"><el-radio-button label="出口" value="出口" /><el-radio-button label="进口" value="进口" /></el-radio-group></el-form-item><el-form-item label="客户参考号"><el-input v-model="form.referenceNo" /></el-form-item><el-form-item label="优先级"><el-select v-model="form.priority"><el-option label="普通" value="普通" /><el-option label="紧急" value="紧急" /><el-option label="加急" value="加急" /></el-select></el-form-item><el-form-item label="负责人"><el-select v-model="form.operator" clearable placeholder="暂不分配"><el-option v-for="item in operatorOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item></div></div>
|
||
<div class="form-section"><h3>运输信息</h3><div class="form-grid three-cols"><el-form-item label="起运地 / POL" required><el-input v-model="form.origin" placeholder="如 宁波" /></el-form-item><el-form-item label="目的地 / POD" required><el-input v-model="form.destination" placeholder="如 洛杉矶" /></el-form-item><el-form-item label="平台收货仓" required><el-select v-model="form.receivingWarehouseId" filterable placeholder="请选择收货仓"><el-option v-for="warehouse in selectableWarehouses" :key="warehouse.id" :label="`${warehouse.name} · ${warehouse.city}`" :value="warehouse.id" :disabled="warehouse.status !== 'ACTIVE'" /></el-select></el-form-item><el-form-item label="柜型 / 箱量"><el-input v-model="form.container" placeholder="如 40HQ x 2" /></el-form-item><el-form-item label="预计出发 ETD"><el-date-picker v-model="form.etd" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" /></el-form-item><el-form-item label="预计到达 ETA"><el-date-picker v-model="form.eta" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" /></el-form-item><el-form-item label="应收金额"><div class="amount-field"><el-select v-model="form.currency"><el-option label="CNY" value="CNY" /><el-option label="USD" value="USD" /></el-select><el-input-number v-model="form.amount" :min="0" :precision="2" controls-position="right" /></div></el-form-item></div></div>
|
||
<div class="form-section"><h3>货物与收发货方</h3><div class="form-grid three-cols"><el-form-item label="货物名称" required><el-input v-model="form.cargoName" /></el-form-item><el-form-item label="件数"><el-input-number v-model="form.pieces" :min="1" controls-position="right" /></el-form-item><el-form-item label="毛重 (kg)"><el-input-number v-model="form.weight" :min="0" :precision="2" controls-position="right" /></el-form-item><el-form-item label="体积 (m³)"><el-input-number v-model="form.volume" :min="0" :precision="2" controls-position="right" /></el-form-item><el-form-item label="发货方"><el-input v-model="form.shipper" /></el-form-item><el-form-item label="收货方"><el-input v-model="form.consignee" /></el-form-item></div><el-form-item label="内部备注"><el-input v-model="form.remark" type="textarea" :rows="3" placeholder="仅平台内部可见" /></el-form-item></div>
|
||
</el-form>
|
||
<template #footer><div class="drawer-footer"><el-button @click="editorOpen = false">取消</el-button><el-button v-if="!form.id && access.can('first-leg:create')" @click="saveOrder('草稿')">保存草稿</el-button><el-button v-if="form.id || access.can('first-leg:create')" type="primary" @click="saveOrder('待审核')">{{ form.id ? '保存修改' : '创建并提交' }}</el-button></div></template>
|
||
</el-drawer>
|
||
|
||
<el-drawer v-model="detailOpen" title="订单详情" size="720px" class="business-drawer">
|
||
<template v-if="currentOrder">
|
||
<div class="order-detail-head"><div><span>{{ currentOrder.transportMode }}</span><h2>{{ currentOrder.no }}</h2><p>{{ currentOrder.customerName }} · {{ currentOrder.referenceNo }}</p></div><StatusBadge :status="currentOrder.status" /></div>
|
||
<div class="order-progress"><el-steps :active="statusStep(currentOrder.status)" finish-status="success" align-center><el-step title="待审核" /><el-step title="已受理" /><el-step title="已订舱" /><el-step title="运输中" /><el-step title="已完成" /></el-steps></div>
|
||
<div class="detail-section"><h3>订单概要</h3><el-descriptions :column="2" border><el-descriptions-item label="运输路线">{{ currentOrder.origin }} → {{ currentOrder.destination }}</el-descriptions-item><el-descriptions-item label="业务类型">{{ currentOrder.businessType }}</el-descriptions-item><el-descriptions-item label="平台收货仓"><strong>{{ currentOrder.receivingWarehouseSnapshot?.name || '-' }}</strong><span v-if="currentOrder.receivingWarehouseSnapshot" class="warehouse-snapshot-detail">{{ currentOrder.receivingWarehouseSnapshot.code }} · {{ currentOrder.receivingWarehouseSnapshot.province }}{{ currentOrder.receivingWarehouseSnapshot.city }}{{ currentOrder.receivingWarehouseSnapshot.address }}</span></el-descriptions-item><el-descriptions-item label="柜型/箱量">{{ currentOrder.container }}</el-descriptions-item><el-descriptions-item label="负责人">{{ currentOrder.operator }}</el-descriptions-item><el-descriptions-item label="ETD / ETA">{{ currentOrder.etd || '-' }} / {{ currentOrder.eta || '-' }}</el-descriptions-item><el-descriptions-item label="应收金额">{{ currentOrder.currency === 'CNY' ? '¥' : '$' }}{{ currentOrder.amount.toLocaleString() }}</el-descriptions-item></el-descriptions></div>
|
||
<div class="detail-section"><h3>货物信息</h3><MetricStrip :items="[{ label: '品名', value: currentOrder.cargoName }, { label: '件数', value: currentOrder.pieces }, { label: '毛重', value: `${currentOrder.weight.toLocaleString()} kg` }, { label: '体积', value: `${currentOrder.volume} m³` }]" /><div class="party-grid"><div><span>发货方</span><strong>{{ currentOrder.shipper }}</strong></div><div><span>收货方</span><strong>{{ currentOrder.consignee }}</strong></div></div></div>
|
||
<div class="detail-section"><h3>履约快递</h3><el-empty v-if="!currentOrder.deliveryInfo" description="尚未维护快递公司和快递单号" :image-size="64" /><el-descriptions v-else :column="2" border><el-descriptions-item label="快递公司">{{ currentOrder.deliveryInfo.expressCompanyName }}({{ currentOrder.deliveryInfo.expressCompanyCode }})</el-descriptions-item><el-descriptions-item label="快递单号">{{ currentOrder.deliveryInfo.trackingNo }}</el-descriptions-item><el-descriptions-item label="维护人">{{ currentOrder.deliveryInfo.operator }}</el-descriptions-item><el-descriptions-item label="更新时间">{{ currentOrder.deliveryInfo.updatedAt }}</el-descriptions-item></el-descriptions></div>
|
||
<div class="detail-section"><h3>处理记录</h3><el-timeline><el-timeline-item v-for="item in [...currentOrder.timeline].reverse()" :key="`${item.time}-${item.title}`" :timestamp="`${item.time} · ${item.operator}`" placement="top"><strong>{{ item.title }}</strong><p>{{ item.description }}</p></el-timeline-item></el-timeline></div>
|
||
</template>
|
||
<template #footer><div class="drawer-footer"><el-button @click="detailOpen = false">关闭</el-button><el-button v-if="currentOrder && canEditOrder(currentOrder)" @click="openEdit(currentOrder)">编辑订单</el-button><el-button v-if="currentOrder?.status === '待审核' && access.can('first-leg:status')" @click="contactCustomerForModification(currentOrder)">联系客户修改</el-button><el-button v-if="access.can('first-leg:delivery')" @click="currentOrder && openDeliveryDialog(currentOrder)">维护快递</el-button><el-button v-if="access.can('first-leg:status')" type="primary" @click="currentOrder && openStatusDialog([currentOrder])">更新状态</el-button></div></template>
|
||
</el-drawer>
|
||
|
||
<el-dialog v-model="statusDialogOpen" :title="statusTargetIds.length > 1 ? `批量更新 ${statusTargetIds.length} 笔订单` : '处理订单'" width="520px" class="status-dialog">
|
||
<el-form label-position="top"><el-form-item label="订单状态"><el-select v-model="statusForm.status"><el-option v-for="item in availableStatusOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item><el-form-item label="负责人"><el-select v-model="statusForm.operator" clearable placeholder="请选择负责人"><el-option v-for="item in operatorOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item><el-form-item label="处理说明"><el-input v-model="statusForm.note" type="textarea" :rows="3" placeholder="记录本次状态变更的原因或下一步动作" /></el-form-item></el-form>
|
||
<template #footer><el-button @click="statusDialogOpen = false">取消</el-button><el-button v-if="access.can('first-leg:status')" type="primary" @click="applyStatus">确认更新</el-button></template>
|
||
</el-dialog>
|
||
<el-dialog v-model="deliveryDialogOpen" title="维护履约快递" width="520px">
|
||
<el-alert title="一期每个订单仅维护一组快递公司和快递单号;修改会覆盖当前值并追加审计记录。" type="info" :closable="false" show-icon />
|
||
<el-form label-position="top" class="delivery-form"><el-form-item label="快递公司" required><el-select v-model="deliveryForm.expressCompanyId" filterable placeholder="请选择合作中的快递公司"><el-option v-for="item in deliveryCompanies(currentOrder)" :key="item.id" :label="`${item.name} · ${item.code}`" :value="item.id" :disabled="item.status !== '合作中'" /></el-select></el-form-item><el-form-item label="快递单号" required><el-input v-model="deliveryForm.trackingNo" maxlength="80" placeholder="请输入完整快递单号" /></el-form-item><el-form-item label="操作人" required><el-select v-model="deliveryForm.operator" filterable placeholder="请选择操作人"><el-option v-for="item in operatorOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item></el-form>
|
||
<template #footer><el-button @click="deliveryDialogOpen = false">取消</el-button><el-button v-if="access.can('first-leg:delivery')" type="primary" @click="saveDeliveryInfo">保存快递信息</el-button></template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.warehouse-snapshot-detail { display: block; margin-top: 4px; color: var(--cc-muted); font-size: 11px; line-height: 1.5; }
|
||
</style>
|