3814 lines
126 KiB
Vue
3814 lines
126 KiB
Vue
<!-- 订单管理页面 -->
|
||
<template>
|
||
<div class="page-container" :class="{ 'order-embedded': embeddedMode, 'order-bridge': bridgeMode }">
|
||
<div class="page-head">
|
||
<h1>订单列表</h1>
|
||
<p class="page-desc">集中查看各区域订单状态、配送进度与支付情况,支持筛选、展开查看商品明细与运营处理。</p>
|
||
</div>
|
||
<div class="region-tabs region-tab-strip">
|
||
<div class="tab-button-group region-tab-group">
|
||
<el-button
|
||
v-for="r in allowedRegionList"
|
||
:key="r.id"
|
||
class="region-tab-btn"
|
||
:type="activeRegionId === r.id ? 'primary' : 'default'"
|
||
@click="selectRegionTab(r.id)"
|
||
>
|
||
<span class="region-tab-inner">
|
||
<span class="region-tab-name">{{ r.name }}</span>
|
||
<span v-if="r.isActive === false" class="region-tab-badge">禁用</span>
|
||
</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="panel">
|
||
<div class="panel-top">
|
||
<h2>订单列表</h2>
|
||
</div>
|
||
|
||
<div class="filter-block">
|
||
<el-form :model="searchForm" class="filter-form" label-position="top">
|
||
<p class="filter-hint">支持按订单状态、配送状态、支付状态等维度快速筛选;可展开更多条件进行精细检索。</p>
|
||
<div class="filter-grid order-filter-main-grid">
|
||
<el-form-item label="关键词">
|
||
<el-input
|
||
v-model="searchForm.keyword"
|
||
placeholder="订单编号/客户名称"
|
||
clearable
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="订单状态">
|
||
<el-select v-model="searchForm.orderStatus" placeholder="全部" clearable>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="待支付" value="pending_payment" />
|
||
<el-option label="待收货" value="pending_confirm" />
|
||
<el-option label="已完成" value="completed" />
|
||
<el-option label="已取消" value="cancelled" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="配送状态">
|
||
<el-select v-model="searchForm.deliveryStatus" placeholder="全部" clearable>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="待分配配送" value="pending_dispatch" />
|
||
<el-option label="配送中" value="delivering" />
|
||
<el-option label="配送完成" value="delivered" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="支付状态">
|
||
<el-select v-model="searchForm.payStatus" placeholder="全部" clearable>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="待支付" value="pending_payment" />
|
||
<el-option label="已支付" value="paid" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<div v-show="filterExpanded" class="filter-extra">
|
||
<div class="filter-grid order-filter-extra-grid">
|
||
<el-form-item label="支付方式">
|
||
<el-select v-model="searchForm.paymentMethod" placeholder="全部" clearable>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="线上支付" value="online" />
|
||
<el-option label="线下支付" value="offline" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-actions">
|
||
<el-button
|
||
class="btn-link btn-filter-toggle"
|
||
:aria-expanded="String(filterExpanded)"
|
||
@click="filterExpanded = !filterExpanded"
|
||
>
|
||
<el-icon class="btn-filter-toggle-ic">
|
||
<ArrowDown v-if="!filterExpanded" />
|
||
<ArrowUp v-else />
|
||
</el-icon>
|
||
{{ filterExpanded ? '收起更多条件' : '展开更多条件' }}
|
||
</el-button>
|
||
|
||
<div class="filter-actions-end">
|
||
<el-button class="btn-ghost" @click="handleReset">
|
||
<el-icon><RefreshLeft /></el-icon>重置
|
||
</el-button>
|
||
<el-button class="btn-main" type="primary" @click="handleSearch">
|
||
<el-icon><Search /></el-icon>搜索
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 数据表格:单元格 flex 纵向居中,避免订单状态列 el-space 与其它列 el-tag 错位 -->
|
||
<div class="list-body list-wrap">
|
||
<el-table
|
||
ref="orderListTableRef"
|
||
class="order-list-table list-table"
|
||
:data="tableData"
|
||
v-loading="loading"
|
||
stripe
|
||
@expand-change="handleOrderRowExpand"
|
||
>
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
width="68"
|
||
align="center"
|
||
header-align="center"
|
||
:index="index => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||
/>
|
||
<el-table-column type="expand" width="48">
|
||
<template #default="{ row }">
|
||
<div class="order-row-expand-wrap" v-loading="!!expandedRowLoadingMap[row.id]">
|
||
<div
|
||
v-if="getExpandedItems(row.id).length"
|
||
class="order-row-expand-table-wrap sqxp-subtable-wrap"
|
||
>
|
||
<el-table
|
||
:data="getExpandedItems(row.id)"
|
||
stripe
|
||
size="small"
|
||
class="order-row-expand-table sqxp-subtable"
|
||
@row-dblclick="openOrderProductView"
|
||
>
|
||
<el-table-column type="index" label="序号" width="68" align="center" header-align="center" :index="index => index + 1" />
|
||
<el-table-column prop="productName" label="商品" min-width="220">
|
||
<template #default="{ row: line }">
|
||
<div class="expand-product-cell">
|
||
<div class="expand-product-thumb-wrap">
|
||
<el-image v-if="line.imageUrl" :src="line.imageUrl" fit="cover" class="expand-product-thumb" />
|
||
<span v-else class="expand-product-thumb placeholder">无图</span>
|
||
</div>
|
||
<span class="expand-product-name">{{ line.productName || '-' }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierName" label="供货商" min-width="120" />
|
||
<el-table-column label="计量" width="88" align="center">
|
||
<template #default="{ row: line }">{{ formatMeasureCell(line.measureValue) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitName" label="单位" width="70" align="center" />
|
||
<el-table-column prop="quantity" label="数量" width="80" align="right" />
|
||
<el-table-column label="供货价" min-width="110" align="right">
|
||
<template #default="{ row: line }">¥{{ formatAmount(line.supplierUnitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="供货金额" min-width="110" align="right">
|
||
<template #default="{ row: line }">¥{{ formatAmount(line.supplierAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品售价" min-width="110" align="right">
|
||
<template #default="{ row: line }">¥{{ formatAmount(line.sellingUnitPrice ?? line.unitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品金额" min-width="110" align="right">
|
||
<template #default="{ row: line }">¥{{ formatAmount(line.sellingAmount ?? line.amount) }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="order-row-expand-summary sqxp-subtable-summary">
|
||
<span
|
||
>供货总金额(元):<span class="sqxp-subtable-summary__amount"
|
||
>¥{{ formatAmount(getExpandedSummary(row.id).supplierTotal) }}</span
|
||
></span
|
||
>
|
||
<span
|
||
>售出总金额(元):<span class="sqxp-subtable-summary__amount"
|
||
>¥{{ formatAmount(getExpandedSummary(row.id).sellingTotal) }}</span
|
||
></span
|
||
>
|
||
</div>
|
||
</div>
|
||
<el-empty v-else-if="!expandedRowLoadingMap[row.id]" description="暂无商品明细" :image-size="72" />
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="orderNo" label="订单编号" min-width="200">
|
||
<template #default="{ row }">
|
||
<el-button link type="primary" @click="toggleRowExpand(row)">{{ row.orderNo }}</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="clientName" label="客户名称" min-width="150" />
|
||
<el-table-column prop="regionName" label="区域" min-width="100" />
|
||
<el-table-column prop="supplierTotalAmount" label="供货总金额(元)" min-width="140">
|
||
<template #default="{ row }">
|
||
<span class="amount-text">¥{{ formatAmount(row.supplierTotalAmount) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="totalAmount" label="订单金额(元)" min-width="120">
|
||
<template #default="{ row }">
|
||
<span class="amount-text">¥{{ row.totalAmount }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="orderStatus" label="订单状态" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-space :size="6">
|
||
<el-tag :type="getOrderStatusType(resolveOrderStatus(row))" size="small">{{
|
||
getOrderStatusText(resolveOrderStatus(row))
|
||
}}</el-tag>
|
||
<el-tag v-if="row.cancelStatus === 'pending_review'" type="warning" size="small"
|
||
>取消审核中</el-tag
|
||
>
|
||
</el-space>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="deliveryStatus" label="配送状态" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getDeliveryStatusType(resolveDeliveryStatus(row))" size="small">{{
|
||
getDeliveryStatusText(resolveDeliveryStatus(row))
|
||
}}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="payStatus" label="支付状态" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getPayStatusType(resolvePayStatus(row))" size="small">{{
|
||
getPayStatusText(resolvePayStatus(row))
|
||
}}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="paymentMethod" label="支付方式" min-width="110" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.paymentMethod === 'offline' ? 'warning' : 'primary'" size="small">
|
||
{{ getPaymentMethod(row.paymentMethod) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="clientSettlementStatus" label="客户结算" min-width="110" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getSettlementTagType(row.clientSettlementStatus)" size="small">
|
||
{{ getClientSettlementLabel(row.clientSettlementStatus) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierSettlementStatus" label="供应商结算" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getSettlementTagType(row.supplierSettlementStatus)" size="small">
|
||
{{ getSupplierSettlementLabel(row.supplierSettlementStatus) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="createAt" label="下单时间" min-width="180" />
|
||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||
<template #default="{ row }">
|
||
<el-button class="btn-op-item primary" @click="handleView(row)">查看/运营</el-button>
|
||
<el-button class="btn-op-item" v-if="canAssign(row)" @click="handleAssign(row)">分配配送员</el-button>
|
||
<el-button class="btn-op-item op-warn" v-if="row.cancelStatus === 'pending_review'" @click="handleReviewCancel(row)"
|
||
>审核取消</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination list-pager-bar">
|
||
<el-pagination
|
||
:current-page="pagination.page"
|
||
:page-size="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@update:current-page="
|
||
(v) => {
|
||
pagination.page = v
|
||
loadData()
|
||
}
|
||
"
|
||
@update:page-size="
|
||
(v) => {
|
||
pagination.pageSize = v
|
||
pagination.page = 1
|
||
loadData()
|
||
}
|
||
"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
<!-- 订单详情:普通模式用 Dialog;嵌入模式下直接渲染内容,避免父层弹窗+子层弹窗叠加 -->
|
||
<el-dialog
|
||
v-if="!embeddedMode"
|
||
v-model="viewVisible"
|
||
title="订单详情"
|
||
width="1400px"
|
||
top="50px"
|
||
destroy-on-close
|
||
class="order-module-dialog order-detail-dialog"
|
||
modal-class="order-module-dialog-mask"
|
||
>
|
||
<div v-loading="detailLoading" class="dialog-scroll order-detail-dialog-scroll">
|
||
<div class="view-hero order-view-hero">
|
||
<div class="order-view-hero-icon">
|
||
<el-icon><ShoppingBag /></el-icon>
|
||
</div>
|
||
<div class="view-hero-main">
|
||
<h2>{{ displayValue(viewData.orderNo) }}</h2>
|
||
<div class="view-hero-tags">
|
||
<el-tag :type="getOrderStatusType(resolveOrderStatus(viewData))" size="small">
|
||
{{ getOrderStatusText(resolveOrderStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag :type="getDeliveryStatusType(resolveDeliveryStatus(viewData))" size="small">
|
||
{{ getDeliveryStatusText(resolveDeliveryStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag :type="getPayStatusType(resolvePayStatus(viewData))" size="small">
|
||
{{ getPayStatusText(resolvePayStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag v-if="viewData.cancelStatus === 'pending_review'" type="warning" size="small">取消审核中</el-tag>
|
||
</div>
|
||
<p class="view-hero-meta">
|
||
客户:{{ displayValue(viewData.clientName) }}
|
||
<span class="dot">·</span>
|
||
区域:{{ displayValue(viewData.regionName) }}
|
||
<span class="dot">·</span>
|
||
下单时间:{{ displayValue(viewData.createAt) }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<!-- Tab切换 -->
|
||
<div class="detail-tabs">
|
||
<div
|
||
v-for="tab in detailTabs"
|
||
:key="tab.key"
|
||
class="detail-tab"
|
||
:class="{ active: activeTab === tab.key }"
|
||
@click="activeTab = tab.key"
|
||
>
|
||
{{ tab.label }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 订单锁定提示 -->
|
||
<div v-if="viewData.lockInfo && viewData.lockInfo.isLocked" class="lock-tip">
|
||
<el-icon><WarningFilled /></el-icon>
|
||
<span>
|
||
此订单已被 <strong>{{ viewData.lockInfo.lockBy }}</strong> 锁定编辑中,锁定时间:{{
|
||
viewData.lockInfo.lockTime
|
||
}}<br />
|
||
请等待其完成操作或联系解除锁定后再进行操作。
|
||
</span>
|
||
<el-button type="danger" size="small">强制解锁</el-button>
|
||
</div>
|
||
|
||
<!-- Tab: 订单信息 -->
|
||
<div v-show="activeTab === 'order'" class="order-tab-pane">
|
||
<div class="view-section">
|
||
<h3>基础信息</h3>
|
||
<dl class="view-kv-grid">
|
||
<div class="view-kv">
|
||
<dt>订单编号</dt>
|
||
<dd>{{ displayValue(viewData.orderNo) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>价格版本</dt>
|
||
<dd>
|
||
<el-button
|
||
v-if="viewData.priceListId"
|
||
class="price-version-link"
|
||
@click="openOrderPriceVersionView"
|
||
>
|
||
V{{ viewData.priceVersion ?? '-' }}
|
||
</el-button>
|
||
<span v-else>V{{ viewData.priceVersion ?? '-' }}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>客户名称</dt>
|
||
<dd>{{ displayValue(viewData.clientName) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>所属区域</dt>
|
||
<dd>{{ displayValue(viewData.regionName) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>支付方式</dt>
|
||
<dd>{{ getPaymentMethod(viewData.paymentMethod) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>客户结算</dt>
|
||
<dd class="view-kv-settlement-dd">
|
||
<el-tag :type="getSettlementTagType(viewData.clientSettlementStatus)" size="small">
|
||
{{ getClientSettlementLabel(viewData.clientSettlementStatus) }}
|
||
</el-tag>
|
||
<span class="view-kv-settlement-time">{{
|
||
settlementAtDisplay(viewData.clientSettlementStatus, viewData.clientSettlementAt)
|
||
}}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>供应商结算</dt>
|
||
<dd class="view-kv-settlement-dd">
|
||
<el-tag :type="getSettlementTagType(viewData.supplierSettlementStatus)" size="small">
|
||
{{ getSupplierSettlementLabel(viewData.supplierSettlementStatus) }}
|
||
</el-tag>
|
||
<span class="view-kv-settlement-time">{{
|
||
settlementAtDisplay(viewData.supplierSettlementStatus, viewData.supplierSettlementAt)
|
||
}}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>支付时间</dt>
|
||
<dd>{{ displayValue(viewData.payTime) }}</dd>
|
||
</div>
|
||
<div class="view-kv view-multiline">
|
||
<dt>备注</dt>
|
||
<dd>{{ displayValue(viewData.remark) }}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
|
||
<h4 class="section-title">订单明细</h4>
|
||
<div class="order-detail-items-scroll">
|
||
<el-table
|
||
:data="viewData.items"
|
||
stripe
|
||
:fit="false"
|
||
table-layout="auto"
|
||
class="order-detail-items-table"
|
||
header-align="center"
|
||
@row-dblclick="openOrderProductView"
|
||
>
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
width="68"
|
||
align="center"
|
||
header-align="center"
|
||
:index="index => index + 1"
|
||
/>
|
||
<el-table-column
|
||
prop="productName"
|
||
label="商品名称"
|
||
min-width="220"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="order-detail-product-col"
|
||
>
|
||
<template #default="{ row }">
|
||
<div class="order-item-name-cell">
|
||
<div class="order-item-thumb-wrap">
|
||
<el-image
|
||
v-if="row.imageUrl"
|
||
:src="row.imageUrl"
|
||
fit="cover"
|
||
class="order-item-thumb"
|
||
/>
|
||
<span v-else class="order-item-thumb placeholder">无图</span>
|
||
</div>
|
||
<el-tooltip
|
||
:content="orderProductNameTooltip(row.productName)"
|
||
placement="top"
|
||
:disabled="!isOrderProductNameTruncated(row.productName)"
|
||
>
|
||
<span class="order-item-name-text">{{
|
||
formatOrderProductNameDisplay(row.productName)
|
||
}}</span>
|
||
</el-tooltip>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="categoryName"
|
||
label="分类"
|
||
min-width="140"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
{{ formatOrderCategory(row) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierName" label="供货商" min-width="130" align="center" />
|
||
<el-table-column label="计量" width="100" align="center">
|
||
<template #default="{ row }">{{ formatMeasureCell(row.measureValue) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitName" label="单位" min-width="80" align="center" />
|
||
<el-table-column prop="quantity" label="数量" min-width="88" align="center" />
|
||
<el-table-column prop="supplierUnitPrice" label="供货价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierUnitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierAmount" label="供货金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sellingUnitPrice" label="商品售价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingUnitPrice ?? row.unitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sellingAmount" label="商品金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingAmount ?? row.amount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="deliveredQuantity"
|
||
class-name="order-delivered-cell"
|
||
min-width="200"
|
||
align="center"
|
||
>
|
||
<template #header>
|
||
<span>已配送</span>
|
||
<el-tooltip
|
||
placement="top"
|
||
content="本列表示该行商品「已向客户登记送达」的数量(与配送员送达填报一致)。尚未登记时,括号内为 0;若配送员尚未取货,会显示「待取货」等阶段说明,与整单配送状态不同。"
|
||
>
|
||
<el-icon class="order-delivered-th-icon"><QuestionFilled /></el-icon>
|
||
</el-tooltip>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<span class="order-delivered-text">{{ formatDeliveredDisplay(row) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getItemStatusType(row.status)" size="small">{{
|
||
getItemStatusText(row.status)
|
||
}}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div class="amount-summary">
|
||
<div class="amount-item">
|
||
<span class="label">供货总金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.supplierTotalAmount) }}</span>
|
||
</div>
|
||
<div class="amount-item">
|
||
<span class="label">订单总金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.totalAmount) }}</span>
|
||
</div>
|
||
<div class="amount-item">
|
||
<span class="label">已付金额(元):</span>
|
||
<span class="value success">¥{{ formatAmount(viewData.paidAmount) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: 配送信息 -->
|
||
<div v-show="activeTab === 'delivery'" class="order-delivery-tab">
|
||
<div class="view-section order-delivery-top">
|
||
<h3>配送概要</h3>
|
||
<dl class="view-kv-grid">
|
||
<div class="view-kv">
|
||
<dt>配送单号</dt>
|
||
<dd>{{ displayValue(viewData.deliveryTaskNo) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>订单配送状态</dt>
|
||
<dd>
|
||
<el-tag :type="getDeliveryStatusType(viewData.deliveryStatus)" size="small">
|
||
{{ getDeliveryStatusText(viewData.deliveryStatus) }}
|
||
</el-tag>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>配送员状态</dt>
|
||
<dd>
|
||
<el-tag
|
||
v-if="viewData.deliveryTaskDelivererStatus"
|
||
:type="getDelivererTaskStatusType(viewData.deliveryTaskDelivererStatus)"
|
||
size="small"
|
||
>
|
||
{{ getDelivererTaskStatusText(viewData.deliveryTaskDelivererStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>供应商备货状态</dt>
|
||
<dd>
|
||
<el-tag
|
||
v-if="viewData.deliveryTaskSupplierPrepareStatus"
|
||
:type="getSupplierPrepareStatusType(viewData.deliveryTaskSupplierPrepareStatus)"
|
||
size="small"
|
||
>
|
||
{{ getSupplierPrepareStatusText(viewData.deliveryTaskSupplierPrepareStatus) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>配送员</dt>
|
||
<dd>{{ displayValue(viewData.deliverName) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>配送员电话</dt>
|
||
<dd>{{ displayValue(viewData.deliverPhone) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>配送时效</dt>
|
||
<dd>{{ displayValue(formatOrderDetailTime(viewData.expectedTime)) }}</dd>
|
||
</div>
|
||
<div class="view-kv view-multiline">
|
||
<dt>配送备注(运营分配)</dt>
|
||
<dd>{{ displayValue(viewData.deliveryDispatchRemark) }}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
|
||
<div class="order-delivery-address-blocks">
|
||
<el-card shadow="never" class="od-address-card od-address-card--supplier">
|
||
<template #header>
|
||
<span class="od-address-card-title">供应商与取货地址</span>
|
||
</template>
|
||
<div class="od-address-card-body">
|
||
<div class="od-address-line">
|
||
<span class="od-label">供应商</span>
|
||
<span class="od-value">{{ displayValue(viewData.supplierName) }}</span>
|
||
</div>
|
||
<div v-if="viewData.supplierPhone" class="od-address-line">
|
||
<span class="od-label">联系电话</span>
|
||
<span class="od-value">{{ viewData.supplierPhone }}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">取货地址</span>
|
||
<span class="od-value">{{ supplierPickupDisplay.address || '-' }}</span>
|
||
</div>
|
||
<div
|
||
v-if="supplierPickupDisplay.contactPerson || supplierPickupDisplay.contactPhone"
|
||
class="od-address-line"
|
||
>
|
||
<span class="od-label">地址联系人</span>
|
||
<span class="od-value">
|
||
{{ supplierPickupDisplay.contactPerson || '-' }}
|
||
<template v-if="supplierPickupDisplay.contactPhone">
|
||
/ {{ supplierPickupDisplay.contactPhone }}</template
|
||
>
|
||
</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">取货时间</span>
|
||
<span class="od-value">{{
|
||
formatOrderDetailTime(viewData.deliveryPickupTime) || '-'
|
||
}}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">取货备注</span>
|
||
<span class="od-value od-text-wrap">{{
|
||
displayValue(viewData.deliveryPickupRemark)
|
||
}}</span>
|
||
</div>
|
||
<div class="od-address-line od-address-line--block">
|
||
<span class="od-label">备货凭证</span>
|
||
<div class="od-value">
|
||
<div v-if="(viewData.deliveryPrepareImages || []).length" class="evidence-grid">
|
||
<el-image
|
||
v-for="(img, idx) in viewData.deliveryPrepareImages"
|
||
:key="`prepare-${idx}`"
|
||
:src="img"
|
||
:preview-src-list="viewData.deliveryPrepareImages"
|
||
:initial-index="idx"
|
||
fit="cover"
|
||
class="evidence-image"
|
||
/>
|
||
</div>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card shadow="never" class="od-address-card od-address-card--customer">
|
||
<template #header>
|
||
<span class="od-address-card-title">客户送货信息</span>
|
||
</template>
|
||
<div class="od-address-card-body">
|
||
<div class="od-address-line">
|
||
<span class="od-label">送货地址</span>
|
||
<span class="od-value">{{ displayValue(viewData.deliveryAddress) }}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">联系人</span>
|
||
<span class="od-value">{{ displayValue(viewData.contactPerson) }}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">联系电话</span>
|
||
<span class="od-value">{{ displayValue(viewData.contactPhone) }}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">送达时间</span>
|
||
<span class="od-value">{{
|
||
formatOrderDetailTime(viewData.deliveryDeliveryTime) || '-'
|
||
}}</span>
|
||
</div>
|
||
<div class="od-address-line">
|
||
<span class="od-label">送达备注</span>
|
||
<span class="od-value od-text-wrap">{{
|
||
displayValue(viewData.deliveryDeliveryRemark)
|
||
}}</span>
|
||
</div>
|
||
<div class="od-address-line od-address-line--block">
|
||
<span class="od-label">取货凭证</span>
|
||
<div class="od-value">
|
||
<div v-if="(viewData.deliveryPickupImages || []).length" class="evidence-grid">
|
||
<el-image
|
||
v-for="(img, idx) in viewData.deliveryPickupImages"
|
||
:key="`pickup-${idx}`"
|
||
:src="img"
|
||
:preview-src-list="viewData.deliveryPickupImages"
|
||
:initial-index="idx"
|
||
fit="cover"
|
||
class="evidence-image"
|
||
/>
|
||
</div>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
<div class="od-address-line od-address-line--block">
|
||
<span class="od-label">送达凭证</span>
|
||
<div class="od-value">
|
||
<div v-if="(viewData.deliveryDeliverImages || []).length" class="evidence-grid">
|
||
<el-image
|
||
v-for="(img, idx) in viewData.deliveryDeliverImages"
|
||
:key="`deliver-${idx}`"
|
||
:src="img"
|
||
:preview-src-list="viewData.deliveryDeliverImages"
|
||
:initial-index="idx"
|
||
fit="cover"
|
||
class="evidence-image"
|
||
/>
|
||
</div>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
|
||
<h4 class="section-title">配送进度</h4>
|
||
<el-empty
|
||
v-if="!viewData.deliveryTimeline || viewData.deliveryTimeline.length === 0"
|
||
description="暂无配送进度记录"
|
||
/>
|
||
<div v-else class="timeline-horizontal">
|
||
<div class="timeline-horizontal-list">
|
||
<div
|
||
v-for="(item, index) in viewData.deliveryTimeline"
|
||
:key="index"
|
||
class="timeline-horizontal-item"
|
||
>
|
||
<div class="timeline-index">{{ index + 1 }}</div>
|
||
<div class="timeline-content">
|
||
<div class="timeline-time">{{ formatOrderDetailTime(item.time) || '-' }}</div>
|
||
<div class="timeline-title">{{ item.title }}</div>
|
||
<div v-if="item.description" class="timeline-desc">{{ item.description }}</div>
|
||
</div>
|
||
<div
|
||
v-if="index < viewData.deliveryTimeline.length - 1"
|
||
class="timeline-horizontal-arrow"
|
||
>→</div
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: 操作记录 -->
|
||
<div v-show="activeTab === 'logs'" class="order-logs-tab">
|
||
<h4 class="section-title">状态变更记录</h4>
|
||
<el-empty
|
||
v-if="!viewData.operationLogs || viewData.operationLogs.length === 0"
|
||
description="暂无状态变更记录"
|
||
/>
|
||
<div v-else class="timeline-horizontal">
|
||
<div class="timeline-horizontal-list">
|
||
<div
|
||
v-for="(item, index) in viewData.operationLogs"
|
||
:key="index"
|
||
class="timeline-horizontal-item"
|
||
>
|
||
<div class="timeline-index">{{ index + 1 }}</div>
|
||
<div class="timeline-content">
|
||
<div class="timeline-time">{{ item.time || '-' }}</div>
|
||
<div class="timeline-title">{{ item.title }}</div>
|
||
<div class="timeline-desc">{{ item.operator }}</div>
|
||
<div v-if="item.description" class="timeline-desc" :style="{ color: item.color }">{{
|
||
item.description
|
||
}}</div>
|
||
<template v-if="item.cancelDetail">
|
||
<div v-if="item.cancelDetail.note" class="timeline-desc cancel-note">
|
||
取消备注:{{ item.cancelDetail.note }}
|
||
</div>
|
||
<div
|
||
v-if="item.cancelDetail.evidenceImageUrls?.length"
|
||
class="timeline-cancel-images"
|
||
>
|
||
<el-image
|
||
v-for="(img, idx) in item.cancelDetail.evidenceImageUrls"
|
||
:key="`cancel-${index}-${idx}`"
|
||
:src="img"
|
||
:preview-src-list="item.cancelDetail.evidenceImageUrls"
|
||
:initial-index="idx"
|
||
fit="cover"
|
||
class="timeline-cancel-image"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div
|
||
v-if="index < viewData.operationLogs.length - 1"
|
||
class="timeline-horizontal-arrow"
|
||
>→</div
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<template #footer>
|
||
<el-button class="btn-ghost" @click="viewVisible = false"><el-icon><Close /></el-icon>关闭</el-button>
|
||
<template v-if="showOrderDetailActions">
|
||
<el-button v-if="showCancelOrderAction" type="danger" @click="showCancelDialog">
|
||
<el-icon><Close /></el-icon>取消订单
|
||
</el-button>
|
||
<el-button
|
||
v-if="showDelivererAssignAction"
|
||
class="btn-main"
|
||
type="primary"
|
||
@click="showAssignDialogFromDetail"
|
||
>
|
||
<el-icon><Check /></el-icon>{{ delivererActionLabel }}
|
||
</el-button>
|
||
</template>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<div v-else v-show="viewVisible" class="order-embedded-detail">
|
||
<div v-loading="detailLoading" class="dialog-scroll order-detail-dialog-scroll">
|
||
<div class="view-hero order-view-hero">
|
||
<div class="order-view-hero-icon">
|
||
<el-icon><ShoppingBag /></el-icon>
|
||
</div>
|
||
<div class="view-hero-main">
|
||
<h2>{{ displayValue(viewData.orderNo) }}</h2>
|
||
<div class="view-hero-tags">
|
||
<el-tag :type="getOrderStatusType(resolveOrderStatus(viewData))" size="small">
|
||
{{ getOrderStatusText(resolveOrderStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag :type="getDeliveryStatusType(resolveDeliveryStatus(viewData))" size="small">
|
||
{{ getDeliveryStatusText(resolveDeliveryStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag :type="getPayStatusType(resolvePayStatus(viewData))" size="small">
|
||
{{ getPayStatusText(resolvePayStatus(viewData)) }}
|
||
</el-tag>
|
||
<el-tag v-if="viewData.cancelStatus === 'pending_review'" type="warning" size="small">取消审核中</el-tag>
|
||
</div>
|
||
<p class="view-hero-meta">
|
||
客户:{{ displayValue(viewData.clientName) }}
|
||
<span class="dot">·</span>
|
||
区域:{{ displayValue(viewData.regionName) }}
|
||
<span class="dot">·</span>
|
||
下单时间:{{ displayValue(viewData.createAt) }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<!-- Tab切换 -->
|
||
<div class="detail-tabs">
|
||
<div
|
||
v-for="tab in detailTabs"
|
||
:key="tab.key"
|
||
class="detail-tab"
|
||
:class="{ active: activeTab === tab.key }"
|
||
@click="activeTab = tab.key"
|
||
>
|
||
{{ tab.label }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 订单锁定提示 -->
|
||
<div v-if="viewData.lockInfo && viewData.lockInfo.isLocked" class="lock-tip">
|
||
<el-icon><WarningFilled /></el-icon>
|
||
<span>
|
||
此订单已被 <strong>{{ viewData.lockInfo.lockBy }}</strong> 锁定编辑中,锁定时间:{{
|
||
viewData.lockInfo.lockTime
|
||
}}<br />
|
||
请等待其完成操作或联系解除锁定后再进行操作。
|
||
</span>
|
||
<el-button type="danger" size="small">强制解锁</el-button>
|
||
</div>
|
||
|
||
<!-- Tab: 订单信息 -->
|
||
<div v-show="activeTab === 'order'" class="order-tab-pane">
|
||
<div class="view-section">
|
||
<h3>基础信息</h3>
|
||
<dl class="view-kv-grid">
|
||
<div class="view-kv">
|
||
<dt>订单编号</dt>
|
||
<dd>{{ displayValue(viewData.orderNo) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>价格版本</dt>
|
||
<dd>
|
||
<el-button
|
||
v-if="viewData.priceListId"
|
||
class="price-version-link"
|
||
@click="openOrderPriceVersionView"
|
||
>
|
||
V{{ viewData.priceVersion ?? '-' }}
|
||
</el-button>
|
||
<span v-else>V{{ viewData.priceVersion ?? '-' }}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>客户名称</dt>
|
||
<dd>{{ displayValue(viewData.clientName) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>所属区域</dt>
|
||
<dd>{{ displayValue(viewData.regionName) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>支付方式</dt>
|
||
<dd>{{ getPaymentMethod(viewData.paymentMethod) }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>客户结算</dt>
|
||
<dd class="view-kv-settlement-dd">
|
||
<el-tag :type="getSettlementTagType(viewData.clientSettlementStatus)" size="small">
|
||
{{ getClientSettlementLabel(viewData.clientSettlementStatus) }}
|
||
</el-tag>
|
||
<span class="view-kv-settlement-time">{{
|
||
settlementAtDisplay(viewData.clientSettlementStatus, viewData.clientSettlementAt)
|
||
}}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>供应商结算</dt>
|
||
<dd class="view-kv-settlement-dd">
|
||
<el-tag :type="getSettlementTagType(viewData.supplierSettlementStatus)" size="small">
|
||
{{ getSupplierSettlementLabel(viewData.supplierSettlementStatus) }}
|
||
</el-tag>
|
||
<span class="view-kv-settlement-time">{{
|
||
settlementAtDisplay(viewData.supplierSettlementStatus, viewData.supplierSettlementAt)
|
||
}}</span>
|
||
</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>支付时间</dt>
|
||
<dd>{{ displayValue(viewData.payTime) }}</dd>
|
||
</div>
|
||
<div class="view-kv view-multiline">
|
||
<dt>备注</dt>
|
||
<dd>{{ displayValue(viewData.remark) }}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
|
||
<h4 class="section-title">订单明细</h4>
|
||
<div class="order-detail-items-scroll">
|
||
<el-table
|
||
:data="viewData.items"
|
||
stripe
|
||
:fit="false"
|
||
table-layout="auto"
|
||
class="order-detail-items-table"
|
||
header-align="center"
|
||
@row-dblclick="openOrderProductView"
|
||
>
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
width="68"
|
||
align="center"
|
||
header-align="center"
|
||
:index="index => index + 1"
|
||
/>
|
||
<el-table-column
|
||
prop="productName"
|
||
label="商品名称"
|
||
min-width="220"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="order-detail-product-col"
|
||
>
|
||
<template #default="{ row }">
|
||
<div class="order-item-name-cell">
|
||
<div class="order-item-thumb-wrap">
|
||
<el-image
|
||
v-if="row.imageUrl"
|
||
:src="row.imageUrl"
|
||
fit="cover"
|
||
class="order-item-thumb"
|
||
/>
|
||
<span v-else class="order-item-thumb placeholder">无图</span>
|
||
</div>
|
||
<el-tooltip
|
||
:content="orderProductNameTooltip(row.productName)"
|
||
placement="top"
|
||
:disabled="!isOrderProductNameTruncated(row.productName)"
|
||
>
|
||
<span class="order-item-name-text">{{
|
||
formatOrderProductNameDisplay(row.productName)
|
||
}}</span>
|
||
</el-tooltip>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="categoryName"
|
||
label="分类"
|
||
min-width="140"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
{{ formatOrderCategory(row) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierName" label="供货商" min-width="130" align="center" />
|
||
<el-table-column label="计量" width="100" align="center">
|
||
<template #default="{ row }">{{ formatMeasureCell(row.measureValue) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitName" label="单位" min-width="80" align="center" />
|
||
<el-table-column prop="quantity" label="数量" min-width="88" align="center" />
|
||
<el-table-column prop="supplierUnitPrice" label="供货价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierUnitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierAmount" label="供货金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sellingUnitPrice" label="商品售价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingUnitPrice ?? row.unitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sellingAmount" label="商品金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingAmount ?? row.amount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="deliveredQuantity"
|
||
class-name="order-delivered-cell"
|
||
min-width="200"
|
||
align="center"
|
||
>
|
||
<template #header>
|
||
<span>已配送</span>
|
||
<el-tooltip
|
||
placement="top"
|
||
content="本列表示该行商品「已向客户登记送达」的数量(与配送员送达填报一致)。尚未登记时,括号内为 0;若配送员尚未取货,会显示「待取货」等阶段说明,与整单配送状态不同。"
|
||
>
|
||
<el-icon class="order-delivered-th-icon"><QuestionFilled /></el-icon>
|
||
</el-tooltip>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<span class="order-delivered-text">{{ formatDeliveredDisplay(row) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" min-width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getItemStatusType(row.status)" size="small">{{
|
||
getItemStatusText(row.status)
|
||
}}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div class="amount-summary">
|
||
<div class="amount-item">
|
||
<span class="label">供货总金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.supplierTotalAmount) }}</span>
|
||
</div>
|
||
<div class="amount-item">
|
||
<span class="label">订单总金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.totalAmount) }}</span>
|
||
</div>
|
||
<div class="amount-item">
|
||
<span class="label">已付金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.paidAmount) }}</span>
|
||
</div>
|
||
<div class="amount-item">
|
||
<span class="label">应付金额(元):</span>
|
||
<span class="value">¥{{ formatAmount(viewData.payableAmount) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: 配送信息 -->
|
||
<div v-show="activeTab === 'delivery'" class="delivery-tab-pane">
|
||
<!-- 原弹窗内容继续复用(此处无需 footer 按钮) -->
|
||
</div>
|
||
|
||
<!-- Tab: 操作记录 -->
|
||
<div v-show="activeTab === 'logs'" class="order-logs-tab">
|
||
<h4 class="section-title">状态变更记录</h4>
|
||
<el-empty
|
||
v-if="!viewData.operationLogs || viewData.operationLogs.length === 0"
|
||
description="暂无状态变更记录"
|
||
/>
|
||
<div v-else class="timeline-horizontal">
|
||
<div class="timeline-horizontal-list">
|
||
<div
|
||
v-for="(item, index) in viewData.operationLogs"
|
||
:key="index"
|
||
class="timeline-horizontal-item"
|
||
>
|
||
<div class="timeline-index">{{ index + 1 }}</div>
|
||
<div class="timeline-content">
|
||
<div class="timeline-time">{{ item.time || '-' }}</div>
|
||
<div class="timeline-title">{{ item.title }}</div>
|
||
<div class="timeline-desc">{{ item.operator }}</div>
|
||
<div v-if="item.description" class="timeline-desc" :style="{ color: item.color }">{{
|
||
item.description
|
||
}}</div>
|
||
<template v-if="item.cancelDetail">
|
||
<div v-if="item.cancelDetail.note" class="timeline-desc cancel-note">
|
||
取消备注:{{ item.cancelDetail.note }}
|
||
</div>
|
||
<div
|
||
v-if="item.cancelDetail.evidenceImageUrls?.length"
|
||
class="timeline-cancel-images"
|
||
>
|
||
<el-image
|
||
v-for="(img, idx) in item.cancelDetail.evidenceImageUrls"
|
||
:key="`cancel-${index}-${idx}`"
|
||
:src="img"
|
||
:preview-src-list="item.cancelDetail.evidenceImageUrls"
|
||
:initial-index="idx"
|
||
fit="cover"
|
||
class="timeline-cancel-image"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div
|
||
v-if="index < viewData.operationLogs.length - 1"
|
||
class="timeline-horizontal-arrow"
|
||
>→</div
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分配 / 更换配送员 -->
|
||
<el-dialog
|
||
v-model="assignVisible"
|
||
:title="assignReassignMode ? '更换配送员' : '分配配送员'"
|
||
width="560px"
|
||
destroy-on-close
|
||
class="order-module-dialog"
|
||
modal-class="order-module-dialog-mask"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-alert v-if="assignReassignMode" type="info" :closable="false" class="mb-4">
|
||
当前订单已分配配送员,提交后将更新为新的配送员(与运营端分配接口一致)。
|
||
</el-alert>
|
||
<el-form :model="assignForm" label-position="top">
|
||
<div class="form-grid-2">
|
||
<el-form-item label="选择配送员" class="form-col-span-2">
|
||
<el-select
|
||
v-model="assignForm.deliverId"
|
||
:placeholder="assignForm.regionId ? '请选择本区域配送员' : '订单无区域信息'"
|
||
filterable
|
||
:disabled="!assignForm.regionId"
|
||
>
|
||
<el-option
|
||
v-for="d in deliverList"
|
||
:key="d.id"
|
||
:label="d.realName || d.username"
|
||
:value="d.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="配送时效" required class="form-col-span-2">
|
||
<el-date-picker
|
||
v-model="assignForm.deliveryDeadlineAt"
|
||
type="datetime"
|
||
placeholder="截止时间(年月日时分)"
|
||
format="YYYY-MM-DD HH:mm"
|
||
value-format="YYYY-MM-DDTHH:mm:00"
|
||
style="width: 100%"
|
||
/>
|
||
<div class="hint-text">
|
||
默认已按「当前时间 +
|
||
区域预计配送(分钟)」计算;区域未配置时使用系统设置中的默认分钟数。须在此时间前完成配送;区域「预警提前」内未完成将出现在时效预警列表。
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="配送备注" class="form-col-span-2">
|
||
<el-input
|
||
v-model="assignForm.dispatchRemark"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="500"
|
||
show-word-limit
|
||
placeholder="给配送员或运营的说明(可选)"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button class="btn-ghost" @click="assignVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||
<el-button class="btn-main" type="primary" @click="confirmAssign" :loading="submitLoading">
|
||
<el-icon><Check /></el-icon>确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 审核取消弹窗 -->
|
||
<el-dialog v-model="cancelVisible" title="审核取消" width="500px">
|
||
<el-form :model="cancelForm" label-width="100px">
|
||
<el-form-item label="审核结果">
|
||
<el-radio-group v-model="cancelForm.approved">
|
||
<el-radio :label="true">通过</el-radio>
|
||
<el-radio :label="false">拒绝</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="审核备注">
|
||
<el-input
|
||
v-model="cancelForm.note"
|
||
type="textarea"
|
||
placeholder="请输入审核备注"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="cancelVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||
<el-button type="primary" @click="confirmCancel" :loading="submitLoading"><el-icon><Check /></el-icon>确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 取消订单弹窗 -->
|
||
<el-dialog v-model="cancelDialogVisible" title="取消订单" width="500px">
|
||
<el-alert type="warning" :closable="false" class="mb-4">
|
||
取消订单后,订单将被标记为已取消状态,已支付的款项将原路退回。
|
||
</el-alert>
|
||
<el-form :model="cancelDialogForm" :rules="cancelDialogRules" label-width="100px">
|
||
<el-form-item label="订单编号">
|
||
<span class="info-text">{{ cancelDialogForm.orderId }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="取消原因" prop="reason">
|
||
<el-input
|
||
v-model="cancelDialogForm.reason"
|
||
type="textarea"
|
||
placeholder="请输入取消原因"
|
||
:rows="3"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="取消备注" prop="note">
|
||
<el-input
|
||
v-model="cancelDialogForm.note"
|
||
type="textarea"
|
||
placeholder="请输入备注(可选)"
|
||
:rows="2"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="佐证图片">
|
||
<el-upload
|
||
action="#"
|
||
list-type="picture-card"
|
||
:auto-upload="false"
|
||
:on-change="handleCancelEvidenceChange"
|
||
:on-remove="handleCancelEvidenceRemove"
|
||
:limit="6"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
</el-upload>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="cancelDialogVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||
<el-button type="danger" @click="confirmCancelOrder" :loading="cancelDialogLoading"
|
||
><el-icon><Check /></el-icon>确认取消</el-button
|
||
>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 调整订单弹窗 -->
|
||
<el-dialog
|
||
v-model="adjustDialogVisible"
|
||
title="订单调整"
|
||
width="1100px"
|
||
@opened="loadOrderItemsForAdjust"
|
||
>
|
||
<el-alert type="info" :closable="false" class="mb-4">
|
||
双击商品行可查看商品详情;新数量默认等于原数量,支持增量、减量和删除(新数量为0)。
|
||
</el-alert>
|
||
<el-table
|
||
:data="adjustOrderItems"
|
||
stripe
|
||
class="adjust-order-items-table"
|
||
max-height="360"
|
||
v-loading="adjustItemsLoading"
|
||
@row-dblclick="openOrderProductView"
|
||
>
|
||
<el-table-column type="index" label="序号" width="68" align="center" header-align="center" :index="index => index + 1" />
|
||
<el-table-column
|
||
prop="productName"
|
||
label="商品名称"
|
||
min-width="240"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="adjust-product-col"
|
||
>
|
||
<template #default="{ row }">
|
||
<div class="order-item-name-cell">
|
||
<div class="order-item-thumb-wrap">
|
||
<el-image v-if="row.imageUrl" :src="row.imageUrl" fit="cover" class="order-item-thumb" />
|
||
<span v-else class="order-item-thumb placeholder">无图</span>
|
||
</div>
|
||
<span class="order-item-name-text">{{ formatOrderProductNameDisplay(row.productName) }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="supplierName" label="供货商" min-width="130" align="center" />
|
||
<el-table-column label="计量" min-width="88" align="center">
|
||
<template #default="{ row }">{{ formatMeasureCell(row.measureValue) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitName" label="单位" min-width="88" align="center" />
|
||
<el-table-column prop="quantity" label="数量" min-width="100" align="center" />
|
||
<el-table-column label="供货价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierUnitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="供货金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.supplierAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品售价" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingUnitPrice ?? row.unitPrice) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品金额" min-width="110" align="center">
|
||
<template #default="{ row }">¥{{ formatAmount(row.sellingAmount ?? row.amount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="新数量" min-width="140" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
type="number"
|
||
step="any"
|
||
v-model="row.newQuantity"
|
||
:min="0"
|
||
:controls-position="'right'"
|
||
@change="(val) => handleAdjustQuantityCommit(row, val)"
|
||
@dblclick.stop
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-form :model="adjustAddressForm" label-width="100px" class="mt-4">
|
||
<el-form-item label="收货地址">
|
||
<el-input v-model="adjustAddressForm.deliveryAddress" placeholder="请输入送货地址" maxlength="500" />
|
||
</el-form-item>
|
||
<el-form-item label="联系人">
|
||
<el-input v-model="adjustAddressForm.contactPerson" placeholder="请输入联系人" maxlength="100" />
|
||
</el-form-item>
|
||
<el-form-item label="联系电话">
|
||
<el-input v-model="adjustAddressForm.contactPhone" placeholder="请输入联系电话" maxlength="20" />
|
||
</el-form-item>
|
||
<el-form-item label="调整原因">
|
||
<el-input v-model="adjustForm.reason" type="textarea" placeholder="商品调整原因(有商品调整时必填)" :rows="2" />
|
||
</el-form-item>
|
||
<el-form-item label="地址备注">
|
||
<el-input
|
||
v-model="adjustAddressForm.reason"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="地址调整说明(仅调整地址时可选)"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<el-button @click="adjustDialogVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||
<el-button type="primary" @click="confirmAdjustOrder" :loading="adjustDialogLoading"><el-icon><Check /></el-icon>确认提交</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<ProductViewDialog v-model="productViewDialogVisible" :product-id="productViewTargetId" />
|
||
|
||
<!-- 订单所用价格版本明细(只读,对齐询价「选中供应商报价」信息结构) -->
|
||
<el-dialog
|
||
v-model="orderPriceVersionVisible"
|
||
title="价格版本明细"
|
||
width="1100px"
|
||
destroy-on-close
|
||
class="order-module-dialog order-price-version-dialog"
|
||
modal-class="order-module-dialog-mask"
|
||
@opened="loadOrderPriceVersionDetail"
|
||
>
|
||
<div v-loading="orderPriceVersionLoading" class="order-pv-body">
|
||
<template v-if="orderPriceVersionDetail">
|
||
<div class="view-hero order-pv-hero">
|
||
<div class="order-view-hero-icon">
|
||
<el-icon><Setting /></el-icon>
|
||
</div>
|
||
<div class="view-hero-main">
|
||
<h2>价格版本 V{{ orderPriceVersionDetail.version ?? orderPriceVersionDetail.versionNo ?? '-' }}</h2>
|
||
<div class="view-hero-tags">
|
||
<el-tag type="success" size="small">{{ orderPriceVersionDetail.supplierName || '-' }}</el-tag>
|
||
<el-tag type="primary" size="small">{{ orderPriceVersionDetail.regionName || '-' }}</el-tag>
|
||
</div>
|
||
<p class="view-hero-meta">用于订单下单时的价格快照,仅供查看,不可编辑。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="view-section order-pv-info-section">
|
||
<h3>基础信息</h3>
|
||
<dl class="view-kv-grid">
|
||
<div class="view-kv">
|
||
<dt>区域</dt>
|
||
<dd>{{ orderPriceVersionDetail.regionName || '-' }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>供应商</dt>
|
||
<dd>{{ orderPriceVersionDetail.supplierName || '-' }}</dd>
|
||
</div>
|
||
<div class="view-kv">
|
||
<dt>版本号</dt>
|
||
<dd>V{{ orderPriceVersionDetail.version ?? orderPriceVersionDetail.versionNo ?? '-' }}</dd>
|
||
</div>
|
||
<div class="view-kv view-multiline">
|
||
<dt>来源询价</dt>
|
||
<dd>{{ orderPriceVersionDetail.inquiryTitle || '-' }}</dd>
|
||
</div>
|
||
</dl>
|
||
</div>
|
||
</template>
|
||
|
||
<el-table
|
||
v-if="orderPriceVersionDetail?.items?.length"
|
||
:data="orderPriceVersionDetail.items"
|
||
stripe
|
||
max-height="480"
|
||
header-align="center"
|
||
@row-dblclick="openOrderProductView"
|
||
>
|
||
<el-table-column type="index" label="序号" width="68" align="center" header-align="center" :index="index => index + 1" />
|
||
<el-table-column prop="productName" label="商品名称" min-width="200" align="center">
|
||
<template #default="{ row }">
|
||
<div class="order-pv-name-cell">
|
||
<div class="order-item-thumb-wrap">
|
||
<el-image
|
||
v-if="row.imageUrl"
|
||
:src="row.imageUrl"
|
||
fit="cover"
|
||
class="order-item-thumb"
|
||
/>
|
||
<span v-else class="order-item-thumb placeholder">无图</span>
|
||
</div>
|
||
<el-tooltip
|
||
:content="orderProductNameTooltip(row.productName)"
|
||
placement="top"
|
||
:disabled="!isOrderProductNameTruncated(row.productName)"
|
||
>
|
||
<span class="order-pv-name-text">{{
|
||
formatOrderProductNameDisplay(row.productName)
|
||
}}</span>
|
||
</el-tooltip>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="categoryName"
|
||
label="分类"
|
||
min-width="120"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column label="供货商" min-width="120" align="center">
|
||
<template #default>{{ orderPriceVersionDetail?.supplierName || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="计量" width="88" align="center">
|
||
<template #default="{ row }">{{ formatMeasureCell(row.measureValue) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="unitName" label="单位" min-width="72" align="center" />
|
||
<el-table-column label="数量" min-width="80" align="center">
|
||
<template #default>-</template>
|
||
</el-table-column>
|
||
<el-table-column label="供货价" min-width="110" align="center">
|
||
<template #default="{ row }">
|
||
<span v-if="!orderPriceVersionDetail?.quotationId">-</span>
|
||
<span v-else>¥{{ (Number(row.supplierPrice || 0) || 0).toFixed(2) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="供货金额" min-width="110" align="center">
|
||
<template #default>-</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品售价" min-width="110" align="center">
|
||
<template #default="{ row }"
|
||
>¥{{ (Number(row.finalPrice ?? row.price) || 0).toFixed(2) }}</template
|
||
>
|
||
</el-table-column>
|
||
<el-table-column label="商品金额" min-width="110" align="center">
|
||
<template #default>-</template>
|
||
</el-table-column>
|
||
<el-table-column label="调价系数(%)" min-width="110" align="center">
|
||
<template #default="{ row }"
|
||
>{{ (Number(row.pricingCoefficient) || 0).toFixed(2) }}%</template
|
||
>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-else-if="!orderPriceVersionLoading" description="暂无明细" />
|
||
</div>
|
||
<template #footer>
|
||
<el-button class="btn-ghost" @click="orderPriceVersionVisible = false"><el-icon><Close /></el-icon>关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
ref,
|
||
reactive,
|
||
computed,
|
||
onMounted,
|
||
onActivated,
|
||
onDeactivated,
|
||
onUnmounted,
|
||
watch
|
||
} from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||
import {
|
||
ArrowDown,
|
||
ArrowUp,
|
||
Check,
|
||
Close,
|
||
Plus,
|
||
QuestionFilled,
|
||
RefreshLeft,
|
||
Search,
|
||
Setting,
|
||
ShoppingBag,
|
||
WarningFilled
|
||
} from '@element-plus/icons-vue'
|
||
import ProductViewDialog from '@/components/business/product-view-dialog/index.vue'
|
||
import {
|
||
fetchOrderList,
|
||
fetchOrderDetail,
|
||
fetchAssignDelivery,
|
||
fetchReviewCancel,
|
||
fetchCancelOrder,
|
||
fetchOrderItems,
|
||
fetchAdjustOrder,
|
||
fetchUploadAttachment,
|
||
fetchOrderAdjusts,
|
||
fetchPriceVersionDetail,
|
||
fetchSystemConfig
|
||
} from '@/api/business'
|
||
import { fetchNotDeletedRegions, fetchUserList } from '@/api/admin'
|
||
import { useUserStore } from '@/store/modules/user'
|
||
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
|
||
import { ensureCategoryPathDataLoaded, formatCategoryPathLabel } from '@/utils/category-path-display'
|
||
|
||
defineOptions({ name: 'OrderManagement' })
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const userStore = useUserStore()
|
||
const embeddedMode = computed(() => String(route.query.embedded || '') === '1')
|
||
const bridgeMode = computed(() => String(route.query.bridge || '') === '1')
|
||
|
||
const applyEmbeddedTransparent = () => {
|
||
if (!embeddedMode.value && !bridgeMode.value) return
|
||
try {
|
||
document.documentElement.style.background = 'transparent'
|
||
document.body.style.background = 'transparent'
|
||
} catch {}
|
||
}
|
||
|
||
const resetEmbeddedTransparent = () => {
|
||
try {
|
||
document.documentElement.style.background = ''
|
||
document.body.style.background = ''
|
||
} catch {}
|
||
}
|
||
|
||
const applyBridgeShellMode = () => {
|
||
if (!bridgeMode.value) return
|
||
try {
|
||
document.documentElement.classList.add('sqxp-order-bridge-mode')
|
||
document.body.classList.add('sqxp-order-bridge-mode')
|
||
} catch {}
|
||
}
|
||
|
||
const resetBridgeShellMode = () => {
|
||
try {
|
||
document.documentElement.classList.remove('sqxp-order-bridge-mode')
|
||
document.body.classList.remove('sqxp-order-bridge-mode')
|
||
} catch {}
|
||
}
|
||
|
||
const searchForm = reactive({
|
||
keyword: '',
|
||
orderStatus: '',
|
||
deliveryStatus: '',
|
||
payStatus: '',
|
||
paymentMethod: '',
|
||
regionId: ''
|
||
})
|
||
const orderListTableRef = ref()
|
||
const expandedOrderItemsMap = ref<Record<string, any[]>>({})
|
||
const expandedRowLoadingMap = ref<Record<string, boolean>>({})
|
||
const tableData = ref<any[]>([])
|
||
const loading = ref(false)
|
||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||
const regionList = ref<any[]>([])
|
||
const activeRegionId = ref('')
|
||
const allowedRegionIds = computed<string[]>(() => {
|
||
const ids = Array.isArray(userStore.getUserInfo?.regionIds) ? userStore.getUserInfo.regionIds : []
|
||
if (ids.length > 0) return ids.map((x: any) => String(x))
|
||
return userStore.getUserInfo?.regionId ? [String(userStore.getUserInfo.regionId)] : []
|
||
})
|
||
const allowedRegionList = computed(() => {
|
||
if (userStore.getUserInfo?.role === 'super_admin') return regionList.value
|
||
const allow = new Set(allowedRegionIds.value)
|
||
return regionList.value.filter((r: any) => allow.has(String(r.id)))
|
||
})
|
||
const filterExpanded = ref(false)
|
||
|
||
const viewVisible = ref(false)
|
||
const detailLoading = ref(false)
|
||
const activeTab = ref('order')
|
||
const detailTabs = [
|
||
{ key: 'order', label: '订单信息' },
|
||
{ key: 'delivery', label: '配送信息' },
|
||
{ key: 'logs', label: '操作记录' }
|
||
]
|
||
const viewData = ref<any>({ items: [], deliveryTimeline: [], operationLogs: [], adjustLogs: [] })
|
||
|
||
const productViewDialogVisible = ref(false)
|
||
const productViewTargetId = ref('')
|
||
const orderPriceVersionVisible = ref(false)
|
||
const orderPriceVersionLoading = ref(false)
|
||
const orderPriceVersionDetail = ref<any>(null)
|
||
|
||
const formatMeasureCell = (v: unknown) => {
|
||
if (v == null || v === '') return '-'
|
||
const n = Number(v)
|
||
if (Number.isNaN(n)) return '-'
|
||
return n.toFixed(3).replace(/\.?0+$/, '')
|
||
}
|
||
|
||
/** 订单明细 / 价格版本弹窗:商品名称过长时省略并 tooltip 展示全文 */
|
||
const ORDER_ITEM_PRODUCT_NAME_MAX_LEN = 12
|
||
const formatOrderProductNameDisplay = (name?: string | null) => {
|
||
const s = name != null && String(name).trim() !== '' ? String(name).trim() : '-'
|
||
if (s === '-') return s
|
||
return s.length > ORDER_ITEM_PRODUCT_NAME_MAX_LEN
|
||
? `${s.slice(0, ORDER_ITEM_PRODUCT_NAME_MAX_LEN)}...`
|
||
: s
|
||
}
|
||
const isOrderProductNameTruncated = (name?: string | null) => {
|
||
const s = name != null && String(name).trim() !== '' ? String(name).trim() : ''
|
||
return s.length > ORDER_ITEM_PRODUCT_NAME_MAX_LEN
|
||
}
|
||
const orderProductNameTooltip = (name?: string | null) => {
|
||
const s = name != null && String(name).trim() !== '' ? String(name).trim() : '-'
|
||
return s
|
||
}
|
||
|
||
const formatOrderCategory = (row: any) => {
|
||
return formatCategoryPathLabel(row?.categoryId ?? row?.category_id, row?.categoryName)
|
||
}
|
||
|
||
/** 已配送数量展示:数量×计量 单位(计量取自商品计量列,与明细表一致) */
|
||
const formatDeliveredQtyMeasureUnit = (row: any, q: number) => {
|
||
const u =
|
||
row?.unitName != null && String(row.unitName).trim() !== '' ? String(row.unitName).trim() : ''
|
||
const m = formatMeasureCell(row?.measureValue)
|
||
const hasMeasure = m !== '' && m !== '-'
|
||
if (hasMeasure && u) return `${q}×${m} ${u}`
|
||
if (hasMeasure) return `${q}×${m}`
|
||
if (u) return `${q}${u}`
|
||
return String(q)
|
||
}
|
||
|
||
/** 订单明细「已配送」:按行已登记送达数量展示;与订单/任务配送阶段区分,避免配送中仍显示「未配送」的误解 */
|
||
const formatDeliveredDisplay = (row: any) => {
|
||
const dq = Number(row?.deliveredQuantity ?? 0)
|
||
const qty = Number(row?.quantity ?? 0)
|
||
|
||
if (dq > 0) {
|
||
if (qty > 0 && dq >= qty) return `已全部送达 ${formatDeliveredQtyMeasureUnit(row, dq)}`
|
||
return `已送 ${formatDeliveredQtyMeasureUnit(row, dq)} / ${formatDeliveredQtyMeasureUnit(row, qty)}`
|
||
}
|
||
|
||
const delivererSt = String(viewData.value?.deliveryTaskDelivererStatus || '').toLowerCase()
|
||
const legacyTaskSt = String(viewData.value?.deliveryTaskStatus || '').toLowerCase()
|
||
const orderDelivery = resolveDeliveryStatus(viewData.value)
|
||
|
||
if (delivererSt === 'pending_pickup' || legacyTaskSt === 'assigned') {
|
||
return `待取货 · 已登记送达 ${formatDeliveredQtyMeasureUnit(row, dq)}`
|
||
}
|
||
if (delivererSt === 'delivering' || legacyTaskSt === 'delivering') return '配送中(待发量)'
|
||
if (orderDelivery === 'delivering' || orderDelivery === 'partial_delivered')
|
||
return '配送中(待发量)'
|
||
if (delivererSt === 'delivered' || orderDelivery === 'delivered')
|
||
return qty > 0
|
||
? `已送 ${formatDeliveredQtyMeasureUnit(row, 0)} / ${formatDeliveredQtyMeasureUnit(row, qty)}`
|
||
: '—'
|
||
return '待发运'
|
||
}
|
||
|
||
const openOrderProductView = (row: any) => {
|
||
const pid = row?.productId
|
||
if (!pid) {
|
||
ElMessage.warning('无法获取商品信息')
|
||
return
|
||
}
|
||
productViewTargetId.value = String(pid)
|
||
productViewDialogVisible.value = true
|
||
}
|
||
|
||
const openOrderPriceVersionView = () => {
|
||
if (!viewData.value.priceListId) {
|
||
ElMessage.warning('暂无关联价格清单')
|
||
return
|
||
}
|
||
orderPriceVersionDetail.value = null
|
||
orderPriceVersionVisible.value = true
|
||
}
|
||
|
||
const loadOrderPriceVersionDetail = async () => {
|
||
const id = viewData.value.priceListId
|
||
if (!id) return
|
||
orderPriceVersionLoading.value = true
|
||
try {
|
||
orderPriceVersionDetail.value = await fetchPriceVersionDetail(String(id))
|
||
} catch (e) {
|
||
console.error(e)
|
||
orderPriceVersionDetail.value = null
|
||
} finally {
|
||
orderPriceVersionLoading.value = false
|
||
}
|
||
}
|
||
|
||
const assignVisible = ref(false)
|
||
const assignReassignMode = ref(false)
|
||
const assignForm = reactive({
|
||
orderId: '',
|
||
regionId: '',
|
||
deliverId: '',
|
||
deliveryDeadlineAt: '' as string,
|
||
dispatchRemark: ''
|
||
})
|
||
|
||
/** 区域未配置预计配送时,与后端 system-config 默认一致(接口未返回前先用 600) */
|
||
const defaultRegionExpectedDeliveryMinutes = ref(600)
|
||
|
||
const resolveAssignOffsetMinutes = (regionMinutes: unknown): number => {
|
||
const fallback = defaultRegionExpectedDeliveryMinutes.value
|
||
if (regionMinutes == null || regionMinutes === '') return fallback
|
||
const n = Number(regionMinutes)
|
||
if (!Number.isFinite(n) || n <= 0) return fallback
|
||
return Math.floor(n)
|
||
}
|
||
|
||
/** 配送截止时间:当前时间 + 区域预计配送分钟(未配置则用系统默认) */
|
||
const computeAssignDeadline = (regionExpectedDeliveryMinutes?: unknown) => {
|
||
const minutes = resolveAssignOffsetMinutes(regionExpectedDeliveryMinutes)
|
||
const d = new Date()
|
||
d.setMinutes(d.getMinutes() + minutes, 0, 0)
|
||
const pad = (n: number) => String(n).padStart(2, '0')
|
||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:00`
|
||
}
|
||
const deliverList = ref<any[]>([])
|
||
|
||
const cancelVisible = ref(false)
|
||
const cancelForm = reactive({ orderId: '', approved: true, note: '' })
|
||
const submitLoading = ref(false)
|
||
|
||
// 取消订单弹窗
|
||
const cancelDialogVisible = ref(false)
|
||
const cancelDialogForm = reactive({
|
||
orderId: '',
|
||
reason: '',
|
||
note: '',
|
||
evidenceImageUrls: [] as string[]
|
||
})
|
||
const cancelDialogLoading = ref(false)
|
||
const cancelDialogRules = {
|
||
reason: [{ required: true, message: '请输入取消原因', trigger: 'blur' }]
|
||
}
|
||
|
||
// 调整订单弹窗
|
||
const adjustDialogVisible = ref(false)
|
||
const adjustDialogLoading = ref(false)
|
||
const adjustItemsLoading = ref(false)
|
||
const adjustLoadSeq = ref(0)
|
||
const adjustOrderItems = ref<any[]>([])
|
||
const adjustAddressForm = reactive({
|
||
deliveryAddress: '',
|
||
contactPerson: '',
|
||
contactPhone: '',
|
||
reason: ''
|
||
})
|
||
const adjustForm = reactive({
|
||
reason: ''
|
||
})
|
||
const changedAdjustItems = computed(() =>
|
||
(adjustOrderItems.value || []).filter((item) => {
|
||
const original = Number(item.quantity || 0)
|
||
const after = Number(item.newQuantity ?? original)
|
||
return after >= 0 && after !== original
|
||
})
|
||
)
|
||
|
||
const handleAdjustQuantityCommit = (row: any, val: number | string | undefined) => {
|
||
const original = Number(row?.quantity || 0)
|
||
const next = Number(val ?? row?.newQuantity ?? original)
|
||
let normalized = next
|
||
if (!Number.isFinite(next)) {
|
||
normalized = original
|
||
} else if (next < 0) {
|
||
normalized = 0
|
||
}
|
||
row.newQuantity = normalized
|
||
console.log('[order-adjust] quantity updated', {
|
||
rowId: row?.id,
|
||
original,
|
||
input: val,
|
||
normalized,
|
||
amount: Number(row.unitPrice || 0) * normalized
|
||
})
|
||
}
|
||
|
||
const getOrderStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_payment: 'warning',
|
||
pending_confirm: 'primary',
|
||
pending: 'primary',
|
||
pending_review_cancel: 'warning',
|
||
completed: 'success',
|
||
cancelled: 'info'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
const getOrderStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_payment: '待支付',
|
||
pending_confirm: '待收货',
|
||
pending: '待收货',
|
||
pending_review_cancel: '待审核取消',
|
||
completed: '已完成',
|
||
cancelled: '已取消'
|
||
}
|
||
return map[status] || status
|
||
}
|
||
|
||
const getPayStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_payment: 'warning',
|
||
paid: 'success'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
const getPayStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_payment: '待支付',
|
||
paid: '已支付'
|
||
}
|
||
return map[status] || status
|
||
}
|
||
|
||
const getItemStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
normal: '',
|
||
pending: 'warning',
|
||
delivered: 'success',
|
||
partial: 'warning'
|
||
}
|
||
return map[status] || ''
|
||
}
|
||
|
||
const getItemStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
normal: '正常',
|
||
pending: '待配送',
|
||
delivered: '已送达',
|
||
partial: '部分送达'
|
||
}
|
||
return map[status] || status
|
||
}
|
||
|
||
const getDeliveryStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_dispatch: 'warning',
|
||
pending_delivery: 'warning',
|
||
delivering: 'info',
|
||
partial_delivered: 'warning',
|
||
delivered: 'success',
|
||
pending: 'warning',
|
||
picking: 'primary',
|
||
completed: 'success'
|
||
}
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
const getDeliveryStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_dispatch: '待分配配送',
|
||
pending_delivery: '待配送',
|
||
delivering: '配送中',
|
||
partial_delivered: '部分送达',
|
||
delivered: '配送完成',
|
||
pending: '待取货',
|
||
picking: '取货中',
|
||
completed: '已完成'
|
||
}
|
||
return map[status] || status
|
||
}
|
||
|
||
const getDelivererTaskStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_pickup: 'warning',
|
||
delivering: 'primary',
|
||
delivered: 'success'
|
||
}
|
||
const k = (status || '').toLowerCase()
|
||
return map[k] || 'info'
|
||
}
|
||
|
||
const getDelivererTaskStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_pickup: '待取货',
|
||
delivering: '送货中',
|
||
delivered: '送货成功'
|
||
}
|
||
const k = (status || '').toLowerCase()
|
||
return (k && map[k]) || status || '-'
|
||
}
|
||
|
||
const getSupplierPrepareStatusType = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_prepare: 'warning',
|
||
prepared: 'primary',
|
||
picked_up: 'success'
|
||
}
|
||
const k = (status || '').toLowerCase()
|
||
return map[k] || 'info'
|
||
}
|
||
|
||
const getSupplierPrepareStatusText = (status: string) => {
|
||
const map: Record<string, string> = {
|
||
pending_prepare: '待备货',
|
||
prepared: '备货完成',
|
||
picked_up: '取货完成'
|
||
}
|
||
const k = (status || '').toLowerCase()
|
||
return (k && map[k]) || status || '-'
|
||
}
|
||
|
||
const formatOrderDetailTime = (t: string | undefined | null) => {
|
||
if (t == null || t === '') return ''
|
||
return String(t).replace('T', ' ').slice(0, 19)
|
||
}
|
||
|
||
/** 结算时间:待结算或无时间戳时统一显示「—」 */
|
||
const settlementAtDisplay = (status: string | undefined, at: string | undefined | null) => {
|
||
const s = (status || '').toLowerCase()
|
||
if (s !== 'settled') return '—'
|
||
const formatted = formatOrderDetailTime(at)
|
||
return formatted || '—'
|
||
}
|
||
|
||
const resolveOrderStatus = (row: any) => {
|
||
if (row?.cancelStatus === 'pending_review') return 'pending_review_cancel'
|
||
if (row?.orderStatus) return row.orderStatus
|
||
if (row?.status === 'pending_payment') return 'pending_payment'
|
||
if (['completed'].includes(row?.status)) return 'completed'
|
||
if (['cancelled'].includes(row?.status)) return 'cancelled'
|
||
return 'pending_confirm'
|
||
}
|
||
|
||
/** 已完成、已取消订单在详情弹窗中不展示调整/取消/分配或更换配送员 */
|
||
const showOrderDetailActions = computed(() => {
|
||
const s = resolveOrderStatus(viewData.value)
|
||
return s !== 'completed' && s !== 'cancelled'
|
||
})
|
||
|
||
/** 订单配送已完成则不再允许分配/更换配送员 */
|
||
const showDelivererAssignAction = computed(() => {
|
||
if (!showOrderDetailActions.value) return false
|
||
return canAssign(viewData.value)
|
||
})
|
||
|
||
/** 调整仅在配送过程中的订单展示 */
|
||
const showAdjustOrderAction = computed(() => {
|
||
if (!showOrderDetailActions.value) return false
|
||
const s = resolveOrderStatus(viewData.value)
|
||
return ['pending_delivery', 'delivering', 'partial_delivered', 'pending_confirm'].includes(s)
|
||
})
|
||
|
||
/** 取消按钮在已申请取消审核状态下隐藏 */
|
||
const showCancelOrderAction = computed(() => {
|
||
if (!showOrderDetailActions.value) return false
|
||
return resolveOrderStatus(viewData.value) !== 'pending_review_cancel'
|
||
})
|
||
|
||
const delivererActionLabel = computed(() => {
|
||
if (viewData.value.deliverId || viewData.value.deliverName) return '更换配送员'
|
||
return '分配配送员'
|
||
})
|
||
|
||
const supplierPickupDisplay = computed(() => {
|
||
const p = viewData.value.supplierPickup
|
||
if (p && typeof p === 'object') {
|
||
return {
|
||
address: p.address || '',
|
||
contactPerson: p.contactPerson || '',
|
||
contactPhone: p.contactPhone || ''
|
||
}
|
||
}
|
||
return { address: '', contactPerson: '', contactPhone: '' }
|
||
})
|
||
|
||
const resolveDeliveryStatus = (row: any) =>
|
||
row.deliveryStatus ||
|
||
(['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
|
||
row.status
|
||
)
|
||
? 'pending_dispatch'
|
||
: row.status === 'delivering'
|
||
? 'delivering'
|
||
: row.status === 'partial_delivered'
|
||
? 'partial_delivered'
|
||
: ['pending_confirm', 'completed'].includes(row.status)
|
||
? 'delivered'
|
||
: 'pending_dispatch')
|
||
const resolvePayStatus = (row: any) =>
|
||
row.payStatus ||
|
||
(row.status === 'refunding' ? 'refunding' : row.paidAmount > 0 ? 'paid' : 'pending_payment')
|
||
const canAssign = (row: any) =>
|
||
resolveOrderStatus(row) === 'pending_confirm' &&
|
||
['pending_dispatch', 'pending_delivery'].includes(resolveDeliveryStatus(row))
|
||
|
||
const getPaymentMethod = (method: string) => {
|
||
const m = (method || '').toLowerCase()
|
||
const map: Record<string, string> = {
|
||
online: '线上支付',
|
||
wechat: '线上支付',
|
||
wx: '线上支付',
|
||
offline: '线下支付'
|
||
}
|
||
return map[m] || method || '-'
|
||
}
|
||
|
||
const getSettlementTagType = (status: string | undefined) => {
|
||
const s = (status || 'pending').toLowerCase()
|
||
return s === 'settled' ? 'success' : 'info'
|
||
}
|
||
|
||
const getClientSettlementLabel = (status: string | undefined) => {
|
||
const s = (status || 'pending').toLowerCase()
|
||
return s === 'settled' ? '已与客户结算' : '待客户结算'
|
||
}
|
||
|
||
const getSupplierSettlementLabel = (status: string | undefined) => {
|
||
const s = (status || 'pending').toLowerCase()
|
||
return s === 'settled' ? '已与供应商结算' : '待供应商结算'
|
||
}
|
||
|
||
const formatAmount = (amount: number | string) => {
|
||
const num = typeof amount === 'string' ? parseFloat(amount) : amount
|
||
return isNaN(num) ? '0.00' : num.toFixed(2)
|
||
}
|
||
|
||
const displayValue = (value: any) => {
|
||
if (value === null || value === undefined) return '-'
|
||
if (typeof value === 'string' && value.trim() === '') return '-'
|
||
return value
|
||
}
|
||
|
||
const getExpandedItems = (orderId: string) => expandedOrderItemsMap.value[orderId] || []
|
||
const getExpandedSummary = (orderId: string) => {
|
||
const rows = getExpandedItems(orderId)
|
||
return rows.reduce(
|
||
(acc, it) => {
|
||
acc.supplierTotal += Number(it.supplierAmount || 0)
|
||
acc.sellingTotal += Number(it.sellingAmount ?? it.amount ?? 0)
|
||
return acc
|
||
},
|
||
{ supplierTotal: 0, sellingTotal: 0 }
|
||
)
|
||
}
|
||
|
||
const ensureExpandedItems = async (orderId: string) => {
|
||
if (expandedOrderItemsMap.value[orderId]) return
|
||
expandedRowLoadingMap.value = { ...expandedRowLoadingMap.value, [orderId]: true }
|
||
try {
|
||
const res: any = await fetchOrderItems(orderId)
|
||
expandedOrderItemsMap.value = {
|
||
...expandedOrderItemsMap.value,
|
||
[orderId]: Array.isArray(res?.list) ? res.list : []
|
||
}
|
||
} finally {
|
||
expandedRowLoadingMap.value = { ...expandedRowLoadingMap.value, [orderId]: false }
|
||
}
|
||
}
|
||
|
||
const handleOrderRowExpand = async (row: any, expandedRows: any[]) => {
|
||
const expanded = expandedRows.some((r) => r.id === row.id)
|
||
if (!expanded) return
|
||
await ensureExpandedItems(row.id)
|
||
}
|
||
|
||
const toggleRowExpand = async (row: any) => {
|
||
orderListTableRef.value?.toggleRowExpansion?.(row)
|
||
await ensureExpandedItems(row.id)
|
||
}
|
||
|
||
const loadData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await fetchOrderList({
|
||
page: pagination.page,
|
||
pageSize: pagination.pageSize,
|
||
keyword: searchForm.keyword || undefined,
|
||
orderStatus: searchForm.orderStatus || undefined,
|
||
deliveryStatus: searchForm.deliveryStatus || undefined,
|
||
payStatus: searchForm.payStatus || undefined,
|
||
paymentMethod: searchForm.paymentMethod || undefined,
|
||
regionId: searchForm.regionId || undefined
|
||
})
|
||
tableData.value = res.list
|
||
pagination.total = res.total
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const AUTO_REFRESH_INTERVAL_MS = 3000
|
||
let lastAutoRefreshAt = 0
|
||
const tryAutoRefresh = () => {
|
||
const now = Date.now()
|
||
if (loading.value) return
|
||
if (now - lastAutoRefreshAt < AUTO_REFRESH_INTERVAL_MS) return
|
||
lastAutoRefreshAt = now
|
||
void loadData()
|
||
}
|
||
|
||
const handleWindowFocus = () => {
|
||
tryAutoRefresh()
|
||
}
|
||
|
||
const handleVisibilityChange = () => {
|
||
if (document.visibilityState === 'visible') {
|
||
tryAutoRefresh()
|
||
}
|
||
}
|
||
|
||
const addAutoRefreshListeners = () => {
|
||
window.addEventListener('focus', handleWindowFocus)
|
||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||
}
|
||
|
||
const loadRegions = async () => {
|
||
try {
|
||
const res = await fetchNotDeletedRegions()
|
||
regionList.value = res
|
||
if (!activeRegionId.value && allowedRegionList.value.length > 0) {
|
||
activeRegionId.value = allowedRegionList.value[0].id
|
||
}
|
||
if (activeRegionId.value) {
|
||
searchForm.regionId = activeRegionId.value
|
||
}
|
||
} catch (error) {
|
||
console.error('加载区域失败:', error)
|
||
}
|
||
}
|
||
|
||
const selectRegionTab = (regionId: string) => {
|
||
if (!regionId || activeRegionId.value === regionId) return
|
||
activeRegionId.value = regionId
|
||
searchForm.regionId = regionId
|
||
pagination.page = 1
|
||
loadData()
|
||
}
|
||
|
||
const loadDeliverList = async () => {
|
||
if (!assignForm.regionId) {
|
||
deliverList.value = []
|
||
return
|
||
}
|
||
try {
|
||
const all = await fetchAllPagedRecords((page, pageSize) =>
|
||
fetchUserList({ role: 'deliver', regionId: assignForm.regionId, page, pageSize })
|
||
)
|
||
deliverList.value = all
|
||
} catch (error) {
|
||
console.error('加载配送员失败:', error)
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => assignVisible.value,
|
||
(visible) => {
|
||
if (visible) void loadDeliverList()
|
||
}
|
||
)
|
||
|
||
const handleSearch = () => {
|
||
pagination.page = 1
|
||
loadData()
|
||
}
|
||
|
||
const tryParseCancelReasonDetail = (rawText?: string) => {
|
||
if (!rawText || !rawText.trim().startsWith('{')) return null
|
||
try {
|
||
const parsed = JSON.parse(rawText) as Record<string, unknown>
|
||
const note = typeof parsed.note === 'string' ? parsed.note.trim() : ''
|
||
const evidenceImageUrls = Array.isArray(parsed.evidenceImageUrls)
|
||
? parsed.evidenceImageUrls.filter((it): it is string => typeof it === 'string' && !!it.trim())
|
||
: []
|
||
if (!note && evidenceImageUrls.length === 0) return null
|
||
return { note, evidenceImageUrls }
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
const handleReset = () => {
|
||
Object.assign(searchForm, {
|
||
keyword: '',
|
||
orderStatus: '',
|
||
deliveryStatus: '',
|
||
payStatus: '',
|
||
paymentMethod: '',
|
||
regionId: activeRegionId.value || ''
|
||
})
|
||
filterExpanded.value = false
|
||
pagination.page = 1
|
||
loadData()
|
||
}
|
||
|
||
watch(
|
||
() => searchForm.regionId,
|
||
(val) => {
|
||
if (val && val !== activeRegionId.value) {
|
||
activeRegionId.value = val
|
||
}
|
||
}
|
||
)
|
||
|
||
const handleView = async (row: any, opts?: { preserveTab?: boolean }) => {
|
||
const tabToUse = opts?.preserveTab === true ? activeTab.value : 'order'
|
||
detailLoading.value = true
|
||
viewVisible.value = true
|
||
try {
|
||
const res: any = await fetchOrderDetail(row.id)
|
||
const statusLogs = Array.isArray(res.statusLogs) ? res.statusLogs : []
|
||
const operationLogs = statusLogs.map((log: any) => {
|
||
const rawNote = log.note as string | undefined
|
||
const rawReason = log.reason as string | undefined
|
||
const cancelDetail = tryParseCancelReasonDetail(rawReason) || tryParseCancelReasonDetail(rawNote)
|
||
let description = rawNote || ''
|
||
if (cancelDetail && rawNote && rawNote.trim().startsWith('{')) description = ''
|
||
if (rawReason && rawReason.trim().startsWith('{')) {
|
||
try {
|
||
const o = JSON.parse(rawReason) as Record<string, string | null | undefined>
|
||
if (o.toDeliverName != null || o.fromDeliverName != null) {
|
||
description = `配送员:${o.fromDeliverName ?? '—'} → ${o.toDeliverName ?? '—'}`
|
||
if (log.note) description += `;${log.note}`
|
||
} else if (!description && !cancelDetail) {
|
||
description = rawReason
|
||
}
|
||
} catch {
|
||
if (!description && !cancelDetail) description = rawReason
|
||
}
|
||
} else if (rawReason && !description) {
|
||
description = rawReason
|
||
}
|
||
const fromT = (log.fromStatusText as string | undefined)?.trim()
|
||
const toT = (log.statusText as string | undefined)?.trim() || (log.status as string) || '-'
|
||
const title = fromT ? `${fromT} → ${toT}` : toT
|
||
return {
|
||
time: log.createAt || '-',
|
||
title,
|
||
operator: log.operatorName || '-',
|
||
type: 'primary',
|
||
hollow: false,
|
||
description,
|
||
cancelDetail
|
||
}
|
||
})
|
||
|
||
viewData.value = {
|
||
...res,
|
||
priceListId: res.priceListId,
|
||
priceVersion: res.priceVersion ?? '-',
|
||
payStatus: res.payStatus || '',
|
||
payTime: res.paymentTime || '',
|
||
deliverId: res.deliverId || '',
|
||
deliverName: res.deliverName || '',
|
||
deliverPhone: res.deliverPhone || '',
|
||
deliveryStatus: res.deliveryStatus || '',
|
||
expectedTime: res.expectedTime || '',
|
||
deliveryDispatchRemark: res.deliveryDispatchRemark || '',
|
||
supplierId: res.supplierId || '',
|
||
supplierName: res.supplierName || '',
|
||
supplierPhone: res.supplierPhone || '',
|
||
regionExpectedDeliveryMinutes: res.regionExpectedDeliveryMinutes ?? null,
|
||
supplierPickup: res.supplierPickup ?? null,
|
||
deliveryTaskNo: res.deliveryTaskNo || '',
|
||
deliveryTaskStatus: res.deliveryTaskStatus || '',
|
||
deliveryTaskDelivererStatus: res.deliveryTaskDelivererStatus || '',
|
||
deliveryTaskSupplierPrepareStatus: res.deliveryTaskSupplierPrepareStatus || '',
|
||
deliveryPickupTime: res.deliveryPickupTime || '',
|
||
deliveryPickupRemark: res.deliveryPickupRemark || '',
|
||
deliveryDeliveryTime: res.deliveryDeliveryTime || '',
|
||
deliveryDeliveryRemark: res.deliveryDeliveryRemark || '',
|
||
lockInfo: res.lockInfo || { isLocked: false },
|
||
deliveryTimeline: Array.isArray(res.deliveryTimeline) ? res.deliveryTimeline : [],
|
||
operationLogs,
|
||
items: Array.isArray(res.items) ? res.items : []
|
||
}
|
||
const adjustRes: any = await fetchOrderAdjusts(row.id)
|
||
viewData.value.adjustLogs = Array.isArray(adjustRes?.list) ? adjustRes.list : []
|
||
activeTab.value = tabToUse
|
||
} catch (error) {
|
||
console.error('加载详情失败:', error)
|
||
viewVisible.value = false
|
||
} finally {
|
||
detailLoading.value = false
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => viewVisible.value,
|
||
(v) => {
|
||
if (v) return
|
||
if (!embeddedMode.value && !bridgeMode.value) return
|
||
try {
|
||
window.parent?.postMessage?.({ type: 'sqxp-order-operate-close' }, '*')
|
||
} catch {}
|
||
}
|
||
)
|
||
|
||
const handleEmbeddedMessage = (ev: MessageEvent) => {
|
||
if (!embeddedMode.value && !bridgeMode.value) return
|
||
const data = ev?.data as any
|
||
if (!data || typeof data !== 'object') return
|
||
if (data.type === 'sqxp-order-operate-open') {
|
||
const id = typeof data.orderId === 'string' ? data.orderId.trim() : ''
|
||
if (!id) return
|
||
void handleView({ id })
|
||
}
|
||
}
|
||
|
||
/** 工作台等入口:带 viewOrderId 进入时打开与「查看/运营」相同的详情弹窗 */
|
||
watch(
|
||
() => route.query.viewOrderId,
|
||
async (raw) => {
|
||
const id = typeof raw === 'string' ? raw.trim() : ''
|
||
if (!id) return
|
||
await handleView({ id })
|
||
const q = { ...route.query } as Record<string, string | string[] | undefined | null>
|
||
delete q.viewOrderId
|
||
await router.replace({ path: route.path, query: q })
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
const handleAssign = (row: any) => {
|
||
assignReassignMode.value = false
|
||
assignForm.orderId = row.id
|
||
assignForm.regionId = row.regionId || ''
|
||
assignForm.deliverId = ''
|
||
assignForm.deliveryDeadlineAt = computeAssignDeadline(row.regionExpectedDeliveryMinutes)
|
||
assignForm.dispatchRemark = ''
|
||
assignVisible.value = true
|
||
}
|
||
|
||
const showAssignDialogFromDetail = () => {
|
||
assignReassignMode.value = !!(viewData.value.deliverId || viewData.value.deliverName)
|
||
assignForm.orderId = viewData.value.id
|
||
assignForm.regionId = viewData.value.regionId || ''
|
||
assignForm.deliverId = assignReassignMode.value ? viewData.value.deliverId || '' : ''
|
||
assignForm.deliveryDeadlineAt = computeAssignDeadline(
|
||
viewData.value.regionExpectedDeliveryMinutes
|
||
)
|
||
assignForm.dispatchRemark = viewData.value.deliveryDispatchRemark || ''
|
||
assignVisible.value = true
|
||
}
|
||
|
||
const confirmAssign = async () => {
|
||
if (!assignForm.regionId) {
|
||
ElMessage.warning('订单缺少区域信息,无法分配')
|
||
return
|
||
}
|
||
if (!assignForm.deliverId) {
|
||
ElMessage.warning('请选择配送员')
|
||
return
|
||
}
|
||
if (!assignForm.deliveryDeadlineAt) {
|
||
ElMessage.warning('请选择配送时效')
|
||
return
|
||
}
|
||
const orderIdSaved = assignForm.orderId
|
||
const wasReassign = assignReassignMode.value
|
||
submitLoading.value = true
|
||
try {
|
||
await fetchAssignDelivery({
|
||
orderId: assignForm.orderId,
|
||
deliverId: assignForm.deliverId,
|
||
deliveryDeadlineAt: assignForm.deliveryDeadlineAt,
|
||
dispatchRemark: assignForm.dispatchRemark
|
||
})
|
||
ElMessage.success(wasReassign ? '更换成功' : '分配成功')
|
||
assignVisible.value = false
|
||
await loadData()
|
||
if (viewVisible.value && viewData.value.id === orderIdSaved) {
|
||
await handleView({ id: orderIdSaved }, { preserveTab: true })
|
||
}
|
||
} catch (error) {
|
||
console.error('分配失败:', error)
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleReviewCancel = (row: any) => {
|
||
cancelForm.orderId = row.id
|
||
cancelForm.approved = true
|
||
cancelForm.note = ''
|
||
cancelVisible.value = true
|
||
}
|
||
|
||
const confirmCancel = async () => {
|
||
submitLoading.value = true
|
||
try {
|
||
await fetchReviewCancel({
|
||
orderId: cancelForm.orderId,
|
||
approved: cancelForm.approved,
|
||
note: cancelForm.note
|
||
})
|
||
ElMessage.success('审核完成')
|
||
cancelVisible.value = false
|
||
loadData()
|
||
} catch (error) {
|
||
console.error('审核失败:', error)
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
const showCancelDialog = () => {
|
||
cancelDialogForm.orderId = viewData.value.id
|
||
cancelDialogForm.reason = ''
|
||
cancelDialogForm.note = ''
|
||
cancelDialogForm.evidenceImageUrls = []
|
||
cancelDialogVisible.value = true
|
||
}
|
||
|
||
const handleCancelEvidenceChange = async (uploadFile: any) => {
|
||
const file = uploadFile?.raw
|
||
if (!file) return
|
||
const fd = new FormData()
|
||
fd.append('business_type', 'order')
|
||
fd.append('business_id', cancelDialogForm.orderId)
|
||
fd.append('file', file)
|
||
const res: any = await fetchUploadAttachment(fd)
|
||
if (res?.fileUrl) cancelDialogForm.evidenceImageUrls.push(res.fileUrl)
|
||
}
|
||
|
||
const handleCancelEvidenceRemove = (_file: any, fileList: any[]) => {
|
||
const urls = fileList.map((f: any) => f?.response?.fileUrl || f?.url).filter(Boolean)
|
||
cancelDialogForm.evidenceImageUrls = urls
|
||
}
|
||
|
||
const confirmCancelOrder = async () => {
|
||
if (!cancelDialogForm.reason) {
|
||
ElMessage.warning('请输入取消原因')
|
||
return
|
||
}
|
||
cancelDialogLoading.value = true
|
||
try {
|
||
await withFullscreenLoading(async () => {
|
||
await fetchCancelOrder({
|
||
orderId: cancelDialogForm.orderId,
|
||
reason: cancelDialogForm.reason,
|
||
note: cancelDialogForm.note,
|
||
evidenceImageUrls: cancelDialogForm.evidenceImageUrls
|
||
})
|
||
ElMessage.success('订单取消成功')
|
||
cancelDialogVisible.value = false
|
||
viewVisible.value = false
|
||
loadData()
|
||
}, fullscreenLoadingText.delete)
|
||
} catch (error) {
|
||
console.error('取消订单失败:', error)
|
||
ElMessage.error('取消订单失败,请重试')
|
||
} finally {
|
||
cancelDialogLoading.value = false
|
||
}
|
||
}
|
||
|
||
const showAdjustDialog = () => {
|
||
adjustForm.reason = ''
|
||
adjustAddressForm.deliveryAddress = viewData.value.deliveryAddress || ''
|
||
adjustAddressForm.contactPerson = viewData.value.contactPerson || ''
|
||
adjustAddressForm.contactPhone = viewData.value.contactPhone || ''
|
||
adjustAddressForm.reason = ''
|
||
adjustOrderItems.value = []
|
||
console.log('[order-adjust] open dialog', { orderId: viewData.value.id })
|
||
adjustDialogVisible.value = true
|
||
}
|
||
|
||
// 加载订单商品用于调整
|
||
const loadOrderItemsForAdjust = async () => {
|
||
if (!viewData.value.id) return
|
||
adjustItemsLoading.value = true
|
||
const seq = ++adjustLoadSeq.value
|
||
console.log('[order-adjust] start load items', { orderId: viewData.value.id, seq })
|
||
try {
|
||
const res: any = await fetchOrderItems(viewData.value.id)
|
||
if (seq !== adjustLoadSeq.value) {
|
||
console.log('[order-adjust] skip stale load result', { seq, currentSeq: adjustLoadSeq.value })
|
||
return
|
||
}
|
||
const src = Array.isArray(res?.list) ? res.list : viewData.value.items || []
|
||
adjustOrderItems.value = src.map((item: any) => ({
|
||
...item,
|
||
newQuantity: Number(item.quantity || 0)
|
||
}))
|
||
console.log('[order-adjust] load items success', {
|
||
seq,
|
||
total: adjustOrderItems.value.length,
|
||
sample: adjustOrderItems.value.slice(0, 3).map((it: any) => ({
|
||
id: it.id,
|
||
quantity: it.quantity,
|
||
unitPrice: it.unitPrice
|
||
}))
|
||
})
|
||
} catch (error) {
|
||
console.error('加载订单商品失败:', error)
|
||
const src = Array.isArray(viewData.value.items) ? viewData.value.items : []
|
||
adjustOrderItems.value = src.map((item: any) => ({
|
||
...item,
|
||
newQuantity: Number(item.quantity || 0)
|
||
}))
|
||
console.log('[order-adjust] fallback items from detail', { total: adjustOrderItems.value.length })
|
||
} finally {
|
||
if (seq === adjustLoadSeq.value) adjustItemsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const confirmAdjustOrder = async () => {
|
||
console.log('[order-adjust] submit check', {
|
||
changedCount: changedAdjustItems.value.length,
|
||
changedItems: changedAdjustItems.value.map((item) => ({
|
||
id: item.id,
|
||
original: item.quantity,
|
||
newQuantity: item.newQuantity
|
||
}))
|
||
})
|
||
const hasAddressChanged =
|
||
(adjustAddressForm.deliveryAddress || '') !== (viewData.value.deliveryAddress || '') ||
|
||
(adjustAddressForm.contactPerson || '') !== (viewData.value.contactPerson || '') ||
|
||
(adjustAddressForm.contactPhone || '') !== (viewData.value.contactPhone || '')
|
||
if (changedAdjustItems.value.length === 0 && !hasAddressChanged) {
|
||
ElMessage.warning('请至少调整商品或修改地址信息')
|
||
return
|
||
}
|
||
if (changedAdjustItems.value.length > 0 && !String(adjustForm.reason || '').trim()) {
|
||
ElMessage.warning('有商品调整时,调整原因必填')
|
||
return
|
||
}
|
||
const invalidQty = changedAdjustItems.value.some((item) => {
|
||
const original = Number(item.quantity || 0)
|
||
const after = Number(item.newQuantity ?? original)
|
||
return Number.isNaN(after) || after < 0
|
||
})
|
||
if (invalidQty) {
|
||
ElMessage.warning('新数量不能小于0')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await ElMessageBox.confirm('确认提交本次订单调整吗?', '确认调整', {
|
||
type: 'warning',
|
||
confirmButtonText: '确认提交',
|
||
cancelButtonText: '取消'
|
||
})
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
adjustDialogLoading.value = true
|
||
try {
|
||
const adjustments = changedAdjustItems.value.map((item) => {
|
||
const originalQty = Number(item.quantity ?? 0)
|
||
const afterQty = Number(item.newQuantity ?? item.quantity ?? 0)
|
||
const adjustType: 'increase' | 'reduce' | 'delete' =
|
||
afterQty === 0 ? 'delete' : afterQty > originalQty ? 'increase' : 'reduce'
|
||
return {
|
||
orderItemId: item.id,
|
||
adjustType,
|
||
newQuantity: afterQty,
|
||
reason: adjustForm.reason
|
||
}
|
||
})
|
||
const addressUpdate = hasAddressChanged
|
||
? {
|
||
deliveryAddress: adjustAddressForm.deliveryAddress,
|
||
contactPerson: adjustAddressForm.contactPerson,
|
||
contactPhone: adjustAddressForm.contactPhone,
|
||
reason: adjustAddressForm.reason || '运营调整收货地址'
|
||
}
|
||
: undefined
|
||
|
||
await fetchAdjustOrder({
|
||
orderId: viewData.value.id,
|
||
adjustments,
|
||
addressUpdate
|
||
})
|
||
ElMessage.success('订单调整已提交')
|
||
adjustDialogVisible.value = false
|
||
await loadData()
|
||
await handleView({ id: viewData.value.id }, { preserveTab: true })
|
||
activeTab.value = 'logs'
|
||
} catch (error) {
|
||
console.error('订单调整失败:', error)
|
||
ElMessage.error('订单调整失败,请重试')
|
||
} finally {
|
||
adjustDialogLoading.value = false
|
||
}
|
||
}
|
||
|
||
const loadSystemDefaultsForAssign = async () => {
|
||
try {
|
||
const res: any = await fetchSystemConfig()
|
||
const m = res?.order?.defaultRegionExpectedDeliveryMinutes
|
||
if (m != null && Number.isFinite(Number(m)) && Number(m) > 0) {
|
||
defaultRegionExpectedDeliveryMinutes.value = Math.floor(Number(m))
|
||
}
|
||
} catch (e) {
|
||
console.error('加载系统默认配送时长失败:', e)
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
applyEmbeddedTransparent()
|
||
applyBridgeShellMode()
|
||
if (embeddedMode.value || bridgeMode.value) {
|
||
window.addEventListener('message', handleEmbeddedMessage)
|
||
}
|
||
void ensureCategoryPathDataLoaded()
|
||
const kw = route.query.keyword
|
||
if (typeof kw === 'string' && kw.trim()) {
|
||
searchForm.keyword = kw.trim()
|
||
}
|
||
void loadRegions().then(() => loadData())
|
||
loadSystemDefaultsForAssign()
|
||
addAutoRefreshListeners()
|
||
})
|
||
|
||
onActivated(() => {
|
||
addAutoRefreshListeners()
|
||
tryAutoRefresh()
|
||
})
|
||
|
||
const removeAutoRefreshListeners = () => {
|
||
window.removeEventListener('focus', handleWindowFocus)
|
||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||
}
|
||
|
||
onDeactivated(() => {
|
||
removeAutoRefreshListeners()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
removeAutoRefreshListeners()
|
||
if (embeddedMode.value || bridgeMode.value) {
|
||
window.removeEventListener('message', handleEmbeddedMessage)
|
||
resetEmbeddedTransparent()
|
||
resetBridgeShellMode()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.order-embedded {
|
||
/* 嵌入模式:隐藏订单列表底层,只保留 el-dialog 详情弹窗 */
|
||
> .page-head,
|
||
> .panel {
|
||
display: none !important;
|
||
}
|
||
|
||
background: transparent !important;
|
||
}
|
||
|
||
.order-bridge {
|
||
> .page-head,
|
||
> .panel {
|
||
display: none !important;
|
||
}
|
||
|
||
background: transparent !important;
|
||
}
|
||
|
||
.order-list-table :deep(th.el-table__cell > .cell),
|
||
.order-list-table :deep(td.el-table__cell > .cell) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.page-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
/* 订单检索区:对齐 fresh-mint-green.html 的筛选块节奏 */
|
||
.filter-hint {
|
||
margin: 0 0 10px;
|
||
font-size: 11px;
|
||
color: var(--mint-text-muted, #64748b);
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.order-filter-main-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
|
||
.order-filter-extra-grid {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.order-filter-main-grid,
|
||
.order-filter-extra-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 560px) {
|
||
.order-filter-main-grid,
|
||
.order-filter-extra-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.filter-form :deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
min-width: 0;
|
||
}
|
||
|
||
.filter-form :deep(.el-form-item__label) {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--mint-text-muted, #64748b);
|
||
padding: 0 0 4px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.filter-extra {
|
||
padding-top: 10px;
|
||
margin-top: 8px;
|
||
border-top: 0; /* 设计图:筛选内部不加分割线 */
|
||
}
|
||
|
||
.filter-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding-top: 10px;
|
||
margin-top: 10px;
|
||
border-top: 0; /* 设计图:筛选内部不加分割线 */
|
||
}
|
||
|
||
.filter-actions-end {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.region-tab-btn {
|
||
position: relative;
|
||
min-width: 108px;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.region-tab-btn.el-button > :deep(span) {
|
||
position: static !important;
|
||
display: inline-flex;
|
||
width: 100%;
|
||
justify-content: center;
|
||
}
|
||
|
||
.region-tab-inner {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
}
|
||
|
||
.region-tab-badge {
|
||
position: absolute;
|
||
top: -8px;
|
||
right: -8px;
|
||
padding: 0 4px;
|
||
border-radius: 999px;
|
||
font-size: 10px;
|
||
line-height: 14px;
|
||
color: #b91c1c;
|
||
background: #fef2f2;
|
||
border: 1px solid rgba(248, 113, 113, 0.6);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.btn-filter-toggle {
|
||
height: 36px;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.btn-filter-toggle-ic {
|
||
margin-right: 4px;
|
||
}
|
||
.order-row-expand-wrap {
|
||
padding: 0;
|
||
background: #f8fafc;
|
||
border-radius: 0;
|
||
}
|
||
|
||
.order-list-table :deep(td.el-table__expanded-cell) {
|
||
padding: 0 !important;
|
||
text-align: left !important;
|
||
}
|
||
|
||
.order-list-table :deep(td.el-table__expanded-cell > .cell) {
|
||
display: block !important;
|
||
padding: 0 !important;
|
||
text-align: left !important;
|
||
}
|
||
|
||
/* 展开按钮:边框 + hover + 展开态旋转(对齐设计图树形列表) */
|
||
.order-list-table :deep(.el-table__expand-icon) {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--mint-border, #d9e8e2);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
||
color: var(--mint-text-muted, #64748b);
|
||
background: var(--mint-surface, #fff);
|
||
}
|
||
|
||
.order-list-table :deep(.el-table__expand-icon:hover) {
|
||
background: var(--mint-primary-bg, #ecfdf5);
|
||
border-color: #10b981;
|
||
color: var(--mint-primary-dark, #059669);
|
||
}
|
||
|
||
.order-list-table :deep(.el-table__expand-icon--expanded) {
|
||
background: var(--mint-primary-bg, #ecfdf5);
|
||
border-color: #10b981;
|
||
color: var(--mint-primary-dark, #059669);
|
||
}
|
||
|
||
.order-list-table :deep(.el-table__expand-icon svg) {
|
||
transition: transform 0.15s ease;
|
||
}
|
||
|
||
.order-list-table :deep(.el-table__expand-icon--expanded svg) {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.order-row-expand-table {
|
||
width: max-content;
|
||
max-width: 100%;
|
||
|
||
:deep(.el-table__body-wrapper) {
|
||
overflow-x: auto;
|
||
}
|
||
}
|
||
|
||
.order-row-expand-table-wrap {
|
||
display: block;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
border-radius: 0;
|
||
text-align: left;
|
||
|
||
:deep(.el-table__inner-wrapper),
|
||
:deep(.el-table__header-wrapper),
|
||
:deep(.el-table__body-wrapper) {
|
||
width: max-content;
|
||
}
|
||
}
|
||
|
||
/* 树形展开子表(对齐 fresh-mint-green.html / 规范 §8) */
|
||
.order-row-expand-wrap {
|
||
padding: 0;
|
||
}
|
||
.order-row-expand-wrap .sqxp-subtable-wrap {
|
||
width: 100%;
|
||
background: #f8fafc;
|
||
}
|
||
.order-row-expand-wrap .sqxp-subtable-wrap::before {
|
||
content: '';
|
||
display: block;
|
||
height: 0;
|
||
}
|
||
.order-row-expand-wrap .sqxp-subtable-wrap {
|
||
border: 0;
|
||
}
|
||
.order-row-expand-table-wrap.sqxp-subtable-wrap {
|
||
padding: 0;
|
||
}
|
||
.order-row-expand-wrap .sqxp-subtable-wrap {
|
||
position: relative;
|
||
}
|
||
.order-row-expand-wrap .sqxp-subtable-wrap::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 3px;
|
||
background: #10b981;
|
||
}
|
||
.order-row-expand-wrap .order-row-expand-table-wrap {
|
||
padding: 12px 12px 14px 16px;
|
||
}
|
||
.order-row-expand-wrap .order-row-expand-table {
|
||
width: 100%;
|
||
border: 1px solid var(--mint-border, #d9e8e2);
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
}
|
||
.order-row-expand-wrap .order-row-expand-table :deep(th.el-table__cell) {
|
||
background: #e2e8f0 !important;
|
||
color: var(--mint-text-muted, #64748b);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
}
|
||
.order-row-expand-wrap .order-row-expand-table :deep(td.el-table__cell) {
|
||
font-size: 13px;
|
||
}
|
||
.order-row-expand-wrap .order-row-expand-table :deep(.el-table__cell) {
|
||
padding: 8px 10px;
|
||
}
|
||
.order-row-expand-summary {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
gap: 16px;
|
||
justify-content: flex-end;
|
||
color: var(--mint-text-muted, #64748b);
|
||
font-size: 13px;
|
||
}
|
||
.order-row-expand-summary {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
gap: 20px;
|
||
justify-content: flex-end;
|
||
color: #606266;
|
||
font-size: 13px;
|
||
}
|
||
.expand-product-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
max-width: 100%;
|
||
min-width: 0;
|
||
}
|
||
.expand-product-thumb-wrap {
|
||
flex: 0 0 auto;
|
||
}
|
||
.expand-product-thumb {
|
||
width: 34px;
|
||
height: 34px;
|
||
border-radius: 6px;
|
||
display: block;
|
||
}
|
||
.expand-product-thumb.placeholder {
|
||
width: 34px;
|
||
height: 34px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 11px;
|
||
color: #909399;
|
||
background: #f5f7fa;
|
||
border: 1px dashed #dcdfe6;
|
||
}
|
||
.expand-product-name {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.amount-text {
|
||
color: #dc2626;
|
||
font-weight: 600;
|
||
}
|
||
.amount-text.danger {
|
||
color: #dc2626;
|
||
}
|
||
.mt-4 {
|
||
margin-top: 16px;
|
||
}
|
||
.mb-2 {
|
||
margin-bottom: 8px;
|
||
}
|
||
.font-medium {
|
||
font-weight: 500;
|
||
}
|
||
.mb-4 {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.order-delivered-th-icon {
|
||
margin-left: 4px;
|
||
vertical-align: middle;
|
||
cursor: help;
|
||
color: #c0c4cc;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.hint-text {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
padding-left: 12px;
|
||
border-left: 4px solid #10b981;
|
||
}
|
||
|
||
.detail-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
background: #f5f7fa;
|
||
padding: 4px;
|
||
border-radius: 8px;
|
||
margin: 8px 22px 16px;
|
||
width: fit-content;
|
||
}
|
||
|
||
.order-tab-pane {
|
||
margin-top: 6px;
|
||
padding: 0 22px 8px;
|
||
}
|
||
|
||
.order-logs-tab {
|
||
padding: 0 22px 8px;
|
||
}
|
||
|
||
.price-version-link {
|
||
border: 0 !important;
|
||
background: transparent !important;
|
||
padding: 0 !important;
|
||
height: auto !important;
|
||
min-height: 0 !important;
|
||
color: #059669 !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.price-version-link:hover,
|
||
.price-version-link:focus {
|
||
color: #047857 !important;
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.detail-tab {
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
transition: all 0.3s;
|
||
|
||
&.active {
|
||
background: #fff;
|
||
color: #059669;
|
||
font-weight: 500;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
&:hover:not(.active) {
|
||
color: #059669;
|
||
}
|
||
}
|
||
|
||
.lock-tip {
|
||
background: #fff1f0;
|
||
border: 1px solid #ffccc7;
|
||
border-radius: 8px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
color: #dc2626;
|
||
font-size: 14px;
|
||
|
||
strong {
|
||
color: #dc2626;
|
||
}
|
||
}
|
||
|
||
.amount-summary {
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 40px;
|
||
padding: 16px 0;
|
||
border-top: 2px solid #10b981;
|
||
margin-top: 16px;
|
||
|
||
.amount-item {
|
||
.label {
|
||
font-size: 16px;
|
||
color: #606266;
|
||
}
|
||
|
||
.value {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #dc2626;
|
||
margin-left: 8px;
|
||
|
||
&.success {
|
||
color: #059669;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.order-delivery-tab {
|
||
padding: 0 22px 8px;
|
||
}
|
||
|
||
.order-delivery-address-blocks {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
.order-delivery-address-blocks {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.od-address-card {
|
||
border-radius: 12px;
|
||
|
||
:deep(.el-card__header) {
|
||
padding: 12px 16px;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
|
||
.od-address-card--supplier :deep(.el-card__header) {
|
||
border-left: 4px solid #d97706;
|
||
}
|
||
|
||
.od-address-card--customer :deep(.el-card__header) {
|
||
border-left: 4px solid #059669;
|
||
}
|
||
|
||
.od-address-card-title {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
}
|
||
|
||
.od-address-card-body {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
|
||
.od-address-line {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
align-items: flex-start;
|
||
|
||
.od-label {
|
||
flex-shrink: 0;
|
||
width: 88px;
|
||
color: #909399;
|
||
}
|
||
|
||
.od-value {
|
||
flex: 1;
|
||
min-width: 0;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.od-value.od-text-wrap {
|
||
white-space: pre-wrap;
|
||
}
|
||
}
|
||
|
||
.od-address-line--block {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.evidence-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.evidence-image {
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 6px;
|
||
border: 1px solid #ebeef5;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.order-delivery-tab {
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.order-delivery-address-blocks {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
.order-delivery-address-blocks {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.od-address-card {
|
||
border-radius: 12px;
|
||
|
||
:deep(.el-card__header) {
|
||
padding: 12px 16px;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
|
||
.od-address-card--supplier :deep(.el-card__header) {
|
||
border-left: 4px solid #d97706;
|
||
}
|
||
|
||
.od-address-card--customer :deep(.el-card__header) {
|
||
border-left: 4px solid #059669;
|
||
}
|
||
|
||
.od-address-card-title {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
}
|
||
|
||
.od-address-card-body {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
|
||
.od-address-line {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
align-items: flex-start;
|
||
|
||
.od-label {
|
||
flex-shrink: 0;
|
||
width: 88px;
|
||
color: #909399;
|
||
}
|
||
|
||
.od-value {
|
||
flex: 1;
|
||
min-width: 0;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.od-value.od-text-wrap {
|
||
white-space: pre-wrap;
|
||
}
|
||
}
|
||
|
||
.timeline-content {
|
||
width: 240px;
|
||
min-width: 240px;
|
||
max-width: 240px;
|
||
min-height: 116px;
|
||
height: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 10px;
|
||
background: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.timeline-time {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.timeline-title {
|
||
font-size: 14px;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.timeline-desc {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.cancel-note {
|
||
color: #606266;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.timeline-cancel-images {
|
||
margin-top: 6px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.timeline-cancel-image {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 6px;
|
||
border: 1px solid #e4e7ed;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
|
||
.timeline-horizontal {
|
||
width: 100%;
|
||
overflow: hidden;
|
||
padding: 8px 0 14px;
|
||
}
|
||
|
||
.timeline-horizontal-list {
|
||
display: flex;
|
||
align-items: stretch;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.timeline-horizontal-item {
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 8px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.timeline-index {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
background: #10b981;
|
||
color: #fff;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 22px;
|
||
text-align: center;
|
||
flex: 0 0 22px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.timeline-horizontal-item:last-child .timeline-horizontal-arrow {
|
||
display: none;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.timeline-content {
|
||
width: 210px;
|
||
min-width: 210px;
|
||
max-width: 210px;
|
||
}
|
||
}
|
||
|
||
.timeline-horizontal-arrow {
|
||
color: #c0c4cc;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
user-select: none;
|
||
align-self: center;
|
||
}
|
||
|
||
.info-text {
|
||
color: #606266;
|
||
}
|
||
|
||
.unit-text {
|
||
margin-left: 8px;
|
||
color: #909399;
|
||
}
|
||
|
||
.adjust-summary {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.adjust-preview {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
/* 覆盖全局表格居中:调整弹窗内商品名称左对齐 */
|
||
.adjust-order-items-table :deep(.el-table__cell.adjust-product-col > .cell) {
|
||
justify-content: flex-start;
|
||
text-align: left;
|
||
}
|
||
|
||
.adjust-order-items-table :deep(.el-table__cell:not(.adjust-product-col) > .cell) {
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.adjust-order-items-table :deep(.el-table__body-wrapper) {
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.text-danger {
|
||
color: #dc2626;
|
||
}
|
||
|
||
/* 订单明细:横向滚动,列保持 min-width 可读 */
|
||
.order-detail-items-scroll {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
overflow-x: hidden;
|
||
overflow-y: visible;
|
||
margin-bottom: 12px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
/*
|
||
* 全局 el-ui.scss 对 .el-table / .el-table__inner-wrapper 使用了 width:100%!important,
|
||
* 会把列压进弹窗宽度导致末列被裁切且无横向滚动。此处覆盖为按列宽撑开并由外层滚动。
|
||
*/
|
||
.order-detail-items-scroll :deep(.el-table) {
|
||
width: 100% !important;
|
||
min-width: 100%;
|
||
}
|
||
|
||
.order-detail-items-scroll :deep(.el-table .el-table__inner-wrapper) {
|
||
width: 100% !important;
|
||
min-width: 100%;
|
||
}
|
||
|
||
.order-detail-items-scroll :deep(.el-table__body-wrapper) {
|
||
overflow-x: auto !important;
|
||
overflow-y: auto !important;
|
||
}
|
||
|
||
.order-detail-items-scroll :deep(.el-table__header-wrapper) {
|
||
overflow-x: hidden !important;
|
||
}
|
||
|
||
/* 已配送列:全局表格 nowrap 会截断长文案,此处允许换行完整展示 */
|
||
.order-detail-items-table :deep(.el-table__cell.order-delivered-cell .cell) {
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
line-height: 1.45;
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
}
|
||
|
||
.order-detail-items-table :deep(.order-delivered-text) {
|
||
display: inline-block;
|
||
max-width: 100%;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 订单明细:除商品名称外均居中(覆盖全局 el-table 与下方通用规则) */
|
||
.order-detail-items-table :deep(.el-table__cell:not(.order-detail-product-col)) {
|
||
text-align: center;
|
||
}
|
||
|
||
.order-detail-items-table :deep(.el-table__cell:not(.order-detail-product-col) > .cell) {
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
|
||
/*
|
||
* showOverflowTooltip 会给 tbody 内层 .cell 设置固定像素宽,块级时贴齐单元格一侧,
|
||
* 表头仍按整格居中,易出现「分类」等列表头与数据纵向不对齐。
|
||
*/
|
||
.order-detail-items-table
|
||
:deep(td.el-table__cell:not(.order-detail-product-col) .cell.el-tooltip) {
|
||
width: 100% !important;
|
||
max-width: 100%;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.order-detail-items-table :deep(th.el-table__cell:not(.order-detail-product-col) > .cell) {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
text-align: center;
|
||
}
|
||
|
||
.order-detail-items-table :deep(.el-table__cell.order-detail-product-col) {
|
||
text-align: left;
|
||
}
|
||
|
||
.order-detail-items-table :deep(.el-table__cell.order-detail-product-col > .cell) {
|
||
justify-content: flex-start;
|
||
text-align: left;
|
||
}
|
||
|
||
.order-detail-items-table :deep(.el-table__cell) {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.order-item-name-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.order-item-thumb-wrap {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.order-item-thumb {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 4px;
|
||
display: block;
|
||
}
|
||
|
||
.order-item-thumb.placeholder {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 11px;
|
||
color: #909399;
|
||
background: #f5f7fa;
|
||
border: 1px dashed #dcdfe6;
|
||
}
|
||
|
||
.order-item-name-text {
|
||
text-align: left;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.order-pv-name-cell {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.order-pv-name-text {
|
||
text-align: left;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.order-pv-body {
|
||
min-height: 120px;
|
||
}
|
||
</style>
|
||
|
||
<style lang="scss" scoped>
|
||
.order-embedded-detail {
|
||
height: 100%;
|
||
}
|
||
|
||
.order-embedded .page-head,
|
||
.order-embedded .panel,
|
||
.order-embedded .pagination,
|
||
.order-embedded .list-toolbar,
|
||
.order-embedded .region-tabs,
|
||
.order-bridge .page-head,
|
||
.order-bridge .panel,
|
||
.order-bridge .pagination,
|
||
.order-bridge .list-toolbar,
|
||
.order-bridge .region-tabs {
|
||
display: none !important;
|
||
}
|
||
|
||
.order-embedded .order-embedded-detail :deep(.order-detail-dialog-scroll) {
|
||
padding: 12px 16px;
|
||
}
|
||
</style>
|
||
|
||
<!-- Teleport 到 body,需非 scoped 才能命中 .order-detail-dialog -->
|
||
<style lang="scss">
|
||
.sqxp-order-bridge-mode #app-sidebar,
|
||
.sqxp-order-bridge-mode #app-header {
|
||
display: none !important;
|
||
}
|
||
|
||
html.sqxp-order-bridge-mode,
|
||
body.sqxp-order-bridge-mode,
|
||
.sqxp-order-bridge-mode #app,
|
||
.sqxp-order-bridge-mode #app-main,
|
||
.sqxp-order-bridge-mode #app-content,
|
||
.sqxp-order-bridge-mode .app-layout,
|
||
.sqxp-order-bridge-mode .layout-content {
|
||
background: transparent !important;
|
||
}
|
||
|
||
.sqxp-order-bridge-mode #app-main,
|
||
.sqxp-order-bridge-mode #app-content {
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
width: 100% !important;
|
||
max-width: none !important;
|
||
}
|
||
|
||
.order-module-dialog.el-dialog {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: calc(100vh - 64px);
|
||
}
|
||
|
||
.order-module-dialog .el-dialog__header {
|
||
flex-shrink: 0;
|
||
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 70%);
|
||
}
|
||
|
||
.order-module-dialog .el-dialog__body {
|
||
flex: 1 1 auto;
|
||
min-height: 0;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.order-module-dialog .dialog-scroll {
|
||
height: min(720px, calc(100vh - 180px));
|
||
max-height: min(720px, calc(100vh - 180px));
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
padding: 0;
|
||
}
|
||
|
||
.order-module-dialog .el-dialog__footer {
|
||
flex-shrink: 0;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.order-module-dialog .el-dialog__footer .el-button {
|
||
height: 36px;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.order-module-dialog .el-dialog__footer .el-button .el-icon {
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.order-module-dialog-mask {
|
||
backdrop-filter: none !important;
|
||
}
|
||
|
||
.order-view-hero {
|
||
align-items: center;
|
||
border-bottom: 1px solid #d9e8e2;
|
||
}
|
||
.order-view-hero-icon {
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||
background: rgba(16, 185, 129, 0.08);
|
||
color: #059669;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28px;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.order-module-dialog .view-hero {
|
||
display: flex;
|
||
gap: 16px;
|
||
padding: 20px 22px;
|
||
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 55%);
|
||
}
|
||
|
||
.order-module-dialog .view-hero-main h2 {
|
||
margin: 0 0 8px;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
|
||
.order-module-dialog .view-hero-tags {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.order-module-dialog .view-hero-meta {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
color: #64748b;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.order-module-dialog .view-hero-meta .dot {
|
||
margin: 0 6px;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.order-module-dialog .view-section {
|
||
padding: 18px 22px;
|
||
}
|
||
|
||
.order-module-dialog .view-section h3 {
|
||
margin: 0 0 14px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
color: #64748b;
|
||
}
|
||
|
||
.view-kv-settlement-dd {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.view-kv-settlement-time {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.order-module-dialog .view-kv-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px 28px;
|
||
}
|
||
|
||
.order-module-dialog .view-kv {
|
||
display: grid;
|
||
grid-template-columns: 96px 1fr;
|
||
gap: 8px 12px;
|
||
font-size: 13px;
|
||
align-items: start;
|
||
}
|
||
|
||
.order-module-dialog .view-kv dt {
|
||
color: #64748b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.order-module-dialog .view-kv dd {
|
||
color: #1e293b;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.order-module-dialog .view-kv.view-multiline {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.order-module-dialog .view-kv-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.order-module-dialog .view-kv {
|
||
grid-template-columns: 1fr;
|
||
gap: 4px;
|
||
}
|
||
.order-module-dialog .view-kv.view-multiline {
|
||
grid-column: auto;
|
||
}
|
||
}
|
||
</style>
|