基本重构完成

This commit is contained in:
lik
2026-10-07 14:55:06 +08:00
parent f655c906f5
commit f0adf77a41
23 changed files with 1284 additions and 685 deletions
+2 -2
View File
@@ -48,7 +48,7 @@ Page({
// 原「需要空腹的项目,就诊当天请勿进食进水」是替医院下医嘱;
// 要求来源交回医院,与 id 4「请提前了解…要求」同口径
'部分检查要求空腹(禁食禁水),请以医院的检查前通知为准',
'提前与陪诊师确认医院、院区与集合地点'
'提前与陪诊师确认医院、院区、集合地点与时间'
],
// 温馨提示:通用提醒,不涉及具体退款或收费规则
tips: [
@@ -59,7 +59,7 @@ Page({
// 8.2 不应签署具有法律效力的医疗文书
'具有法律效力的医疗文书(手术、麻醉、特殊检查知情同意书等)需您本人或家属签署,陪诊师不代签',
// 全站统一时限,与服务页 notices、协议第 2 节一致(原「请尽早联系客服调整」没给时限,等于缺公示)
'如需改期或取消,请至少提前4小时联系客服'
'如需改期或取消,请至少提前1天联系客服'
]
},
+60 -52
View File
@@ -16,9 +16,11 @@ Page({
totalPrice: '0.00',
showOrderPopup: false,
profiles: [], // 已保存的就诊人
selectedProfileId: '', // 当前选中的就诊人档案 ID,为空表示手动填写
patientName: '',
patientPhone: '',
selectedProfileId: '', // 必填:订单必须关联就诊人档案
patientName: '', // 只读展示,来自所选档案
patientPhone: '', // 只读展示,来自所选档案
// 选中档案的全部信息,下单时整体写入订单 patient 快照
selectedPatient: null,
// 默认北京:取值必须与 provinces[0] 完全一致('北京市' 而非 '北京'),
// 否则 picker 的 value=0 与显示文案会对不上
province: '北京市',
@@ -99,11 +101,12 @@ Page({
return
}
const user = app.globalData.user
// 订单必须关联健康档案,就诊人不再手输:打开弹窗时清空,等用户选择就诊人后带入
this.setData({
patientName: user.profile?.name || '',
patientPhone: user.profile?.mobile || '',
patientName: '',
patientPhone: '',
selectedProfileId: '',
selectedPatient: null,
showOrderPopup: true
})
@@ -120,11 +123,21 @@ Page({
if (res.code !== 0) {
return
}
const list = ((res.data && res.data.list) || []).map((item) => ({
_id: item._id,
name: item.profile?.name || '',
mobile: item.profile?.mobile || ''
}))
// 档案是 profile + health 两层嵌套,这里拍平成选人后可直接带入的字段
const list = ((res.data && res.data.list) || []).map((item) => {
const profile = item.profile || {}
const health = item.health || {}
return {
_id: item._id,
name: profile.name || '',
mobile: profile.mobile || '',
sex: profile.sex || '',
birth: profile.birth || '',
weight: health.weight || 0,
height: health.height || 0,
idnumber: profile.idnumber || ''
}
})
this.setData({
profiles: list
})
@@ -134,7 +147,7 @@ Page({
})
},
// 选择就诊人:自动带入姓名与联系电话
// 选择就诊人:姓名/电话回填展示,其余字段存入 selectedPatient 供下单使用
onSelectProfile(e) {
const id = e.currentTarget.dataset.id
const profile = this.data.profiles.find((item) => item._id === id)
@@ -144,8 +157,16 @@ Page({
this.setData({
selectedProfileId: id,
// patientName / patientPhone 只用于弹窗内只读展示
patientName: profile.name,
patientPhone: profile.mobile
patientPhone: profile.mobile,
// 订单 patient 快照的唯一来源:所选档案的名单字段(体征与证件号不进快照)
selectedPatient: {
name: profile.name,
mobile: profile.mobile,
sex: profile.sex,
birth: profile.birth
}
})
},
@@ -162,21 +183,6 @@ Page({
})
},
// 手动修改姓名/电话后,不再与所选档案对应,清空选择避免存档信息不一致
onPatientNameChange(e) {
this.setData({
patientName: e.detail.value,
selectedProfileId: ''
})
},
onPatientPhoneChange(e) {
this.setData({
patientPhone: e.detail.value,
selectedProfileId: ''
})
},
onProvinceChange(e) {
const index = e.detail.value
const province = this.data.provinces[index]
@@ -228,8 +234,8 @@ Page({
submitOrder() {
const {
service,
patientName,
patientPhone,
selectedProfileId,
selectedPatient,
province,
hospital,
department,
@@ -239,16 +245,10 @@ Page({
remark
} = this.data
if (!patientName.trim()) {
// 订单必须关联就诊人:没有档案就没有 patient 快照,不允许下单
if (!selectedProfileId || !selectedPatient) {
wx.showToast({
title: '请输入就诊人姓名',
icon: 'none'
})
return
}
if (!patientPhone.trim()) {
wx.showToast({
title: '请输入联系电话',
title: '请选择就诊人',
icon: 'none'
})
return
@@ -283,9 +283,9 @@ Page({
}
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(patientPhone.trim())) {
if (!phoneRegex.test(selectedPatient.mobile.trim())) {
wx.showToast({
title: '请输入正确的手机号',
title: '就诊人手机号有误,请到就诊人档案修改',
icon: 'none'
})
return
@@ -297,12 +297,22 @@ Page({
if (gender == '男') sex = 'male'
else if (gender == '女') sex = 'female'
// 就诊人快照只取名单字段(schema 口径:name/mobile/sex/birth);
// sex 是 male/female 枚举,空值不写该字段
const patient = {
name: selectedPatient.name.trim(),
mobile: selectedPatient.mobile.trim(),
birth: selectedPatient.birth || ''
}
if (selectedPatient.sex) {
patient.sex = selectedPatient.sex
}
const orderData = {
userId: user._id,
patient: {
name: patientName.trim(),
mobile: patientPhone.trim(),
},
// 订单与就诊人档案的关联,必填(patient 只是下单当刻的快照)
healthProfileId: selectedProfileId,
patient: patient,
escort: {
serviceId: service.id,
serviceName: service.title,
@@ -325,11 +335,6 @@ Page({
}
}
// 选择了已保存的就诊人时,一并记录档案 ID
if (this.data.selectedProfileId) {
orderData.healthProfileId = this.data.selectedProfileId
}
wx.showLoading({
title: '提交中...'
})
@@ -346,7 +351,8 @@ Page({
showOrderPopup: false
})
this.resetForm()
wx.navigateTo({url: '/pages/escort/recordlist?tab=pending'})
// 下单成功回到记录页,直接落到「进行中的」:新单是待确认,属未完成档
wx.navigateTo({url: '/pages/escort/recordlist?tab=ongoing'})
} else {
wx.showToast({
title: data.msg || '下单失败',
@@ -369,6 +375,7 @@ Page({
selectedProfileId: '',
patientName: '',
patientPhone: '',
selectedPatient: null,
// 重置后仍回到默认北京,与 data 初值保持一致
province: '北京市',
provinceIndex: 0,
@@ -376,7 +383,8 @@ Page({
department: '',
appointmentDate: '',
appointmentTime: '',
gender: '',
// 重置后回到默认「不限」,与 data 初值(gender: '不限' / genderIndex: 0)保持一致
gender: '不限',
genderIndex: 0,
remark: ''
})
+4 -4
View File
@@ -91,7 +91,7 @@
</view>
</view>
<!-- 选择就诊人:从已保存的就诊人一键带入姓名与电话 -->
<!-- 选择就诊人:订单必须关联健康档案,就诊人信息全部由此带入 -->
<view class="order-row column">
<text class="order-label">选择就诊人</text>
<scroll-view class="patient-scroll" scroll-x wx:if="{{profiles.length > 0}}">
@@ -112,16 +112,16 @@
</view>
</view>
<!-- 就诊人姓名 -->
<!-- 就诊人姓名/电话:只读,与所选档案严格一致(要改就去「编辑就诊人」) -->
<view class="order-row">
<text class="order-label">就诊人姓名</text>
<input class="order-input" value="{{patientName}}" placeholder="请输入就诊人姓名" placeholder-class="input-placeholder" bindinput="onPatientNameChange" />
<text class="order-value {{patientName ? '' : 'placeholder'}}">{{patientName || '请先选择就诊人'}}</text>
</view>
<!-- 就诊人电话 -->
<view class="order-row">
<text class="order-label">联系电话</text>
<input class="order-input" value="{{patientPhone}}" placeholder="请输入联系电话" placeholder-class="input-placeholder" type="number" bindinput="onPatientPhoneChange" />
<text class="order-value {{patientPhone ? '' : 'placeholder'}}">{{patientPhone || '请先选择就诊人'}}</text>
</view>
<!-- 就诊省份 -->
+16
View File
@@ -462,6 +462,22 @@ page {
font-weight: 400;
}
/* 只读值(就诊人姓名/电话):与 .order-input 同形,仅去掉输入框语义 */
.order-value {
flex: 1;
min-width: 0;
margin-left: 24rpx;
text-align: right;
font-size: 28rpx;
font-weight: 700;
color: var(--ink);
}
.order-value.placeholder {
color: var(--muted);
font-weight: 400;
}
/* picker 需要占满剩余宽度,值才能靠右对齐 */
.order-picker {
flex: 1;
+253 -91
View File
@@ -3,18 +3,22 @@ const API = require('../../utils/api.js')
// 状态配色遵循 doc/DESIGN.md §三:四档状态色 + 状态说明文字,
// 颜色只做强化,文字才是信息。待确认与已确认同属「等待」阶段,共用待服务档
const STATUS_MAP = {
pending: { text: '待确认', color: '#0F766E', bg: '#ECF7F4', border: '#C7E5DF', desc: '您的订单已提交,等待陪诊员确认' },
confirmed: { text: '已确认', color: '#0F766E', bg: '#ECF7F4', border: '#C7E5DF', desc: '订单已确认,陪诊员将准时为您服务' },
// 待确认是唯一「等用户自己决定」的阶段(底栏此时一定有「取消订单」),
// 说明文字因此带上可执行动作,靠文字而不是靠再叠一层色块来提请注意
pending: { text: '待确认', color: '#0F766E', bg: '#ECF7F4', border: '#C7E5DF', desc: '订单已提交,等待陪诊师确认;确认前可取消' },
confirmed: { text: '已确认', color: '#0F766E', bg: '#ECF7F4', border: '#C7E5DF', desc: '订单已确认,陪诊师将准时为您服务' },
in_progress: { text: '进行中', color: '#B8520A', bg: '#FFF4E8', border: '#FFE0C2', desc: '陪诊服务正在进行中' },
completed: { text: '已完成', color: '#1F7A4D', bg: '#EAF6EE', border: '#CDE9D7', desc: '陪诊服务已完成,感谢您的使用' },
cancelled: { text: '已取消', color: '#B4231A', bg: '#FCF0EE', border: '#F2D6D1', desc: '该订单已取消' }
}
// 支付状态:已支付走「已完成」档;未支付 / 部分支付复用主色浅档提示待处理;
// 已退款走中性档(--muted 字 + --sep 描边 + 透明底)
// 支付状态胶囊:未支付 / 部分支付复用主色浅档提示待处理;已退款走中性档(--muted 字 + --sep 描边 + 透明底);
// 已支付不显示胶囊(支付完成是常态,不需要状态强调),paid 档仅为映射兜底保留。
// hint 是未付清时的出口指引:全站没有线上支付入口,只能引导走客服;
// 部分支付要说清是「补齐」而不是「完成支付」,否则用户会以为要再付一次全款
const PAYMENT_STATUS_MAP = {
unpaid: { text: '未支付', color: '#B8520A', bg: '#FFF4E8', border: '#FFE0C2' },
partial: { text: '部分支付', color: '#B8520A', bg: '#FFF4E8', border: '#FFE0C2' },
unpaid: { text: '未支付', color: '#B8520A', bg: '#FFF4E8', border: '#FFE0C2', hint: '请联系客服完成支付' },
partial: { text: '部分支付', color: '#B8520A', bg: '#FFF4E8', border: '#FFE0C2', hint: '请联系客服补齐剩余费用' },
paid: { text: '已支付', color: '#1F7A4D', bg: '#EAF6EE', border: '#CDE9D7' },
refunded: { text: '已退款', color: '#6F655C', bg: 'transparent', border: '#E5DACC' }
}
@@ -30,22 +34,26 @@ const SEX_REQUIREMENT_MAP = {
female: '女'
}
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
Page({
data: {
recordId: '',
record: {},
loading: true,
editing: false,
// 编辑全部收进底部弹窗:页面只读,表单只活在弹窗里,两个弹窗各自一个开关
showNotePopup: false,
editPatientNote: '',
editingPatient: false,
editPatient: {},
// 医院预约信息编辑弹窗:省份列表与下单页(itemdetail.js)保持同一份口径
showHospitalPopup: false,
provinces: ['北京市', '天津市', '河北省', '山西省', '内蒙古自治区', '辽宁省', '吉林省', '黑龙江省', '上海市', '江苏省', '浙江省', '安徽省', '福建省', '江西省', '山东省', '河南省', '湖北省', '湖南省', '广东省', '广西壮族自治区', '海南省', '重庆市', '四川省', '贵州省', '云南省', '西藏自治区', '陕西省', '甘肃省', '青海省', '宁夏回族自治区', '新疆维吾尔自治区', '台湾省', '香港特别行政区', '澳门特别行政区'],
editHospital: {},
showCancelPopup: false,
cancelReason: '',
cancelReasons: [
'临时有事,无法前往',
'病情好转,无需陪诊',
'医院停诊/改期',
'陪诊员无法到达',
'陪诊师无法到达',
'其他原因'
]
},
@@ -58,9 +66,9 @@ Page({
return
}
this.setData({ recordId: id })
this.loadRecord(id)
},
// onShow 在首次渲染时紧跟 onLoad 触发,加载只放在这里,避免进页面发两次请求
onShow() {
if (this.data.recordId) {
this.loadRecord(this.data.recordId)
@@ -75,13 +83,17 @@ Page({
}
},
// 渲染到位判据是 record._id(WXML 用它守内容),本方法只负责取数
loadRecord(id) {
this.setData({ loading: true })
return API.escort.getRecordById(id)
.then((res) => {
if (res.code === 0 && res.data) {
const record = this.formatRecord(res.data.record)
this.setData({ record })
// 订单关联了健康档案时,就诊人信息以档案最新数据为准
if (record.healthProfileId) {
this.loadProfileSnapshot(record.healthProfileId)
}
} else {
wx.showToast({ title: res.msg || '加载失败', icon: 'none' })
}
@@ -89,60 +101,178 @@ Page({
.catch(() => {
wx.showToast({ title: '网络请求失败', icon: 'none' })
})
.finally(() => {
this.setData({ loading: false })
},
// 用档案最新信息覆盖订单里的 patient 快照:
// 订单必须关联就诊人,档案才是信息源;读取失败(如档案已删除)时保留快照兜底
loadProfileSnapshot(profileId) {
return API.healthProfile.getMyProfileById(profileId)
.then((res) => {
if (res.code !== 0 || !res.data) {
return
}
const detail = res.data.profile || {}
const profile = detail.profile || {}
const health = detail.health || {}
// 与 formatRecord 同一批就诊人字段:档案加载完成后覆盖订单快照,
// 摘要行与首字牌因此需要一起重算
const patient = {
name: profile.name || '',
sexText: SEX_MAP[profile.sex] || '',
age: this.calcAge(profile.birth),
weight: health.weight || 0,
height: health.height || 0,
mobile: profile.mobile || '',
idnumber: profile.idnumber || ''
}
this.setData({
'record.patientName': patient.name,
'record.patientAge': patient.age,
'record.patientWeight': patient.weight,
'record.patientHeight': patient.height,
'record.patientMobile': patient.mobile,
'record.patientIdnumber': patient.idnumber,
'record.patientInitial': patient.name.charAt(0),
'record.patientMeta': this.buildPatientMeta(patient)
})
})
.catch((err) => {
console.log('获取就诊人档案失败,展示订单快照', err)
})
},
formatRecord(item) {
const statusInfo = STATUS_MAP[item.status] || STATUS_MAP.pending
const paymentInfo = PAYMENT_STATUS_MAP[item.payment?.status] || PAYMENT_STATUS_MAP.unpaid
const status = item.status
const paymentStatus = item.payment?.status || 'unpaid'
const statusInfo = STATUS_MAP[status] || STATUS_MAP.pending
const paymentInfo = PAYMENT_STATUS_MAP[paymentStatus] || PAYMENT_STATUS_MAP.unpaid
// 底栏与「修改」入口的口径在这里算一次,WXML 只读结果,不再重复一串状态判断。
// 删除收窄为「未支付 且 未进入服务阶段」:进行中的订单已产生服务成本,不允许直接删
const canCancel = status === 'pending' || status === 'confirmed'
const canDelete = paymentStatus === 'unpaid'
&& (status === 'pending' || status === 'confirmed' || status === 'cancelled')
const canComplete = status === 'completed'
const canRebook = status === 'completed' || status === 'cancelled'
// 终态单据只读:医院预约与陪诊需求不允许回改(就诊人「修改」改的是档案,不在此列)
const canEditOrder = status !== 'completed' && status !== 'cancelled'
// 就诊人字段先落到局部变量:摘要行(patientMeta)与首字牌(patientInitial)
// 都从同一批值派生,口径不在 WXML 与 JS 两处各写一份
const patient = {
name: item.patient?.name || '',
sexText: SEX_MAP[item.patient?.sex] || '',
age: this.calcAge(item.patient?.birth),
weight: item.patient?.weight || 0,
height: item.patient?.height || 0,
mobile: item.patient?.mobile || '',
idnumber: item.patient?.idnumber || ''
}
// 预约时间派生:日期行(带星期)、时间行(时段 + 陪诊时长)
const scheduleMeta = this.buildScheduleMeta(item.schedule || {})
return {
_id: item._id,
patientName: item.patient?.name || '',
patientSexText: SEX_MAP[item.patient?.sex] || '',
patientBirth: item.patient?.birth || '',
patientAge: this.calcAge(item.patient?.birth),
patientWeight: item.patient?.weight || 0,
patientHeight: item.patient?.height || 0,
patientMobile: item.patient?.mobile || '',
patientIdnumber: item.patient?.idnumber || '',
healthProfileId: item.healthProfileId || '',
patientName: patient.name,
patientAge: patient.age,
patientMobile: patient.mobile,
patientIdnumber: patient.idnumber,
patientInitial: patient.name.charAt(0),
patientMeta: this.buildPatientMeta(patient),
hospitalProvince: item.hospital?.province || '',
hospitalName: item.hospital?.name || '',
hospitalAddress: item.hospital?.address || '',
hospitalMeta: this.buildHospitalMeta(item.hospital || {}),
// 以下三个原始字段不进展示(展示走 hospitalMeta),是编辑弹窗的表单回填源
hospitalDepartment: item.hospital?.department || '',
hospitalDoctor: item.hospital?.doctor || '',
medicalRecordNo: item.hospital?.medicalRecordNo || '',
// scheduleDate 保留 YYYY-MM-DD 原格式:编辑弹窗的 date picker 直接喂它
scheduleDate: item.schedule?.date ? this.formatDate(item.schedule.date) : '',
scheduleStartTime: item.schedule?.startTime || '',
scheduleEndTime: item.schedule?.endTime || '',
scheduleDuration: item.schedule?.duration || 0,
scheduleDateText: scheduleMeta.dateText,
scheduleTimeText: scheduleMeta.timeText,
escortServiceName: item.escort?.serviceName || '',
attendantName: item.attendant?.name || '',
attendantMobile: item.attendant?.mobile || '',
sexRequirementText: SEX_REQUIREMENT_MAP[item.escort?.sexRequirement] || '',
totalFee: (item.payment?.totalFee || 0).toFixed(2),
paidFee: (item.payment?.paidFee || 0).toFixed(2),
paymentStatus: item.payment?.status || 'unpaid',
paymentStatus,
paymentStatusText: paymentInfo.text,
paymentStatusColor: paymentInfo.color,
paymentStatusBg: paymentInfo.bg,
paymentStatusBorder: paymentInfo.border,
paymentHint: paymentInfo.hint || '',
patientNote: item.notes?.patientNote || '',
escortNote: item.notes?.escortNote || '',
medicalSummary: item.notes?.medicalSummary || '',
status: item.status,
status,
statusText: statusInfo.text,
statusColor: statusInfo.color,
statusBg: statusInfo.bg,
statusBorder: statusInfo.border,
statusDesc: statusInfo.desc,
canCancel,
canDelete,
canComplete,
canRebook,
canEditOrder,
// 底栏只在确实有按钮时出现,避免无动作状态空留一条白栏
showBottomBar: canCancel || canDelete || canComplete || canRebook,
createTime: item.meta?.createtime ? this.formatDateTime(item.meta.createtime) : '',
updateTime: item.meta?.updatetime ? this.formatDateTime(item.meta.updatetime) : ''
}
},
// 就诊人摘要行的字段序列:空值整项剔除,WXML 只负责在项之间插竖线,
// 不靠一串 && 条件去猜哪条分隔线该出现(避免留下孤立的分隔线)
buildPatientMeta(patient) {
const list = []
if (patient.sexText) list.push(patient.sexText)
if (patient.age) list.push(`${patient.age}岁`)
if (patient.height) list.push(`身高 ${patient.height}cm`)
if (patient.weight) list.push(`体重 ${patient.weight}kg`)
return list
},
// 医院元信息行:科室 / 医生 / 病历号,与就诊人摘要同一套插线逻辑;
// 省份不进元信息行,由 hospitalProvince 单独提到卡内右上角
buildHospitalMeta(hospital) {
const list = []
if (hospital.department) list.push(hospital.department)
if (hospital.doctor) list.push(hospital.doctor)
if (hospital.medicalRecordNo) list.push(`病历号 ${hospital.medicalRecordNo}`)
return list
},
// 预约时间派生:日期行(带星期)、时间行(时段 + 陪诊时长);
// 日期无效或缺失时返回空串,由 WXML 走兜底文案
buildScheduleMeta(schedule) {
const result = { dateText: '', timeText: '' }
if (schedule.date) {
const d = new Date(schedule.date)
if (!isNaN(d.getTime())) {
result.dateText = `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 ${WEEKDAYS[d.getDay()]}`
}
}
const start = schedule.startTime || ''
const end = schedule.endTime || ''
let timeText = start && end ? `${start} - ${end}` : (start ? `${start} 开始` : '')
const duration = schedule.duration || 0
if (timeText && duration > 0) {
const hours = Math.floor(duration / 60)
const minutes = duration % 60
const durationText = hours > 0
? (minutes > 0 ? `${hours}小时${minutes}分钟` : `${hours}小时`)
: `${minutes}分钟`
timeText = `${timeText} · 陪诊${durationText}`
}
result.timeText = timeText
return result
},
// 根据出生年月(YYYY-MM-DD)计算年龄
calcAge(birth) {
if (!birth) return 0
@@ -179,19 +309,11 @@ Page({
wx.makePhoneCall({ phoneNumber: mobile })
},
onCallService() {
wx.makePhoneCall({ phoneNumber: '18618162956' })
},
onCallAttendant() {
const mobile = this.data.record.attendantMobile
if (!mobile || mobile === '--') return
wx.makePhoneCall({ phoneNumber: mobile })
},
// ===== 陪诊需求编辑(底部弹窗) =====
onEditPatientNote() {
this.setData({
editing: true,
showNotePopup: true,
editPatientNote: this.data.record.patientNote || ''
})
},
@@ -201,7 +323,7 @@ Page({
},
onCancelEdit() {
this.setData({ editing: false })
this.setData({ showNotePopup: false })
},
onSavePatientNote() {
@@ -215,7 +337,7 @@ Page({
if (res.code === 0) {
wx.showToast({ title: '保存成功', icon: 'success' })
this.setData({
editing: false,
showNotePopup: false,
'record.patientNote': note
})
} else {
@@ -228,74 +350,108 @@ Page({
})
},
onEditPatientInfo() {
const record = this.data.record
const now = new Date()
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
// ===== 医院预约信息编辑(底部弹窗) =====
// 打开弹窗:用当前展示值填充表单(scheduleDate 已是 YYYY-MM-DD,可直接喂给 date picker)
onEditHospital() {
const r = this.data.record
const provinceIndex = Math.max(0, this.data.provinces.indexOf(r.hospitalProvince))
this.setData({
today,
editingPatient: true,
editPatient: {
name: record.patientName || '',
sex: record.patientSexText === '男' ? 'male' : record.patientSexText === '女' ? 'female' : '',
birth: record.patientBirth || '',
weight: record.patientWeight ? String(record.patientWeight) : '',
height: record.patientHeight ? String(record.patientHeight) : '',
mobile: record.patientMobile || '',
idnumber: record.patientIdnumber || ''
showHospitalPopup: true,
editHospital: {
province: r.hospitalProvince || '',
provinceIndex,
name: r.hospitalName || '',
address: r.hospitalAddress || '',
department: r.hospitalDepartment || '',
doctor: r.hospitalDoctor || '',
medicalRecordNo: r.medicalRecordNo || '',
scheduleDate: r.scheduleDate || '',
startTime: r.scheduleStartTime || '',
endTime: r.scheduleEndTime || ''
}
})
},
onPatientBirthChange(e) {
this.setData({ 'editPatient.birth': e.detail.value })
},
onPatientInfoInput(e) {
// 文本字段统一走 data-field 分发,省掉每字段一个 handler
onHospitalFieldInput(e) {
const field = e.currentTarget.dataset.field
const value = e.detail.value
this.setData({ [`editHospital.${field}`]: e.detail.value })
},
onProvinceChange(e) {
const index = Number(e.detail.value)
this.setData({
[`editPatient.${field}`]: value
'editHospital.provinceIndex': index,
'editHospital.province': this.data.provinces[index]
})
},
onPatientSexChange(e) {
const value = e.currentTarget.dataset.value
this.setData({
'editPatient.sex': value
})
onScheduleDateChange(e) {
this.setData({ 'editHospital.scheduleDate': e.detail.value })
},
onCancelEditPatientInfo() {
this.setData({ editingPatient: false })
onStartTimeChange(e) {
this.setData({ 'editHospital.startTime': e.detail.value })
},
onSavePatientInfo() {
const patient = this.data.editPatient
if (!patient.name.trim()) {
wx.showToast({ title: '请输入姓名', icon: 'none' })
onEndTimeChange(e) {
this.setData({ 'editHospital.endTime': e.detail.value })
},
// 起止时间都在时重算陪诊时长(分钟);结束不晚于开始视为无效,拦在保存前
calcDuration(startTime, endTime) {
if (!startTime || !endTime) return null
const [sh, sm] = startTime.split(':').map(Number)
const [eh, em] = endTime.split(':').map(Number)
const diff = eh * 60 + em - (sh * 60 + sm)
return diff > 0 ? diff : -1
},
onCancelHospitalEdit() {
this.setData({ showHospitalPopup: false })
},
onSaveHospital() {
const form = this.data.editHospital
if (!form.name || !form.name.trim()) {
wx.showToast({ title: '请输入就诊医院', icon: 'none' })
return
}
if (!patient.mobile.trim()) {
wx.showToast({ title: '请输入联系电话', icon: 'none' })
if (!form.scheduleDate) {
wx.showToast({ title: '请选择预约日期', icon: 'none' })
return
}
const duration = this.calcDuration(form.startTime, form.endTime)
if (duration === -1) {
wx.showToast({ title: '结束时间需晚于开始时间', icon: 'none' })
return
}
const update = {
'hospital.province': form.province || '',
'hospital.name': form.name.trim(),
'hospital.address': (form.address || '').trim(),
'hospital.department': (form.department || '').trim(),
'hospital.doctor': (form.doctor || '').trim(),
'hospital.medicalRecordNo': (form.medicalRecordNo || '').trim(),
// schedule.date 后端是 Date 类型,mongoose 会把 YYYY-MM-DD 字符串自动转换
'schedule.date': form.scheduleDate,
'schedule.startTime': form.startTime || '',
'schedule.endTime': form.endTime || ''
}
if (duration !== null) {
update['schedule.duration'] = duration
}
wx.showLoading({ title: '保存中...' })
API.escort.updateRecord(this.data.recordId, {
'patient.name': patient.name.trim(),
'patient.sex': patient.sex,
'patient.birth': patient.birth || '',
'patient.weight': parseFloat(patient.weight) || 0,
'patient.height': parseFloat(patient.height) || 0,
'patient.mobile': patient.mobile.trim(),
'patient.idnumber': patient.idnumber.trim()
})
API.escort.updateRecord(this.data.recordId, update)
.then((res) => {
wx.hideLoading()
if (res.code === 0) {
wx.showToast({ title: '保存成功', icon: 'success' })
this.setData({ editingPatient: false })
this.setData({ showHospitalPopup: false })
// 重新拉取,保证 formatRecord 出的展示字段(日历牌、时长)与库内一致
this.loadRecord(this.data.recordId)
} else {
wx.showToast({ title: res.msg || '保存失败', icon: 'none' })
@@ -307,6 +463,17 @@ Page({
})
},
// 就诊人信息统一在健康档案里维护,这里只负责跳转
onEditProfile() {
const profileId = this.data.record.healthProfileId
if (!profileId) {
return
}
wx.navigateTo({
url: `/pages/health/profileedit?id=${profileId}`
})
},
onCancelOrder() {
this.setData({
showCancelPopup: true,
@@ -349,10 +516,6 @@ Page({
})
},
onPayOrder() {
wx.showToast({ title: '支付功能开发中', icon: 'none' })
},
onConfirmComplete() {
wx.showModal({
title: '确认完成',
@@ -412,7 +575,6 @@ Page({
},
onRebook() {
const serviceId = this.data.record.escortServiceName
wx.navigateTo({
url: `/pages/escort/itemlist`
})
+268 -204
View File
@@ -1,8 +1,10 @@
<view class="page-bg">
<view class="container">
<!-- 数据到位前不渲染内容:record 初始是 {},直接铺开会先闪一帧
「就诊医院待补充 / ¥0.00 / 暂无备注信息」的假数据,用 _id 作到位判据 -->
<view class="container" wx:if="{{record._id}}">
<!-- 状态头:短横色标 + 状态胶囊 + 状态说明。
状态色只包住胶囊文字本身(DESIGN.md §三),去掉旧版渐变横幅与大色块 -->
<!-- 状态头:短横色标 + 状态胶囊 + 状态说明,是本页的「单据抬头」,不走卡片。
状态色只包住胶囊文字本身(DESIGN.md §三),不做整块主色面板 -->
<view class="page-header">
<view class="header-accent"></view>
<view class="status-row">
@@ -10,193 +12,107 @@
class="status-chip status-chip-lg"
style="background: {{record.statusBg}}; color: {{record.statusColor}}; border-color: {{record.statusBorder}};"
>{{record.statusText}}</view>
<text class="header-subtitle">{{record.statusDesc}}</text>
</view>
<text class="header-subtitle">{{record.statusDesc}}</text>
</view>
<!-- 就诊人信息 -->
<!-- 全页信息架构:抬头之下五张同构白卡(就诊人 / 医院预约 / 需求与症状 / 服务与费用 / 订单),
标题在纸面上、内容在白卡里——同一层级只用一种载体。
页面本身纯展示(不铺「标签左/值右」的表单行),所有修改都收进底部弹窗:
卡片回答「是什么」,弹窗回答「改成什么」,两种语境不混在同一版面里 -->
<!-- 就诊人信息:订单关联健康档案,就诊人信息以档案为准,
因此「修改」直接去档案页,不在订单侧另开一套编辑(同一意图只保留一个入口) -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">就诊人信息</text>
<view class="section-link" wx:if="{{!editingPatient}}" bindtap="onEditPatientInfo">
<view class="section-link" wx:if="{{record.healthProfileId}}" bindtap="onEditProfile">
<text class="section-link-text">修改</text>
<t-icon name="edit" size="26rpx" color="#9B4508" />
<t-icon name="edit" size="26rpx" color="#6F655C" />
</view>
</view>
<block wx:if="{{!editingPatient}}">
<view class="info-row">
<text class="info-label">姓名</text>
<text class="info-value">{{record.patientName || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">性别</text>
<text class="info-value">{{record.patientSexText || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">年龄</text>
<text class="info-value">{{record.patientAge ? record.patientAge + '岁' : '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">体重</text>
<text class="info-value">{{record.patientWeight ? record.patientWeight + 'kg' : '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">身高</text>
<text class="info-value">{{record.patientHeight ? record.patientHeight + 'cm' : '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">联系电话</text>
<text class="info-value info-link" bindtap="onCallPatient">{{record.patientMobile || '--'}}</text>
</view>
<view class="info-row" wx:if="{{record.patientIdnumber}}">
<text class="info-label">身份证号</text>
<text class="info-value">{{record.patientIdnumber}}</text>
</view>
</block>
<!-- 身份牌:首字牌定位「是谁」,姓名、体征元信息、证件号收进首字牌右侧纵列;
发丝线之下只留一个动作(拨打) -->
<view class="card patient-card">
<view class="patient-head">
<!-- 首字牌沿用「浅橙衬底承载标识」:有姓名取姓氏首字,无则退到 user 图标 -->
<view class="patient-mark">
<text wx:if="{{record.patientInitial}}" class="patient-mark-text">{{record.patientInitial}}</text>
<t-icon wx:else name="user" size="44rpx" color="#9B4508" />
</view>
<view class="patient-info">
<text class="patient-name">{{record.patientName || '--'}}</text>
<block wx:else>
<view class="edit-row">
<text class="edit-label">姓名</text>
<input class="edit-input" value="{{editPatient.name}}" placeholder="请输入姓名" placeholder-class="input-placeholder" data-field="name" bindinput="onPatientInfoInput" />
</view>
<view class="edit-row">
<text class="edit-label">性别</text>
<view class="edit-radio-group">
<view class="edit-radio {{editPatient.sex === 'male' ? 'active' : ''}}" data-value="male" bindtap="onPatientSexChange">男</view>
<view class="edit-radio {{editPatient.sex === 'female' ? 'active' : ''}}" data-value="female" bindtap="onPatientSexChange">女</view>
<!-- 体征行:空值已在 JS 里整项剔除,这里只按序插竖线 -->
<view class="meta-list patient-meta-row" wx:if="{{record.patientMeta.length > 0}}">
<block wx:for="{{record.patientMeta}}" wx:key="*this">
<view class="meta-sep" wx:if="{{index > 0}}"></view>
<text class="meta-item">{{item}}</text>
</block>
</view>
<text class="patient-id" wx:if="{{record.patientIdnumber}}">身份证号 {{record.patientIdnumber}}</text>
</view>
</view>
<view class="edit-row">
<text class="edit-label">出生年月</text>
<picker class="edit-picker" mode="date" value="{{editPatient.birth}}" end="{{today}}" bindchange="onPatientBirthChange">
<view class="edit-input {{editPatient.birth ? '' : 'placeholder'}}">{{editPatient.birth || '请选择'}}</view>
</picker>
<!-- 发丝线之上是「是谁」,之下是唯一动作:整行可点拨打 -->
<view class="patient-contact" bindtap="onCallPatient">
<t-icon name="call" size="30rpx" color="#9B4508" />
<text class="patient-contact-text">{{record.patientMobile || '--'}}</text>
</view>
<view class="edit-row">
<text class="edit-label">体重(kg)</text>
<input class="edit-input" type="digit" value="{{editPatient.weight}}" placeholder="请输入" placeholder-class="input-placeholder" data-field="weight" bindinput="onPatientInfoInput" />
</view>
<view class="edit-row">
<text class="edit-label">身高(cm)</text>
<input class="edit-input" type="digit" value="{{editPatient.height}}" placeholder="请输入" placeholder-class="input-placeholder" data-field="height" bindinput="onPatientInfoInput" />
</view>
<view class="edit-row">
<text class="edit-label">联系电话</text>
<input class="edit-input" type="number" value="{{editPatient.mobile}}" placeholder="请输入" placeholder-class="input-placeholder" data-field="mobile" bindinput="onPatientInfoInput" />
</view>
<view class="edit-row">
<text class="edit-label">身份证号</text>
<input class="edit-input" value="{{editPatient.idnumber}}" placeholder="请输入" placeholder-class="input-placeholder" data-field="idnumber" bindinput="onPatientInfoInput" />
</view>
<view class="edit-actions">
<view class="btn btn-ghost" bindtap="onCancelEditPatientInfo">取消</view>
<view class="btn btn-primary" bindtap="onSavePatientInfo">保存</view>
</view>
</block>
</view>
</view>
<!-- 医院预约信息 -->
<!-- 医院预约信息:医院名为标题,省份提到标题行右上角,科室/医生/病历号收进元信息行;
预约时间收在卡末发丝线之下——日期左、时段与时长右,一行排开。修改走底部弹窗 -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">医院预约信息</text>
<view class="section-link" wx:if="{{record.canEditOrder}}" bindtap="onEditHospital">
<text class="section-link-text">修改</text>
<t-icon name="edit" size="26rpx" color="#6F655C" />
</view>
</view>
<view class="info-row">
<text class="info-label">就诊省份</text>
<text class="info-value">{{record.hospitalProvince || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">就诊医院</text>
<text class="info-value">{{record.hospitalName || '--'}}</text>
</view>
<view class="info-row" wx:if="{{record.hospitalAddress}}">
<text class="info-label">医院地址</text>
<text class="info-value">{{record.hospitalAddress}}</text>
</view>
<view class="info-row">
<text class="info-label">就诊科室</text>
<text class="info-value">{{record.hospitalDepartment || '--'}}</text>
</view>
<view class="info-row" wx:if="{{record.hospitalDoctor}}">
<text class="info-label">就诊医生</text>
<text class="info-value">{{record.hospitalDoctor}}</text>
</view>
<view class="info-row" wx:if="{{record.medicalRecordNo}}">
<text class="info-label">病历号</text>
<text class="info-value">{{record.medicalRecordNo}}</text>
</view>
<view class="info-row">
<text class="info-label">预约日期</text>
<text class="info-value">{{record.scheduleDate || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">预约时间</text>
<text class="info-value">{{record.scheduleStartTime || '--'}}</text>
</view>
<view class="info-row" wx:if="{{record.scheduleEndTime}}">
<text class="info-label">结束时间</text>
<text class="info-value">{{record.scheduleEndTime}}</text>
</view>
<view class="info-row" wx:if="{{record.scheduleDuration}}">
<text class="info-label">陪诊时长</text>
<text class="info-value">{{record.scheduleDuration}}分钟</text>
<view class="card hospital-card">
<!-- 省份提到右上角,与医院名同一行:名称是主信息,省份降为右侧小字标签 -->
<view class="hospital-head">
<text class="hospital-name {{!record.hospitalName ? 'is-empty' : ''}}">{{record.hospitalName || '就诊医院待补充'}}</text>
<text class="hospital-province" wx:if="{{record.hospitalProvince}}">{{record.hospitalProvince}}</text>
</view>
<view class="meta-list hospital-meta" wx:if="{{record.hospitalMeta.length > 0}}">
<block wx:for="{{record.hospitalMeta}}" wx:key="*this">
<view class="meta-sep" wx:if="{{index > 0}}"></view>
<text class="meta-item">{{item}}</text>
</block>
</view>
<text class="hospital-address" wx:if="{{record.hospitalAddress}}">{{record.hospitalAddress}}</text>
<!-- 预约时间条:日期(带星期)在左、时段与陪诊时长在右,一行排开;空值有兜底文案 -->
<view class="schedule-row">
<text class="schedule-date {{!record.scheduleDateText ? 'is-empty' : ''}}">{{record.scheduleDateText || '预约日期待定'}}</text>
<text class="schedule-time" wx:if="{{record.scheduleTimeText}}">{{record.scheduleTimeText}}</text>
</view>
</view>
</view>
<!-- 陪诊服务 -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">陪诊服务</text>
</view>
<view class="info-row">
<text class="info-label">服务类型</text>
<text class="info-value">{{record.escortServiceName || '陪诊服务'}}</text>
</view>
<view class="info-row" wx:if="{{record.sexRequirementText}}">
<text class="info-label">性别要求</text>
<text class="info-value">{{record.sexRequirementText}}</text>
</view>
<view class="info-row">
<text class="info-label">陪诊员</text>
<text class="info-value">{{record.attendantName || '待分配'}}</text>
</view>
<view class="info-row" wx:if="{{record.attendantMobile}}">
<text class="info-label">陪诊员电话</text>
<text class="info-value info-link" bindtap="onCallAttendant">{{record.attendantMobile}}</text>
</view>
</view>
<!-- 陪诊需求与症状 -->
<!-- 陪诊需求与症状:纯文本块展示,修改走底部弹窗(textarea 在弹窗里) -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">陪诊需求与症状</text>
<view class="section-link" wx:if="{{!editing}}" bindtap="onEditPatientNote">
<view class="section-link" wx:if="{{record.canEditOrder}}" bindtap="onEditPatientNote">
<text class="section-link-text">修改</text>
<t-icon name="edit" size="26rpx" color="#9B4508" />
<t-icon name="edit" size="26rpx" color="#6F655C" />
</view>
</view>
<block wx:if="{{editing}}">
<textarea
class="note-textarea"
value="{{editPatientNote}}"
placeholder="请输入陪诊需求或症状描述"
maxlength="500"
placeholder-class="input-placeholder"
bindinput="onPatientNoteInput"
focus="{{editing}}"
/>
<view class="edit-actions">
<view class="btn btn-ghost" bindtap="onCancelEdit">取消</view>
<view class="btn btn-primary" bindtap="onSavePatientNote">保存</view>
</view>
</block>
<block wx:else>
<view class="card">
<view class="note-block" wx:if="{{record.patientNote}}">
<text class="note-text">{{record.patientNote}}</text>
</view>
@@ -211,88 +127,236 @@
<view class="note-empty" wx:if="{{!record.patientNote && !record.escortNote && !record.medicalSummary}}">
<text class="note-empty-text">暂无备注信息</text>
</view>
</block>
</view>
</view>
<!-- 费用信息 -->
<!-- 服务与费用:服务名称为主、支付状态胶囊收尾同行(仅待处理时显示,已付清不出现);
费用行用字号差做层级(金额是全卡唯一的大字号焦点),不铺清单行;
未付清时在费用行下补一句客服指引(全站没有线上支付入口,本页给不出可点的动作)。
位置收在末尾:前面各块回答「服务什么」(人 → 去哪 → 要什么),
费用与订单号同属「账与单」,连排在最后 -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">费用信息</text>
<text class="section-title">服务与费用</text>
</view>
<view class="info-row">
<text class="info-label">服务费用</text>
<text class="info-value fee-value">¥{{record.totalFee}}</text>
</view>
<view class="info-row">
<text class="info-label">已支付</text>
<text class="info-value">¥{{record.paidFee}}</text>
</view>
<view class="info-row">
<text class="info-label">支付状态</text>
<view
class="status-chip"
style="background: {{record.paymentStatusBg}}; color: {{record.paymentStatusColor}}; border-color: {{record.paymentStatusBorder}};"
>{{record.paymentStatusText}}</view>
<view class="card service-card">
<view class="service-head">
<text class="service-name">{{record.escortServiceName || '陪诊服务'}}</text>
<!-- 支付完成(paid)是常态不显示胶囊;只有待处理(未支付/部分支付/已退款)才提示 -->
<view
class="status-chip"
wx:if="{{record.paymentStatus !== 'paid'}}"
style="background: {{record.paymentStatusBg}}; color: {{record.paymentStatusColor}}; border-color: {{record.paymentStatusBorder}};"
>{{record.paymentStatusText}}</view>
</view>
<view class="meta-list service-meta" wx:if="{{record.sexRequirementText}}">
<text class="meta-item">陪诊师性别要求 {{record.sexRequirementText}}</text>
</view>
<view class="fee-row">
<view class="fee-main">
<text class="fee-label">服务费用</text>
<text class="fee-amount">¥{{record.totalFee}}</text>
</view>
<text class="fee-paid">已支付 ¥{{record.paidFee}}</text>
</view>
<!-- 未付清时的出口指引:全站没有线上支付入口,只能引到客服;
收在费用行之下——「已支付 ¥0.00」之后正是用户想问「怎么付」的位置 -->
<text class="pay-hint" wx:if="{{record.paymentHint}}">{{record.paymentHint}}</text>
</view>
</view>
<!-- 订单信息 -->
<!-- 订单信息:只留编号与时间,订单状态在抬头已出现一次,不重复 -->
<view class="section">
<view class="section-head">
<view class="section-bar"></view>
<text class="section-title">订单信息</text>
</view>
<view class="info-row">
<text class="info-label">订单编号</text>
<text class="info-value order-id">{{record._id || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">订单状态</text>
<view
class="status-chip"
style="background: {{record.statusBg}}; color: {{record.statusColor}}; border-color: {{record.statusBorder}};"
>{{record.statusText}}</view>
</view>
<view class="info-row">
<text class="info-label">创建时间</text>
<text class="info-value">{{record.createTime || '--'}}</text>
</view>
<view class="info-row">
<text class="info-label">更新时间</text>
<text class="info-value">{{record.updateTime || '--'}}</text>
<view class="card order-card">
<text class="order-no">订单编号 {{record._id || '--'}}</text>
<view class="meta-list order-meta">
<text class="meta-item">创建于 {{record.createTime || '--'}}</text>
<view class="meta-sep"></view>
<text class="meta-item">更新于 {{record.updateTime || '--'}}</text>
</view>
</view>
</view>
<!-- 底部占位 -->
<view class="bottom-placeholder"></view>
<!-- 底部占位:只为垫开固定底栏,底栏不出现时不留这段空 -->
<view class="bottom-placeholder" wx:if="{{record.showBottomBar}}"></view>
</view>
<!-- 底部操作栏:同类实心块只保留一处——
再次预约为「已完成」时让位给「确认完成」,为「已取消」时自己成为主操作(§2.1) -->
<view class="bottom-bar">
再次预约为「已完成」时让位给「确认完成」,为「已取消」时自己成为主操作(§2.1)。
四种动作都为假时整条栏不出现:进行中且已支付这类状态没有动作可给,不留空白栏 -->
<view class="bottom-bar" wx:if="{{record.showBottomBar}}">
<view
class="action-btn action-ghost"
wx:if="{{record.status === 'pending' || record.status === 'confirmed'}}"
wx:if="{{record.canCancel}}"
bindtap="onCancelOrder"
>取消订单</view>
<view
class="action-btn action-ghost"
wx:if="{{record.paymentStatus === 'unpaid' && record.status !== 'completed'}}"
class="action-btn action-ghost action-danger"
wx:if="{{record.canDelete}}"
bindtap="onDeleteOrder"
>删除订单</view>
<view
class="action-btn action-primary"
wx:if="{{record.status === 'completed'}}"
wx:if="{{record.canComplete}}"
bindtap="onConfirmComplete"
>确认完成</view>
<view
class="action-btn {{record.status === 'cancelled' ? 'action-primary' : 'action-ghost'}}"
wx:if="{{record.status === 'completed' || record.status === 'cancelled'}}"
wx:if="{{record.canRebook}}"
bindtap="onRebook"
>再次预约</view>
</view>
<!-- 医院预约信息编辑弹窗:页面内嵌表单全部收进这里。
弹窗比取消弹窗高(popup-tall),表单行可滚动 -->
<view class="popup-mask {{showHospitalPopup ? 'show' : ''}}" bindtap="onCancelHospitalEdit"></view>
<view class="popup-content popup-tall {{showHospitalPopup ? 'show' : ''}}">
<view class="popup-header">
<text class="popup-title">修改医院预约信息</text>
<view class="popup-close" bindtap="onCancelHospitalEdit">
<t-icon name="close" size="40rpx" color="#6F655C" />
</view>
</view>
<view class="popup-body">
<view class="form-row">
<text class="form-label">就诊省份</text>
<picker
class="form-picker"
mode="selector"
range="{{provinces}}"
value="{{editHospital.provinceIndex}}"
bindchange="onProvinceChange"
>
<view class="form-picker-value">{{editHospital.province || '请选择省份'}}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">就诊医院</text>
<input
class="form-input"
value="{{editHospital.name}}"
placeholder="请输入医院名称"
placeholder-class="input-placeholder"
bindinput="onHospitalFieldInput"
data-field="name"
/>
</view>
<view class="form-row">
<text class="form-label">医院地址</text>
<input
class="form-input"
value="{{editHospital.address}}"
placeholder="请输入医院地址"
placeholder-class="input-placeholder"
bindinput="onHospitalFieldInput"
data-field="address"
/>
</view>
<view class="form-row">
<text class="form-label">就诊科室</text>
<input
class="form-input"
value="{{editHospital.department}}"
placeholder="请输入就诊科室"
placeholder-class="input-placeholder"
bindinput="onHospitalFieldInput"
data-field="department"
/>
</view>
<view class="form-row">
<text class="form-label">就诊医生</text>
<input
class="form-input"
value="{{editHospital.doctor}}"
placeholder="请输入就诊医生"
placeholder-class="input-placeholder"
bindinput="onHospitalFieldInput"
data-field="doctor"
/>
</view>
<view class="form-row">
<text class="form-label">病历号</text>
<input
class="form-input"
value="{{editHospital.medicalRecordNo}}"
placeholder="请输入病历号"
placeholder-class="input-placeholder"
bindinput="onHospitalFieldInput"
data-field="medicalRecordNo"
/>
</view>
<view class="form-row">
<text class="form-label">预约日期</text>
<picker
class="form-picker"
mode="date"
value="{{editHospital.scheduleDate}}"
bindchange="onScheduleDateChange"
>
<view class="form-picker-value">{{editHospital.scheduleDate || '请选择日期'}}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">开始时间</text>
<picker
class="form-picker"
mode="time"
value="{{editHospital.startTime}}"
bindchange="onStartTimeChange"
>
<view class="form-picker-value">{{editHospital.startTime || '请选择时间'}}</view>
</picker>
</view>
<view class="form-row">
<text class="form-label">结束时间</text>
<picker
class="form-picker"
mode="time"
value="{{editHospital.endTime}}"
bindchange="onEndTimeChange"
>
<view class="form-picker-value">{{editHospital.endTime || '请选择时间'}}</view>
</picker>
</view>
</view>
<view class="popup-footer">
<view class="popup-btn popup-btn-ghost" bindtap="onCancelHospitalEdit">取消</view>
<view class="popup-btn popup-btn-primary" bindtap="onSaveHospital">保存</view>
</view>
</view>
<!-- 陪诊需求编辑弹窗 -->
<view class="popup-mask {{showNotePopup ? 'show' : ''}}" bindtap="onCancelEdit"></view>
<view class="popup-content {{showNotePopup ? 'show' : ''}}">
<view class="popup-header">
<text class="popup-title">修改陪诊需求</text>
<view class="popup-close" bindtap="onCancelEdit">
<t-icon name="close" size="40rpx" color="#6F655C" />
</view>
</view>
<view class="popup-body">
<textarea
class="note-textarea"
value="{{editPatientNote}}"
placeholder="请输入陪诊需求或症状描述"
maxlength="500"
placeholder-class="input-placeholder"
bindinput="onPatientNoteInput"
focus="{{showNotePopup}}"
/>
</view>
<view class="popup-footer">
<view class="popup-btn popup-btn-ghost" bindtap="onCancelEdit">取消</view>
<view class="popup-btn popup-btn-primary" bindtap="onSavePatientNote">保存</view>
</view>
</view>
<!-- 取消订单弹窗 -->
<view class="popup-mask {{showCancelPopup ? 'show' : ''}}" bindtap="onCloseCancelPopup"></view>
<view class="popup-content {{showCancelPopup ? 'show' : ''}}">
@@ -303,7 +367,7 @@
</view>
</view>
<view class="popup-body">
<text class="cancel-tip">确定要取消该陪诊订单吗?取消后陪诊员将收到通知。</text>
<text class="cancel-tip">确定要取消该陪诊订单吗?取消后陪诊师将收到通知。</text>
<view class="cancel-reason-list">
<view
class="cancel-reason-item {{cancelReason === item ? 'active' : ''}}"
@@ -324,4 +388,4 @@
<view class="popup-btn popup-btn-primary" bindtap="onConfirmCancel">确认取消</view>
</view>
</view>
</view>
</view>
+357 -152
View File
@@ -1,12 +1,17 @@
/* pages/escort/recorddetail.wxss
设计方向:本页是一张陪诊订单的详情,按「状态 → 就诊人 → 医院 → 服务 → 需求 → 费用 → 订单」的顺序铺开。
- 配色并入全站暖橙体系(doc/DESIGN.md):令牌来自 app.wxss,本页不再留副本;
设计方向:本页是一张陪诊订单的详情,按「状态抬头 → 就诊人 → 医院预约 → 需求与症状 → 服务与费用 → 订单」铺开。
- 配色并入全站暖橙体系(doc/DESIGN.md):令牌来自 app.wxss,本页不留副本;
状态色只用 §三 四档,旧版渐变横幅与金色体系全部移除
- 头部:短横色标 + 状态胶囊 + 状态说明(内页规范,不做整块主色面板)
- 分区:发丝线切分,标题走统一的 8×34rpx 单竖条 .section-bar
- 表单:标签 --muted + 值 --ink 700;选中项走浅橙底 + 实心橙描边
- 按钮:底栏主操作实心胶囊,其余为空心胶囊;同类实心块全页只保留一处(§2.1)
- 字号阶梯:34 分节标题 / 28 正文 · 表单 / 26 标签 / 24 元信息,最小 24rpx(12px) */
- 抬头:短横色标 + 状态胶囊 + 状态说明,是唯一的非卡片区(内页规范,不做整块主色面板);
下方用 2rpx --line 通栏发丝线收口,靠字号两档差(32 胶囊 / 26 说明)与留白立住,不套第二套容器
- 分区:标题在纸面上(8×32rpx 单竖条),内容统一收进同一种白卡
(§2.3:2rpx --line + 中性阴影 --shadow-card;圆角本页取 24rpx 为例外,见下方 .card 注释)
- 展示去表单化:不铺「标签左/值右」清单行,层级由结构给出——
就诊人用首字牌、医院预约用预约时间条、费用用字号差;元信息一律竖线分隔行
- 编辑全部收进底部弹窗:页面只读,表单只活在弹窗里
- 按钮:底栏与弹窗的主操作实心胶囊、其余空心胶囊,同类实心块全页只保留一处(§2.1)
- 字号阶梯:40 首字牌 / 32 分节标题 · 卡片主名 · 金额 · 状态胶囊 / 28 服务名 · 备注正文 · 预约日期
/ 26 次强 · 「修改」入口 · 预约时段 · 订单号 / 24 元信息 · 已支付 · 支付指引,最小 24rpx(12px) */
page {
background-color: var(--paper);
@@ -23,9 +28,15 @@ page {
padding: 0 32rpx 40rpx;
}
/* ===== 头部状态 ===== */
/* ===== 抬头状态 ===== */
.page-header {
padding-top: 32rpx;
/* 抬头自成一区,靠空间 + 一条线立住,不引入第二套容器语言:
上方留白 40rpx(比卡间距 48rpx 略紧,让抬头贴住顶栏而非浮在中间);
左右负 margin 抵消 .container 的 32rpx 内距,使下方发丝线通栏顶到屏幕两侧,
同时用 padding 把色标与胶囊重新拉回卡片同一条左对齐线 */
margin: 0 -32rpx;
padding: 40rpx 32rpx 28rpx;
border-bottom: 2rpx solid var(--line);
}
.header-accent {
@@ -37,6 +48,8 @@ page {
.status-row {
margin-top: 22rpx;
display: flex;
align-items: center;
}
/* 状态胶囊:底色 + 描边 + 字色三件套,颜色只做强化,文字才是信息(§三) */
@@ -55,42 +68,46 @@ page {
.status-chip-lg {
padding: 8rpx 22rpx;
font-size: 28rpx;
/* 28rpx 抬到 32rpx:与分节标题、卡片主名同档。
同字号下胶囊靠状态底色 + 2rpx 描边 + 内距压过裸字标题,
层级才回到「单据抬头 > 卡片标题」,不再是从前抬头比它统领的内容还小 */
font-size: 32rpx;
}
.header-subtitle {
display: block;
margin-top: 14rpx;
font-size: 28rpx;
margin-left: 20rpx;
/* 28rpx 降到 26rpx:说明退到「次强」档,与 32rpx 的胶囊拉开一档差,
抬头内部才有焦点;此前两者同为 28rpx,只靠字重区分,谁都不是主角 */
font-size: 26rpx;
color: var(--muted);
line-height: 1.6;
letter-spacing: 1rpx;
}
/* ===== 分区:发丝线 + 单竖条标题 ===== */
/* ===== 分区:纸面留白分隔,标题在纸面上、内容在白卡里 ===== */
.section {
margin-top: 44rpx;
padding-top: 30rpx;
border-top: 2rpx solid var(--line);
margin-top: 48rpx;
}
.section-head {
display: flex;
align-items: center;
margin-bottom: 6rpx;
}
.section-bar {
flex-shrink: 0;
width: 8rpx;
height: 34rpx;
/* 色标高度跟随标题字号,缩字号时同步收,保持「短横 + 标题」的等高一档 */
height: 32rpx;
border-radius: 4rpx;
background: var(--brand-strong);
}
/* 34rpx 降一档到 32rpx:与 attendantdetail / agreement / about 的分区标题同一档,
不新增字号档位(DESIGN.md 字号阶梯) */
.section-title {
margin-left: 16rpx;
font-size: 34rpx;
font-size: 32rpx;
font-weight: 700;
color: var(--ink);
letter-spacing: 1rpx;
@@ -108,166 +125,294 @@ page {
opacity: 0.6;
}
/* 「修改」降为中性字:全页三处入口若都用暖橙,会和所在行的标题抢注意力,
一个分组标题下不该有两个同权重焦点。入口靠位置(标题行右端)和热区成立,
不靠颜色——颜色留给底栏主按钮 */
.section-link-text {
margin-right: 4rpx;
font-size: 24rpx;
color: var(--brand-ink);
}
/* ===== 信息行(只读) ===== */
.info-row {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 18rpx 0;
border-bottom: 2rpx solid var(--line);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
flex-shrink: 0;
/* 26rpx 与图标同档:入口降为中性的同时留一点体量,
避免和卡片正文(26rpx)拉开层级后又退到看不清 */
font-size: 26rpx;
color: var(--muted);
}
.info-value {
/* ===== 白卡:全页唯一的容器语言(§2.3 主规则) =====
2rpx --line 发丝边、中性阴影 --shadow-card;
卡内首尾留白由卡片自己的 padding 给。
本页例外(已登记):圆角取 24rpx 而非 §2.3 的 28rpx——
与卡内首字牌(24rpx)同档,圆角家族内部不倒挂;
注意全站其余九页的白卡仍是 28rpx,从订单列表切进本页时卡片会略方一点 */
.card {
box-sizing: border-box;
margin-top: 16rpx;
padding: 20rpx 24rpx;
background: var(--white);
border: 2rpx solid var(--line);
border-radius: 24rpx;
box-shadow: var(--shadow-card);
}
/* ===== 通用元信息行:竖线分隔,空值已在 JS 整项剔除 ===== */
.meta-list {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.meta-item {
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
line-height: 1.6;
}
/* 元信息竖线:2rpx×20rpx,取 --sep(比 --line 深一档,白卡与纸面上都看得见) */
.meta-sep {
flex-shrink: 0;
width: 2rpx;
height: 20rpx;
margin: 0 14rpx;
background: var(--sep);
}
/* ===== 就诊人卡:首字牌 + 元信息行 + 发丝线下的拨打动作 ===== */
.patient-head {
display: flex;
align-items: center;
}
/* 首字牌:浅橙衬底 + --brand-line 描边,与陪诊师列表的兜底头像同源 */
.patient-mark {
box-sizing: border-box;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 88rpx;
height: 88rpx;
border: 2rpx solid var(--brand-line);
border-radius: 24rpx;
background: var(--brand-soft);
}
.patient-mark-text {
font-size: 40rpx;
font-weight: 700;
color: var(--brand-ink);
letter-spacing: 1rpx;
}
/* 首字牌右侧纵列:姓名 / 体征元信息 / 证件号自上而下 */
.patient-info {
flex: 1;
min-width: 0;
margin-left: 24rpx;
text-align: right;
font-size: 28rpx;
color: var(--ink-2);
margin-left: 20rpx;
}
/* 姓名 32rpx/700:低于区块标题 34rpx 一档,不与标题争层级 */
.patient-name {
font-size: 32rpx;
font-weight: 700;
color: var(--ink);
letter-spacing: 1rpx;
}
.patient-meta-row {
margin-top: 10rpx;
}
/* 身份证 18 位,折行而不是撑破卡片 */
.patient-id {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
word-break: break-all;
}
.info-link {
/* 发丝线之上是「是谁」,之下是「怎么联系」;整行即热区 */
.patient-contact {
display: flex;
align-items: center;
margin-top: 22rpx;
padding-top: 20rpx;
border-top: 2rpx solid var(--line);
}
.patient-contact-text {
margin-left: 10rpx;
font-size: 28rpx;
font-weight: 700;
color: var(--brand-ink);
font-weight: 700;
letter-spacing: 1rpx;
}
.fee-value {
.patient-contact:active {
opacity: 0.6;
}
/* ===== 医院预约卡:日历牌是全卡视觉锚点 ===== */
.hospital-head {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.hospital-name {
flex: 1;
min-width: 0;
font-size: 32rpx;
font-weight: 700;
color: var(--ink);
letter-spacing: 1rpx;
line-height: 1.4;
}
.hospital-name.is-empty {
font-weight: 400;
color: var(--muted);
}
.hospital-province {
margin-left: 20rpx;
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
line-height: 1.4;
white-space: nowrap;
}
.hospital-meta {
margin-top: 12rpx;
}
.hospital-address {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
line-height: 1.5;
}
/* 预约时间条:发丝线之上是「去哪看」,之下是「什么时候看」——日期左、时段右 */
.schedule-row {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-top: 22rpx;
padding-top: 20rpx;
border-top: 2rpx solid var(--line);
}
.schedule-date {
flex: 1;
min-width: 0;
font-size: 28rpx;
font-weight: 700;
color: var(--ink);
letter-spacing: 1rpx;
}
.schedule-date.is-empty {
font-weight: 400;
color: var(--muted);
}
.schedule-time {
margin-left: 20rpx;
font-size: 26rpx;
font-weight: 700;
color: var(--brand-ink);
letter-spacing: 1rpx;
white-space: nowrap;
}
/* ===== 服务与费用卡:金额是全卡唯一的大字号焦点 ===== */
.service-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.service-name {
font-size: 28rpx;
font-weight: 700;
color: var(--ink);
letter-spacing: 1rpx;
}
.service-meta {
margin-top: 10rpx;
}
.fee-row {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-top: 18rpx;
padding-top: 18rpx;
border-top: 2rpx solid var(--line);
}
.fee-main {
display: flex;
align-items: baseline;
}
.fee-label {
font-size: 26rpx;
color: var(--muted);
}
.fee-amount {
margin-left: 16rpx;
/* 34rpx 降一档到 32rpx,与分区标题同一档,不新增字号档位;
仍明显高于标签(26rpx)与「已支付」(24rpx),保住金额在卡内的大字号焦点地位 */
font-size: 32rpx;
font-weight: 700;
color: var(--brand-strong);
font-weight: 700;
letter-spacing: 1rpx;
}
.order-id {
.fee-paid {
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
}
/* ===== 表单行(编辑态) ===== */
.edit-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 2rpx solid var(--line);
}
.edit-label {
flex-shrink: 0;
font-size: 28rpx;
/* 未付清时的出口指引:与元信息同一档(24rpx / --muted),只做说明不做按钮——
付款动作发生在客服侧,本页给不出可点的东西,硬做描边按钮会是空动作 */
.pay-hint {
display: block;
margin-top: 12rpx;
font-size: 24rpx;
color: var(--muted);
letter-spacing: 1rpx;
line-height: 1.6;
}
.edit-picker {
flex: 1;
min-width: 0;
margin-left: 24rpx;
}
/* 选择器内层已由 .edit-picker 让出间距,内层值不再重复缩进 */
.edit-picker .edit-input {
margin-left: 0;
}
.edit-input {
flex: 1;
min-width: 0;
margin-left: 24rpx;
font-size: 28rpx;
font-weight: 700;
color: var(--ink);
text-align: right;
}
.edit-input.placeholder {
font-weight: 400;
color: var(--muted);
}
.input-placeholder {
color: var(--muted);
}
.edit-radio-group {
display: flex;
gap: 16rpx;
}
/* 选中项:浅橙底 + 实心橙描边(表单选中语言) */
.edit-radio {
padding: 8rpx 28rpx;
border: 2rpx solid var(--line);
border-radius: 999rpx;
/* ===== 订单卡:编号为主,时间收进元信息行 ===== */
.order-no {
display: block;
font-size: 26rpx;
color: var(--muted);
background: transparent;
color: var(--ink-2);
letter-spacing: 1rpx;
word-break: break-all;
}
.edit-radio.active {
background: var(--brand-soft);
border-color: var(--brand-strong);
color: var(--brand-ink);
font-weight: 700;
.order-meta {
margin-top: 10rpx;
}
/* ===== 按键:空心胶囊 / 实心胶囊 ===== */
.edit-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
margin-top: 28rpx;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 44rpx;
border-radius: 999rpx;
font-size: 28rpx;
line-height: 1.4;
}
.btn:active {
opacity: 0.7;
}
.btn-ghost {
border: 3rpx solid var(--sep);
color: var(--muted);
background: transparent;
}
.btn-primary {
background: var(--brand-strong);
color: var(--white);
font-weight: 700;
box-shadow: var(--shadow-cta);
}
/* ===== 备注 ===== */
/* ===== 备注(只读文本块) ===== */
.note-block {
margin-top: 18rpx;
}
.note-block:first-child {
margin-top: 6rpx;
margin-top: 4rpx;
}
.note-label {
@@ -286,8 +431,7 @@ page {
}
.note-empty {
margin-top: 6rpx;
padding: 24rpx 0;
padding: 6rpx 0;
}
.note-empty-text {
@@ -295,11 +439,52 @@ page {
color: var(--muted);
}
/* ===== 表单行:只活在编辑弹窗里 =====
行结构仍是「标签左、值右、发丝线分隔」,但它是弹窗内的输入语境,不是页面展示语言 */
.form-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 72rpx;
padding: 12rpx 0;
border-bottom: 2rpx solid var(--line);
}
.form-row:last-child {
border-bottom: none;
}
.form-label {
flex-shrink: 0;
font-size: 26rpx;
color: var(--muted);
}
.form-input {
flex: 1;
min-width: 0;
margin-left: 24rpx;
font-size: 28rpx;
color: var(--ink);
text-align: right;
}
.form-picker {
flex: 1;
min-width: 0;
margin-left: 24rpx;
}
.form-picker-value {
font-size: 28rpx;
color: var(--ink);
text-align: right;
}
.note-textarea {
box-sizing: border-box;
width: 100%;
min-height: 200rpx;
margin-top: 6rpx;
padding: 24rpx;
border: 2rpx solid var(--line);
border-radius: 16rpx;
@@ -309,6 +494,10 @@ page {
line-height: 1.7;
}
.input-placeholder {
color: var(--muted);
}
/* ===== 底部占位与操作栏 ===== */
.bottom-placeholder {
height: 160rpx;
@@ -322,7 +511,6 @@ page {
z-index: 100;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16rpx;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
@@ -331,7 +519,10 @@ page {
box-shadow: var(--shadow-bar);
}
/* 按钮等宽铺满:与同页弹窗脚(.popup-btn 的 flex:1)同一套排布,
热区也更大;单个按钮时即整条通栏主操作 */
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
@@ -351,6 +542,12 @@ page {
background: transparent;
}
/* 破坏性动作:只换字色为状态色「已取消」档(#B4231A 已在色板内,不新增色相),
与并排的取消订单区分开——删除不可恢复,确认弹窗也已走同色 */
.action-danger {
color: #B4231A;
}
.action-primary {
background: var(--brand-strong);
color: var(--white);
@@ -358,7 +555,7 @@ page {
box-shadow: var(--shadow-cta);
}
/* ===== 取消订单弹窗 ===== */
/* ===== 底部弹窗(编辑与取消共用一套骨架) ===== */
.popup-mask {
position: fixed;
left: 0;
@@ -392,6 +589,11 @@ page {
transition: transform 0.3s ease-out;
}
/* 医院预约编辑有 9 行表单,弹窗加高 */
.popup-content.popup-tall {
max-height: 86vh;
}
.popup-content.show {
transform: translateY(0);
}
@@ -400,6 +602,7 @@ page {
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
padding: 28rpx 32rpx;
border-bottom: 2rpx solid var(--line);
}
@@ -422,9 +625,10 @@ page {
.popup-body {
flex: 1;
overflow-y: auto;
padding: 28rpx 32rpx;
padding: 20rpx 32rpx 28rpx;
}
/* ===== 取消订单弹窗内的原因单选 ===== */
.cancel-tip {
font-size: 28rpx;
color: var(--muted);
@@ -481,6 +685,7 @@ page {
.popup-footer {
display: flex;
gap: 16rpx;
flex-shrink: 0;
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
border-top: 2rpx solid var(--line);
@@ -510,4 +715,4 @@ page {
background: var(--brand-strong);
color: var(--white);
font-weight: 700;
}
}
+28 -35
View File
@@ -10,6 +10,11 @@ const STATUS_MAP = {
cancelled: { text: '已取消', color: '#B4231A', bg: '#FCF0EE', border: '#F2D6D1' }
}
// 「进行中的」= 未完成全部:待确认 / 已确认 / 进行中 都还没结束,
// 只是处在预约的不同阶段;只有已完成、已取消才算结束。
// 全局筛选只分「全部预约 / 进行中的」两档,卡片上的状态胶囊仍按 STATUS_MAP 逐档显示
const ONGOING_STATUSES = ['pending', 'confirmed', 'in_progress']
const TYPE_MAP = {
outpatient: '门诊陪诊',
inpatient: '住院陪诊',
@@ -41,24 +46,23 @@ Page({
hasMore: true,
page: 1,
pageSize: 10,
statusFilter: '',
// 供筛选条 scroll-into-view 使用,滑动切换后把选中胶囊滚入可视区
tabIntoView: '',
// 默认落在左格「进行中的」:用户进记录页首先要看的是手上还没结束的单子
statusFilter: 'ongoing',
// 跟手位移(px)与拖动标记:拖动中内容随手左右移动,松手后弹回
dragX: 0,
dragging: false,
// 两段等宽:左 = 进行中的,右 = 全部预约;顺序即 index 顺序,滑动切换按 index 递增递减
statusTabs: [
{ value: '', label: '全部', count: 0 },
{ value: 'pending', label: '待确认', count: 0 },
{ value: 'confirmed', label: '已确认', count: 0 },
{ value: 'in_progress', label: '进行中', count: 0 },
{ value: 'completed', label: '已完成', count: 0 }
{ value: 'ongoing', label: '进行中的', count: 0 },
{ value: '', label: '全部预约', count: 0 }
]
},
onLoad(options) {
const tab = options.tab || ''
this.setData({ statusFilter: tab })
// tab 来自 URL,只接受当前两档内的值,避免旧参数(如 tab=pending)把列表筛空
const tab = options.tab || this.data.statusFilter
const valid = this.data.statusTabs.some(item => item.value === tab)
this.setData({ statusFilter: valid ? tab : this.data.statusFilter })
this.loadRecords(true)
},
@@ -96,20 +100,14 @@ Page({
pageSize: this.data.pageSize
}
if (this.data.statusFilter) {
//params.status = this.data.statusFilter
}
return API.escort.getMyRecords(params)
.then((res) => {
if (res.code === 0) {
let list = res.data.records || []
let formatted = list.map(item => this.formatRecord(item))
// 计数按当前页全量算,再按筛选档裁剪展示,保证两档的角标口径一致
const statusTabs = this.computeStatusCounts(formatted, this.data.statusTabs)
// 根据this.data.formatted
if (this.data.statusFilter) {
formatted = formatted.filter(item => item.status === this.data.statusFilter)
}
formatted = formatted.filter(item => this.matchStatusFilter(item, this.data.statusFilter))
this.setData({
records: reset ? formatted : this.data.records.concat(formatted),
page,
@@ -128,19 +126,17 @@ Page({
})
},
// 两档筛选的唯一判定处:'' = 全部预约,'ongoing' = 未完成的三种状态
matchStatusFilter(record, filter) {
if (!filter) return true
if (filter === 'ongoing') return ONGOING_STATUSES.indexOf(record.status) > -1
return record.status === filter
},
computeStatusCounts(records, tabs) {
tabs.forEach(tab => {
tab.count = 0
})
tabs[0].count = records.length
records.forEach(item => {
tabs.forEach(tab => {
if (tab.value === item.status) {
tab.count++
}
})
})
return tabs
return tabs.map(tab => Object.assign({}, tab, {
count: records.filter(item => this.matchStatusFilter(item, tab.value)).length
}))
},
formatRecord(item) {
@@ -187,14 +183,11 @@ Page({
this.switchStatusTab(Number(e.currentTarget.dataset.index))
},
// 统一的状态页切换入口:点胶囊与左右滑动都走这里
// 统一的状态页切换入口:点分段与左右滑动都走这里
switchStatusTab(index) {
const tab = this.data.statusTabs[index]
if (!tab || tab.value === this.data.statusFilter) return
this.setData({
statusFilter: tab.value,
tabIntoView: `tab-${index}`
})
this.setData({ statusFilter: tab.value })
this.loadRecords(true)
},
+21 -28
View File
@@ -1,32 +1,24 @@
<view class="page-bg">
<!-- 状态筛选:吸顶横滑胶囊。
选中态 = 浅橙底 + 2rpx 实心橙描边 + 暖橙字(全页唯一「选中」语言);
未选中 = 纸面安静胶囊(透明底 + 发丝边 + --muted 字)。
胶囊必须带文字标签,颜色只做强化(DESIGN.md §四.8) -->
<!-- 状态筛选:吸顶两段式分段控件。
只有两档,横滑胶囊没有意义(两根胶囊横着摆会显得松散、且左右都不占满),
改为等宽两段:一个白底胶囊容器(发丝边 + 紧阴影)内分两格,
选中格 = 浅橙底 + 暖橙字(全页唯一「选中」语言),未选中格 = --muted 字。
选中格不再单独描边——容器已有发丝边,内层再描一圈会出现双描边;
两段必须带文字标签,颜色只做强化(DESIGN.md §四.8) -->
<view class="tabs-bar">
<!-- scroll-into-view 让左右滑动切换后,选中胶囊自动滚入可视区 -->
<scroll-view
class="tabs-scroll"
scroll-x
scroll-with-animation
scroll-into-view="{{tabIntoView}}"
show-scrollbar="{{false}}"
>
<view class="tabs">
<view
id="tab-{{index}}"
class="tab {{statusFilter === item.value ? 'active' : ''}}"
wx:for="{{statusTabs}}"
wx:key="value"
data-index="{{index}}"
bindtap="onTabChange"
>
<text class="tab-label">{{item.label}}</text>
<text class="tab-count" wx:if="{{item.count > 0}}">{{item.count}}</text>
</view>
<view class="seg">
<view
class="seg-item {{statusFilter === item.value ? 'active' : ''}}"
wx:for="{{statusTabs}}"
wx:key="label"
data-index="{{index}}"
bindtap="onTabChange"
>
<text class="seg-label">{{item.label}}</text>
<text class="seg-count" wx:if="{{item.count > 0}}">{{item.count}}</text>
</view>
</scroll-view>
</view>
</view>
<!-- 记录区承载左右滑动手势:横向拖动时内容跟手平移做反馈,纵向滑动仍交给页面滚动 -->
@@ -96,11 +88,12 @@
</view>
</view>
<!-- 空状态 -->
<!-- 空状态:两档口径不同,文案跟着当前档走,
避免「进行中的」为空时误报成「您还没有预约陪诊服务」 -->
<view class="empty-state" wx:if="{{!loading && records.length === 0}}">
<t-icon name="file-unknown" size="120rpx" color="#A79C92" />
<text class="empty-text">暂无陪诊记录</text>
<text class="empty-subtext">您还没有预约陪诊服务</text>
<text class="empty-text">{{statusFilter ? '暂无进行中的预约' : '暂无陪诊记录'}}</text>
<text class="empty-subtext">{{statusFilter ? '已结束的预约可在「全部预约」里查看' : '您还没有预约陪诊服务'}}</text>
</view>
<!-- 加载中 -->
+40 -32
View File
@@ -2,13 +2,14 @@
设计方向:本页是「一单一卡」的预约记录表,扫读优先。
- 配色并入全站暖橙体系(doc/DESIGN.md):令牌来自 app.wxss,本页不再留副本;
状态色只用 §三 四档,旧的金色体系与全部渐变已移除
- 顶部筛选做成吸顶横滑胶囊:选中态 = 浅橙底 + 2rpx 实心橙描边 + 暖橙字,
未选中 = 纸面安静胶囊;胶囊带文字标签,颜色只做强化(§四.8)
- 顶部筛选做成吸顶两段式分段控件(只有「进行中的 / 全部预约」两档):
白底胶囊容器(发丝边 + 紧阴影)内两格等宽,选中格 = 浅橙底 + 暖橙字,
未选中格 = --muted 字;两格带文字标签,颜色只做强化(§四.8)
- 卡面走「白底 + 发丝边 + 紧阴影」(中性 --shadow-card,28rpx 圆角),
与首页、清单页同一套白面语言
- 卡内四段:类型 + 状态 → 时间 → 就诊信息 → 费用 + 支付状态;
长文本(需求、陪诊记录)交给详情页,保证每张卡一眼看完
- 形状锁定:卡 28rpx、状态胶囊 10rpx、筛选胶囊 999rpx
- 形状锁定:卡 28rpx、状态胶囊 10rpx、分段控件 999rpx
- 左右滑动切换状态页:记录区横向拖动时跟手平移(有阻尼与上限),
松手弹回;拖动中关过渡保即时响应,松手后由过渡做回弹
- 字号阶梯:费用 34 / 类型 30 / 就诊信息 28 / 时间 · 胶囊 24(rpx),
@@ -33,59 +34,66 @@ page {
transition: none;
}
/* ===== 顶部筛选:吸顶横滑 ===== */
/* ===== 顶部筛选:吸顶两段式分段控件 ===== */
.tabs-bar {
position: sticky;
top: 0;
z-index: 10;
padding: 20rpx 0 12rpx;
padding: 20rpx 32rpx 16rpx;
background: var(--paper);
}
.tabs-scroll {
white-space: nowrap;
}
.tabs {
display: inline-flex;
align-items: center;
gap: 16rpx;
padding: 0 32rpx;
}
.tab {
display: inline-flex;
align-items: baseline;
padding: 12rpx 26rpx;
/* 容器走白面语言:白底 + 发丝边 + 紧阴影,与下方记录卡同源,
让筛选条从纸面上拎起来,不再是一排浮在纸面上的空心胶囊 */
.seg {
display: flex;
padding: 6rpx;
border: 2rpx solid var(--line);
border-radius: 999rpx;
background: transparent;
transition: background-color 0.15s ease;
background: var(--white);
box-shadow: var(--shadow-card);
}
.tab:active {
/* 两格等宽:容器内距各 32rpx 后可用宽 622rpx,一格 305rpx,
四字标签(28rpx)+ 角标横向只占约 150rpx,居中后左右留白充足 */
.seg-item {
flex: 1;
display: flex;
align-items: baseline;
justify-content: center;
padding: 14rpx 0;
border-radius: 999rpx;
/* 只过渡颜色:选中格靠底色块平移切换,不做位移动画(两格等宽无需滑块) */
transition: background-color 0.18s ease;
}
.seg-item:active {
opacity: 0.75;
}
.tab-label {
font-size: 26rpx;
.seg-label {
font-size: 28rpx;
color: var(--muted);
}
.tab-count {
margin-left: 6rpx;
.seg-count {
margin-left: 8rpx;
font-size: 24rpx;
color: var(--muted);
}
/* 选中:浅橙底 + 实心橙描边 + 暖橙字(浅橙底 5.96:1,过 AA) */
.tab.active {
/* 选中:浅橙底 + 暖橙字(浅橙底 5.96:1,过 AA)。
内层不描边——容器已有发丝边,再描一圈会成双描边 */
.seg-item.active {
background: var(--brand-soft);
border-color: var(--brand-strong);
}
.tab.active .tab-label,
.tab.active .tab-count {
.seg-item.active .seg-label {
color: var(--brand-ink);
font-weight: 700;
}
.seg-item.active .seg-count {
color: var(--brand-ink);
}