Files
medical-mall/pages/mall/admin/order/order-management/components/OrderDetailDrawer.uvue
2026-02-27 19:42:30 +08:00

415 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>