This commit is contained in:
lik
2026-06-14 11:46:10 +08:00
parent 894a9881d7
commit f8f7afceb8
14 changed files with 1340 additions and 509 deletions

View File

@@ -152,6 +152,7 @@ Page({
return orders.map(order => ({
...order,
statusText: STATUS_MAP[order.status]?.text || order.status,
patientFirstChar: (order.patient?.name || '?')[0],
schedule: {
...order.schedule,
dateText: this.formatDate(order.schedule?.date)
@@ -250,7 +251,7 @@ Page({
onOrderDetail(e) {
const id = e.currentTarget.dataset.id;
wx.navigateTo({
url: `/pages/order/detail?id=${id}`
url: `/pages/order/orderDetail?id=${id}`
});
},
@@ -275,7 +276,7 @@ Page({
if (action === 'detail') {
wx.navigateTo({
url: `/pages/order/detail?id=${id}`
url: `/pages/order/orderDetail?id=${id}`
});
return;
}

View File

@@ -1,42 +1,88 @@
/* pages/order/index.less */
// 颜色变量 - 参考图浅色社交风格
@bg-primary: #f5f6fa;
@bg-secondary: #ffffff;
@bg-card: #ffffff;
@accent-primary: #4c6ef5;
@accent-secondary: #6b7aff;
@accent-gradient-start: #4c6ef5;
@accent-gradient-end: #748ffc;
@text-primary: #1a1a2e;
@text-secondary: #6b7280;
@text-muted: #9ca3af;
@border-color: #e5e7eb;
@status-pending: #f59f00;
@status-confirmed: #4c6ef5;
@status-in-progress: #20c997;
@status-completed: #51cf66;
@status-cancelled: #ff6b6b;
@divider-color: #f3f4f6;
@bg: #f7f8fc;
@card: #ffffff;
@dark: #1a1a2e;
@text: #3a3d5c;
@muted: #a0a3bd;
@border: #f0f1f5;
@accent: #667eea;
@accent-end: #764ba2;
@pending: #e6a23c;
@confirmed: #409eff;
@in-progress: #67c23a;
@completed: #7c5cfc;
@cancelled: #b0b0b0;
page {
background-color: @bg-primary;
color: @text-primary;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: @bg;
}
.order-page {
.page {
min-height: 100vh;
display: flex;
flex-direction: column;
height: 100vh;
background-color: @bg-primary;
background: @bg;
}
// ========== 筛选区域 ==========
.filter-section {
/* === 顶部 === */
.header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
padding: 60rpx 40rpx 70rpx;
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: -80rpx;
right: -60rpx;
width: 280rpx;
height: 280rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.03);
}
&::before {
content: '';
position: absolute;
bottom: -120rpx;
left: -80rpx;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.02);
}
}
.header-content {
position: relative;
z-index: 1;
}
.header-title {
display: block;
font-size: 44rpx;
font-weight: 700;
color: #fff;
letter-spacing: 2rpx;
margin-bottom: 12rpx;
}
.header-sub {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.5);
font-weight: 300;
letter-spacing: 1rpx;
}
/* === 筛选栏 === */
.filter-bar {
background: @card;
padding: 20rpx 0;
background-color: @bg-secondary;
border-bottom: 1rpx solid @border-color;
border-bottom: 1rpx solid @border;
}
.filter-scroll {
@@ -46,69 +92,61 @@ page {
.filter-list {
display: inline-flex;
padding: 0 24rpx;
gap: 20rpx;
gap: 16rpx;
}
.filter-item {
.filter-tag {
display: inline-flex;
align-items: center;
padding: 16rpx 32rpx;
background-color: @bg-primary;
border-radius: 32rpx;
border: 1rpx solid @border-color;
transition: all 0.3s ease;
padding: 12rpx 28rpx;
background: @bg;
border-radius: 28rpx;
font-size: 24rpx;
color: @muted;
border: 1rpx solid transparent;
transition: all 0.25s;
&.active {
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
background: linear-gradient(135deg, @accent, @accent-end);
color: #fff;
border-color: transparent;
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
.filter-text {
color: #ffffff;
font-weight: 600;
}
.filter-badge {
background-color: #ffffff;
color: @accent-primary;
.filter-count {
background: rgba(255, 255, 255, 0.25);
color: #fff;
}
}
}
.filter-text {
font-size: 26rpx;
color: @text-secondary;
line-height: 1;
}
.filter-badge {
display: inline-flex;
align-items: center;
justify-content: center;
.filter-count {
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
margin-left: 8rpx;
background-color: @status-cancelled;
background: rgba(0, 0, 0, 0.06);
border-radius: 16rpx;
font-size: 20rpx;
color: #ffffff;
color: @muted;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
// ========== 订单列表区域 ==========
.order-list {
/* === 列表区域 === */
.list-area {
flex: 1;
overflow: hidden;
}
.order-scroll {
.list-scroll {
height: 100%;
padding: 0 24rpx;
}
.loading-container,
.empty-container {
.loading-box,
.empty-box {
display: flex;
align-items: center;
justify-content: center;
@@ -116,239 +154,197 @@ page {
}
.loading-text {
color: @text-secondary !important;
color: @muted !important;
font-size: 26rpx !important;
}
.empty-text {
color: @text-secondary !important;
color: @muted !important;
font-size: 28rpx !important;
}
.empty-action {
margin-top: 30rpx;
padding: 16rpx 48rpx;
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
color: #ffffff;
font-size: 28rpx;
border-radius: 32rpx;
.empty-btn {
margin-top: 24rpx;
padding: 14rpx 48rpx;
background: linear-gradient(135deg, @accent, @accent-end);
color: #fff;
font-size: 26rpx;
border-radius: 28rpx;
display: inline-block;
}
// ========== 订单卡片 ==========
.order-cards {
/* === 订单卡片 === */
.card-list {
padding: 24rpx 0 40rpx;
}
.order-card {
margin-bottom: 24rpx;
background-color: @bg-card;
border-radius: 24rpx;
border: 1rpx solid @border-color;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
margin-bottom: 20rpx;
background: @card;
border-radius: 20rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
opacity: 0.92;
}
}
.card-header {
.card-top {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
padding: 28rpx 28rpx 20rpx;
padding: 20rpx 28rpx 0;
}
.order-id {
display: flex;
align-items: center;
gap: 10rpx;
}
.order-id-text {
.order-no {
font-size: 24rpx;
color: @text-muted;
color: @muted;
font-weight: 500;
font-family: 'DIN Alternate', 'Helvetica Neue', monospace;
}
.status-badge {
font-size: 22rpx;
padding: 6rpx 20rpx;
border-radius: 24rpx;
font-weight: 500;
}
.status-tag {
display: inline-flex;
align-items: center;
padding: 8rpx 18rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 600;
&.pending {
background-color: rgba(245, 159, 0, 0.1);
color: @status-pending;
}
&.confirmed {
background-color: rgba(76, 110, 245, 0.1);
color: @accent-primary;
}
&.in_progress {
background-color: rgba(32, 201, 151, 0.1);
color: @status-in-progress;
}
&.completed {
background-color: rgba(81, 207, 102, 0.1);
color: @status-completed;
}
&.cancelled {
background-color: rgba(255, 107, 107, 0.1);
color: @status-cancelled;
}
.status-pending {
background: #fff8e6;
color: @pending;
}
// ========== 卡片主体 ==========
.card-body {
.status-confirmed {
background: #ecf5ff;
color: @confirmed;
}
.status-in_progress {
background: #f0f9eb;
color: @in-progress;
}
.status-completed {
background: #f4f0ff;
color: @completed;
}
.status-cancelled {
background: #f5f5f5;
color: @cancelled;
}
/* === 卡片主体 === */
.card-main {
padding: 0 28rpx;
}
.patient-row {
margin-bottom: 20rpx;
}
.patient-info {
.patient-line {
display: flex;
align-items: center;
gap: 24rpx;
gap: 20rpx;
margin-bottom: 16rpx;
}
.patient-avatar {
width: 80rpx;
height: 80rpx;
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: linear-gradient(135deg, @accent, @accent-end);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
}
.avatar-text {
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
.avatar-char {
font-size: 28rpx;
font-weight: 700;
color: #fff;
}
.patient-detail {
.patient-meta {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.patient-name-row {
display: flex;
align-items: center;
gap: 14rpx;
gap: 12rpx;
margin-bottom: 4rpx;
}
.patient-name {
font-size: 32rpx;
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
color: @dark;
}
.patient-gender {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 10rpx;
background-color: rgba(76, 110, 245, 0.1);
color: @accent-primary;
.patient-sex {
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 8rpx;
font-weight: 500;
&.male {
background-color: rgba(76, 110, 245, 0.1);
color: @accent-primary;
background: rgba(102, 126, 234, 0.1);
color: @accent;
}
&.female {
background-color: rgba(255, 107, 107, 0.1);
color: @status-cancelled;
background: rgba(234, 102, 150, 0.1);
color: #ea6696;
}
}
.patient-age {
font-size: 24rpx;
color: @text-secondary;
font-weight: 500;
font-size: 22rpx;
color: @muted;
}
.patient-phone {
font-size: 24rpx;
color: @text-muted;
color: @muted;
}
.info-divider {
.divider {
height: 1rpx;
background-color: @divider-color;
margin: 20rpx 0;
background: @border;
margin: 16rpx 0;
}
.info-item {
.info-line {
display: flex;
align-items: flex-start;
gap: 14rpx;
margin-bottom: 14rpx;
align-items: baseline;
margin-bottom: 12rpx;
}
.info-content {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.hospital-name {
font-size: 28rpx;
color: @text-primary;
font-weight: 500;
}
.department-name {
.line-label {
font-size: 24rpx;
color: @text-secondary;
color: @muted;
width: 64rpx;
flex-shrink: 0;
}
.service-name {
.line-value {
font-size: 26rpx;
color: @text-secondary;
font-weight: 500;
color: @text;
flex: 1;
}
.time-text {
font-size: 26rpx;
color: @text-secondary;
font-weight: 500;
}
.attendant-name {
font-size: 26rpx;
color: @text-secondary;
font-weight: 500;
}
// ========== 卡片底部 ==========
.card-footer {
/* === 卡片底部 === */
.card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 28rpx 28rpx;
margin-top: 12rpx;
border-top: 1rpx solid @divider-color;
align-items: center;
padding: 20rpx 28rpx 24rpx;
margin-top: 8rpx;
border-top: 1rpx solid @border;
}
.fee-section {
.fee-area {
display: flex;
align-items: baseline;
gap: 8rpx;
@@ -356,61 +352,54 @@ page {
.fee-label {
font-size: 24rpx;
color: @text-muted;
color: @muted;
}
.fee-value {
font-size: 38rpx;
.fee-amount {
font-size: 36rpx;
font-weight: 700;
color: @status-pending;
color: @dark;
font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif;
}
.action-buttons {
.action-area {
display: flex;
gap: 16rpx;
gap: 14rpx;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16rpx 36rpx;
border-radius: 28rpx;
font-size: 26rpx;
.action-btn {
padding: 14rpx 32rpx;
border-radius: 24rpx;
font-size: 24rpx;
font-weight: 500;
transition: all 0.2s ease;
transition: all 0.2s;
&:active {
transform: scale(0.95);
opacity: 0.9;
}
}
.btn-primary {
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
color: #ffffff;
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
.btn-solid {
background: linear-gradient(135deg, @accent, @accent-end);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.25);
}
.btn-secondary {
background-color: @bg-primary;
color: @text-secondary;
border: 1rpx solid @border-color;
&:active {
background-color: rgba(76, 110, 245, 0.05);
}
.btn-ghost {
background: @bg;
color: @muted;
border: 1rpx solid @border;
}
// ========== 加载更多 ==========
/* === 加载更多 === */
.load-more {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx 20rpx;
padding: 32rpx 20rpx;
}
.no-more {
font-size: 24rpx;
color: @text-muted;
color: @muted;
}

View File

@@ -1,166 +1,143 @@
<!--pages/order/index.wxml-->
<view class="order-page">
<view class="page">
<!-- 顶部标题 -->
<view class="header">
<view class="header-content">
<text class="header-title">订单管理</text>
<text class="header-sub">共 {{stats.total}} 个订单</text>
</view>
</view>
<!-- 状态筛选标签 -->
<view class="filter-section">
<!-- 状态筛选 -->
<view class="filter-bar">
<scroll-view class="filter-scroll" scroll-x enhanced show-scrollbar="{{false}}">
<view class="filter-list">
<view
class="filter-item {{currentStatus === item.value ? 'active' : ''}}"
wx:for="{{statusFilters}}"
<view
class="filter-tag {{currentStatus === item.value ? 'active' : ''}}"
wx:for="{{statusFilters}}"
wx:key="value"
data-status="{{item.value}}"
bindtap="onStatusChange"
>
<text class="filter-text">{{item.label}}</text>
<view class="filter-badge" wx:if="{{item.count > 0}}">{{item.count}}</view>
<text>{{item.label}}</text>
<text class="filter-count" wx:if="{{item.count > 0}}">{{item.count}}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 订单列表 -->
<view class="order-list">
<scroll-view
scroll-y
class="order-scroll"
<view class="list-area">
<scroll-view
scroll-y
class="list-scroll"
refresher-enabled
refresher-triggered="{{isRefreshing}}"
bindrefresherrefresh="onPullDownRefresh"
bindscrolltolower="onReachBottom"
>
<!-- 加载中 -->
<view class="loading-container" wx:if="{{isLoading && orderList.length === 0}}">
<view class="loading-box" wx:if="{{isLoading && orderList.length === 0}}">
<t-loading theme="spinner" size="40rpx" text="加载中..." t-class-text="loading-text" />
</view>
<!-- 空状态 -->
<view class="empty-container" wx:elif="{{!isLoading && orderList.length === 0}}">
<t-empty icon="file" description="暂无订单数据" t-class-description="empty-text">
<view class="empty-box" wx:elif="{{!isLoading && orderList.length === 0}}">
<t-empty icon="file" description="暂无订单" t-class-description="empty-text">
<view slot="action">
<view class="empty-action" bindtap="loadOrderList">刷新试试</view>
<view class="empty-btn" bindtap="loadOrderList">刷新</view>
</view>
</t-empty>
</view>
<!-- 订单卡片列表 -->
<view class="order-cards" wx:else>
<view
class="order-card {{item.status}}"
wx:for="{{orderList}}"
<!-- 卡片列表 -->
<view class="card-list" wx:else>
<view
class="order-card"
wx:for="{{orderList}}"
wx:key="_id"
data-id="{{item._id}}"
bindtap="onOrderDetail"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="order-id">
<t-icon name="file" size="28rpx" color="#6b7aff" />
<text class="order-id-text">订单 {{item.orderNo || item._id}}</text>
</view>
<view class="status-tag {{item.status}}">
<view class="card-top">
<view class="status-badge status-{{item.status}}">
<text>{{item.statusText}}</text>
</view>
</view>
<!-- 患者信息 -->
<view class="card-body">
<view class="patient-row">
<view class="patient-info">
<view class="patient-avatar">
<text class="avatar-text">{{item.patient.name ? item.patient.name[0] : '患'}}</text>
</view>
<view class="patient-detail">
<view class="patient-name-row">
<text class="patient-name">{{item.patient.name || '未知患者'}}</text>
<text class="patient-gender {{item.patient.sex}}">{{item.patient.sex === 'male' ? '男' : item.patient.sex === 'female' ? '女' : '未知'}}</text>
<text class="patient-age" wx:if="{{item.patient.age}}">{{item.patient.age}}岁</text>
</view>
<text class="patient-phone">{{item.patient.mobile || '暂无电话'}}</text>
<view class="card-main">
<view class="patient-line">
<view class="patient-avatar">
<text class="avatar-char">{{item.patientFirstChar}}</text>
</view>
<view class="patient-meta">
<view class="patient-name-row">
<text class="patient-name">{{item.patient.name || '未知患者'}}</text>
<text class="patient-sex {{item.patient.sex}}">{{item.patient.sex === 'male' ? '男' : item.patient.sex === 'female' ? '女' : ''}}</text>
<text class="patient-age" wx:if="{{item.patient.age}}">{{item.patient.age}}</text>
</view>
<text class="patient-phone">{{item.patient.mobile || '暂无电话'}}</text>
</view>
</view>
<view class="info-divider"></view>
<view class="divider"></view>
<!-- 医院信息 -->
<view class="hospital-row">
<view class="info-item">
<t-icon name="location" size="26rpx" color="#8b9bff" />
<view class="info-content">
<text class="hospital-name">{{item.hospital.name || '未知医院'}}</text>
<text class="department-name" wx:if="{{item.hospital.department}}">{{item.hospital.department}}</text>
</view>
</view>
<!-- 就诊信息 -->
<view class="info-line">
<text class="line-label">医院</text>
<text class="line-value">{{item.hospital.name || '未知医院'}}{{item.hospital.department ? ' · ' + item.hospital.department : ''}}</text>
</view>
<!-- 陪诊服务 -->
<view class="service-row" wx:if="{{item.escort.serviceName}}">
<view class="info-item">
<t-icon name="service" size="26rpx" color="#8b9bff" />
<text class="service-name">{{item.escort.serviceName}}</text>
</view>
<view class="info-line" wx:if="{{item.escort.serviceName}}">
<text class="line-label">服务</text>
<text class="line-value">{{item.escort.serviceName}}</text>
</view>
<!-- 预约时间 -->
<view class="time-row">
<view class="info-item">
<t-icon name="time" size="26rpx" color="#8b9bff" />
<text class="time-text">{{item.schedule.dateText}} {{item.schedule.startTime || ''}}</text>
</view>
<view class="info-line">
<text class="line-label">时间</text>
<text class="line-value">{{item.schedule.dateText}} {{item.schedule.startTime || ''}}</text>
</view>
<!-- 陪诊员信息 -->
<view class="attendant-row" wx:if="{{item.attendant.name}}">
<view class="info-item">
<t-icon name="user" size="26rpx" color="#8b9bff" />
<text class="attendant-name">陪诊员:{{item.attendant.name}}</text>
</view>
<view class="info-line" wx:if="{{item.attendant.name}}">
<text class="line-label">陪诊</text>
<text class="line-value">{{item.attendant.name}}</text>
</view>
</view>
<!-- 卡片底部 -->
<view class="card-footer">
<view class="fee-section">
<text class="fee-label">服务费用</text>
<text class="fee-value">¥{{item.payment.totalFee || 0}}</text>
<view class="card-bottom">
<view class="fee-area">
<text class="fee-label">费用</text>
<text class="fee-amount">¥{{item.payment.totalFee || 0}}</text>
</view>
<view class="action-buttons">
<view
class="btn btn-secondary"
<view class="action-area">
<view
class="action-btn btn-ghost"
wx:if="{{item.status === 'pending'}}"
data-id="{{item._id}}"
data-action="cancel"
catchtap="onOrderAction"
>取消</view>
<view
class="btn btn-primary"
<view
class="action-btn btn-solid"
wx:if="{{item.status === 'pending'}}"
data-id="{{item._id}}"
data-action="confirm"
catchtap="onOrderAction"
>确认</view>
<view
class="btn btn-primary"
<view
class="action-btn btn-solid"
wx:if="{{item.status === 'confirmed'}}"
data-id="{{item._id}}"
data-action="start"
catchtap="onOrderAction"
>开始服务</view>
<view
class="btn btn-primary"
<view
class="action-btn btn-solid"
wx:if="{{item.status === 'in_progress'}}"
data-id="{{item._id}}"
data-action="complete"
catchtap="onOrderAction"
>完成</view>
<view
class="btn btn-secondary"
wx:if="{{item.status === 'completed' || item.status === 'cancelled'}}"
data-id="{{item._id}}"
data-action="detail"
catchtap="onOrderAction"
>查看详情</view>
</view>
</view>
</view>

232
pages/order/orderDetail.js Normal file
View File

@@ -0,0 +1,232 @@
// pages/order/orderDetail.js
const API = require('../../utils/api.js')
const STATUS_MAP = {
pending: '待确认',
confirmed: '已确认',
in_progress: '进行中',
completed: '已完成',
cancelled: '已取消'
}
const STATUS_DESC_MAP = {
pending: '订单已创建,等待确认',
confirmed: '订单已确认,等待服务',
in_progress: '陪诊服务正在进行',
completed: '陪诊服务已完成',
cancelled: '订单已取消'
}
const PAYMENT_STATUS_MAP = {
unpaid: '未支付',
partial: '部分支付',
paid: '已支付',
refunded: '已退款'
}
function formatDate(timestamp) {
if (!timestamp) return ''
const date = new Date(timestamp)
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return y + '-' + m + '-' + d
}
function formatTime(timestamp) {
if (!timestamp) return ''
const date = new Date(timestamp)
const h = String(date.getHours()).padStart(2, '0')
const min = String(date.getMinutes()).padStart(2, '0')
return h + ':' + min
}
function formatDateTime(timestamp) {
if (!timestamp) return ''
return formatDate(timestamp) + ' ' + formatTime(timestamp)
}
Page({
data: {
orderId: '',
loaded: false,
// 订单数据
order: null,
// 格式化后的显示字段
statusText: '',
statusDesc: '',
patientFirstChar: '',
attendantFirstChar: '',
scheduleDateText: '',
scheduleStartTimeText: '',
scheduleEndTimeText: '',
createtimeText: '',
updatetimeText: '',
paymentStatusText: '',
patientSexText: '',
attendantSexText: '',
sexRequirementText: '',
// 操作按钮配置
showConfirmBtn: false,
showCancelBtn: false,
showStartBtn: false,
showCompleteBtn: false
},
onLoad(options) {
const id = options.id || ''
this.setData({ orderId: id })
if (id) {
this.loadOrderDetail(id)
}
},
onPullDownRefresh() {
if (this.data.orderId) {
this.loadOrderDetail(this.data.orderId).then(() => {
wx.stopPullDownRefresh()
})
} else {
wx.stopPullDownRefresh()
}
},
async loadOrderDetail(id) {
wx.showLoading({ title: '加载中...' })
try {
const res = await API.escort.getRecordById(id)
wx.hideLoading()
if (res.code !== 0) {
wx.showToast({ title: res.message || '加载失败', icon: 'none' })
return
}
const order = res.data || {}
this.applyOrderData(order)
} catch (err) {
wx.hideLoading()
console.error('加载订单详情失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
},
applyOrderData(order) {
const status = order.status || 'pending'
const patient = order.patient || {}
const attendant = order.attendant || {}
const schedule = order.schedule || {}
const payment = order.payment || {}
const escort = order.escort || {}
const meta = order.meta || {}
this.setData({
loaded: true,
order: order,
statusText: STATUS_MAP[status] || status,
statusDesc: STATUS_DESC_MAP[status] || '',
patientFirstChar: (patient.name || '?')[0],
attendantFirstChar: (attendant.name || '?')[0],
scheduleDateText: formatDate(schedule.date),
scheduleStartTimeText: schedule.startTime || '',
scheduleEndTimeText: schedule.endTime || '',
createtimeText: formatDateTime(meta.createtime),
updatetimeText: formatDateTime(meta.updatetime),
paymentStatusText: PAYMENT_STATUS_MAP[payment.status] || payment.status || '未支付',
patientSexText: patient.sex === 'male' ? '男' : patient.sex === 'female' ? '女' : '',
attendantSexText: attendant.sex === 'male' ? '男' : attendant.sex === 'female' ? '女' : '',
sexRequirementText: escort.sexRequirement === 'male' ? '要求男陪诊' : escort.sexRequirement === 'female' ? '要求女陪诊' : '不限',
// 操作按钮
showConfirmBtn: status === 'pending',
showCancelBtn: status === 'pending',
showStartBtn: status === 'confirmed',
showCompleteBtn: status === 'in_progress'
})
},
// 拨打电话
callPhone(e) {
const phone = e.currentTarget.dataset.phone
if (!phone) {
wx.showToast({ title: '暂无电话', icon: 'none' })
return
}
wx.makePhoneCall({ phoneNumber: phone })
},
// 确认订单
onConfirm() {
wx.showModal({
title: '确认订单',
content: '确认接受此订单?',
confirmColor: '#4c6ef5',
success: (res) => {
if (res.confirm) {
this.updateStatus('confirmed')
}
}
})
},
// 取消订单
onCancel() {
wx.showModal({
title: '取消订单',
content: '确定要取消此订单?',
confirmColor: '#ff6b6b',
success: (res) => {
if (res.confirm) {
this.updateStatus('cancelled')
}
}
})
},
// 开始服务
onStart() {
wx.showModal({
title: '开始服务',
content: '确认开始陪诊服务?',
confirmColor: '#4c6ef5',
success: (res) => {
if (res.confirm) {
this.updateStatus('in_progress')
}
}
})
},
// 完成服务
onComplete() {
wx.showModal({
title: '完成服务',
content: '确认陪诊服务已完成?',
confirmColor: '#4c6ef5',
success: (res) => {
if (res.confirm) {
this.updateStatus('completed')
}
}
})
},
async updateStatus(newStatus) {
wx.showLoading({ title: '处理中...' })
try {
const res = await API.escort.updateStatus(this.data.orderId, { status: newStatus })
wx.hideLoading()
if (res.code !== 0) {
wx.showToast({ title: res.message || '操作失败', icon: 'none' })
return
}
wx.showToast({ title: '操作成功', icon: 'success' })
this.loadOrderDetail(this.data.orderId)
} catch (err) {
wx.hideLoading()
console.error('更新状态失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
}
})

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "订单详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"usingComponents": {}
}

View File

@@ -0,0 +1,202 @@
<!--pages/order/orderDetail.wxml-->
<view class="detail-page">
<!-- 加载中 -->
<view class="loading-box" wx:if="{{!loaded}}">
<text class="loading-text">加载中...</text>
</view>
<block wx:if="{{loaded && order}}">
<!-- 顶部状态栏 -->
<view class="status-header status-{{order.status}}">
<view class="status-content">
<text class="status-text">{{statusText}}</text>
<text class="status-desc">{{statusDesc}}</text>
</view>
</view>
<!-- 患者信息 -->
<view class="info-section">
<text class="section-title">患者信息</text>
<view class="info-card">
<view class="patient-header">
<view class="patient-avatar">
<text class="avatar-text">{{patientFirstChar}}</text>
</view>
<view class="patient-info">
<view class="patient-name-row">
<text class="patient-name">{{order.patient.name || '未知患者'}}</text>
<text class="patient-sex {{order.patient.sex}}" wx:if="{{patientSexText}}">{{patientSexText}}</text>
<text class="patient-age" wx:if="{{order.patient.age}}">{{order.patient.age}}岁</text>
</view>
<view class="patient-phone" data-phone="{{order.patient.mobile}}" bindtap="callPhone">
<text>{{order.patient.mobile || '暂无电话'}}</text>
<text class="call-icon" wx:if="{{order.patient.mobile}}"> 拨打</text>
</view>
</view>
</view>
<view class="divider"></view>
<view class="info-row" wx:if="{{order.patient.weight}}">
<text class="info-label">体重</text>
<text class="info-value">{{order.patient.weight}}kg</text>
</view>
<view class="info-row" wx:if="{{order.patient.height}}">
<text class="info-label">身高</text>
<text class="info-value">{{order.patient.height}}cm</text>
</view>
<view class="info-row" wx:if="{{order.patient.idnumber}}">
<text class="info-label">身份证号</text>
<text class="info-value">{{order.patient.idnumber}}</text>
</view>
</view>
</view>
<!-- 就诊信息 -->
<view class="info-section">
<text class="section-title">就诊信息</text>
<view class="info-card">
<view class="info-row">
<text class="info-label">医院</text>
<text class="info-value">{{order.hospital.name || '未知医院'}}</text>
</view>
<view class="info-row" wx:if="{{order.hospital.province}}">
<text class="info-label">地区</text>
<text class="info-value">{{order.hospital.province}}</text>
</view>
<view class="info-row" wx:if="{{order.hospital.address}}">
<text class="info-label">地址</text>
<text class="info-value">{{order.hospital.address}}</text>
</view>
<view class="info-row" wx:if="{{order.hospital.department}}">
<text class="info-label">科室</text>
<text class="info-value">{{order.hospital.department}}</text>
</view>
<view class="info-row" wx:if="{{order.hospital.doctor}}">
<text class="info-label">医生</text>
<text class="info-value">{{order.hospital.doctor}}</text>
</view>
<view class="info-row" wx:if="{{order.hospital.medicalRecordNo}}">
<text class="info-label">病历号</text>
<text class="info-value">{{order.hospital.medicalRecordNo}}</text>
</view>
</view>
</view>
<!-- 服务信息 -->
<view class="info-section">
<text class="section-title">服务信息</text>
<view class="info-card">
<view class="info-row" wx:if="{{order.escort.serviceName}}">
<text class="info-label">服务类型</text>
<text class="info-value">{{order.escort.serviceName}}</text>
</view>
<view class="info-row">
<text class="info-label">性别要求</text>
<text class="info-value">{{sexRequirementText}}</text>
</view>
<view class="info-row">
<text class="info-label">预约日期</text>
<text class="info-value">{{scheduleDateText}}</text>
</view>
<view class="info-row" wx:if="{{scheduleStartTimeText}}">
<text class="info-label">开始时间</text>
<text class="info-value">{{scheduleStartTimeText}}</text>
</view>
<view class="info-row" wx:if="{{scheduleEndTimeText}}">
<text class="info-label">结束时间</text>
<text class="info-value">{{scheduleEndTimeText}}</text>
</view>
<view class="info-row" wx:if="{{order.schedule.duration}}">
<text class="info-label">服务时长</text>
<text class="info-value">{{order.schedule.duration}}分钟</text>
</view>
</view>
</view>
<!-- 陪诊员信息 -->
<view class="info-section" wx:if="{{order.attendant.name}}">
<text class="section-title">陪诊员</text>
<view class="info-card">
<view class="attendant-header">
<view class="attendant-avatar">
<text class="avatar-text">{{attendantFirstChar}}</text>
</view>
<view class="attendant-info">
<view class="attendant-name-row">
<text class="attendant-name">{{order.attendant.name}}</text>
<text class="patient-sex male" wx:if="{{attendantSexText}}">{{attendantSexText}}</text>
</view>
<view class="attendant-phone" data-phone="{{order.attendant.mobile}}" bindtap="callPhone">
<text>{{order.attendant.mobile || '暂无电话'}}</text>
<text class="call-icon" wx:if="{{order.attendant.mobile}}"> 拨打</text>
</view>
</view>
</view>
</view>
</view>
<!-- 费用信息 -->
<view class="info-section">
<text class="section-title">费用信息</text>
<view class="info-card">
<view class="info-row">
<text class="info-label">服务费用</text>
<text class="info-value fee-value">¥{{order.payment.totalFee || 0}}</text>
</view>
<view class="info-row" wx:if="{{order.payment.paidFee}}">
<text class="info-label">已支付</text>
<text class="info-value">¥{{order.payment.paidFee}}</text>
</view>
<view class="info-row">
<text class="info-label">支付状态</text>
<text class="info-value">{{paymentStatusText}}</text>
</view>
</view>
</view>
<!-- 备注信息 -->
<view class="info-section" wx:if="{{order.notes.patientNote || order.notes.escortNote || order.notes.medicalSummary}}">
<text class="section-title">备注信息</text>
<view class="info-card">
<view class="note-block" wx:if="{{order.notes.patientNote}}">
<text class="note-label">患者备注</text>
<text class="note-content">{{order.notes.patientNote}}</text>
</view>
<view class="note-block" wx:if="{{order.notes.escortNote}}">
<text class="note-label">陪诊记录</text>
<text class="note-content">{{order.notes.escortNote}}</text>
</view>
<view class="note-block" wx:if="{{order.notes.medicalSummary}}">
<text class="note-label">就诊摘要</text>
<text class="note-content">{{order.notes.medicalSummary}}</text>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="info-section">
<text class="section-title">订单信息</text>
<view class="info-card">
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value">{{order._id}}</text>
</view>
<view class="info-row" wx:if="{{createtimeText}}">
<text class="info-label">创建时间</text>
<text class="info-value">{{createtimeText}}</text>
</view>
<view class="info-row" wx:if="{{updatetimeText}}">
<text class="info-label">更新时间</text>
<text class="info-value">{{updatetimeText}}</text>
</view>
</view>
</view>
<!-- 底部操作 -->
<view class="bottom-actions" wx:if="{{showConfirmBtn || showCancelBtn || showStartBtn || showCompleteBtn}}">
<view class="action-btn btn-ghost" wx:if="{{showCancelBtn}}" bindtap="onCancel">取消订单</view>
<view class="action-btn btn-solid" wx:if="{{showConfirmBtn}}" bindtap="onConfirm">确认订单</view>
<view class="action-btn btn-solid" wx:if="{{showStartBtn}}" bindtap="onStart">开始服务</view>
<view class="action-btn btn-solid" wx:if="{{showCompleteBtn}}" bindtap="onComplete">完成服务</view>
</view>
</block>
</view>

View File

@@ -0,0 +1,309 @@
/* pages/order/orderDetail.wxss */
page {
background-color: #f7f8fc;
}
.detail-page {
min-height: 100vh;
background: #f7f8fc;
padding-bottom: 120rpx;
}
.loading-box {
display: flex;
align-items: center;
justify-content: center;
padding: 200rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #a0a3bd;
}
/* === 顶部状态栏 === */
.status-header {
padding: 60rpx 40rpx 80rpx;
position: relative;
overflow: hidden;
}
.status-header::after {
content: '';
position: absolute;
top: -60rpx;
right: -40rpx;
width: 240rpx;
height: 240rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.03);
}
.status-header.status-pending {
background: linear-gradient(135deg, #e6a23c 0%, #f5b041 100%);
}
.status-header.status-confirmed {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
}
.status-header.status-in_progress {
background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
}
.status-header.status-completed {
background: linear-gradient(135deg, #7c5cfc 0%, #9b7dff 100%);
}
.status-header.status-cancelled {
background: linear-gradient(135deg, #b0b0b0 0%, #c0c0c0 100%);
}
.status-content {
position: relative;
z-index: 1;
}
.status-text {
display: block;
font-size: 44rpx;
font-weight: 700;
color: #fff;
letter-spacing: 2rpx;
margin-bottom: 12rpx;
}
.status-desc {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
font-weight: 300;
}
/* === 信息区块 === */
.info-section {
margin: 24rpx 24rpx 0;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1a1a2e;
margin-bottom: 16rpx;
letter-spacing: 1rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
}
.info-row {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 24rpx;
color: #a0a3bd;
width: 140rpx;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: #3a3d5c;
flex: 1;
}
/* === 患者信息 === */
.patient-header {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 16rpx;
}
.patient-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.avatar-text {
font-size: 32rpx;
font-weight: 700;
color: #fff;
}
.patient-info {
flex: 1;
}
.patient-name-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 6rpx;
}
.patient-name {
font-size: 32rpx;
font-weight: 600;
color: #1a1a2e;
}
.patient-sex {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 10rpx;
font-weight: 500;
}
.patient-sex.male {
background: rgba(102, 126, 234, 0.1);
color: #667eea;
}
.patient-sex.female {
background: rgba(234, 102, 150, 0.1);
color: #ea6696;
}
.patient-age {
font-size: 24rpx;
color: #a0a3bd;
}
.patient-phone {
font-size: 24rpx;
color: #a0a3bd;
}
.call-icon {
color: #667eea;
font-weight: 500;
}
.divider {
height: 1rpx;
background: #f0f1f5;
margin: 16rpx 0;
}
/* === 陪诊员信息 === */
.attendant-header {
display: flex;
align-items: center;
gap: 20rpx;
}
.attendant-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.attendant-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.attendant-name {
font-size: 30rpx;
font-weight: 600;
color: #1a1a2e;
}
.attendant-phone {
font-size: 24rpx;
color: #a0a3bd;
}
/* === 费用信息 === */
.fee-value {
font-size: 32rpx;
font-weight: 700;
color: #1a1a2e;
font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif;
}
/* === 备注信息 === */
.note-block {
margin-bottom: 20rpx;
}
.note-block:last-child {
margin-bottom: 0;
}
.note-label {
display: block;
font-size: 24rpx;
color: #a0a3bd;
margin-bottom: 8rpx;
font-weight: 500;
}
.note-content {
display: block;
font-size: 26rpx;
color: #3a3d5c;
line-height: 1.6;
}
/* === 底部操作 === */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.06);
display: flex;
gap: 20rpx;
z-index: 100;
}
.action-btn {
flex: 1;
padding: 24rpx 0;
border-radius: 28rpx;
font-size: 28rpx;
font-weight: 600;
text-align: center;
}
.btn-solid {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
}
.btn-ghost {
background: #f7f8fc;
color: #a0a3bd;
border: 1rpx solid #f0f1f5;
}