This commit is contained in:
lik
2026-09-03 11:35:21 +08:00
parent f8f7afceb8
commit b4c8bf0add
45 changed files with 2989 additions and 426 deletions
+38 -15
View File
@@ -1,10 +1,11 @@
// pages/order/index.js
const API = require('../../utils/api.js')
const { calcAge } = require('../../utils/format.js')
// 状态映射配置
const STATUS_MAP = {
pending: { label: '待确认', text: '待确认', color: '#f59f00' },
confirmed: { label: '已确认', text: '已确认', color: '#4c6ef5' },
confirmed: { label: '已确认', text: '已确认', color: '#1abc9c' },
in_progress: { label: '进行中', text: '进行中', color: '#20c997' },
completed: { label: '已完成', text: '已完成', color: '#51cf66' },
cancelled: { label: '已取消', text: '已取消', color: '#ff6b6b' }
@@ -37,7 +38,7 @@ Page({
},
// 状态筛选
statusFilters: STATUS_FILTERS,
currentStatus: '',
currentStatus: 'pending',
// 订单列表
orderList: [],
// 分页
@@ -149,15 +150,19 @@ Page({
* 处理订单数据
*/
processOrders(orders) {
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)
}
}));
return orders.map(order => {
const age = calcAge(order.patient?.birth)
return {
...order,
statusText: STATUS_MAP[order.status]?.text || order.status,
patientFirstChar: (order.patient?.name || '?')[0],
patientAgeText: age ? `${age}` : '',
schedule: {
...order.schedule,
dateText: this.formatDate(order.schedule?.date)
}
};
});
},
/**
@@ -176,10 +181,10 @@ Page({
status: 'pending,confirmed,in_progress,completed,cancelled'
};
API.escort.getMyRecords(params)
API.escort.getAllRecords(params)
.then(res => {
if (res.code !== 0) {
wx.showToast({ title: res.message || '获取订单失败', icon: 'none' });
wx.showToast({ title: res.msg || '获取订单失败', icon: 'none' });
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
return;
}
@@ -228,6 +233,24 @@ Page({
});
},
/**
* 跳转新增预约(页面内选择健康档案)
*/
onAddOrder() {
wx.navigateTo({
url: '/pages/order/orderEdit'
});
},
/**
* 空状态刷新
*/
onReload() {
this.setData({ page: 1, hasMore: true }, () => {
this.loadOrderList();
});
},
/**
* 状态筛选切换
*/
@@ -284,7 +307,7 @@ Page({
wx.showModal({
title: actionConfig.title,
content: actionConfig.content,
confirmColor: '#4c6ef5',
confirmColor: '#1abc9c',
success: (res) => {
if (res.confirm) {
this.updateOrderStatus(id, actionConfig.nextStatus);
@@ -302,7 +325,7 @@ Page({
API.escort.updateStatus(id, { status: newStatus })
.then(res => {
if (res.code !== 0) {
wx.showToast({ title: res.message || '操作失败', icon: 'none' });
wx.showToast({ title: res.msg || '操作失败', icon: 'none' });
wx.hideLoading();
return;
}
+1 -5
View File
@@ -4,12 +4,8 @@
"navigationBarTextStyle": "black",
"backgroundColor": "#ffffff",
"usingComponents": {
"t-tabs": "tdesign-miniprogram/tabs/tabs",
"t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
"t-tag": "tdesign-miniprogram/tag/tag",
"t-empty": "tdesign-miniprogram/empty/empty",
"t-loading": "tdesign-miniprogram/loading/loading",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-divider": "tdesign-miniprogram/divider/divider"
"t-icon": "tdesign-miniprogram/icon/icon"
}
}
+27 -9
View File
@@ -1,13 +1,13 @@
/* pages/order/index.less */
@bg: #f7f8fc;
@bg: #f5f6fa;
@card: #ffffff;
@dark: #1a1a2e;
@text: #3a3d5c;
@muted: #a0a3bd;
@border: #f0f1f5;
@accent: #667eea;
@accent-end: #764ba2;
@accent: #1abc9c;
@accent-end: #17c3a5;
@pending: #e6a23c;
@confirmed: #409eff;
@@ -17,6 +17,8 @@
page {
background-color: @bg;
color: @dark;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.page {
@@ -28,10 +30,14 @@ page {
/* === 顶部 === */
.header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
background: linear-gradient(135deg, #2dd36f 0%, #17c3a5 100%);
box-shadow: 0 8rpx 24rpx rgba(23, 195, 165, 0.25);
padding: 60rpx 40rpx 70rpx;
position: relative;
overflow: hidden;
display: flex;
align-items: flex-start;
justify-content: space-between;
&::after {
content: '';
@@ -78,6 +84,18 @@ page {
letter-spacing: 1rpx;
}
.header-add {
position: relative;
z-index: 1;
flex-shrink: 0;
padding: 16rpx 28rpx;
font-size: 26rpx;
color: #fff;
background: rgba(255, 255, 255, 0.18);
border: 1rpx solid rgba(255, 255, 255, 0.4);
border-radius: 32rpx;
}
/* === 筛选栏 === */
.filter-bar {
background: @card;
@@ -110,7 +128,7 @@ page {
background: linear-gradient(135deg, @accent, @accent-end);
color: #fff;
border-color: transparent;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.3);
.filter-count {
background: rgba(255, 255, 255, 0.25);
@@ -181,8 +199,8 @@ page {
.order-card {
margin-bottom: 20rpx;
background: @card;
border-radius: 20rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
border-radius: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
&:active {
@@ -289,7 +307,7 @@ page {
font-weight: 500;
&.male {
background: rgba(102, 126, 234, 0.1);
background: rgba(26, 188, 156, 0.1);
color: @accent;
}
@@ -382,7 +400,7 @@ page {
.btn-solid {
background: linear-gradient(135deg, @accent, @accent-end);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.25);
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.25);
}
.btn-ghost {
+3 -2
View File
@@ -6,6 +6,7 @@
<text class="header-title">订单管理</text>
<text class="header-sub">共 {{stats.total}} 个订单</text>
</view>
<view class="header-add" bindtap="onAddOrder">新增预约</view>
</view>
<!-- 状态筛选 -->
@@ -45,7 +46,7 @@
<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-btn" bindtap="loadOrderList">刷新</view>
<view class="empty-btn" bindtap="onReload">刷新</view>
</view>
</t-empty>
</view>
@@ -76,7 +77,7 @@
<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>
<text class="patient-age" wx:if="{{item.patientAgeText}}">{{item.patientAgeText}}</text>
</view>
<text class="patient-phone">{{item.patient.mobile || '暂无电话'}}</text>
</view>
+19 -13
View File
@@ -55,7 +55,6 @@ Page({
// 格式化后的显示字段
statusText: '',
statusDesc: '',
patientFirstChar: '',
attendantFirstChar: '',
scheduleDateText: '',
scheduleStartTimeText: '',
@@ -63,7 +62,6 @@ Page({
createtimeText: '',
updatetimeText: '',
paymentStatusText: '',
patientSexText: '',
attendantSexText: '',
sexRequirementText: '',
// 操作按钮配置
@@ -98,11 +96,11 @@ Page({
wx.hideLoading()
if (res.code !== 0) {
wx.showToast({ title: res.message || '加载失败', icon: 'none' })
wx.showToast({ title: res.msg || '加载失败', icon: 'none' })
return
}
const order = res.data || {}
const order = res.data?.record || {}
this.applyOrderData(order)
} catch (err) {
wx.hideLoading()
@@ -113,7 +111,6 @@ Page({
applyOrderData(order) {
const status = order.status || 'pending'
const patient = order.patient || {}
const attendant = order.attendant || {}
const schedule = order.schedule || {}
const payment = order.payment || {}
@@ -123,9 +120,10 @@ Page({
this.setData({
loaded: true,
order: order,
// 有关联档案时交由 patient-info 按 ID 加载(不读缓存,以档案最新数据为准)
healthProfileId: order.healthProfileId || '',
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 || '',
@@ -133,10 +131,10 @@ Page({
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' ? '要求女陪诊' : '不限',
// 操作按钮
// 操作按钮(待确认/已确认状态下可编辑订单信息)
showEditBtn: status === 'pending' || status === 'confirmed',
showConfirmBtn: status === 'pending',
showCancelBtn: status === 'pending',
showStartBtn: status === 'confirmed',
@@ -146,7 +144,8 @@ Page({
// 拨打电话
callPhone(e) {
const phone = e.currentTarget.dataset.phone
// 兼容 patient-info 组件的 call 事件与 data-phone 用法
const phone = (e.detail && e.detail.mobile) || e.currentTarget.dataset.phone
if (!phone) {
wx.showToast({ title: '暂无电话', icon: 'none' })
return
@@ -154,12 +153,19 @@ Page({
wx.makePhoneCall({ phoneNumber: phone })
},
// 编辑订单
onEdit() {
wx.navigateTo({
url: `/pages/order/orderEdit?orderId=${this.data.orderId}`
})
},
// 确认订单
onConfirm() {
wx.showModal({
title: '确认订单',
content: '确认接受此订单?',
confirmColor: '#4c6ef5',
confirmColor: '#1abc9c',
success: (res) => {
if (res.confirm) {
this.updateStatus('confirmed')
@@ -187,7 +193,7 @@ Page({
wx.showModal({
title: '开始服务',
content: '确认开始陪诊服务?',
confirmColor: '#4c6ef5',
confirmColor: '#1abc9c',
success: (res) => {
if (res.confirm) {
this.updateStatus('in_progress')
@@ -201,7 +207,7 @@ Page({
wx.showModal({
title: '完成服务',
content: '确认陪诊服务已完成?',
confirmColor: '#4c6ef5',
confirmColor: '#1abc9c',
success: (res) => {
if (res.confirm) {
this.updateStatus('completed')
@@ -217,7 +223,7 @@ Page({
wx.hideLoading()
if (res.code !== 0) {
wx.showToast({ title: res.message || '操作失败', icon: 'none' })
wx.showToast({ title: res.msg || '操作失败', icon: 'none' })
return
}
+3 -1
View File
@@ -2,5 +2,7 @@
"navigationBarTitleText": "订单详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"usingComponents": {}
"usingComponents": {
"patient-info": "../../components/patient-info/index"
}
}
+9 -17
View File
@@ -18,22 +18,13 @@
<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>
<!-- 订单必须关联健康档案:按档案ID加载(不读缓存,直接请求最新) -->
<patient-info
profile-id="{{healthProfileId}}"
use-cache="{{false}}"
dial="{{true}}"
bind:call="callPhone"
/>
<view class="divider"></view>
<view class="info-row" wx:if="{{order.patient.weight}}">
<text class="info-label">体重</text>
@@ -192,7 +183,8 @@
</view>
<!-- 底部操作 -->
<view class="bottom-actions" wx:if="{{showConfirmBtn || showCancelBtn || showStartBtn || showCompleteBtn}}">
<view class="bottom-actions" wx:if="{{showEditBtn || showConfirmBtn || showCancelBtn || showStartBtn || showCompleteBtn}}">
<view class="action-btn btn-ghost" wx:if="{{showEditBtn}}" bindtap="onEdit">编辑订单</view>
<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>
+24 -52
View File
@@ -1,12 +1,14 @@
/* pages/order/orderDetail.wxss */
page {
background-color: #f7f8fc;
background-color: #f5f6fa;
color: #1a1a2e;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.detail-page {
min-height: 100vh;
background: #f7f8fc;
background: #f5f6fa;
padding-bottom: 120rpx;
}
@@ -87,18 +89,19 @@ page {
}
.section-title {
font-size: 30rpx;
font-size: 32rpx;
font-weight: 700;
color: #1a1a2e;
color: #1a1a1a;
margin-bottom: 16rpx;
margin-left: 8rpx;
letter-spacing: 1rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
border-radius: 24rpx;
padding: 28rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.info-row {
@@ -125,45 +128,8 @@ page {
}
/* === 患者信息 === */
.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;
.info-card patient-info {
display: block;
}
.patient-sex {
@@ -174,8 +140,8 @@ page {
}
.patient-sex.male {
background: rgba(102, 126, 234, 0.1);
color: #667eea;
background: rgba(26, 188, 156, 0.1);
color: #1abc9c;
}
.patient-sex.female {
@@ -194,7 +160,7 @@ page {
}
.call-icon {
color: #667eea;
color: #1abc9c;
font-weight: 500;
}
@@ -215,13 +181,19 @@ page {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
background: linear-gradient(135deg, #2dd36f, #17c3a5);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.avatar-text {
font-size: 32rpx;
font-weight: 700;
color: #fff;
}
.attendant-info {
flex: 1;
display: flex;
@@ -297,13 +269,13 @@ page {
}
.btn-solid {
background: linear-gradient(135deg, #667eea, #764ba2);
background: linear-gradient(135deg, #2dd36f, #17c3a5);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.3);
}
.btn-ghost {
background: #f7f8fc;
background: #f5f6fa;
color: #a0a3bd;
border: 1rpx solid #f0f1f5;
}
+390
View File
@@ -0,0 +1,390 @@
// pages/order/orderEdit.js
const API = require('../../utils/api.js')
const { calcAge } = require('../../utils/format.js')
Page({
/**
* 页面的初始数据
*/
data: {
isEdit: false,
form: {
hospital: { name: '', department: '', doctor: '' },
schedule: { date: '' },
escort: { serviceName: '' },
payment: { totalFee: '' },
notes: { patientNote: '' }
},
profileSummary: null,
// 新增模式下尚未选择档案时为 true,展示选择占位
needPickProfile: false,
// 档案选择弹层
showProfilePicker: false,
profileList: [],
filteredProfiles: [],
profileKeyword: '',
today: '',
submitting: false
},
/**
* 生命周期函数--监听页面加载
* 新增:?id=健康档案ID(患者信息取自档案),不带 id 则进入档案选择
* 编辑:?orderId=订单ID(回填订单数据)
*/
onLoad(options) {
const now = new Date()
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
this.setData({ today })
if (options && options.orderId) {
// 编辑模式
this.orderId = options.orderId
this.setData({ isEdit: true })
wx.setNavigationBarTitle({ title: '编辑订单' })
this.loadOrder(options.orderId)
return
}
wx.setNavigationBarTitle({ title: '新增预约' })
if (!options || !options.id) {
// 未携带档案ID:弹出档案选择
this.setData({ needPickProfile: true })
this.openProfilePicker()
return
}
this.profileId = options.id
// 组件根据档案ID自行加载(缓存优先)
this.setData({ profileId: options.id })
},
/**
* 空方法:阻止弹层内容区域点击冒泡关闭
*/
noop() {},
/**
* 档案选择弹层:打开并加载档案列表
*/
openProfilePicker() {
this.setData({ showProfilePicker: true, profileKeyword: '' })
if (this.data.profileList.length === 0) {
this.loadProfiles()
} else {
this.filterProfiles()
}
},
closeProfilePicker() {
this.setData({ showProfilePicker: false })
},
loadProfiles() {
API.healthProfile.getProfiles({ page: 1, pageSize: 100, sortBy: 'updatetime' })
.then(res => {
if (res.code !== 0) {
return wx.showToast({ title: res.msg || '获取档案失败', icon: 'none' })
}
const list = ((res.data && res.data.list) || []).map(item => {
const p = item.profile || {}
const age = calcAge(p.birth)
return {
id: item._id,
name: p.name || '',
mobile: p.mobile || '',
sexLabel: p.sex === 'male' ? '男' : (p.sex === 'female' ? '女' : ''),
ageText: age ? `${age}` : ''
}
})
this.setData({ profileList: list })
this.filterProfiles()
})
.catch(err => {
console.error('获取健康档案失败', err)
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
})
},
onProfileSearch(e) {
this.setData({ profileKeyword: e.detail.value })
this.filterProfiles()
},
filterProfiles() {
const kw = (this.data.profileKeyword || '').trim()
const list = this.data.profileList
const filtered = kw
? list.filter(item => item.name.includes(kw) || (item.mobile || '').includes(kw))
: list
this.setData({ filteredProfiles: filtered })
},
/**
* 选中档案:加载档案信息并进入表单
*/
onProfileSelect(e) {
const id = e.currentTarget.dataset.id
if (!id) return
this.profileId = id
// 组件根据档案ID自行加载(缓存优先)
this.setData({ profileId: id, needPickProfile: false, showProfilePicker: false })
},
/**
* 编辑模式:加载订单并回填表单
*/
loadOrder(id) {
wx.showLoading({ title: '加载中...' })
API.escort.getRecordById(id)
.then(res => {
wx.hideLoading()
if (res.code !== 0 || !res.data || !res.data.record) {
wx.showToast({ title: res.msg || '订单加载失败', icon: 'none' })
setTimeout(() => wx.navigateBack(), 800)
return
}
const order = res.data.record
this.profileId = order.healthProfileId || null
// 患者信息取自订单快照(作为无关联档案时的回退)
const p = order.patient || {}
this.patientInfo = {
name: p.name || '',
mobile: p.mobile || '',
sex: p.sex || '',
birth: p.birth || '',
idnumber: p.idnumber || ''
}
const sexLabel = p.sex === 'male' ? '男' : (p.sex === 'female' ? '女' : '')
const age = calcAge(p.birth)
this.setData({
// 有关联档案时交由 patient-info 按 ID 加载(不读缓存,以档案最新数据为准)
profileId: order.healthProfileId || '',
profileSummary: {
name: p.name || '',
mobile: p.mobile || '',
sexLabel,
ageText: age ? `${age}` : '',
bloodType: (order.health && order.health.bloodType) || '',
height: (order.health && order.health.height) || '',
weight: (order.health && order.health.weight) || '',
remark: (order.health && order.health.remark) || ''
},
form: {
hospital: {
name: (order.hospital && order.hospital.name) || '',
department: (order.hospital && order.hospital.department) || '',
doctor: (order.hospital && order.hospital.doctor) || ''
},
schedule: { date: this.formatDate(order.schedule && order.schedule.date) },
escort: { serviceName: (order.escort && order.escort.serviceName) || '' },
payment: {
totalFee: order.payment && order.payment.totalFee ? String(order.payment.totalFee) : '',
paid: !!(order.payment && order.payment.status === 'paid')
},
notes: { patientNote: (order.notes && order.notes.patientNote) || '' }
}
})
})
.catch(err => {
console.error('获取订单失败', err)
wx.hideLoading()
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
})
},
/**
* 格式化日期为 YYYY-MM-DD(兼容时间戳/字符串)
*/
formatDate(date) {
if (!date) return ''
const d = new Date(date)
if (isNaN(d.getTime())) return String(date)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
},
/**
* patient-info 组件按档案ID加载完成:保存患者原始信息(供提交)及档案附加信息(供标签展示)
*/
onProfileLoaded(e) {
const profile = e.detail.profile || {}
const p = profile.profile || {}
this.patientInfo = {
name: p.name || '',
mobile: p.mobile || '',
sex: p.sex || '',
birth: p.birth || '',
idnumber: p.idnumber || ''
}
this.setData({
profileSummary: {
bloodType: (profile.health && profile.health.bloodType) || '',
height: (profile.health && profile.health.height) || '',
weight: (profile.health && profile.health.weight) || '',
remark: (profile.health && profile.health.remark) || ''
}
})
},
/**
* 表单输入处理
*/
onInput(e) {
const key = e.currentTarget.dataset.key
const value = e.detail.value
this.setData({ [`form.${key}`]: value })
},
/**
* 就诊日期选择
*/
onDateChange(e) {
this.setData({ 'form.schedule.date': e.detail.value })
},
/**
* 是否已支付切换
*/
onPaidChange(e) {
this.setData({ 'form.payment.paid': e.detail.value })
},
/**
* 提交订单(新增或保存修改)
*/
onSubmit() {
if (this.data.submitting) return
const { form } = this.data
if (!this.profileId) return wx.showToast({ title: '请先选择健康档案', icon: 'none' })
// 患者信息取自档案/订单
const patient = this.patientInfo
if (!patient || !patient.name) return wx.showToast({ title: '健康档案信息未加载', icon: 'none' })
if (!form.hospital.name) return wx.showToast({ title: '请输入医院名称', icon: 'none' })
if (!form.schedule.date) return wx.showToast({ title: '请选择就诊日期', icon: 'none' })
const app = getApp()
const userId = app.globalData.user?._id
if (!userId) return wx.showToast({ title: '未获取到用户信息,请重新登录', icon: 'none' })
if (this.data.isEdit) {
this.updateOrder(form)
} else {
this.createOrder(form, userId)
}
},
/**
* 新增订单
*/
createOrder(form, userId) {
const payload = {
userId,
healthProfileId: this.profileId || null,
patient: this.patientInfo,
hospital: {
name: form.hospital.name,
department: form.hospital.department,
doctor: form.hospital.doctor
},
schedule: {
date: form.schedule.date,
duration: 60
},
escort: {
serviceId: -1,
serviceName: form.escort.serviceName
},
payment: {
totalFee: form.payment.totalFee ? Number(form.payment.totalFee) : 0,
paidFee: 0,
status: 'unpaid'
},
notes: {
patientNote: form.notes.patientNote
}
}
this.setData({ submitting: true })
wx.showLoading({ title: '提交中...' })
API.escort.createRecord(payload)
.then(res => {
wx.hideLoading()
if (res.code !== 0) {
this.setData({ submitting: false })
return wx.showToast({ title: res.msg || '创建失败', icon: 'none' })
}
wx.showToast({ title: '创建成功', icon: 'success' })
setTimeout(() => {
wx.navigateBack()
}, 600)
})
.catch(err => {
console.error('创建订单失败', err)
this.setData({ submitting: false })
wx.hideLoading()
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
})
},
/**
* 编辑模式:保存修改
*/
updateOrder(form) {
const payload = {
'hospital.name': form.hospital.name,
'hospital.department': form.hospital.department,
'hospital.doctor': form.hospital.doctor,
'schedule.date': form.schedule.date,
'escort.serviceName': form.escort.serviceName,
'payment.totalFee': form.payment.totalFee ? Number(form.payment.totalFee) : 0,
'payment.paidFee': form.payment.paid ? (form.payment.totalFee ? Number(form.payment.totalFee) : 0) : 0,
'payment.status': form.payment.paid ? 'paid' : 'unpaid',
'notes.patientNote': form.notes.patientNote
}
this.setData({ submitting: true })
wx.showLoading({ title: '保存中...' })
API.escort.updateRecord(this.orderId, payload)
.then(res => {
wx.hideLoading()
if (res.code !== 0) {
this.setData({ submitting: false })
return wx.showToast({ title: res.msg || '保存失败', icon: 'none' })
}
wx.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => {
wx.navigateBack()
}, 600)
})
.catch(err => {
console.error('更新订单失败', err)
this.setData({ submitting: false })
wx.hideLoading()
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
})
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
+9
View File
@@ -0,0 +1,9 @@
{
"navigationBarTitleText": "新增预约",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#ffffff",
"usingComponents": {
"patient-info": "/components/patient-info/index"
}
}
+316
View File
@@ -0,0 +1,316 @@
/* pages/order/orderEdit.wxss */
page {
background-color: #f5f6fa;
color: #1a1a2e;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.page {
min-height: 100vh;
background: #f5f6fa;
padding: 24rpx;
box-sizing: border-box;
}
.form {
padding-bottom: 40rpx;
}
/* 健康档案简要信息 */
.profile-brief {
background: #ffffff;
border-radius: 24rpx;
padding: 28rpx 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.brief-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 20rpx;
}
.brief-tag {
font-size: 22rpx;
background-color: rgba(26, 188, 156, 0.1);
color: #1abc9c;
border-radius: 16rpx;
padding: 6rpx 16rpx;
}
.brief-remark {
display: block;
margin-top: 16rpx;
font-size: 24rpx;
color: #888888;
line-height: 1.5;
}
/* 未选择档案占位 */
.profile-empty {
display: flex;
align-items: center;
justify-content: space-between;
}
.profile-empty-main {
display: flex;
flex-direction: column;
}
.profile-empty-title {
font-size: 30rpx;
font-weight: 600;
color: #1a1a1a;
}
.profile-empty-sub {
margin-top: 6rpx;
font-size: 24rpx;
color: #888888;
}
.profile-empty-btn {
padding: 14rpx 32rpx;
border-radius: 40rpx;
background-color: rgba(26, 188, 156, 0.1);
color: #1abc9c;
font-size: 26rpx;
font-weight: 600;
}
/* 档案选择弹层 */
.picker-mask {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.45);
z-index: 999;
display: flex;
align-items: flex-end;
}
.picker-sheet {
width: 100%;
max-height: 75vh;
background: #ffffff;
border-radius: 32rpx 32rpx 0 0;
display: flex;
flex-direction: column;
padding-bottom: env(safe-area-inset-bottom);
}
.picker-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 32rpx 0;
}
.picker-title {
font-size: 32rpx;
font-weight: 700;
color: #1a1a1a;
}
.picker-close {
font-size: 32rpx;
color: #999;
padding: 8rpx;
}
.picker-search {
padding: 24rpx 32rpx;
}
.picker-search-input {
width: 100%;
height: 72rpx;
background: #f5f6fa;
border-radius: 36rpx;
padding: 0 32rpx;
box-sizing: border-box;
font-size: 28rpx;
color: #1a1a2e;
}
.picker-list {
flex: 1;
min-height: 0;
max-height: 55vh;
padding: 0 32rpx 32rpx;
box-sizing: border-box;
}
.picker-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f2;
}
.picker-item:last-child {
border-bottom: none;
}
.picker-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: #e6f9f3;
color: #17c3a5;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
flex-shrink: 0;
}
.picker-item-main {
flex: 1;
margin-left: 20rpx;
min-width: 0;
}
.picker-item-name-row {
display: flex;
align-items: center;
}
.picker-item-name {
font-size: 30rpx;
font-weight: 600;
color: #1a1a2e;
}
.picker-item-sub {
margin-left: 12rpx;
font-size: 24rpx;
color: #999;
}
.picker-item-mobile {
display: block;
margin-top: 4rpx;
font-size: 24rpx;
color: #999;
}
.picker-arrow {
color: #c0c4cc;
font-size: 32rpx;
}
.picker-empty {
padding: 60rpx 0;
text-align: center;
font-size: 26rpx;
color: #999;
}
.section {
background: #ffffff;
border-radius: 24rpx;
padding: 0 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 32rpx;
font-weight: 700;
color: #1a1a1a;
padding: 28rpx 0 12rpx;
}
.field {
display: flex;
align-items: center;
min-height: 96rpx;
border-bottom: 1rpx solid #f0f0f2;
}
.field:last-child {
border-bottom: none;
}
.field-label {
width: 160rpx;
flex-shrink: 0;
font-size: 28rpx;
color: #666;
}
.field-input {
flex: 1;
font-size: 28rpx;
color: #1a1a2e;
}
.field-picker {
flex: 1;
}
.field-switch {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
}
.switch-label {
margin-right: 16rpx;
font-size: 26rpx;
&.paid {
color: #17c3a5;
}
&.unpaid {
color: #9ca3af;
}
}
.field-value {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #1a1a2e;
}
.field-value.placeholder {
color: #c0c4cc;
}
.arrow {
color: #c0c4cc;
font-size: 32rpx;
}
.field-textarea {
align-items: flex-start;
padding: 24rpx 0;
}
.field-area {
flex: 1;
min-height: 120rpx;
font-size: 28rpx;
color: #1a1a2e;
}
.submit-btn {
margin-top: 16rpx;
height: 96rpx;
line-height: 96rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
background: linear-gradient(135deg, #2dd36f, #17c3a5);
border-radius: 48rpx;
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.25);
}
+135
View File
@@ -0,0 +1,135 @@
<!--pages/order/orderEdit.wxml-->
<view class="page">
<!-- 未选择档案时的占位提示 -->
<view class="profile-brief profile-empty" wx:if="{{needPickProfile}}" bindtap="openProfilePicker">
<view class="profile-empty-main">
<text class="profile-empty-title">尚未选择健康档案</text>
<text class="profile-empty-sub">患者信息将取自所选档案</text>
</view>
<view class="profile-empty-btn">选择档案</view>
</view>
<!-- 健康档案简要信息 -->
<view class="profile-brief" wx:if="{{profileId || profileSummary}}">
<!-- 有关联档案:统一按档案ID加载(不读缓存,直接请求最新),患者信息以档案为准 -->
<patient-info
wx:if="{{profileId}}"
profile-id="{{profileId}}"
use-cache="{{false}}"
bindloaded="onProfileLoaded"
/>
<!-- 无关联档案(如订单未关联档案):使用订单快照 -->
<patient-info
wx:else
name="{{profileSummary.name}}"
sex-label="{{profileSummary.sexLabel}}"
age-text="{{profileSummary.ageText}}"
mobile="{{profileSummary.mobile}}"
location-text="{{profileSummary.locationText}}"
/>
<view class="brief-tags" wx:if="{{profileSummary.bloodType || profileSummary.height || profileSummary.weight}}">
<text wx:if="{{profileSummary.bloodType}}" class="brief-tag">血型 {{profileSummary.bloodType}}</text>
<text wx:if="{{profileSummary.height}}" class="brief-tag">身高 {{profileSummary.height}}cm</text>
<text wx:if="{{profileSummary.weight}}" class="brief-tag">体重 {{profileSummary.weight}}kg</text>
</view>
<text wx:if="{{profileSummary.remark}}" class="brief-remark">备注:{{profileSummary.remark}}</text>
</view>
<view class="form">
<!-- 就诊信息 -->
<view class="section">
<view class="section-title">就诊信息</view>
<view class="field">
<text class="field-label">医院</text>
<input class="field-input" placeholder="请输入医院名称" value="{{form.hospital.name}}" data-key="hospital.name" bindinput="onInput" />
</view>
<view class="field">
<text class="field-label">科室</text>
<input class="field-input" placeholder="请输入科室" value="{{form.hospital.department}}" data-key="hospital.department" bindinput="onInput" />
</view>
<view class="field">
<text class="field-label">医生姓名</text>
<input class="field-input" placeholder="请输入医生姓名(选填)" value="{{form.hospital.doctor}}" data-key="hospital.doctor" bindinput="onInput" />
</view>
<view class="field">
<text class="field-label">就诊日期</text>
<picker class="field-picker" mode="date" value="{{form.schedule.date}}" start="{{today}}" bindchange="onDateChange">
<view class="field-value {{form.schedule.date ? '' : 'placeholder'}}">
{{form.schedule.date || '请选择就诊日期'}}
<text class="arrow"></text>
</view>
</picker>
</view>
</view>
<!-- 服务信息 -->
<view class="section">
<view class="section-title">服务信息</view>
<view class="field">
<text class="field-label">服务名称</text>
<input class="field-input" placeholder="请输入服务名称" value="{{form.escort.serviceName}}" data-key="escort.serviceName" bindinput="onInput" />
</view>
<view class="field">
<text class="field-label">费用(元)</text>
<input class="field-input" type="digit" placeholder="请输入服务费用" value="{{form.payment.totalFee}}" data-key="payment.totalFee" bindinput="onInput" />
</view>
<view class="field">
<text class="field-label">是否已支付</text>
<view class="field-switch">
<text class="switch-label {{form.payment.paid ? 'paid' : 'unpaid'}}">{{form.payment.paid ? '已支付' : '未支付'}}</text>
<switch checked="{{form.payment.paid}}" color="#17c3a5" bindchange="onPaidChange" />
</view>
</view>
<view class="field field-textarea">
<text class="field-label">备注</text>
<textarea class="field-area" placeholder="请输入备注信息(选填)" value="{{form.notes.patientNote}}" data-key="notes.patientNote" bindinput="onInput" />
</view>
</view>
<!-- 提交 -->
<view class="submit-btn" bindtap="onSubmit">{{isEdit ? '保存修改' : '提交订单'}}</view>
</view>
<!-- 健康档案选择弹层 -->
<view class="picker-mask" wx:if="{{showProfilePicker}}" bindtap="closeProfilePicker">
<view class="picker-sheet" catchtap="noop">
<view class="picker-head">
<text class="picker-title">选择健康档案</text>
<text class="picker-close" bindtap="closeProfilePicker">✕</text>
</view>
<view class="picker-search">
<input class="picker-search-input" placeholder="搜索姓名 / 手机号" value="{{profileKeyword}}" bindinput="onProfileSearch" />
</view>
<scroll-view scroll-y class="picker-list">
<view
class="picker-item"
wx:for="{{filteredProfiles}}"
wx:key="id"
data-id="{{item.id}}"
bindtap="onProfileSelect"
>
<view class="picker-avatar">
<text>{{item.name[0]}}</text>
</view>
<view class="picker-item-main">
<view class="picker-item-name-row">
<text class="picker-item-name">{{item.name}}</text>
<text class="picker-item-sub">{{item.sexLabel}}{{item.sexLabel ? ' · ' : ''}}{{item.ageText}}</text>
</view>
<text class="picker-item-mobile">{{item.mobile || '暂无电话'}}</text>
</view>
<text class="picker-arrow"></text>
</view>
<view class="picker-empty" wx:if="{{filteredProfiles.length === 0}}">未找到匹配的档案</view>
</scroll-view>
</view>
</view>
</view>