Files
xk-doctor-wx/subPackages/sub_clinic_admin/order/detail.vue
李琦 27202c0740 1. 优化了一些交互,中药药品编辑交互
2. dev和生产环境统一鉴别
2026-05-30 13:53:14 +08:00

558 lines
15 KiB
Vue
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>
<clinic-page-layout title="订单详情" :show-back="true">
<view class="detail-container">
<!-- 头部面板通栏展示金额与状态 -->
<view v-if="info.id" class="detail-panel header-panel">
<view class="order-head">
<text class="order-no-main">订单号{{ info.order_no }}</text>
<text class="pay-total">{{ money(info.total_pay_price) }}</text>
</view>
<view class="tags">
<text class="modern-tag tag-status">{{ detailExtra.statusText }}</text>
<text class="modern-tag tag-type">{{ detailExtra.prescriptionTypeText }}</text>
<text class="modern-tag tag-delivery">{{ detailExtra.deliveryText }}</text>
</view>
<view class="sub-block">
<view class="sub-block-title">基本信息</view>
<view class="row"><text>订单类型</text><text>{{ detailExtra.orderTypeText }}</text></view>
<view class="row"><text>患者</text><text>{{ info.patient || '-' }}</text></view>
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
<view class="row"><text>ERP状态</text><text>{{ erpSyncText(info.is_sync_erp) }}</text></view>
<view class="row"><text>支付时间</text><text>{{ info.pay_time || '-' }}</text></view>
<view class="row"><text>下单时间</text><text>{{ info.created_at || '-' }}</text></view>
<view class="row" v-if="detailExtra.cancelRemark"><text>发货备注</text><text>{{ detailExtra.cancelRemark }}</text></view>
</view>
<view class="sub-block">
<view class="sub-block-title">费用明细</view>
<view class="row"><text>药品总价</text><text>{{ money(info.items_price) }}</text></view>
<view class="row"><text>供货价</text><text>{{ detailExtra.marketPrice }}</text></view>
<view class="row"><text>运费</text><text>{{ money(info.trans_expenses) }}</text></view>
<view class="row"><text>挂号费</text><text>{{ money(info.register_price) }}</text></view>
<view class="row"><text>是否包邮</text><text>{{ detailExtra.freeShippingText }}</text></view>
</view>
</view>
<!-- 配送信息面板 -->
<view v-if="info.id" class="detail-panel">
<view class="section-title">{{ receiverBlockTitle }}</view>
<view class="row"><text>{{ receiverLabel }}</text><text>{{ receiverName }}</text></view>
<view class="row"><text>联系电话</text><text>{{ receiverMobile }}</text></view>
<view class="row" v-if="info.delivery_method === 0">
<text>配送地址</text><text class="addr">{{ deliveryAddress }}</text>
</view>
<view class="row" v-else>
<text>自提地址</text><text class="addr">{{ pickupAddress }}</text>
</view>
</view>
<!-- 商品列表面板 -->
<view v-if="productItems.length" class="detail-panel">
<view class="section-title">订单商品</view>
<!-- 如果是中药高密度网格排列一行多个小且精致 -->
<view v-if="isTcmOrder" class="tcm-grid-wrap">
<view class="tcm-grid">
<view v-for="(p, idx) in productDisplayList" :key="idx" class="herb-box">
<text class="herb-name">{{ p.drugName }}</text>
<text class="herb-qty">{{ p.qty }}</text>
</view>
</view>
</view>
<!-- 非中药标准西药图文列表 -->
<view v-else>
<view
v-for="(p, idx) in productDisplayList"
:key="idx"
class="product-item"
:class="p.isTcm ? 'product-tcm' : 'product-west'"
>
<image
v-if="!p.isTcm"
class="product-img"
:src="p.imageUrl"
mode="aspectFill"
/>
<view class="product-body">
<view class="product-name">{{ p.drugName }}</view>
<view class="product-meta">
<text v-if="p.drugNumber">编号{{ p.drugNumber }}</text>
<text>数量{{ p.qty }}</text>
</view>
<view class="product-line" v-if="!p.isTcm">规格{{ p.specification }}</view>
<view class="product-line" v-if="!p.isTcm">厂家{{ p.manufacturer }}</view>
<view class="product-price-bar">
<text class="price-main">单价{{ p.price }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 处方信息内页入口 (保留作为备用) -->
<view
v-if="detailExtra.canViewPrescription"
class="detail-panel card-link"
hover-class="panel-hover"
@click="openPrescription"
>
<view class="section-head">
<text class="section-title">处方信息</text>
<text class="section-arrow">查看完整处方 </text>
</view>
<template v-if="detailExtra.showPrescription">
<view class="row"><text>处方编号</text><text>{{ detailExtra.showPrescription.prescriptionNo }}</text></view>
<view class="row"><text>诊断</text><text>{{ detailExtra.showPrescription.diagnose }}</text></view>
<view class="row"><text>医嘱</text><text>{{ detailExtra.showPrescription.doctorOrder }}</text></view>
<view class="row"><text>开药医生</text><text>{{ detailExtra.showPrescription.doctorName }}</text></view>
</template>
<view v-else class="prescription-hint">点击加载完整处方内容</view>
</view>
<!-- 物流时间轴 -->
<express-timeline
v-if="info.id && info.delivery_method === 0"
:express="expressInfo"
:show="true"
/>
<!-- 底部悬浮操作栏移除确认发货全宽查看处方 -->
<view class="actions-bar" v-if="info.id && (canRefund || detailExtra.canViewPrescription)">
<!-- 退款按钮为次要操作 -->
<button v-if="canRefund" class="btn-warn" @click="refund">申请退款</button>
<!-- 查看处方按钮为主操作利用 flex: 1 自动铺满剩余宽度 -->
<button v-if="detailExtra.canViewPrescription" class="btn-primary btn-full" @click="openPrescription">查看处方</button>
</view>
</view>
<!-- 处方弹窗 -->
<prescription-detail-popup ref="prescriptionPopup" />
</clinic-page-layout>
</template>
<script>
import ClinicPageLayout from '../components/clinic-page-layout.vue'
import ExpressTimeline from './components/ExpressTimeline.vue'
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
import {
getOrderDetail,
refundOrder,
getExpressDetailByOrderId,
} from '@/api/clinicAdmin.js'
import {
orderStatusText,
deliveryMethodText,
orderOnlineText,
prescriptionTypeText,
erpSyncText,
mapOrderDetailDisplay,
mapOrderProductItem,
} from '../common/display.js'
import { formatMoney } from '../common/format.js'
export default {
components: { ClinicPageLayout, ExpressTimeline, PrescriptionDetailPopup },
data() {
return {
id: 0,
info: {},
expressInfo: null,
detailExtra: {},
}
},
computed: {
productItems() {
return this.info.product_order_items || []
},
productDisplayList() {
const pt = this.info.prescription_type
return this.productItems.map(p => mapOrderProductItem(p, pt))
},
// 判断是否为中药处方,决定是否使用网格布局
isTcmOrder() {
return this.info.prescription_type === 1 || (this.productDisplayList.length > 0 && this.productDisplayList[0].isTcm);
},
doctorName() {
const d = this.info.doctor
return (d && d.name) ? d.name : '-'
},
storeName() {
const s = this.info.store
return (s && s.name) ? s.name : '-'
},
receiverBlockTitle() {
return this.info.delivery_method === 0 ? '收货人信息' : '就诊人信息'
},
receiverLabel() {
return this.info.delivery_method === 0 ? '收货人' : '就诊人'
},
receiverName() {
if (this.info.delivery_method === 0) {
const a = this.info.address
return (a && a.name) ? a.name : '-'
}
return this.info.express_name || this.info.patient || '-'
},
receiverMobile() {
const a = this.info.address
if (a && a.mobile) return a.mobile
return this.info.patient_mobile || '-'
},
deliveryAddress() {
const a = this.info.address
if (!a || typeof a !== 'object') return '-'
return [a.province, a.region, a.detail_address].filter(Boolean).join(' ') || '-'
},
pickupAddress() {
const s = this.info.store
return (s && s.position) ? s.position : '-'
},
canRefund() {
return this.info.status === 2 || this.info.status === 3
}
},
onLoad(q) {
this.id = Number(q.id || 0)
this.load()
},
methods: {
load() {
getOrderDetail(this.id).then(w => {
if (!w.ok) return
this.info = w.data || {}
this.detailExtra = mapOrderDetailDisplay(this.info)
if (this.info.delivery_method === 0) {
this.loadExpress()
}
})
},
loadExpress() {
getExpressDetailByOrderId(this.id).then(w => {
if (w.ok) this.expressInfo = w.data
})
},
money(v) {
return formatMoney(v)
},
statusText(s) {
return orderStatusText(s)
},
deliveryText(dm) {
return deliveryMethodText(dm)
},
onlineText(v) {
return orderOnlineText(v)
},
prescriptionText(pt) {
return prescriptionTypeText(pt)
},
erpSyncText(v) {
return erpSyncText(v)
},
refund() {
uni.showModal({
title: '确认退款',
confirmColor: '#F53F3F',
success: (r) => {
if (!r.confirm) return
refundOrder({ id: this.id }).then(w => {
if (w.ok) {
uni.showToast({ title: '已提交' })
this.load()
}
})
},
})
},
openPrescription() {
const pid = this.detailExtra.prescriptionId
if (!pid) return
this.$refs.prescriptionPopup.open(pid)
},
},
}
</script>
<style lang="scss" scoped>
$theme-primary: #6acdbb;
$color-page-bg: #F5F7F8;
$color-text-title: #222B2A;
$color-text-body: #4E5969;
$color-text-muted: #86909C;
$color-border: #F2F3F5;
$color-danger: #F53F3F;
.detail-container {
background-color: $color-page-bg;
min-height: 100vh;
/* 适配底部操作栏,留出极大的安全边距 */
padding-bottom: 180rpx;
}
/* 通栏面板 */
.detail-panel {
background: #ffffff;
padding: 32rpx 40rpx;
margin-bottom: 24rpx;
}
.header-panel {
padding-top: 40rpx;
}
.order-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.order-no-main {
font-size: 28rpx;
color: $color-text-muted;
}
.pay-total {
font-size: 40rpx;
color: $color-text-title;
font-weight: 700;
font-family: monospace, sans-serif;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 32rpx;
}
.modern-tag {
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 8rpx;
font-weight: 500;
}
.tag-status { background: rgba(106, 205, 187, 0.1); color: $theme-primary; }
.tag-type { background: rgba(230, 162, 60, 0.1); color: #e6a23c; }
.tag-delivery { background: rgba(91, 127, 214, 0.1); color: #5b7fd6; }
.sub-block {
margin-top: 32rpx;
}
.sub-block-title {
font-size: 30rpx;
font-weight: 600;
color: $color-text-title;
margin-bottom: 8rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid $color-border;
}
.section-title {
font-size: 32rpx;
font-weight: 700;
color: $color-text-title;
margin-bottom: 16rpx;
}
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section-arrow {
font-size: 26rpx;
color: $theme-primary;
}
.panel-hover {
background-color: #FAFAFA;
}
.prescription-hint {
font-size: 26rpx;
color: $color-text-muted;
padding: 16rpx 0;
}
.row {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
font-size: 28rpx;
border-bottom: 1rpx dashed $color-border;
gap: 24rpx;
&:last-child {
border-bottom: none;
}
text:first-child {
color: $color-text-muted;
}
text:last-child {
color: $color-text-title;
text-align: right;
flex: 1;
word-break: break-all;
}
}
.addr { max-width: 460rpx; line-height: 1.4; }
/* ================= 专属中药网格区 ================= */
.tcm-grid-wrap {
margin-top: 24rpx;
background: #F8FBFB;
border-radius: 16rpx;
padding: 24rpx;
}
.tcm-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 一排严格4个精致小巧 */
gap: 16rpx;
}
.herb-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #ffffff;
border-radius: 12rpx;
padding: 16rpx 8rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.015);
border: 1rpx solid #EAEFEF;
}
.herb-name {
font-size: 26rpx;
color: $color-text-title;
font-weight: 600;
margin-bottom: 6rpx;
text-align: center;
}
.herb-qty {
font-size: 24rpx;
color: $theme-primary;
font-weight: 500;
}
/* 商品信息 (西药模式) */
.product-item {
display: flex;
padding: 32rpx 0;
border-bottom: 1rpx solid $color-border;
&:last-child {
border-bottom: none;
padding-bottom: 0;
}
}
.product-img {
width: 140rpx;
height: 140rpx;
border-radius: 20rpx;
flex-shrink: 0;
background: $color-page-bg;
border: 1rpx solid $color-border;
}
.product-body {
flex: 1;
margin-left: 24rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.product-name {
font-size: 30rpx;
color: $color-text-title;
font-weight: 600;
margin-bottom: 8rpx;
}
.product-meta {
display: flex;
gap: 24rpx;
font-size: 24rpx;
color: $color-text-muted;
margin-bottom: 8rpx;
}
.product-line {
font-size: 24rpx;
color: $color-text-muted;
margin-bottom: 6rpx;
}
.product-price-bar {
margin-top: auto;
display: flex;
justify-content: flex-end;
align-items: center;
}
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
/* ================= 底部动作栏 ================= */
.actions-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 24rpx 40rpx;
/* 自动撑起 iOS 底部安全距离 */
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 24rpx rgba(0,0,0,0.04);
display: flex;
gap: 24rpx;
z-index: 99;
}
.actions-bar button {
margin: 0;
height: 88rpx;
line-height: 88rpx;
font-size: 30rpx;
border-radius: 44rpx;
font-weight: 600;
}
.actions-bar button::after { display: none; }
/* 主按钮使用 flex: 1 铺满 */
.btn-full {
flex: 1;
}
.btn-primary {
background: $theme-primary;
color: #fff;
}
/* 次要操作(退款)给固定宽度 */
.btn-warn {
width: 220rpx;
background: #fff;
color: $color-danger;
border: 2rpx solid $color-danger;
}
</style>