基本重构完成
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
const AIChatSocket = require('./utils/chatmsg.js')
|
||||
const API = require('./utils/api.js')
|
||||
const request = require('./utils/request.js')
|
||||
|
||||
App({
|
||||
onLaunch() {
|
||||
// request 层遇到 code 401(token 已失效)时回调这里:静默重登后自动重试原请求。
|
||||
// 登录逻辑只在 app 里有一份,request 层不碰
|
||||
request.setUnauthorizedHandler(() => this.silentSignin())
|
||||
|
||||
let appId = wx.getStorageSync('attendant-appid')
|
||||
if (!appId) {
|
||||
appId = 'app_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9)
|
||||
@@ -63,39 +68,64 @@ App({
|
||||
this.checkUpdate();
|
||||
},
|
||||
|
||||
onShow(options) {
|
||||
const request = require('./utils/request.js')
|
||||
// 用户主动退出过就不再静默重登:wxsignin 只凭 code 换到 openid 就能重新
|
||||
// 签发 token(api/user/handler/users.js 的 wxSignin),不拦这一下,
|
||||
// 「我的」页的退出登录会被切后台/重进抵消。
|
||||
// 用户在「我的」页重新点登录并授权成功时会清掉这个标记
|
||||
if (wx.getStorageSync('has_logout')) {
|
||||
onShow() {
|
||||
this.silentSignin()
|
||||
this.initChatSocket()
|
||||
return
|
||||
},
|
||||
|
||||
// 静默重登:app.onShow 与 request 层的 401 兜底共用这一个入口
|
||||
// (wxsignin 只凭 code 换到 openid 就能签发 token,见 api/user/handler/users.js)。
|
||||
//
|
||||
// 单飞:并发只发一次——每次 wxsignin 都会重签 token 覆盖上一次,
|
||||
// 两个请求重叠时 DB 里存的是后保存的那个、而 globalData 存的是后返回的那个,
|
||||
// 一旦错位本机就拿着作废的 token,之后所有接口都 401。
|
||||
//
|
||||
// 用户主动退出过(has_logout)就不再静默重登,否则「我的」页的退出登录
|
||||
// 会被切后台/重进抵消;用户在「我的」页重新授权成功时会清掉这个标记
|
||||
silentSignin() {
|
||||
if (wx.getStorageSync('has_logout')) {
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
if (this.signinPromise) {
|
||||
return this.signinPromise
|
||||
}
|
||||
|
||||
this.signinPromise = new Promise((resolve) => {
|
||||
wx.login({
|
||||
success: res => {
|
||||
if (res.code) {
|
||||
request.post('https://api.huashengtec.com/user/wxsignin', {
|
||||
if (!res.code) {
|
||||
console.log('登录失败!' + res.errMsg)
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
API.user.wxSignin({
|
||||
code: res.code
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 0) {
|
||||
this.globalData.user = data.data.user
|
||||
resolve(true)
|
||||
} else {
|
||||
console.log('error:', data)
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('请求失败', err)
|
||||
resolve(false)
|
||||
})
|
||||
} else {
|
||||
console.log('登录失败!' + res.errMsg)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('登录失败!' + err.errMsg)
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}).then((ok) => {
|
||||
this.signinPromise = null
|
||||
return ok
|
||||
})
|
||||
|
||||
this.initChatSocket()
|
||||
return this.signinPromise
|
||||
},
|
||||
|
||||
initChatSocket() {
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
// 由 YYYY-MM-DD 生日换算周岁。
|
||||
// 后端只下发到「日」、不含时分秒——公示场景要的是"多大",不是哪一天生的。
|
||||
// 生日缺失 / 非法 / 算出不合常理(负数或 >120),一律返回 0,模板按 0 隐藏该项
|
||||
function calcAge(birthday) {
|
||||
if (!birthday) {
|
||||
return 0
|
||||
}
|
||||
// 把 '-' 换成 '/' 再解析:小程序 iOS 对 'YYYY-MM-DD HH:mm:ss' 这类格式
|
||||
// 会得到 Invalid Date,'/' 分隔是各端都稳的写法
|
||||
const birth = new Date(String(birthday).replace(/-/g, '/'))
|
||||
if (Number.isNaN(birth.getTime())) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - birth.getFullYear()
|
||||
// 今年生日还没到,减一岁
|
||||
const monthDiff = today.getMonth() - birth.getMonth()
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
||||
age -= 1
|
||||
}
|
||||
|
||||
// 上限 120 是给运营手抖填错年份兜底,否则页面上会出现"年龄 226 岁"
|
||||
if (!Number.isFinite(age) || age < 0 || age > 120) {
|
||||
return 0
|
||||
}
|
||||
return age
|
||||
}
|
||||
|
||||
// 陪诊师详情:数据来自后端公开接口 /health/attendant/:id
|
||||
// 与列表页是同一份白名单数据(不含手机号 / 身份证号 / 证书编号原文),
|
||||
// 差别在于详情页按 GB/T 47801—2026 4.2.1.5 把公示项补全:
|
||||
@@ -55,6 +84,8 @@ Page({
|
||||
initial: name.slice(0, 1),
|
||||
// 后端 sex 为空串表示未登记,模板据此隐藏该行
|
||||
sexText: item.sex === 'male' ? '男' : item.sex === 'female' ? '女' : '',
|
||||
// 周岁由本页现算,未登记生日时为 0,模板据 0 隐藏该行
|
||||
age: calcAge(item.birthday),
|
||||
workYears: item.workYears || 0,
|
||||
orderCount: item.orderCount || 0,
|
||||
qualificationVerified: !!item.qualificationVerified,
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 服务信息:有哪项展示哪项,整行不留空标签 -->
|
||||
<view class="section" wx:if="{{ attendant.sexText || areaText || langText }}">
|
||||
<view class="section" wx:if="{{ attendant.sexText || attendant.age > 0 || areaText || langText }}">
|
||||
<view class="section-head">
|
||||
<view class="section-bar"></view>
|
||||
<text class="section-title">服务信息</text>
|
||||
@@ -68,6 +68,10 @@
|
||||
<text class="info-label">性别</text>
|
||||
<text class="info-value">{{ attendant.sexText }}</text>
|
||||
</view>
|
||||
<view class="info-row" wx:if="{{ attendant.age > 0 }}">
|
||||
<text class="info-label">年龄</text>
|
||||
<text class="info-value">{{ attendant.age }} 岁</text>
|
||||
</view>
|
||||
<view class="info-row" wx:if="{{ areaText }}">
|
||||
<text class="info-label">服务区域</text>
|
||||
<text class="info-value">{{ areaText }}</text>
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
// 陪诊师公示列表:数据来自后端公开接口 /health/attendant
|
||||
// 该接口已做脱敏(不含手机号 / 身份证号 / 证书编号原文),
|
||||
// 前端不再做二次裁剪,只把后端字段整形为展示所需结构
|
||||
// 该接口已做脱敏(不含手机号 / 身份证号 / 证书编号原文,生日只下发到日),
|
||||
// 前端不做二次裁剪,只把后端字段整形为展示所需结构(周岁由生日现算)
|
||||
|
||||
// 由 YYYY-MM-DD 生日换算周岁。
|
||||
// 后端只下发到「日」、不含时分秒——公示场景要的是"多大",不是哪一天生的。
|
||||
// 生日缺失 / 非法 / 算出不合常理(负数或 >120),一律返回 0,模板按 0 隐藏该项
|
||||
function calcAge(birthday) {
|
||||
if (!birthday) {
|
||||
return 0
|
||||
}
|
||||
// 把 '-' 换成 '/' 再解析:小程序 iOS 对 'YYYY-MM-DD HH:mm:ss' 这类格式
|
||||
// 会得到 Invalid Date,'/' 分隔是各端都稳的写法
|
||||
const birth = new Date(String(birthday).replace(/-/g, '/'))
|
||||
if (Number.isNaN(birth.getTime())) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - birth.getFullYear()
|
||||
// 今年生日还没到,减一岁
|
||||
const monthDiff = today.getMonth() - birth.getMonth()
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
||||
age -= 1
|
||||
}
|
||||
|
||||
// 上限 120 是给运营手抖填错年份兜底,否则页面上会出现"年龄 226 岁"
|
||||
if (!Number.isFinite(age) || age < 0 || age > 120) {
|
||||
return 0
|
||||
}
|
||||
return age
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: true,
|
||||
@@ -36,6 +66,8 @@ Page({
|
||||
// 头像兜底用的首字:姓名已脱敏为「姓 + 陪诊师」,取第一个字即可
|
||||
initial: (item.name || '').slice(0, 1),
|
||||
workYears: item.workYears || 0,
|
||||
// 周岁由本页现算,未登记生日时为 0,模板据 0 隐藏该段
|
||||
age: calcAge(item.birthday),
|
||||
orderCount: item.orderCount || 0,
|
||||
qualificationVerified: !!item.qualificationVerified,
|
||||
// 擅长场景最多展示 3 个,避免长尾把卡片撑高;完整列表在详情页
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
</view>
|
||||
<view class="staff-meta">
|
||||
<text class="staff-meta-text">从业 {{ item.workYears }} 年</text>
|
||||
<!-- 年龄未登记时(后端给 0)整段连同分隔线一起隐藏,不留孤立竖线 -->
|
||||
<block wx:if="{{ item.age > 0 }}">
|
||||
<view class="meta-sep"></view>
|
||||
<text class="staff-meta-text">{{ item.age }} 岁</text>
|
||||
</block>
|
||||
<view class="meta-sep"></view>
|
||||
<text class="staff-meta-text">已服务 {{ item.orderCount }} 次</text>
|
||||
</view>
|
||||
|
||||
@@ -48,7 +48,7 @@ Page({
|
||||
// 原「需要空腹的项目,就诊当天请勿进食进水」是替医院下医嘱;
|
||||
// 要求来源交回医院,与 id 4「请提前了解…要求」同口径
|
||||
'部分检查要求空腹(禁食禁水),请以医院的检查前通知为准',
|
||||
'提前与陪诊师确认医院、院区与集合地点'
|
||||
'提前与陪诊师确认医院、院区、集合地点与时间'
|
||||
],
|
||||
// 温馨提示:通用提醒,不涉及具体退款或收费规则
|
||||
tips: [
|
||||
@@ -59,7 +59,7 @@ Page({
|
||||
// 8.2 不应签署具有法律效力的医疗文书
|
||||
'具有法律效力的医疗文书(手术、麻醉、特殊检查知情同意书等)需您本人或家属签署,陪诊师不代签',
|
||||
// 全站统一时限,与服务页 notices、协议第 2 节一致(原「请尽早联系客服调整」没给时限,等于缺公示)
|
||||
'如需改期或取消,请至少提前4小时联系客服'
|
||||
'如需改期或取消,请至少提前1天联系客服'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
+59
-51
@@ -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) => ({
|
||||
// 档案是 profile + health 两层嵌套,这里拍平成选人后可直接带入的字段
|
||||
const list = ((res.data && res.data.list) || []).map((item) => {
|
||||
const profile = item.profile || {}
|
||||
const health = item.health || {}
|
||||
return {
|
||||
_id: item._id,
|
||||
name: item.profile?.name || '',
|
||||
mobile: item.profile?.mobile || ''
|
||||
}))
|
||||
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: ''
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- 就诊省份 -->
|
||||
|
||||
@@ -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
@@ -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`
|
||||
})
|
||||
|
||||
+258
-194
@@ -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>
|
||||
</view>
|
||||
<text class="header-subtitle">{{record.statusDesc}}</text>
|
||||
</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="{{!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 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="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="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>
|
||||
</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>
|
||||
<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>
|
||||
<!-- 体征行:空值已在 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="patient-contact" bindtap="onCallPatient">
|
||||
<t-icon name="call" size="30rpx" color="#9B4508" />
|
||||
<text class="patient-contact-text">{{record.patientMobile || '--'}}</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.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="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="section">
|
||||
<view class="section-head">
|
||||
<view class="section-bar"></view>
|
||||
<text class="section-title">陪诊服务</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="info-row">
|
||||
<text class="info-label">服务类型</text>
|
||||
<text class="info-value">{{record.escortServiceName || '陪诊服务'}}</text>
|
||||
|
||||
<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>
|
||||
<view class="info-row" wx:if="{{record.sexRequirementText}}">
|
||||
<text class="info-label">性别要求</text>
|
||||
<text class="info-value">{{record.sexRequirementText}}</text>
|
||||
|
||||
<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 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="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 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 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>
|
||||
</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' : ''}}"
|
||||
|
||||
+356
-151
@@ -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);
|
||||
|
||||
+28
-35
@@ -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)
|
||||
},
|
||||
|
||||
|
||||
@@ -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 class="seg">
|
||||
<view
|
||||
id="tab-{{index}}"
|
||||
class="tab {{statusFilter === item.value ? 'active' : ''}}"
|
||||
class="seg-item {{statusFilter === item.value ? 'active' : ''}}"
|
||||
wx:for="{{statusTabs}}"
|
||||
wx:key="value"
|
||||
wx:key="label"
|
||||
data-index="{{index}}"
|
||||
bindtap="onTabChange"
|
||||
>
|
||||
<text class="tab-label">{{item.label}}</text>
|
||||
<text class="tab-count" wx:if="{{item.count > 0}}">{{item.count}}</text>
|
||||
<text class="seg-label">{{item.label}}</text>
|
||||
<text class="seg-count" wx:if="{{item.count > 0}}">{{item.count}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-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>
|
||||
|
||||
<!-- 加载中 -->
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
无法与标签同排,故留在本块末尾单独一排——标签在上、文本域在下、字数右下 -->
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<text class="form-label">身高</text>
|
||||
<text class="form-label">身高(cm)</text>
|
||||
<input class="form-input" value="{{form.height}}" type="digit" placeholder="选填,单位 cm" placeholder-class="form-placeholder"
|
||||
maxlength="5" data-field="height" bindinput="onFieldInput" />
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">体重</text>
|
||||
<text class="form-label">体重(kg)</text>
|
||||
<input class="form-input" value="{{form.weight}}" type="digit" placeholder="选填,单位 kg" placeholder-class="form-placeholder"
|
||||
maxlength="5" data-field="weight" bindinput="onFieldInput" />
|
||||
</view>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
<text class="tip-text">下单时可直接选择已保存的就诊人,无需重复填写</text>
|
||||
</view>
|
||||
|
||||
<!-- 就诊人列表:通栏白块(白面 + 上下 2rpx 发丝线 + 中性阴影,圆角 0),
|
||||
与「邮寄地址」列表同源。每条主体可点进编辑(右侧 chevron 是点击暗示),
|
||||
不做无暗示的整卡可点;删除动作单独一行、走 --muted -->
|
||||
<!-- 就诊人列表:一卡一人(白面 + 2rpx 发丝边 + 28rpx 圆角 + 中性紧阴影),
|
||||
与预约记录卡同一配方,卡间留出 24rpx 纸面。
|
||||
每条主体可点进编辑,点击暗示由按下态承担(hover-class="profile-main-hover");
|
||||
删除动作另起一行、与主体隔一条发丝线,走 --muted -->
|
||||
<view class="profile-list" wx:if="{{profiles.length > 0}}">
|
||||
<view
|
||||
class="profile-card {{index === profiles.length - 1 ? 'profile-card-last' : ''}}"
|
||||
class="profile-card"
|
||||
wx:for="{{profiles}}"
|
||||
wx:key="_id"
|
||||
>
|
||||
@@ -39,7 +40,6 @@
|
||||
<text class="info-value">{{item.remark}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<t-icon name="chevron-right" class="profile-arrow" size="32rpx" />
|
||||
</view>
|
||||
|
||||
<view class="profile-actions">
|
||||
|
||||
@@ -37,29 +37,30 @@ page {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===== 通栏白块 =====
|
||||
白面 + 上下各 2rpx 发丝线 + 中性阴影,圆角 0;
|
||||
负向 margin 抵消容器 32rpx 内距后左右顶到屏幕两侧,
|
||||
卡内再用 32rpx 内距回到同一左基线,卡间发丝线因此通到底。
|
||||
圆角必须为 0,否则满宽块的圆角会切在屏幕边上 */
|
||||
/* ===== 就诊人卡片:配方与预约记录卡完全同源 =====
|
||||
--white + 2rpx --line 发丝边 + 28rpx 圆角 + --shadow-card,内距也对齐 28/28/24。
|
||||
原先走的是通栏白块(负 margin 顶到屏幕两侧、圆角 0、卡间靠发丝线分隔),
|
||||
改圆角卡片后负 margin 与上下两条发丝线一并去掉,卡片缩回容器 32rpx 内距里 */
|
||||
.profile-list {
|
||||
margin: 0 -32rpx;
|
||||
background: var(--white);
|
||||
border-top: 2rpx solid var(--line);
|
||||
border-bottom: 2rpx solid var(--line);
|
||||
box-shadow: var(--shadow-card);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 2rpx solid var(--line);
|
||||
padding: 28rpx 28rpx 24rpx;
|
||||
border-radius: 28rpx;
|
||||
background: var(--white);
|
||||
border: 2rpx solid var(--line);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.profile-card-last {
|
||||
border-bottom: none;
|
||||
/* 卡间距用相邻兄弟 margin,不用 flex 的 gap:
|
||||
gap 在部分微信基础库 / 老设备上不生效,一旦失效几张卡会挨在一起连成一条白条 */
|
||||
.profile-card + .profile-card {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
/* 主体整块可点进编辑,右侧 chevron 即点击暗示 */
|
||||
/* 主体整块可点进编辑。chevron 已去掉,点击暗示由按下态(.profile-main-hover)承担 */
|
||||
.profile-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -127,12 +128,6 @@ page {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.profile-arrow {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* 卡片操作行:与主体隔一条发丝线,删除靠右排(与「邮寄地址」卡同一位置) */
|
||||
.profile-actions {
|
||||
display: flex;
|
||||
|
||||
@@ -198,7 +198,9 @@ Page({
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 0) {
|
||||
app.globalData.user = data.data.user
|
||||
// 与 profileedit 同一处坑:/user/update 的响应不含 security.token,
|
||||
// 整替会抹掉本机 token,security 必须保留本地那份
|
||||
app.globalData.user = { ...user, ...data.data.user, security: user.security || {} }
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: data.msg || '保存失败', icon: 'none' })
|
||||
|
||||
+18
-14
@@ -230,29 +230,31 @@ Page({
|
||||
},
|
||||
|
||||
handleLogin(e) {
|
||||
// 微信手机号 code 是一次性凭证(5 分钟内有效、只能用一次),只能在服务端消费一次。
|
||||
// 所以不能先调 /user/wxgetphonenumber 把它用掉、再在 wxSignin 里复用——
|
||||
// 那样第二次向微信换号必然报 invalid code。
|
||||
// wxSignin 内部已做可信的服务端换号(api/user/handler/users.js),直接交给它即可。
|
||||
const phoneCode = e.detail.code ? e.detail.code : ''
|
||||
API.user.wxGetPhoneNumber({ code: phoneCode })
|
||||
.then((data) => {
|
||||
const phoneNumber = data.data.phoneNumber
|
||||
wx.setStorageSync('user_phonenumber', phoneNumber)
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
API.user.wxSignin({ phoneNumber, phoneCode, code: res.code })
|
||||
if (!res.code) {
|
||||
console.log('登录失败!' + res.errMsg)
|
||||
return
|
||||
}
|
||||
API.user.wxSignin({ phoneCode, code: res.code })
|
||||
.then((data) => {
|
||||
if (data.code == 0) {
|
||||
if (data.code !== 0) {
|
||||
// 后端失败(如 code 过期、该手机号已注册等)此前被静默吞掉,
|
||||
// 用户点了授权却毫无反馈,这里补一个提示
|
||||
wx.showToast({ title: data.msg || '登录失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const app = getApp()
|
||||
app.globalData.user = data.data.user
|
||||
wx.setStorageSync('user_phonenumber', data.data.user?.profile?.mobile || '')
|
||||
// 用户重新授权登录,解除对静默重登的拦截
|
||||
wx.removeStorageSync('has_logout')
|
||||
this.loadUserInfo()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('登录失败!' + res.errMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.showToast({ title: '网络请求失败', icon: 'none' })
|
||||
@@ -260,3 +262,5 @@ Page({
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -130,8 +130,11 @@ Page({
|
||||
API.user.update(updateData)
|
||||
.then((data) => {
|
||||
if (data.code === 0) {
|
||||
// 回写全局对象:「我的」页 onShow 依赖它刷新头像行与资料
|
||||
app.globalData.user = data.data.user
|
||||
// 回写全局对象:「我的」页 onShow 依赖它刷新头像行与资料。
|
||||
// 不能整替——/user/update 的响应是脱敏用户(security.token 已被删掉),
|
||||
// 整替会把本机 token 抹掉,之后所有接口都变成未登录。security 保留本地那份
|
||||
const prev = app.globalData.user || {}
|
||||
app.globalData.user = { ...prev, ...data.data.user, security: prev.security || {} }
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
// 返回上一页(「我的」),由它的 onShow 重新读取全局对象
|
||||
wx.navigateBack()
|
||||
|
||||
+7
-4
@@ -1,11 +1,14 @@
|
||||
const request = require('./request.js')
|
||||
// user 服务与 health 服务是两个独立进程(本地 :9010 / :9004,线上才由 nginx 同域分流),
|
||||
// 所以 user 段必须用单独实例,否则本地调试时 user 接口会打到 health 端口上走不通
|
||||
const requestUser = new request.Request(request.USER_BASE_URL)
|
||||
|
||||
const API = {
|
||||
user: {
|
||||
wxGetPhoneNumber: (data) => request.post('/user/wxgetphonenumber', data),
|
||||
wxSignin: (data) => request.post('/user/wxsignin', data),
|
||||
signout: (data) => request.post('/user/signout', data),
|
||||
update: (data) => request.post('/user/update', data),
|
||||
wxGetPhoneNumber: (data) => requestUser.post('/user/wxgetphonenumber', data),
|
||||
wxSignin: (data) => requestUser.post('/user/wxsignin', data),
|
||||
signout: (data) => requestUser.post('/user/signout', data),
|
||||
update: (data) => requestUser.post('/user/update', data),
|
||||
},
|
||||
|
||||
escort: {
|
||||
|
||||
+45
-4
@@ -1,14 +1,50 @@
|
||||
// 本地调试开关:置 true 时直连本机进程(health :9004、user :9010)
|
||||
// 线上由 nginx 按路径分流(/user → 9010、/health → 9004),两个服务共用同一域名
|
||||
const DEBUG = false
|
||||
|
||||
const BASE_URL = DEBUG ? 'http://127.0.0.1:9004' : 'https://api.huashengtec.com'
|
||||
const USER_BASE_URL = DEBUG ? 'http://127.0.0.1:9010' : 'https://api.huashengtec.com'
|
||||
|
||||
class Request {
|
||||
|
||||
constructor(baseURL = 'https://api.huashengtec.com') {
|
||||
//constructor(baseURL = 'http://127.0.0.1:9004') {
|
||||
constructor(baseURL = BASE_URL) {
|
||||
this.baseURL = baseURL
|
||||
// 401 兜底回调,由 app.js 注入(静默重登)。
|
||||
// request 层不直接写登录逻辑:utils/api.js → request.js 是单向依赖,
|
||||
// 反过来引 api.js 会成环
|
||||
this.onUnauthorized = null
|
||||
}
|
||||
|
||||
setUnauthorizedHandler(handler) {
|
||||
this.onUnauthorized = handler
|
||||
}
|
||||
|
||||
request(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { url, method = 'GET', data = {}, header = {}, ...rest } = options
|
||||
return this.send(options).then((body) => this.recoverUnauthorized(body, options))
|
||||
}
|
||||
|
||||
// token 失效时后端返回 HTTP 200 + code 401(api/health/utils/responseUtil.js),
|
||||
// 这里静默重登一次再重试:token 会被别处登录轮换,小程序长时间停在前台
|
||||
// 也不会再触发 app.onShow 的重登,本机 token 因此会变陈旧
|
||||
recoverUnauthorized(body, options) {
|
||||
if (!body || body.code !== 401 || options._retried || !this.onUnauthorized) {
|
||||
return body
|
||||
}
|
||||
|
||||
// 重登失败不当成网络错误抛出去:回一句可执行的提示,
|
||||
// 调用方现有的「res.msg 直接弹 toast」就能显示出来(「登录已失效」是死胡同)
|
||||
const notLoggedIn = { code: 401, msg: '请先登录', data: null }
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() => this.onUnauthorized())
|
||||
.then((ok) => (ok ? this.request({ ...options, _retried: true }) : notLoggedIn))
|
||||
.catch(() => notLoggedIn)
|
||||
}
|
||||
|
||||
send(options) {
|
||||
const { url, method = 'GET', data = {}, header = {}, _retried, ...rest } = options
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const app = getApp()
|
||||
const token = app?.globalData?.user?.security?.token || ''
|
||||
|
||||
@@ -61,4 +97,9 @@ class Request {
|
||||
|
||||
const request = new Request()
|
||||
|
||||
// 默认导出 health 实例(保持既有 require('./request.js') 用法不变);
|
||||
// 类与 user 侧基址一并挂出,供 api.js 派生 user 服务专用实例
|
||||
request.Request = Request
|
||||
request.USER_BASE_URL = USER_BASE_URL
|
||||
|
||||
module.exports = request
|
||||
Reference in New Issue
Block a user