415 lines
14 KiB
Plaintext
415 lines
14 KiB
Plaintext
<template>
|
||
<view v-if="visible" class="drawer-mask" @click="close">
|
||
<view class="drawer-container" @click.stop>
|
||
<view class="drawer-header">
|
||
<view class="header-left">
|
||
<text class="title">订单详情</text>
|
||
</view>
|
||
<view class="close-btn" @click="close">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="drawer-body" scroll-y>
|
||
<!-- 订单概况 KPIS -->
|
||
<view class="order-summary-card">
|
||
<view class="order-type-icon">
|
||
<image src="/static/icons/order_blue.png" mode="aspectFit" class="type-icon" />
|
||
</view>
|
||
<view class="summary-info">
|
||
<view class="top-row">
|
||
<text class="order-type-text">{{ orderInfo['typeName'] || '普通订单' }}</text>
|
||
<text class="order-sn-text">订单号:{{ orderInfo['sn'] }}</text>
|
||
<text class="shop-tag" v-if="orderInfo['store_name'] != '--'">{{ orderInfo['store_name'] }}</text>
|
||
</view>
|
||
<view class="bottom-grids">
|
||
<view class="summary-grid">
|
||
<text class="label">订单状态</text>
|
||
<text class="value status-val">{{ orderInfo['statusName'] }}</text>
|
||
</view>
|
||
<view class="summary-grid">
|
||
<text class="label">总金额</text>
|
||
<text class="value price-val">¥ {{ orderInfo['actualPrice'] }}</text>
|
||
</view>
|
||
<view class="summary-grid">
|
||
<text class="label">已支付</text>
|
||
<text class="value status-val">¥ {{ orderInfo['paidAmount'] }}</text>
|
||
</view>
|
||
<view class="summary-grid">
|
||
<text class="label">支付方式</text>
|
||
<text class="value">{{ orderInfo['payMethod'] }}</text>
|
||
</view>
|
||
<view class="summary-grid">
|
||
<text class="label">配送人员</text>
|
||
<text class="value">{{ orderInfo['delivery_name'] }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Tabs -->
|
||
<view class="drawer-tabs">
|
||
<view v-for="(tab, index) in tabs" :key="index" class="tab-item" :class="{ active: activeTab === index }" @click="activeTab = index">
|
||
<text class="tab-text">{{ tab }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Tab Content -->
|
||
<view class="tab-content">
|
||
<!-- 订单信息 -->
|
||
<view v-if="activeTab === 0" class="info-section">
|
||
<view class="section-block">
|
||
<view class="section-title">
|
||
<view class="blue-bar"></view>
|
||
<text>用户信息</text>
|
||
</view>
|
||
<view class="info-grid">
|
||
<view class="info-item">
|
||
<text class="label">用户名称:</text>
|
||
<text class="value">{{ orderInfo['user']['name'] }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">绑定电话:</text>
|
||
<text class="value">{{ orderInfo['user']['phone'] }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-block">
|
||
<view class="section-title">
|
||
<view class="blue-bar"></view>
|
||
<text>收货信息</text>
|
||
</view>
|
||
<view class="info-grid">
|
||
<view class="info-item">
|
||
<text class="label">收货人:</text>
|
||
<text class="value">{{ orderInfo['real_name'] || orderInfo['user']['name'] }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">收货电话:</text>
|
||
<text class="value">{{ orderInfo['user_phone'] || orderInfo['user']['phone'] }}</text>
|
||
</view>
|
||
<view class="info-item full">
|
||
<text class="label">收货地址:</text>
|
||
<text class="value">{{ orderInfo['user_address'] || '暂无地址信息' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-block">
|
||
<view class="section-title">
|
||
<view class="blue-bar"></view>
|
||
<text>订单信息</text>
|
||
</view>
|
||
<view class="info-grid">
|
||
<view class="info-item">
|
||
<text class="label">创建时间:</text>
|
||
<text class="value">{{ orderInfo['created_at'] || '--' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">商品总数:</text>
|
||
<text class="value">{{ orderInfo['total_num'] || '0' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">产品金额:</text>
|
||
<text class="value">¥ {{ orderInfo['total_price'] || '0.00' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">运费:</text>
|
||
<text class="value">¥ {{ orderInfo['shipping_fee'] || '0.00' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">折扣金额:</text>
|
||
<text class="value">- ¥ {{ orderInfo['coupon_price'] || '0.00' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">总金额:</text>
|
||
<text class="value">¥ {{ orderInfo['actualPrice'] }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">支付金额:</text>
|
||
<text class="value price-red">¥ {{ orderInfo['paidAmount'] }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-block">
|
||
<view class="section-title">
|
||
<view class="blue-bar"></view>
|
||
<text>买家留言</text>
|
||
</view>
|
||
<view class="info-grid">
|
||
<view class="info-item full">
|
||
<text class="value">{{ orderInfo['mark'] || '-' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-block">
|
||
<view class="section-title">
|
||
<view class="blue-bar"></view>
|
||
<text>订单备注</text>
|
||
</view>
|
||
<view class="info-grid">
|
||
<view class="info-item full">
|
||
<text class="value">{{ orderInfo['remark'] || '-' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品信息 -->
|
||
<view v-if="activeTab === 1" class="info-section">
|
||
<view class="product-table">
|
||
<view class="product-thead">
|
||
<text class="p-th p-info">商品信息</text>
|
||
<text class="p-th p-sku">规格</text>
|
||
<text class="p-th p-price">单价</text>
|
||
<text class="p-th p-num">数量</text>
|
||
<text class="p-th p-total">小计</text>
|
||
</view>
|
||
<view class="product-tbody">
|
||
<view v-for="(p, pi) in productItems" :key="pi" class="p-tr">
|
||
<view class="p-td p-info">
|
||
<image :src="p['image'] || '/static/logo.png'" mode="aspectFill" class="p-img" />
|
||
<text class="p-name">{{ p['name'] }}</text>
|
||
</view>
|
||
<view class="p-td p-sku">
|
||
<text class="p-sku-txt">{{ p['sku_info'] || '-' }}</text>
|
||
</view>
|
||
<view class="p-td p-price">¥{{ p['price'] }}</view>
|
||
<view class="p-td p-num">{{ p['quantity'] }}</view>
|
||
<view class="p-td p-total">¥{{ (parseFloat(p['price'] as string) * parseInt(p['quantity'] as string)).toFixed(2) }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 订单记录 -->
|
||
<view v-if="activeTab === 2" class="info-section">
|
||
<view class="timeline">
|
||
<view v-for="(log, li) in logs" :key="li" class="timeline-item">
|
||
<view class="dot"></view>
|
||
<view class="line" v-if="li !== logs.length - 1"></view>
|
||
<view class="log-content">
|
||
<text class="log-title">{{ log.title }}</text>
|
||
<text class="log-time">{{ log.time }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed, watch } from 'vue'
|
||
|
||
const props = defineProps({
|
||
visible: { type: Boolean, default: false },
|
||
orderInfo: { type: Object, default: () : UTSJSONObject => ({}) as UTSJSONObject }
|
||
})
|
||
|
||
const emit = defineEmits(['update:visible'])
|
||
|
||
const activeTab = ref(0)
|
||
const tabs = ['订单信息', '商品信息', '订单记录']
|
||
|
||
const productItems = computed<UTSJSONObject[]>(() => {
|
||
return (props.orderInfo['items'] || []) as UTSJSONObject[]
|
||
})
|
||
|
||
const logs = ref([
|
||
{ title: '订单生成', time: '2026-02-27 15:47:25' },
|
||
{ title: '支付成功', time: '2026-02-27 15:48:30' }
|
||
])
|
||
|
||
const close = () => {
|
||
emit('update:visible', false)
|
||
}
|
||
|
||
watch(() => props.visible, (newVal) => {
|
||
if (newVal) {
|
||
activeTab.value = 0
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.drawer-mask {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background-color: rgba(0,0,0,0.5);
|
||
z-index: 2000;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.drawer-container {
|
||
width: 800px;
|
||
max-width: 90%;
|
||
height: 100vh;
|
||
background-color: #fff;
|
||
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.drawer-header {
|
||
height: 56px;
|
||
padding: 0 24px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.title { font-size: 16px; font-weight: 600; color: #333; }
|
||
.close-btn {
|
||
width: 32px; height: 32px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
.close-icon { font-size: 24px; color: #999; line-height: 1; }
|
||
|
||
.drawer-body {
|
||
flex: 1;
|
||
background-color: #f5f7f9;
|
||
}
|
||
|
||
.order-summary-card {
|
||
background-color: #fff;
|
||
padding: 24px;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 16px;
|
||
}
|
||
|
||
.type-icon { width: 48px; height: 48px; }
|
||
|
||
.summary-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.top-row {
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.order-type-text { font-size: 16px; font-weight: 600; color: #333; }
|
||
.order-sn-text { font-size: 14px; color: #666; }
|
||
.shop-tag {
|
||
font-size: 12px; color: #1890ff; background: #e6f7ff;
|
||
border: 1px solid #91d5ff; padding: 2px 8px; border-radius: 2px;
|
||
}
|
||
|
||
.bottom-grids {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 40px;
|
||
}
|
||
|
||
.summary-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
.label { font-size: 12px; color: #999; }
|
||
.value { font-size: 14px; color: #333; }
|
||
.status-val { font-weight: 600; }
|
||
.price-val { color: #f5222d; font-weight: 600; }
|
||
}
|
||
|
||
.drawer-tabs {
|
||
background-color: #fff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
padding: 0 24px;
|
||
}
|
||
|
||
.tab-item {
|
||
padding: 12px 20px;
|
||
margin-right: 12px;
|
||
position: relative;
|
||
cursor: pointer;
|
||
.tab-text { font-size: 14px; color: #595959; }
|
||
&.active {
|
||
.tab-text { color: #1890ff; font-weight: 500; }
|
||
&::after {
|
||
content: '';
|
||
position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: #1890ff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 16px;
|
||
}
|
||
|
||
.section-block {
|
||
background-color: #fff;
|
||
padding: 24px;
|
||
margin-bottom: 16px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.section-title {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 20px;
|
||
text { font-size: 14px; font-weight: 600; color: #333; }
|
||
}
|
||
|
||
.blue-bar { width: 3px; height: 14px; background-color: #1890ff; }
|
||
|
||
.info-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
gap: y;
|
||
}
|
||
|
||
.info-item {
|
||
width: 33.33%;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
.label { font-size: 13px; color: #666; width: 80px; flex-shrink: 0; }
|
||
.value { font-size: 13px; color: #333; line-height: 1.4; word-break: break-all; }
|
||
.price-red { color: #f5222d; font-weight: 600; }
|
||
&.full { width: 100%; }
|
||
}
|
||
|
||
/* 商品表格 */
|
||
.product-table { padding: 8px; background-color: #fff; }
|
||
.product-thead { display: flex; flex-direction: row; background-color: #fafafa; border-bottom: 1px solid #f0f0f0; }
|
||
.p-th { padding: 12px 8px; font-size: 13px; font-weight: 500; color: #333; text-align: left; }
|
||
.p-tr { display: flex; flex-direction: row; border-bottom: 1px solid #f0f0f0; }
|
||
.p-td { padding: 12px 8px; font-size: 13px; color: #595959; display: flex; align-items: center; }
|
||
|
||
.p-info { flex: 1; display: flex; flex-direction: row; align-items: center; gap: 8px; }
|
||
.p-img { width: 40px; height: 40px; border-radius: 4px; }
|
||
.p-sku { width: 120px; }
|
||
.p-price { width: 100px; }
|
||
.p-num { width: 80px; }
|
||
.p-total { width: 100px; }
|
||
|
||
/* 记录 */
|
||
.timeline { padding: 24px; background-color: #fff; }
|
||
.timeline-item { position: relative; padding-left: 24px; padding-bottom: 24px; }
|
||
.dot { position: absolute; left: 0; top: 4px; width: 10px; height: 10px; border-radius: 5px; background-color: #1890ff; z-index: 2; }
|
||
.line { position: absolute; left: 4.5px; top: 14px; bottom: -4px; width: 1px; background-color: #e8e8e8; }
|
||
.log-content { display: flex; flex-direction: column; gap: 4px; }
|
||
.log-title { font-size: 14px; color: #333; }
|
||
.log-time { font-size: 12px; color: #999; }
|
||
</style>
|