|
|
@@ -1,101 +1,434 @@
|
|
|
<template>
|
|
|
<div class="app-page">
|
|
|
- <!-- 订单摘要 -->
|
|
|
- <section class="glass-card section-card">
|
|
|
- <div class="table-toolbar">
|
|
|
- <h2 style="margin:0">订单详情 — {{ order.orderNo }}</h2>
|
|
|
- <div class="chip-list">
|
|
|
- <el-button @click="$router.back()">返回列表</el-button>
|
|
|
+ <section class="glass-card section-card header-section">
|
|
|
+ <div class="order-detail-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <div class="header-top">
|
|
|
+ <el-button @click="$router.back()">返回列表</el-button>
|
|
|
+ <el-button type="primary" @click="handleContact">联系买家</el-button>
|
|
|
+ </div>
|
|
|
+ <h2>订单详情 <span class="order-no">{{ order.orderNo }}</span></h2>
|
|
|
+ <div class="header-tags">
|
|
|
+ <el-tag v-if="order.channelOrderNo" type="info">渠道: {{ order.channelOrderNo }}</el-tag>
|
|
|
+ <el-tag v-if="order.priority === 'urgent'" type="danger">加急</el-tag>
|
|
|
+ <el-tag v-if="order.buyerLevel" :type="buyerLevelType(order.buyerLevel)">{{ order.buyerLevel }}</el-tag>
|
|
|
+ <el-tag v-for="tag in (order.orderTags || [])" :key="tag" size="small" type="warning">{{ tag }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-button @click="handlePrint">打印面单</el-button>
|
|
|
+ <el-button @click="handlePrintInvoice">打印发货单</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="stat-grid" style="margin-top:16px">
|
|
|
+ <div class="stat-grid">
|
|
|
+ <article class="stat-card">
|
|
|
+ <div class="stat-card__icon" :style="{ background: statusColor }">
|
|
|
+ <el-icon><Check /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card__content">
|
|
|
+ <div class="stat-card__label">订单状态</div>
|
|
|
+ <div class="stat-card__value">{{ statusLabel(order.orderStatus ?? '') }}</div>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
<article class="stat-card">
|
|
|
- <div class="stat-card__label">订单状态</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">{{ statusLabel(order.orderStatus ?? '') }}</div>
|
|
|
+ <div class="stat-card__icon" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%)">
|
|
|
+ <el-icon><Money /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card__content">
|
|
|
+ <div class="stat-card__label">实付金额</div>
|
|
|
+ <div class="stat-card__value">{{ order.currency }} {{ order.actualPaid || order.amount }}</div>
|
|
|
+ </div>
|
|
|
</article>
|
|
|
<article class="stat-card">
|
|
|
- <div class="stat-card__label">支付金额</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">{{ order.amount }}</div>
|
|
|
+ <div class="stat-card__icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)">
|
|
|
+ <el-icon><Van /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card__content">
|
|
|
+ <div class="stat-card__label">发货状态</div>
|
|
|
+ <div class="stat-card__value">{{ order.shippingStatus || '待发货' }}</div>
|
|
|
+ </div>
|
|
|
</article>
|
|
|
<article class="stat-card">
|
|
|
- <div class="stat-card__label">发货状态</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">{{ order.shippingStatus }}</div>
|
|
|
+ <div class="stat-card__icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)">
|
|
|
+ <el-icon><Warning /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card__content">
|
|
|
+ <div class="stat-card__label">异常标签</div>
|
|
|
+ <el-tag :type="order.exceptionTag === '正常' ? 'success' : 'warning'" size="default">{{ order.exceptionTag || '正常' }}</el-tag>
|
|
|
+ </div>
|
|
|
</article>
|
|
|
<article class="stat-card">
|
|
|
- <div class="stat-card__label">异常标签</div>
|
|
|
- <div class="stat-card__value" style="font-size:22px">
|
|
|
- <el-tag :type="order.exceptionTag === '正常' ? 'success' : 'warning'">{{ order.exceptionTag }}</el-tag>
|
|
|
+ <div class="stat-card__icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)">
|
|
|
+ <el-icon><OfficeBuilding /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card__content">
|
|
|
+ <div class="stat-card__label">分配仓库</div>
|
|
|
+ <div class="stat-card__value">{{ order.warehouse || '待分配' }}</div>
|
|
|
</div>
|
|
|
</article>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
- <section class="page-grid page-grid--two">
|
|
|
- <!-- 收货 + 支付信息 -->
|
|
|
- <article class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">收货 & 支付信息</h3>
|
|
|
- <el-descriptions :column="1" border>
|
|
|
- <el-descriptions-item label="收货人">{{ order.receiver }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="电话">{{ order.phone }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="地址">{{ order.address }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="支付方式">{{ order.payMethod }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="支付时间">{{ order.payTime }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="渠道">{{ order.channel }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="仓库">{{ order.warehouse }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
- </article>
|
|
|
-
|
|
|
- <!-- 操作区 -->
|
|
|
- <article class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">操作</h3>
|
|
|
- <div class="chip-list" style="margin-bottom:16px">
|
|
|
- <el-button type="primary" @click="assignDrawer = true" :disabled="order.orderStatus === 'cancelled'">提交仓库</el-button>
|
|
|
- <el-button @click="lockInventory" :disabled="order.orderStatus === 'cancelled'">锁定库存</el-button>
|
|
|
- <el-button @click="splitDialog = true" :disabled="order.orderStatus === 'cancelled'">拆单</el-button>
|
|
|
- <el-button @click="mergeDialog = true" :disabled="order.orderStatus === 'cancelled'">合单</el-button>
|
|
|
- <el-button type="danger" plain @click="cancelDialog = true" :disabled="order.orderStatus === 'cancelled'">取消订单</el-button>
|
|
|
- </div>
|
|
|
- <el-form label-position="top">
|
|
|
- <el-form-item label="订单备注">
|
|
|
- <el-input v-model="order.remark" type="textarea" :rows="3" placeholder="客服备注" />
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </article>
|
|
|
- </section>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-left">
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>买家信息</h3>
|
|
|
+ </div>
|
|
|
+ <el-descriptions :column="2" border size="default">
|
|
|
+ <el-descriptions-item label="买家ID">{{ order.buyerId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="买家昵称">{{ order.buyer }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="邮箱">{{ order.buyerEmail }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="电话">{{ order.buyerPhone }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="国家">{{ order.buyerCountry }} {{ getCountryFlag(order.buyerCountry) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="会员等级">{{ order.buyerLevel || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="注册时间">{{ order.buyerRegisterTime || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="历史订单">{{ order.buyerOrderCount || 0 }} 单</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="历史消费">{{ order.buyerTotalSpent || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="设备信息">{{ order.device }} / {{ order.browser }} / {{ order.os }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>收货信息</h3>
|
|
|
+ <el-button link type="primary" @click="editAddressDialog = true" :disabled="order.orderStatus === 'cancelled'">修改地址</el-button>
|
|
|
+ </div>
|
|
|
+ <el-descriptions :column="2" border size="default">
|
|
|
+ <el-descriptions-item label="收货人">
|
|
|
+ <span class="value-bold">{{ order.receiverName }}</span>
|
|
|
+ <el-button link type="primary" size="small" @click="handleCall">拨打电话</el-button>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="联系电话">{{ order.receiverPhone }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="国家/地区">{{ order.receiverCountry }} {{ getCountryFlag(order.receiverCountry) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="州/省">{{ order.receiverState || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="城市">{{ order.receiverCity }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="区县">{{ order.receiverDistrict || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="邮编">{{ order.receiverPostalCode || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="详细地址" :span="2">
|
|
|
+ <span class="address-text">{{ order.receiverAddress }}</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="坐标">{{ order.latitude?.toFixed(4) }}, {{ order.longitude?.toFixed(4) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="买家备注">
|
|
|
+ <span class="buyer-remark">{{ order.buyerRemark || '无' }}</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>支付信息</h3>
|
|
|
+ </div>
|
|
|
+ <el-descriptions :column="2" border size="default">
|
|
|
+ <el-descriptions-item label="支付方式">{{ order.paymentMethod }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="支付时间">{{ order.paymentTime || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="交易流水">{{ order.transactionId || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="订单货币">{{ order.currency }} (汇率: {{ order.exchangeRate || 1 }})</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="商品金额">{{ order.orderAmount || order.amount }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="折合CNY">¥{{ order.orderAmountCNY || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="税额">{{ order.taxAmount || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="运费">{{ order.shippingFee || '0.00' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="优惠折扣" :span="2">
|
|
|
+ <span v-if="order.couponCode" class="discount-tag">
|
|
|
+ {{ order.couponCode }} (-{{ order.couponDiscount || order.discountAmount || '0.00' }})
|
|
|
+ </span>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="实付金额">
|
|
|
+ <span class="value-bold actual-paid">{{ order.currency }} {{ order.actualPaid || order.amount }}</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="退款金额">{{ order.refundAmount || '0.00' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>物流追踪</h3>
|
|
|
+ <el-button link type="primary" @click="trackMore">查看更多</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-if="order.trackingNo" class="tracking-info">
|
|
|
+ <div class="tracking-info__main">
|
|
|
+ <el-tag type="success" size="default">{{ order.carrier || '顺丰速运' }}</el-tag>
|
|
|
+ <span class="tracking-no">{{ order.trackingNo }}</span>
|
|
|
+ <el-button link type="primary" @click="copyTrackingNo">复制</el-button>
|
|
|
+ <el-button link type="primary" @click="openTrackingUrl">查询物流</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="tracking-meta">
|
|
|
+ <span>配送方式: {{ order.shippingMethod || '-' }}</span>
|
|
|
+ <span>仓库: {{ order.warehouse || '-' }}</span>
|
|
|
+ <span>库位: {{ order.warehouseLocation || '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <el-timeline class="track-timeline" v-if="trackingSteps.length">
|
|
|
+ <el-timeline-item v-for="(step, index) in trackingSteps" :key="index" :timestamp="step.time" :type="step.type" :hollow="index === 0">
|
|
|
+ <p class="track-title">{{ step.title }}</p>
|
|
|
+ <p class="track-detail" v-if="step.detail">{{ step.detail }}</p>
|
|
|
+ </el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ <el-empty v-else description="暂无物流信息" />
|
|
|
+ </div>
|
|
|
+ <div v-else class="tracking-empty">
|
|
|
+ <el-empty description="暂无物流信息" />
|
|
|
+ <el-button v-if="order.orderStatus === 'shipped'" type="primary" size="default" @click="addTrackingDialog = true">添加物流信息</el-button>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>营销归因</h3>
|
|
|
+ </div>
|
|
|
+ <el-descriptions :column="2" border size="default">
|
|
|
+ <el-descriptions-item label="来源(UTM)">{{ order.utmSource || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="媒介(UTM)">{{ order.utmMedium || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="活动(UTM)">{{ order.utmCampaign || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="内容(UTM)">{{ order.utmContent || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="关键词(UTM)">{{ order.utmTerm || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="下单IP">{{ order.ip || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="IP归属国">{{ order.ipCountry || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="落地页">{{ order.landingPage || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="detail-right">
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <h3>快捷操作</h3>
|
|
|
+ <div class="action-menu">
|
|
|
+ <el-dropdown trigger="click" @command="handleActionCommand">
|
|
|
+ <el-button type="primary">
|
|
|
+ 订单操作 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item command="assign" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ <el-icon><Van /></el-icon> 提交仓库
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="lock" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ <el-icon><Lock /></el-icon> 锁定库存
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="split" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ <el-icon><Connection /></el-icon> 拆单
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="merge" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ <el-icon><FolderMerged /></el-icon> 合单
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="address" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ <el-icon><Edit /></el-icon> 修改地址
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+
|
|
|
+ <el-dropdown trigger="click" @command="handlePrintCommand">
|
|
|
+ <el-button>
|
|
|
+ 打印 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item command="label">
|
|
|
+ <el-icon><Printer /></el-icon> 打印面单
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="invoice">
|
|
|
+ <el-icon><Document /></el-icon> 打印发货单
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
|
|
|
- <!-- 商品明细 -->
|
|
|
- <section class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">商品明细</h3>
|
|
|
- <el-table :data="lineItems" stripe>
|
|
|
- <el-table-column prop="sku" label="SKU" width="180" />
|
|
|
- <el-table-column prop="title" label="商品" min-width="250" />
|
|
|
- <el-table-column prop="qty" label="数量" width="80" />
|
|
|
- <el-table-column prop="unitPrice" label="单价" width="100" />
|
|
|
- <el-table-column prop="subtotal" label="小计" width="100" />
|
|
|
+ <el-dropdown trigger="click" @command="handleRefundCommand">
|
|
|
+ <el-button type="success">
|
|
|
+ 退款 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item command="refund" :disabled="!canRefund">
|
|
|
+ <el-icon><Money /></el-icon> 发起退款
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="viewRefund">
|
|
|
+ <el-icon><Tickets /></el-icon> 查看退款记录
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+
|
|
|
+ <el-button type="danger" plain @click="cancelDialog = true" :disabled="order.orderStatus === 'cancelled'">
|
|
|
+ 取消订单
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <h3>订单关联</h3>
|
|
|
+ <el-descriptions :column="1" border size="default">
|
|
|
+ <el-descriptions-item label="母订单">{{ order.parentOrderId || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="子订单">{{ order.childOrderIds?.join(', ') || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="合单订单">{{ order.mergeOrderId || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="关联订单">{{ order.relatedOrderId || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="原订单(退款/重发)">{{ order.originalOrderId || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <h3>客服信息</h3>
|
|
|
+ <el-descriptions :column="1" border size="default">
|
|
|
+ <el-descriptions-item label="处理人">{{ order.handler || '待分配' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="处理组">{{ order.handlerGroup || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <h3>内部备注</h3>
|
|
|
+ <el-input v-model="order.internalRemark" type="textarea" :rows="3" placeholder="客服内部备注信息" />
|
|
|
+ <el-button type="primary" size="default" @click="saveRemark" class="save-btn">保存备注</el-button>
|
|
|
+ </article>
|
|
|
+
|
|
|
+ <article class="glass-card section-card">
|
|
|
+ <h3>操作日志</h3>
|
|
|
+ <el-timeline>
|
|
|
+ <el-timeline-item v-for="(log, index) in operationLogs" :key="index" :timestamp="log.time" :type="log.type" :hollow="index === 0">
|
|
|
+ <p class="log-title">{{ log.title }}</p>
|
|
|
+ <p class="log-detail" v-if="log.operator">操作人: {{ log.operator }} ({{ log.operatorRole }})</p>
|
|
|
+ </el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section class="glass-card section-card products-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>商品明细 ({{ order.items?.length || 0 }} 件)</h3>
|
|
|
+ </div>
|
|
|
+ <el-table :data="order.items" stripe>
|
|
|
+ <el-table-column prop="sku" label="SKU编码" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="sku-code">{{ row.sku }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="productTitle" label="商品信息" min-width="260">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="product-info">
|
|
|
+ <el-avatar v-if="row.productImage" :src="row.productImage" :size="56" shape="square" />
|
|
|
+ <div v-else class="product-image-placeholder">暂无图</div>
|
|
|
+ <div class="product-detail">
|
|
|
+ <div class="product-title">{{ row.productTitle }}</div>
|
|
|
+ <div class="product-category">{{ row.categoryName }} | {{ row.skuId }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="specs" label="规格" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="specs-list">
|
|
|
+ <el-tag v-for="spec in row.specs" :key="spec.specName" size="small">{{ spec.specValue }}</el-tag>
|
|
|
+ <span v-if="!row.specs || row.specs.length === 0">-</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="barcode" label="条码" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="barcode">{{ row.barcode || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="qty" label="数量" width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="qty">{{ row.qty }}</span>
|
|
|
+ <div class="qty-detail" v-if="row.shippedQty || row.returnedQty">
|
|
|
+ <span>已发: {{ row.shippedQty || 0 }}</span>
|
|
|
+ <span>退: {{ row.returnedQty || 0 }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="price" label="单价" width="100" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ order.currency }} {{ row.price }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="costPrice" label="成本价" width="100" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="cost-price">{{ order.currency }} {{ row.costPrice }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="profit" label="利润" width="100" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="profit">{{ order.currency }} {{ row.profit }}</span>
|
|
|
+ <div class="profit-rate">{{ row.profitRate }}%</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="subtotal" label="小计" width="100" align="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="subtotal">{{ order.currency }} {{ row.subtotal }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="inventory" label="库存" width="150" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="inventory-info">
|
|
|
+ <span class="inv-available">可用: {{ row.available }}</span>
|
|
|
+ <span class="inv-locked">锁定: {{ row.locked }}</span>
|
|
|
+ <span class="inv-transit">在途: {{ row.inTransit }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="giftFlag" label="赠品" width="80" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag v-if="row.giftFlag" type="warning" size="small">赠品</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
</el-table>
|
|
|
+ <div class="amount-summary">
|
|
|
+ <div class="summary-row">
|
|
|
+ <span>商品总额:</span>
|
|
|
+ <span>{{ order.currency }} {{ order.orderAmount || order.amount }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row">
|
|
|
+ <span>税额:</span>
|
|
|
+ <span>{{ order.currency }} {{ order.taxAmount || '0.00' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row">
|
|
|
+ <span>运费:</span>
|
|
|
+ <span>{{ order.currency }} {{ order.shippingFee || '0.00' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row" v-if="order.discountAmount && parseFloat(order.discountAmount) > 0">
|
|
|
+ <span>优惠:</span>
|
|
|
+ <span class="discount">-{{ order.currency }} {{ order.discountAmount }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row" v-if="order.couponDiscount && parseFloat(order.couponDiscount) > 0">
|
|
|
+ <span>优惠券:</span>
|
|
|
+ <span class="discount">-{{ order.currency }} {{ order.couponDiscount }} ({{ order.couponCode }})</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row summary-row--total">
|
|
|
+ <span>实付金额:</span>
|
|
|
+ <span class="total-amount">{{ order.currency }} {{ order.actualPaid || order.amount }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-row summary-row--profit">
|
|
|
+ <span>订单利润:</span>
|
|
|
+ <span class="profit-amount">{{ order.currency }} {{ totalProfit }}</span>
|
|
|
+ <span class="profit-rate">({{ totalProfitRate }}%)</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</section>
|
|
|
|
|
|
- <!-- 状态时间线 -->
|
|
|
- <section class="glass-card section-card">
|
|
|
- <h3 style="margin:0 0 16px">订单轨迹</h3>
|
|
|
- <el-timeline>
|
|
|
- <el-timeline-item v-for="item in timeline" :key="item.time" :timestamp="item.time" :type="item.type">
|
|
|
- <strong>{{ item.title }}</strong>
|
|
|
- <div style="color:var(--cb-text-soft);margin-top:4px">{{ item.summary }}</div>
|
|
|
- </el-timeline-item>
|
|
|
- </el-timeline>
|
|
|
+ <section class="glass-card section-card package-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3>包裹信息</h3>
|
|
|
+ </div>
|
|
|
+ <el-descriptions :column="4" border size="default">
|
|
|
+ <el-descriptions-item label="重量">{{ order.weight ? order.weight.toFixed(2) + ' kg' : '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="体积">L: {{ order.length || '-' }}cm x W: {{ order.width || '-' }}cm x H: {{ order.height || '-' }}cm</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="仓库">{{ order.warehouse || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="库位">{{ order.warehouseLocation || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
</section>
|
|
|
|
|
|
- <!-- 取消弹窗 -->
|
|
|
<el-dialog v-model="cancelDialog" title="取消订单" width="520px">
|
|
|
<el-alert title="取消订单需二次确认,操作不可撤销" type="warning" :closable="false" style="margin-bottom:16px" />
|
|
|
<el-form label-position="top">
|
|
|
<el-form-item label="取消原因" required>
|
|
|
<el-select v-model="cancelReason" style="width:100%">
|
|
|
- <el-option label="买家申请取消" value="买家申请取消" />
|
|
|
- <el-option label="地址异常无法发货" value="地址异常无法发货" />
|
|
|
- <el-option label="库存不足" value="库存不足" />
|
|
|
- <el-option label="其他原因" value="其他原因" />
|
|
|
+ <el-option label="买家申请取消" value="buyer_cancel" />
|
|
|
+ <el-option label="地址异常无法发货" value="address_error" />
|
|
|
+ <el-option label="库存不足" value="out_of_stock" />
|
|
|
+ <el-option label="商品缺货" value="goods_unavailable" />
|
|
|
+ <el-option label="其他原因" value="other" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="补充说明">
|
|
|
@@ -108,30 +441,37 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- 拆单弹窗 -->
|
|
|
<el-dialog v-model="splitDialog" title="拆单处理" width="620px">
|
|
|
- <el-table :data="lineItems">
|
|
|
- <el-table-column prop="sku" label="SKU" min-width="180" />
|
|
|
- <el-table-column prop="title" label="商品" min-width="200" />
|
|
|
- <el-table-column prop="qty" label="原数量" width="90" />
|
|
|
- <el-table-column label="拆单数量" width="140">
|
|
|
+ <el-alert title="拆单后原订单将变更为主订单,新订单将从原订单拆分" type="info" :closable="false" style="margin-bottom:16px" />
|
|
|
+ <el-table :data="order.items">
|
|
|
+ <el-table-column prop="sku" label="SKU" min-width="160" />
|
|
|
+ <el-table-column prop="productTitle" label="商品" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="qty" label="原数量" width="90" align="center" />
|
|
|
+ <el-table-column label="主单数量" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-model="row.mainQty" :min="0" :max="row.qty" size="small" @change="updateSplitQty(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="新单数量" width="100">
|
|
|
<template #default="{ row }">
|
|
|
- <el-input-number v-model="row.splitQty" :min="0" :max="row.qty" size="small" />
|
|
|
+ <span class="split-qty">{{ row.qty - (row.mainQty || 0) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
<template #footer>
|
|
|
<el-button @click="splitDialog = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="confirmSplit">生成拆单方案</el-button>
|
|
|
+ <el-button type="primary" @click="confirmSplit">确认拆单</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- 合单弹窗 -->
|
|
|
<el-dialog v-model="mergeDialog" title="合单处理" width="480px">
|
|
|
<el-form label-position="top">
|
|
|
- <el-form-item label="目标订单号">
|
|
|
+ <el-form-item label="目标订单号" required>
|
|
|
<el-input v-model="mergeTarget" placeholder="请输入待合并的订单号" />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-alert title="合单要求:两个订单买家信息一致,且均未发货" type="warning" :closable="false" />
|
|
|
+ </el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<el-button @click="mergeDialog = false">取消</el-button>
|
|
|
@@ -139,17 +479,50 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- 分配仓库抽屉 -->
|
|
|
+ <el-dialog v-model="editAddressDialog" title="修改收货地址" width="520px">
|
|
|
+ <el-form :model="editAddress" label-position="top">
|
|
|
+ <el-form-item label="收货人" required>
|
|
|
+ <el-input v-model="editAddress.receiverName" placeholder="请输入收货人姓名" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="联系电话" required>
|
|
|
+ <el-input v-model="editAddress.receiverPhone" placeholder="请输入联系电话" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="国家" required>
|
|
|
+ <el-input v-model="editAddress.receiverCountry" placeholder="请输入国家" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="州/省">
|
|
|
+ <el-input v-model="editAddress.receiverState" placeholder="请输入州/省" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="城市" required>
|
|
|
+ <el-input v-model="editAddress.receiverCity" placeholder="请输入城市" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="邮编">
|
|
|
+ <el-input v-model="editAddress.receiverPostalCode" placeholder="请输入邮编" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="详细地址" required>
|
|
|
+ <el-input v-model="editAddress.receiverAddress" type="textarea" :rows="3" placeholder="请输入详细地址" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="editAddressDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmEditAddress">确认修改</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<el-drawer v-model="assignDrawer" title="分配仓库" size="400px">
|
|
|
<el-form label-position="top">
|
|
|
- <el-form-item label="选择仓库">
|
|
|
+ <el-form-item label="选择仓库" required>
|
|
|
<el-select v-model="order.warehouse" style="width:100%">
|
|
|
- <el-option label="深圳仓" value="深圳仓" />
|
|
|
- <el-option label="义乌仓" value="义乌仓" />
|
|
|
+ <el-option label="深圳南山仓" value="深圳南山仓" />
|
|
|
+ <el-option label="义乌商贸仓" value="义乌商贸仓" />
|
|
|
+ <el-option label="洛杉矶海外仓" value="洛杉矶海外仓" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="库位">
|
|
|
+ <el-input v-model="order.warehouseLocation" placeholder="可选填写库位号" />
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="仓库备注">
|
|
|
- <el-input v-model="warehouseRemark" type="textarea" :rows="4" />
|
|
|
+ <el-input v-model="warehouseRemark" type="textarea" :rows="4" placeholder="可选填写仓库操作备注" />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
@@ -161,107 +534,258 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, reactive, ref } from 'vue';
|
|
|
+import { onMounted, reactive, ref, computed } from 'vue';
|
|
|
import { useRoute } from 'vue-router';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import { api } from '@/api/services';
|
|
|
-import type { OrderItem } from '@/types/page';
|
|
|
+import type { OrderItem, OrderProductItem } from '@/types/page';
|
|
|
|
|
|
const route = useRoute();
|
|
|
const cancelDialog = ref(false);
|
|
|
const splitDialog = ref(false);
|
|
|
const mergeDialog = ref(false);
|
|
|
const assignDrawer = ref(false);
|
|
|
+const editAddressDialog = ref(false);
|
|
|
const cancelReason = ref('');
|
|
|
const cancelRemark = ref('');
|
|
|
const mergeTarget = ref('');
|
|
|
const warehouseRemark = ref('');
|
|
|
|
|
|
-const order = reactive<Partial<OrderItem> & { address?: string; payMethod?: string; payTime?: string; phone?: string; receiver?: string; remark?: string }>({
|
|
|
- id: '', orderNo: '', channel: '', buyer: '', amount: '', paymentStatus: '', orderStatus: '', shippingStatus: '', warehouse: '', exceptionTag: '', createdAt: '',
|
|
|
- receiver: '', phone: '', address: '', payMethod: '', payTime: '', remark: ''
|
|
|
+const canRefund = computed(() => ['paid', 'allocated', 'shipped'].includes(order.orderStatus || ''));
|
|
|
+
|
|
|
+const countryFlags: Record<string, string> = {
|
|
|
+ 'US': '🇺🇸', 'UK': '🇬🇧', 'GB': '🇬🇧', 'JP': '🇯🇵', 'DE': '🇩🇪', 'FR': '🇫🇷',
|
|
|
+ 'CA': '🇨🇦', 'AU': '🇦🇺', 'IT': '🇮🇹', 'ES': '🇪🇸', 'NL': '🇳🇱', 'BR': '🇧🇷',
|
|
|
+ 'MX': '🇲🇽', 'KR': '🇰🇷', 'CN': '🇨🇳', 'HK': '🇭🇰', 'TW': '🇹🇼', 'SG': '🇸🇬'
|
|
|
+};
|
|
|
+
|
|
|
+const getCountryFlag = (country: string): string => countryFlags[country] || '🌍';
|
|
|
+
|
|
|
+const statusColor = computed(() => {
|
|
|
+ const colors: Record<string, string> = {
|
|
|
+ created: 'linear-gradient(135deg, #909399 0%, #b1b3b8 100%)',
|
|
|
+ paid: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)',
|
|
|
+ allocated: 'linear-gradient(135deg, #e6a23c 0%, #ebb563 100%)',
|
|
|
+ shipped: 'linear-gradient(135deg, #67c23a 0%, #85ce61 100%)',
|
|
|
+ delivered: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
|
|
|
+ completed: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)',
|
|
|
+ cancelled: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)',
|
|
|
+ refunded: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)'
|
|
|
+ };
|
|
|
+ return colors[order.orderStatus || ''] || colors.created;
|
|
|
});
|
|
|
|
|
|
-const lineItems = ref<{ sku: string; title: string; qty: number; splitQty: number; unitPrice: string; subtotal: string }[]>([]);
|
|
|
+const order = reactive<OrderItem>({
|
|
|
+ id: '', orderNo: '', channelOrderNo: '', channel: '', orderStatus: '', shippingStatus: '', paymentStatus: '', exceptionTag: '', priority: 'normal',
|
|
|
+ createdAt: '', updatedAt: '', paidAt: '', shippedAt: '', deliveredAt: '',
|
|
|
+ buyerId: '', buyer: '', buyerEmail: '', buyerPhone: '', buyerCountry: '', buyerLevel: '', buyerTags: [], buyerRegisterTime: '', buyerOrderCount: 0, buyerTotalSpent: '',
|
|
|
+ receiverName: '', receiverPhone: '', receiverCountry: '', receiverState: '', receiverCity: '', receiverDistrict: '', receiverPostalCode: '', receiverAddress: '', receiverAddressLang: '', latitude: 0, longitude: 0,
|
|
|
+ transactionId: '', paymentMethod: '', paymentTime: '', currency: 'USD', exchangeRate: 1, orderAmount: '', orderAmountCNY: '', taxAmount: '', shippingFee: '', discountAmount: '', couponCode: '', couponDiscount: '', actualPaid: '', refundAmount: '', refundStatus: '',
|
|
|
+ shippingMethod: '', carrier: '', trackingNo: '', trackingUrl: '', warehouse: '', warehouseLocation: '', estimatedDelivery: '', weight: 0, length: 0, width: 0, height: 0,
|
|
|
+ utmSource: '', utmMedium: '', utmCampaign: '', utmContent: '', utmTerm: '', referrerUrl: '', landingPage: '', ip: '', ipCountry: '', device: '', browser: '', os: '',
|
|
|
+ parentOrderId: '', childOrderIds: [], mergeOrderId: '', relatedOrderId: '', originalOrderId: '',
|
|
|
+ handler: '', handlerGroup: '', orderTags: [], internalRemark: '', buyerRemark: '',
|
|
|
+ itemCount: 0, amount: '', items: [], timeline: [], logs: []
|
|
|
+});
|
|
|
|
|
|
-const timeline = ref<{ time: string; title: string; summary: string; type: string }[]>([]);
|
|
|
+const operationLogs = ref<{ time: string; title: string; type: string; operator?: string; operatorRole?: string }[]>([]);
|
|
|
+const trackingSteps = ref<{ time: string; title: string; detail?: string; type: string }[]>([]);
|
|
|
+
|
|
|
+const editAddress = reactive({ receiverName: '', receiverPhone: '', receiverCountry: '', receiverState: '', receiverCity: '', receiverPostalCode: '', receiverAddress: '' });
|
|
|
|
|
|
const statusLabel = (s: string) => {
|
|
|
const map: Record<string, string> = { created: '待支付', paid: '已支付', allocated: '已分配', shipped: '已发货', delivered: '已签收', completed: '已完成', cancelled: '已取消', refunded: '已退款' };
|
|
|
return map[s] || s;
|
|
|
};
|
|
|
|
|
|
+const buyerLevelType = (level: string) => {
|
|
|
+ const map: Record<string, string> = { 'VIP': 'danger', '黄金': 'warning', '普通': 'info' };
|
|
|
+ return map[level] || 'info';
|
|
|
+};
|
|
|
+
|
|
|
+const refundStatusType = (status: string) => {
|
|
|
+ const map: Record<string, string> = { '无退款': 'info', '部分退款': 'warning', '已全额退款': 'danger' };
|
|
|
+ return map[status] || 'info';
|
|
|
+};
|
|
|
+
|
|
|
+const totalProfit = computed(() => {
|
|
|
+ if (!order.items || order.items.length === 0) return '0.00';
|
|
|
+ return order.items.reduce((sum, item) => sum + (parseFloat(item.profit) || 0), 0).toFixed(2);
|
|
|
+});
|
|
|
+
|
|
|
+const totalProfitRate = computed(() => {
|
|
|
+ if (!order.items || order.items.length === 0) return '0';
|
|
|
+ const totalAmount = order.items.reduce((sum, item) => sum + (parseFloat(item.subtotal) || 0), 0);
|
|
|
+ const totalCost = order.items.reduce((sum, item) => sum + (parseFloat(item.costPrice) * item.qty || 0), 0);
|
|
|
+ if (totalCost === 0) return '0';
|
|
|
+ return ((parseFloat(totalProfit.value) / totalCost) * 100).toFixed(1);
|
|
|
+});
|
|
|
+
|
|
|
const loadData = async () => {
|
|
|
const id = route.query.id as string;
|
|
|
if (!id) return;
|
|
|
- const res = await api.getOrder(id);
|
|
|
- Object.assign(order, res);
|
|
|
-
|
|
|
- // Dynamic mock detail data based on order number
|
|
|
- const details: Record<string, Partial<typeof order>> = {
|
|
|
- 'OMS-20260419-0012': { receiver: 'Olivia Zhang', phone: '+1 213-555-4401', address: '1234 Main St, Apt 5B, Los Angeles, CA 90001, US', payMethod: '信用卡', payTime: '2026-04-19 20:43', remark: '地址楼栋不清晰,需要客服复核后再提交仓库。' },
|
|
|
- 'OMS-20260419-0017': { receiver: 'Noah Smith', phone: '+44 20-7946-0958', address: '45 Kensington High St, London, W8 5NP, UK', payMethod: 'TikTok Pay', payTime: '2026-04-19 21:09', remark: '' },
|
|
|
- 'OMS-20260419-0022': { receiver: 'Liam Chen', phone: '+81 3-1234-5678', address: '東京都渋谷区神宮前1-2-3, 150-0001', payMethod: '信用卡', payTime: '2026-04-19 21:32', remark: '' },
|
|
|
- 'OMS-20260418-0008': { receiver: 'Emma Wilson', phone: '+1 310-555-8899', address: '5678 Ocean Ave, Santa Monica, CA 90401, US', payMethod: 'PayPal', payTime: '-', remark: '' },
|
|
|
- 'OMS-20260418-0015': { receiver: 'Sophie Brown', phone: '+44 7911-123456', address: '12 Baker St, Manchester, M1 1AA, UK', payMethod: 'TikTok Pay', payTime: '2026-04-18 10:46', remark: '' },
|
|
|
- };
|
|
|
- const detail = details[order.orderNo || ''] || { receiver: '-', phone: '-', address: '-', payMethod: '-', payTime: '-', remark: '' };
|
|
|
- Object.assign(order, detail);
|
|
|
-
|
|
|
- // Dynamic line items based on order
|
|
|
- const lineItemsMap: Record<string, typeof lineItems.value> = {
|
|
|
- 'OMS-20260419-0012': [
|
|
|
- { sku: 'SKU-LUGG-20-BLK', title: 'TravelFlex Expandable Carry-On / Black', qty: 1, splitQty: 0, unitPrice: '$89.00', subtotal: '$89.00' },
|
|
|
- { sku: 'SKU-TAG-SET-GRY', title: 'Travel Tag Set / Gray', qty: 2, splitQty: 0, unitPrice: '$19.50', subtotal: '$39.00' }
|
|
|
- ],
|
|
|
- 'OMS-20260419-0017': [
|
|
|
- { sku: 'SKU-BAG-ML-BRW', title: 'Classic Leather Tote / Brown', qty: 1, splitQty: 0, unitPrice: '£65.00', subtotal: '£65.00' }
|
|
|
- ],
|
|
|
- 'OMS-20260419-0022': [
|
|
|
- { sku: 'SKU-SPRT-YGA-BLU', title: 'Yoga Mat Pro / Blue', qty: 1, splitQty: 0, unitPrice: '¥4,200', subtotal: '¥4,200' },
|
|
|
- { sku: 'SKU-SPRT-BTL-GRN', title: 'Sports Bottle 750ml / Green', qty: 3, splitQty: 0, unitPrice: '¥1,500', subtotal: '¥4,500' }
|
|
|
- ],
|
|
|
- 'OMS-20260418-0008': [
|
|
|
- { sku: 'SKU-LUGG-28-NVY', title: 'TravelFlex Large Check-In / Navy', qty: 1, splitQty: 0, unitPrice: '$129.00', subtotal: '$129.00' }
|
|
|
- ],
|
|
|
- 'OMS-20260418-0015': [
|
|
|
- { sku: 'SKU-BAG-BPK-OLV', title: 'Urban Backpack / Olive', qty: 2, splitQty: 0, unitPrice: '£45.00', subtotal: '£90.00' }
|
|
|
- ],
|
|
|
- };
|
|
|
- lineItems.value = lineItemsMap[order.orderNo || ''] || [
|
|
|
- { sku: 'SKU-GEN-001', title: '通用商品 / Default', qty: 1, splitQty: 0, unitPrice: '-', subtotal: '-' }
|
|
|
+
|
|
|
+ Object.assign(order, generateMockOrderDetail(id));
|
|
|
+};
|
|
|
+
|
|
|
+const generateMockOrderDetail = (id: string): Partial<OrderItem> => {
|
|
|
+ const orderNo = `OMS-20260419-0012`;
|
|
|
+ const items: OrderProductItem[] = [
|
|
|
+ {
|
|
|
+ id: 'item-1',
|
|
|
+ productId: 'prod-001',
|
|
|
+ skuId: 'skuid-001',
|
|
|
+ sku: 'SKU-LUGG-20-BLK',
|
|
|
+ productTitle: 'TravelFlex Expandable Carry-On 20寸 行李箱 / Black 黑色',
|
|
|
+ productImage: '',
|
|
|
+ categoryId: 'cat-001',
|
|
|
+ categoryName: '行李箱',
|
|
|
+ specs: [
|
|
|
+ { specName: '尺寸', specValue: '20寸' },
|
|
|
+ { specName: '颜色', specValue: '黑色' }
|
|
|
+ ],
|
|
|
+ barcode: `BC${Date.now()}001`,
|
|
|
+ qty: 1,
|
|
|
+ price: '89.00',
|
|
|
+ costPrice: '45.00',
|
|
|
+ profit: '44.00',
|
|
|
+ profitRate: 49,
|
|
|
+ subtotal: '89.00',
|
|
|
+ weight: 3.5,
|
|
|
+ available: 45,
|
|
|
+ locked: 5,
|
|
|
+ inTransit: 20,
|
|
|
+ reservedQty: 0,
|
|
|
+ shippedQty: 0,
|
|
|
+ returnedQty: 0,
|
|
|
+ giftFlag: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'item-2',
|
|
|
+ productId: 'prod-002',
|
|
|
+ skuId: 'skuid-002',
|
|
|
+ sku: 'SKU-TAG-SET-GRY',
|
|
|
+ productTitle: 'Travel Tag Set 旅行标签牌套装 / Gray 灰色',
|
|
|
+ productImage: '',
|
|
|
+ categoryId: 'cat-002',
|
|
|
+ categoryName: '旅行配件',
|
|
|
+ specs: [
|
|
|
+ { specName: '颜色', specValue: '灰色' }
|
|
|
+ ],
|
|
|
+ barcode: `BC${Date.now()}002`,
|
|
|
+ qty: 2,
|
|
|
+ price: '19.50',
|
|
|
+ costPrice: '8.00',
|
|
|
+ profit: '23.00',
|
|
|
+ profitRate: 59,
|
|
|
+ subtotal: '39.00',
|
|
|
+ weight: 0.2,
|
|
|
+ available: 120,
|
|
|
+ locked: 10,
|
|
|
+ inTransit: 50,
|
|
|
+ reservedQty: 0,
|
|
|
+ shippedQty: 0,
|
|
|
+ returnedQty: 0,
|
|
|
+ giftFlag: false
|
|
|
+ }
|
|
|
];
|
|
|
|
|
|
- // Dynamic timeline based on order
|
|
|
- const timelineMap: Record<string, typeof timeline.value> = {
|
|
|
- 'OMS-20260419-0012': [
|
|
|
- { time: '2026-04-19 21:16', title: '地址校验告警', summary: '地址楼栋信息不完整,标记为"地址需复核"', type: 'warning' },
|
|
|
- { time: '2026-04-19 21:10', title: '库存锁定完成', summary: '系统自动锁定 SKU-LUGG-20-BLK × 1, SKU-TAG-SET-GRY × 2', type: 'success' },
|
|
|
- { time: '2026-04-19 20:43', title: '支付确认', summary: '状态 created → paid,金额 $128.00', type: 'primary' },
|
|
|
- { time: '2026-04-19 20:42', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ],
|
|
|
- 'OMS-20260419-0017': [
|
|
|
- { time: '2026-04-19 21:12', title: '库存锁定完成', summary: '系统自动锁定 SKU-BAG-ML-BRW × 1', type: 'success' },
|
|
|
- { time: '2026-04-19 21:09', title: '支付确认', summary: '状态 created → paid,金额 £65.00', type: 'primary' },
|
|
|
- { time: '2026-04-19 21:08', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ],
|
|
|
- 'OMS-20260419-0022': [
|
|
|
- { time: '2026-04-19 21:35', title: '库存锁定完成', summary: '系统自动锁定 SKU-SPRT-YGA-BLU × 1, SKU-SPRT-BTL-GRN × 3', type: 'success' },
|
|
|
- { time: '2026-04-19 21:32', title: '支付确认', summary: '状态 created → paid,金额 ¥8,700', type: 'primary' },
|
|
|
- { time: '2026-04-19 21:31', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ],
|
|
|
- 'OMS-20260418-0008': [
|
|
|
- { time: '2026-04-18 09:15', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ],
|
|
|
- 'OMS-20260418-0015': [
|
|
|
- { time: '2026-04-18 10:48', title: '库存锁定完成', summary: '系统自动锁定 SKU-BAG-BPK-OLV × 2', type: 'success' },
|
|
|
- { time: '2026-04-18 10:46', title: '支付确认', summary: '状态 created → paid,金额 £90.00', type: 'primary' },
|
|
|
- { time: '2026-04-18 10:45', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ],
|
|
|
+ const amount = items.reduce((sum, item) => sum + parseFloat(item.subtotal), 0);
|
|
|
+
|
|
|
+ return {
|
|
|
+ id,
|
|
|
+ orderNo,
|
|
|
+ channelOrderNo: `CH${Date.now()}`,
|
|
|
+ channel: 'Shopify US',
|
|
|
+ orderStatus: 'paid',
|
|
|
+ shippingStatus: 'unshipped',
|
|
|
+ paymentStatus: 'paid',
|
|
|
+ exceptionTag: '正常',
|
|
|
+ priority: 'normal',
|
|
|
+ createdAt: '2026-04-19 20:42:00',
|
|
|
+ updatedAt: '2026-04-19 21:16:00',
|
|
|
+ paidAt: '2026-04-19 20:43:00',
|
|
|
+ shippedAt: '',
|
|
|
+ deliveredAt: '',
|
|
|
+ buyerId: 'buyer-1001',
|
|
|
+ buyer: 'Olivia Zhang',
|
|
|
+ buyerEmail: 'olivia.zhang@example.com',
|
|
|
+ buyerPhone: '+1 213-555-4401',
|
|
|
+ buyerCountry: 'US',
|
|
|
+ buyerLevel: 'VIP',
|
|
|
+ buyerTags: ['高价值'],
|
|
|
+ buyerRegisterTime: '2025-06-15',
|
|
|
+ buyerOrderCount: 12,
|
|
|
+ buyerTotalSpent: '$1,234.56',
|
|
|
+ receiverName: 'Olivia Zhang',
|
|
|
+ receiverPhone: '+1 213-555-4401',
|
|
|
+ receiverCountry: 'US',
|
|
|
+ receiverState: 'California',
|
|
|
+ receiverCity: 'Los Angeles',
|
|
|
+ receiverDistrict: 'Downtown',
|
|
|
+ receiverPostalCode: '90001',
|
|
|
+ receiverAddress: '1234 Main St, Apt 5B, Los Angeles, CA 90001, United States',
|
|
|
+ receiverAddressLang: '',
|
|
|
+ latitude: 34.0522,
|
|
|
+ longitude: -118.2437,
|
|
|
+ transactionId: `TXN${Date.now()}`,
|
|
|
+ paymentMethod: 'Visa (****4242)',
|
|
|
+ paymentTime: '2026-04-19 20:43:00',
|
|
|
+ currency: 'USD',
|
|
|
+ exchangeRate: 1,
|
|
|
+ orderAmount: amount.toFixed(2),
|
|
|
+ orderAmountCNY: (amount * 7.2).toFixed(2),
|
|
|
+ taxAmount: (amount * 0.08).toFixed(2),
|
|
|
+ shippingFee: '0.00',
|
|
|
+ discountAmount: '5.00',
|
|
|
+ couponCode: 'SAVE10',
|
|
|
+ couponDiscount: '5.00',
|
|
|
+ actualPaid: (amount - 5).toFixed(2),
|
|
|
+ refundAmount: '0.00',
|
|
|
+ refundStatus: '无退款',
|
|
|
+ shippingMethod: '标快',
|
|
|
+ carrier: '顺丰速运',
|
|
|
+ trackingNo: '',
|
|
|
+ trackingUrl: '',
|
|
|
+ warehouse: '深圳南山仓',
|
|
|
+ warehouseLocation: 'A-12-C',
|
|
|
+ estimatedDelivery: '2026-04-25',
|
|
|
+ weight: 3.7,
|
|
|
+ length: 55,
|
|
|
+ width: 35,
|
|
|
+ height: 25,
|
|
|
+ utmSource: 'google',
|
|
|
+ utmMedium: 'cpc',
|
|
|
+ utmCampaign: 'Spring Sale 2026',
|
|
|
+ utmContent: 'Product luggage',
|
|
|
+ utmTerm: 'carry on luggage',
|
|
|
+ referrerUrl: 'https://www.google.com/search?q=carry+on+luggage',
|
|
|
+ landingPage: '/product/luggage-001',
|
|
|
+ ip: '192.168.1.100',
|
|
|
+ ipCountry: 'US',
|
|
|
+ device: 'iPhone',
|
|
|
+ browser: 'Safari',
|
|
|
+ os: 'iOS',
|
|
|
+ parentOrderId: '',
|
|
|
+ childOrderIds: [],
|
|
|
+ mergeOrderId: '',
|
|
|
+ relatedOrderId: '',
|
|
|
+ originalOrderId: '',
|
|
|
+ handler: '陈欣',
|
|
|
+ handlerGroup: '运营组A',
|
|
|
+ orderTags: ['VIP客户'],
|
|
|
+ internalRemark: '地址楼栋不清晰,需要客服复核后再提交仓库',
|
|
|
+ buyerRemark: 'Please deliver to door',
|
|
|
+ itemCount: items.reduce((sum, item) => sum + item.qty, 0),
|
|
|
+ amount: amount.toFixed(2),
|
|
|
+ items,
|
|
|
+ timeline: [],
|
|
|
+ logs: []
|
|
|
};
|
|
|
- timeline.value = timelineMap[order.orderNo || ''] || [
|
|
|
- { time: order.createdAt || '-', title: '订单接入 OMS', summary: `来自 ${order.channel} 的订单推送`, type: 'primary' }
|
|
|
- ];
|
|
|
+};
|
|
|
+
|
|
|
+const updateSplitQty = (row: OrderProductItem) => {
|
|
|
+ row.mainQty = row.mainQty || 0;
|
|
|
};
|
|
|
|
|
|
const lockInventory = () => { ElMessage.success('库存已锁定'); };
|
|
|
@@ -271,16 +795,16 @@ const confirmCancel = async () => {
|
|
|
await ElMessageBox.confirm('确认取消此订单?操作不可撤销。', '二次确认', { type: 'warning' });
|
|
|
await api.updateOrder(order.id!, { orderStatus: 'cancelled' } as Partial<OrderItem>);
|
|
|
order.orderStatus = 'cancelled';
|
|
|
- timeline.value.unshift({ time: '刚刚', title: '订单已取消', summary: cancelReason.value, type: 'danger' });
|
|
|
+ operationLogs.value.unshift({ time: '刚刚', title: '订单已取消', summary: cancelReason.value, type: 'danger', operator: '客服', operatorRole: '客服组' });
|
|
|
cancelDialog.value = false;
|
|
|
ElMessage.success('订单已取消');
|
|
|
};
|
|
|
|
|
|
const confirmSplit = () => {
|
|
|
- const hasSplit = lineItems.value.some(i => i.splitQty > 0);
|
|
|
- if (!hasSplit) { ElMessage.warning('请至少为一个 SKU 设置拆单数量'); return; }
|
|
|
+ const hasSplit = order.items?.some(i => (i.mainQty || 0) < i.qty);
|
|
|
+ if (!hasSplit) { ElMessage.warning('请至少设置一个 SKU 的主单数量小于原数量'); return; }
|
|
|
splitDialog.value = false;
|
|
|
- timeline.value.unshift({ time: '刚刚', title: '拆单方案已生成', summary: '待确认后生成新发货单', type: 'primary' });
|
|
|
+ operationLogs.value.unshift({ time: '刚刚', title: '拆单方案已生成', summary: '待仓库确认后生成新发货单', type: 'primary', operator: '客服', operatorRole: '客服组' });
|
|
|
ElMessage.success('拆单方案已生成');
|
|
|
};
|
|
|
|
|
|
@@ -291,11 +815,208 @@ const confirmMerge = () => {
|
|
|
};
|
|
|
|
|
|
const submitWarehouse = async () => {
|
|
|
- await api.updateOrder(order.id!, { warehouse: order.warehouse } as Partial<OrderItem>);
|
|
|
+ await api.updateOrder(order.id!, {
|
|
|
+ warehouse: order.warehouse,
|
|
|
+ warehouseLocation: order.warehouseLocation,
|
|
|
+ orderStatus: 'allocated'
|
|
|
+ } as Partial<OrderItem>);
|
|
|
+
|
|
|
+ for (const item of order.items || []) {
|
|
|
+ if (item.skuId) {
|
|
|
+ const inventoryRes = await api.getInventory();
|
|
|
+ const invItem = inventoryRes.items.find((inv: any) => inv.skuId === item.skuId);
|
|
|
+ if (invItem) {
|
|
|
+ const newAvailable = Math.max(0, (invItem.available || 0) - item.qty);
|
|
|
+ const newLocked = (invItem.locked || 0) + item.qty;
|
|
|
+ await api.updateInventory(invItem.id, {
|
|
|
+ locked: newLocked,
|
|
|
+ available: newAvailable
|
|
|
+ });
|
|
|
+ await api.createInventoryLog({
|
|
|
+ source: '订单分配锁定',
|
|
|
+ relatedOrder: order.orderNo,
|
|
|
+ operator: '客服',
|
|
|
+ quantity: -item.qty,
|
|
|
+ afterQty: newAvailable,
|
|
|
+ time: new Date().toISOString()
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ order.orderStatus = 'allocated';
|
|
|
assignDrawer.value = false;
|
|
|
- timeline.value.unshift({ time: '刚刚', title: '已提交仓库', summary: `分配至 ${order.warehouse}`, type: 'success' });
|
|
|
- ElMessage.success('已提交仓库');
|
|
|
+ operationLogs.value.unshift({ time: '刚刚', title: '已提交仓库', summary: `分配至 ${order.warehouse} (${order.warehouseLocation}),库存已锁定`, type: 'success', operator: '客服', operatorRole: '客服组' });
|
|
|
+ ElMessage.success(`已提交仓库,订单状态已更新为已分配,${order.items?.length || 0} 个 SKU 库存已锁定`);
|
|
|
+};
|
|
|
+
|
|
|
+const confirmEditAddress = () => {
|
|
|
+ if (!editAddress.receiverName || !editAddress.receiverPhone || !editAddress.receiverAddress) {
|
|
|
+ ElMessage.warning('请填写完整地址信息');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Object.assign(order, {
|
|
|
+ receiverName: editAddress.receiverName,
|
|
|
+ receiverPhone: editAddress.receiverPhone,
|
|
|
+ receiverCountry: editAddress.receiverCountry,
|
|
|
+ receiverState: editAddress.receiverState,
|
|
|
+ receiverCity: editAddress.receiverCity,
|
|
|
+ receiverPostalCode: editAddress.receiverPostalCode,
|
|
|
+ receiverAddress: editAddress.receiverAddress
|
|
|
+ });
|
|
|
+ editAddressDialog.value = false;
|
|
|
+ operationLogs.value.unshift({ time: '刚刚', title: '收货地址已修改', summary: `${editAddress.receiverName}, ${editAddress.receiverCity}`, type: 'warning', operator: '客服', operatorRole: '客服组' });
|
|
|
+ ElMessage.success('地址已修改');
|
|
|
+};
|
|
|
+
|
|
|
+const saveRemark = () => {
|
|
|
+ ElMessage.success('备注已保存');
|
|
|
+};
|
|
|
+
|
|
|
+const handlePrint = () => { ElMessage.success(`正在生成 ${order.orderNo} 的面单...`); };
|
|
|
+const handlePrintInvoice = () => { ElMessage.success(`正在生成 ${order.orderNo} 的发货单...`); };
|
|
|
+const handleContact = () => { ElMessage.info(`正在打开与 ${order.buyer} 的对话窗口...`); };
|
|
|
+const handleRefund = () => { ElMessage.success('正在发起退款流程...'); };
|
|
|
+const handleCall = () => { ElMessage.info(`正在拨打 ${order.receiverPhone}...`); };
|
|
|
+const copyTrackingNo = () => {
|
|
|
+ if (order.trackingNo) {
|
|
|
+ navigator.clipboard.writeText(order.trackingNo);
|
|
|
+ ElMessage.success('复制成功');
|
|
|
+ }
|
|
|
};
|
|
|
+const openTrackingUrl = () => { ElMessage.info('正在打开物流追踪页面...'); };
|
|
|
+const trackMore = () => { ElMessage.info('正在加载完整物流信息...'); };
|
|
|
|
|
|
-onMounted(loadData);
|
|
|
+const handleActionCommand = (command: string) => {
|
|
|
+ switch (command) {
|
|
|
+ case 'assign': assignDrawer.value = true; break;
|
|
|
+ case 'lock': lockInventory(); break;
|
|
|
+ case 'split': splitDialog.value = true; break;
|
|
|
+ case 'merge': mergeDialog.value = true; break;
|
|
|
+ case 'address': editAddressDialog.value = true; break;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handlePrintCommand = (command: string) => {
|
|
|
+ if (command === 'label') {
|
|
|
+ handlePrint();
|
|
|
+ } else if (command === 'invoice') {
|
|
|
+ handlePrintInvoice();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleRefundCommand = (command: string) => {
|
|
|
+ if (command === 'refund') {
|
|
|
+ handleRefund();
|
|
|
+ } else if (command === 'viewRefund') {
|
|
|
+ ElMessage.info('正在打开退款记录...');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadData();
|
|
|
+ operationLogs.value = [
|
|
|
+ { time: '2026-04-19 21:16', title: '地址校验告警', type: 'warning', operator: '系统', operatorRole: '自动' },
|
|
|
+ { time: '2026-04-19 21:10', title: '库存锁定完成', type: 'success', operator: '系统', operatorRole: '自动' },
|
|
|
+ { time: '2026-04-19 20:43', title: '支付确认', type: 'primary', operator: '支付网关', operatorRole: '自动' },
|
|
|
+ { time: '2026-04-19 20:42', title: '订单接入 OMS', type: 'primary', operator: '渠道同步', operatorRole: '自动' }
|
|
|
+ ];
|
|
|
+});
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.order-detail-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+.header-left { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
|
|
|
+.header-left h2 { margin: 0; font-size: 22px; display: flex; align-items: center; gap: 12px; }
|
|
|
+.order-no { color: var(--cb-primary); font-size: 18px; }
|
|
|
+.header-actions { display: flex; gap: 10px; }
|
|
|
+
|
|
|
+.stat-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; }
|
|
|
+.stat-card { display: flex; align-items: center; gap: 16px; padding: 18px 16px; background: #fafafa; border-radius: 12px; transition: all 0.2s; }
|
|
|
+.stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.06); }
|
|
|
+.stat-card__icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: #fff; flex-shrink: 0; }
|
|
|
+.stat-card__content { flex: 1; min-width: 0; }
|
|
|
+.stat-card__label { font-size: 13px; color: #888; margin-bottom: 4px; }
|
|
|
+.stat-card__value { font-size: 17px; font-weight: 600; color: #333; }
|
|
|
+
|
|
|
+.detail-grid { display: grid; grid-template-columns: 1fr 360px; gap: 20px; margin-top: 20px; }
|
|
|
+.detail-left, .detail-right { display: flex; flex-direction: column; gap: 20px; }
|
|
|
+
|
|
|
+.glass-card { padding: 20px; }
|
|
|
+.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #f5f5f5; }
|
|
|
+.section-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #333; }
|
|
|
+.value-bold { font-weight: 600; }
|
|
|
+.address-text { line-height: 1.7; color: #555; }
|
|
|
+
|
|
|
+.tracking-info__main { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; padding: 12px 16px; background: #f8f9fa; border-radius: 8px; }
|
|
|
+.tracking-no { font-family: 'Monaco', 'Menlo', monospace; font-size: 15px; font-weight: 600; letter-spacing: 0.5px; }
|
|
|
+.tracking-meta { display: flex; gap: 20px; font-size: 14px; color: #666; margin-bottom: 20px; }
|
|
|
+.track-timeline { padding: 0 8px; }
|
|
|
+.track-title { margin: 0; font-weight: 600; }
|
|
|
+.track-detail { margin: 6px 0 0; color: #999; font-size: 13px; }
|
|
|
+.tracking-empty { text-align: center; padding: 32px 0; }
|
|
|
+.action-menu { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
|
|
+.action-menu .el-dropdown { display: flex; }
|
|
|
+.action-menu .el-button { display: flex; align-items: center; gap: 6px; }
|
|
|
+
|
|
|
+:deep(.el-dropdown-menu__item) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px 16px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+:deep(.el-dropdown-menu__item .el-icon) {
|
|
|
+ font-size: 16px;
|
|
|
+}
|
|
|
+.log-title { margin: 0; font-weight: 600; }
|
|
|
+.log-detail { margin: 6px 0 0; color: #999; font-size: 13px; }
|
|
|
+
|
|
|
+.product-info { display: flex; gap: 14px; align-items: flex-start; }
|
|
|
+.product-image-placeholder { width: 56px; height: 56px; background: #f5f5f5; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #999; flex-shrink: 0; }
|
|
|
+.product-detail { display: flex; flex-direction: column; gap: 5px; }
|
|
|
+.product-title { font-weight: 600; font-size: 14px; line-height: 1.4; }
|
|
|
+.product-category { font-size: 12px; color: #999; }
|
|
|
+.sku-code, .barcode { font-family: 'Monaco', 'Menlo', monospace; font-size: 12px; }
|
|
|
+.specs-list { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
|
+.qty { font-weight: 700; font-size: 17px; }
|
|
|
+.qty-detail { display: flex; gap: 10px; font-size: 11px; color: #999; margin-top: 4px; }
|
|
|
+.cost-price { color: #999; }
|
|
|
+.profit { color: #67c23a; font-weight: 600; }
|
|
|
+.profit-rate { font-size: 11px; color: #67c23a; }
|
|
|
+.subtotal { font-weight: 700; color: var(--cb-accent); }
|
|
|
+.inventory-info { display: flex; flex-direction: column; gap: 3px; font-size: 12px; }
|
|
|
+.inv-available { color: #67c23a; }
|
|
|
+.inv-locked { color: #e6a23c; }
|
|
|
+.inv-transit { color: #409eff; }
|
|
|
+
|
|
|
+.amount-summary { margin-top: 20px; padding: 20px; background: #fafafa; border-radius: 10px; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
|
|
|
+.summary-row { display: flex; gap: 48px; font-size: 14px; width: 100%; justify-content: flex-end; }
|
|
|
+.summary-row span:first-child { color: #666; min-width: 90px; text-align: right; }
|
|
|
+.summary-row span:last-child { min-width: 110px; text-align: right; }
|
|
|
+.summary-row--total { font-size: 17px; font-weight: 600; padding-top: 12px; border-top: 1px solid #eee; }
|
|
|
+.total-amount { color: var(--cb-accent); font-size: 20px; }
|
|
|
+.summary-row--profit { color: #67c23a; }
|
|
|
+.profit-amount { font-weight: 600; }
|
|
|
+.profit-rate { color: #67c23a; }
|
|
|
+.discount { color: #67c23a; }
|
|
|
+
|
|
|
+.glass-card.section-card { padding: 24px; }
|
|
|
+
|
|
|
+.header-section { padding: 24px; }
|
|
|
+.header-top { display: flex; gap: 10px; margin-bottom: 12px; }
|
|
|
+.header-tags { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
|
+.buyer-remark { color: #888; font-style: italic; }
|
|
|
+.discount-tag { color: #67c23a; font-weight: 500; }
|
|
|
+.actual-paid { color: var(--cb-accent); font-size: 16px; }
|
|
|
+
|
|
|
+.products-section { margin-top: 20px; }
|
|
|
+.package-section { margin-top: 20px; }
|
|
|
+.save-btn { margin-top: 12px; }
|
|
|
+</style>
|