tmp
This commit is contained in:
@@ -28,22 +28,51 @@ App({
|
||||
},
|
||||
|
||||
onShow(options) {
|
||||
// 每次回前台重新登录刷新用户信息;进行中的页面请求会复用同一次登录
|
||||
this.signin();
|
||||
},
|
||||
|
||||
/** 执行登录,返回可复用的 Promise(避免页面请求早于登录完成的竞态) */
|
||||
signin() {
|
||||
if (this._loginPromise) return this._loginPromise;
|
||||
|
||||
this._loginPromise = new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
if (!res.code) {
|
||||
this._loginPromise = null;
|
||||
return reject(new Error('wx.login 未返回 code'));
|
||||
}
|
||||
API.user.wxSignin({ code: res.code })
|
||||
.then((data) => {
|
||||
if (data.code == 0) {
|
||||
this.globalData.user = data.data.user
|
||||
this.eventBus.emit('user-login', data.data.user)
|
||||
this.globalData.user = data.data.user;
|
||||
this.eventBus.emit('user-login', data.data.user);
|
||||
resolve(data.data.user);
|
||||
} else {
|
||||
console.log('登录失败!')
|
||||
this._loginPromise = null;
|
||||
reject(new Error(data.msg || '登录失败'));
|
||||
}
|
||||
})
|
||||
} else {
|
||||
.catch((err) => {
|
||||
this._loginPromise = null;
|
||||
reject(err);
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
this._loginPromise = null;
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
return this._loginPromise;
|
||||
},
|
||||
|
||||
/** 保证已登录:已有用户信息立即返回,否则等待登录完成 */
|
||||
ensureLogin() {
|
||||
if (this.globalData.user) return Promise.resolve(this.globalData.user);
|
||||
return this.signin();
|
||||
},
|
||||
|
||||
/** 全局事件总线 */
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
"pages/set/index",
|
||||
"pages/order/index",
|
||||
"pages/customer/index",
|
||||
"pages/order/orderDetail"
|
||||
"pages/order/orderDetail",
|
||||
"pages/order/orderEdit",
|
||||
"pages/healthprofile/index",
|
||||
"pages/healthprofile/profileInfo",
|
||||
"pages/me/index"
|
||||
],
|
||||
"usingComponents": {
|
||||
"t-toast": "tdesign-miniprogram/toast/toast"
|
||||
},
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"subpackages": [],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
@@ -30,21 +35,9 @@
|
||||
"iconPath": "images/home.png",
|
||||
"selectedIconPath": "images/home-blue.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/order/index",
|
||||
"text": "订单",
|
||||
"iconPath": "images/order.png",
|
||||
"selectedIconPath": "images/order-blue.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/customer/index",
|
||||
"text": "客户",
|
||||
"iconPath": "images/customer.png",
|
||||
"selectedIconPath": "images/customer-blue.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/ai/index",
|
||||
"text": "消息",
|
||||
"text": "AI",
|
||||
"iconPath": "images/chat.png",
|
||||
"selectedIconPath": "images/chat-blue.png"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// components/order-stats/index.js
|
||||
// 首页订单统计卡片:全部完成 / 本月完成 / 未完成
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
Component({
|
||||
data: {
|
||||
allCount: 0,
|
||||
allFee: 0,
|
||||
monthCount: 0,
|
||||
monthFee: 0,
|
||||
uncompletedCount: 0,
|
||||
uncompletedFee: 0
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
this.load()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 拉取订单记录并统计三个口径(数量与费用)
|
||||
* 全部完成:status === 'completed'
|
||||
* 本月完成:completed 且预约日期在本月
|
||||
* 未完成:status !== 'completed'
|
||||
* 费用取 payment.totalFee 合计
|
||||
*/
|
||||
load() {
|
||||
API.escort.getAllRecords({ page: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
if (res.code !== 0) return
|
||||
const records = res.data.records || []
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = now.getMonth()
|
||||
const feeOf = (item) => Number(item.payment && item.payment.totalFee) || 0
|
||||
|
||||
let allCount = 0
|
||||
let allFee = 0
|
||||
let monthCount = 0
|
||||
let monthFee = 0
|
||||
let uncompletedCount = 0
|
||||
let uncompletedFee = 0
|
||||
|
||||
records.forEach(item => {
|
||||
if (item.status === 'completed') {
|
||||
allCount++
|
||||
allFee += feeOf(item)
|
||||
const d = item.schedule && item.schedule.date
|
||||
? new Date(item.schedule.date)
|
||||
: null
|
||||
if (d && !isNaN(d.getTime()) && d.getFullYear() === year && d.getMonth() === month) {
|
||||
monthCount++
|
||||
monthFee += feeOf(item)
|
||||
}
|
||||
} else {
|
||||
uncompletedCount++
|
||||
uncompletedFee += feeOf(item)
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({ allCount, allFee, monthCount, monthFee, uncompletedCount, uncompletedFee })
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取订单统计失败', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<view class="os-section">
|
||||
<view class="os-header">
|
||||
<text class="os-title">订单统计</text>
|
||||
</view>
|
||||
<view class="os-card">
|
||||
<view class="os-item">
|
||||
<text class="os-num green">{{allCount || 0}}</text>
|
||||
<text class="os-label">全部完成</text>
|
||||
<text class="os-fee">¥{{allFee || 0}}</text>
|
||||
</view>
|
||||
<view class="os-divider"></view>
|
||||
<view class="os-item">
|
||||
<text class="os-num blue">{{monthCount || 0}}</text>
|
||||
<text class="os-label">本月完成</text>
|
||||
<text class="os-fee">¥{{monthFee || 0}}</text>
|
||||
</view>
|
||||
<view class="os-divider"></view>
|
||||
<view class="os-item">
|
||||
<text class="os-num orange">{{uncompletedCount || 0}}</text>
|
||||
<text class="os-label">未完成</text>
|
||||
<text class="os-fee">¥{{uncompletedFee || 0}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,109 @@
|
||||
/* components/order-stats/index.wxss */
|
||||
|
||||
.os-section {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.os-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.os-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-left: 8rpx;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.os-card {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f3fbf8 100%);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(23, 195, 165, 0.10);
|
||||
padding: 36rpx 0 32rpx;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左上角装饰色块,呼应首页头部渐变 */
|
||||
.os-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -40rpx;
|
||||
left: -40rpx;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(45, 211, 111, 0.10), rgba(23, 195, 165, 0.10));
|
||||
}
|
||||
|
||||
.os-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.os-num {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
font-family: 'DIN Alternate', -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.os-num.green {
|
||||
color: #2dd36f;
|
||||
}
|
||||
|
||||
.os-num.blue {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.os-num.orange {
|
||||
color: #ff9f43;
|
||||
}
|
||||
|
||||
.os-label {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.os-fee {
|
||||
margin-top: 10rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba(26, 26, 46, 0.05);
|
||||
}
|
||||
|
||||
.os-item:nth-child(1) .os-fee {
|
||||
color: #1ea55c;
|
||||
background: rgba(45, 211, 111, 0.12);
|
||||
}
|
||||
|
||||
.os-item:nth-child(3) .os-fee {
|
||||
color: #3d8fe0;
|
||||
background: rgba(64, 158, 255, 0.12);
|
||||
}
|
||||
|
||||
.os-item:nth-child(5) .os-fee {
|
||||
color: #e08a1e;
|
||||
background: rgba(255, 159, 67, 0.14);
|
||||
}
|
||||
|
||||
.os-divider {
|
||||
width: 1rpx;
|
||||
margin: 8rpx 0;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #e8e8e8 30%, #e8e8e8 70%, rgba(0, 0, 0, 0) 100%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// components/patient-info/index.js
|
||||
// 患者/健康档案两行信息项组件
|
||||
// 第一行:姓名 + 性别(左) · 年龄(右)
|
||||
// 第二行:电话(左,可选拨打) · 所在地(右)
|
||||
//
|
||||
// 两种用法:
|
||||
// 1. 直接传展示字段(name/sexLabel/ageText/mobile/locationText),适用于列表批量渲染
|
||||
// 2. 只传 profile-id,组件内部加载档案:优先读全局缓存(utils/store.js),未命中再请求
|
||||
// 加载完成后触发 loaded 事件(携带档案原始文档),展示字段以加载结果为准
|
||||
const store = require('../../utils/store.js')
|
||||
const { calcAge } = require('../../utils/format.js')
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
name: { type: String, value: '' },
|
||||
sexLabel: { type: String, value: '' },
|
||||
ageText: { type: String, value: '' },
|
||||
mobile: { type: String, value: '' },
|
||||
locationText: { type: String, value: '' },
|
||||
// 是否显示"拨打"并触发 call 事件
|
||||
dial: { type: Boolean, value: false },
|
||||
// 健康档案ID:传入后组件内部加载,展示字段以加载结果为准
|
||||
profileId: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer(newVal) {
|
||||
if (newVal) this.loadProfile(newVal)
|
||||
}
|
||||
},
|
||||
// 是否读取缓存:true 缓存优先(默认),false 直接请求最新数据(请求成功后仍回填缓存)
|
||||
useCache: { type: Boolean, value: true }
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCallTap() {
|
||||
if (!this.data.mobile) return
|
||||
this.triggerEvent('call', { mobile: this.data.mobile })
|
||||
},
|
||||
|
||||
/**
|
||||
* 按档案ID加载:缓存优先,未命中再请求
|
||||
*/
|
||||
loadProfile(id) {
|
||||
const cached = store.getProfile(id)
|
||||
if (cached) {
|
||||
this.applyProfile(cached)
|
||||
return
|
||||
}
|
||||
const API = require('../../utils/api.js')
|
||||
API.healthProfile.getProfileById(id)
|
||||
.then(res => {
|
||||
if (res.code !== 0 || !res.data || !res.data.profile) return
|
||||
const profile = res.data.profile
|
||||
store.cacheProfile(profile)
|
||||
this.applyProfile(profile)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取健康档案失败', err)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 将档案文档映射为展示字段,并抛出 loaded 事件
|
||||
*/
|
||||
applyProfile(profile) {
|
||||
const p = profile.profile || {}
|
||||
const loc = profile.location || {}
|
||||
const age = calcAge(p.birth)
|
||||
const sexLabel = p.sex === 'male' ? '男' : (p.sex === 'female' ? '女' : '')
|
||||
this.setData({
|
||||
name: p.name || '',
|
||||
sexLabel,
|
||||
ageText: age ? `${age}岁` : '',
|
||||
mobile: p.mobile || '',
|
||||
locationText: [loc.province, loc.city].filter(Boolean).join(' ')
|
||||
})
|
||||
this.triggerEvent('loaded', { profile })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<view class="pi-item">
|
||||
<view class="pi-avatar">
|
||||
<text>{{name[0]}}</text>
|
||||
</view>
|
||||
<view class="pi-main">
|
||||
<view class="pi-line">
|
||||
<view class="pi-name-row">
|
||||
<text class="pi-name">{{name}}</text>
|
||||
<text wx:if="{{sexLabel}}" class="pi-sex {{sexLabel === '男' ? 'male' : 'female'}}">{{sexLabel}}</text>
|
||||
</view>
|
||||
<text wx:if="{{ageText}}" class="pi-extra">{{ageText}}</text>
|
||||
</view>
|
||||
<view class="pi-line">
|
||||
<view class="pi-mobile-wrap">
|
||||
<text class="pi-mobile">{{mobile || '暂无电话'}}</text>
|
||||
<text wx:if="{{dial && mobile}}" class="pi-call" catchtap="onCallTap">拨打</text>
|
||||
</view>
|
||||
<text wx:if="{{locationText}}" class="pi-extra">{{locationText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,93 @@
|
||||
.pi-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pi-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #e6f9f3;
|
||||
color: #1abc9c;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pi-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.pi-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pi-line + .pi-line {
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.pi-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pi-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.pi-sex {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pi-sex.male {
|
||||
background: rgba(26, 188, 156, 0.1);
|
||||
color: #1abc9c;
|
||||
}
|
||||
|
||||
.pi-sex.female {
|
||||
background: rgba(234, 102, 150, 0.1);
|
||||
color: #ea6696;
|
||||
}
|
||||
|
||||
.pi-mobile-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pi-mobile {
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.pi-call {
|
||||
font-size: 24rpx;
|
||||
color: #1abc9c;
|
||||
font-weight: 500;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.pi-extra {
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
+11
-11
@@ -60,10 +60,10 @@ page {
|
||||
|
||||
.quick-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #4c6ef5;
|
||||
color: #1abc9c;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
text-align: center;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
@@ -72,7 +72,7 @@ page {
|
||||
|
||||
.quick-item:active {
|
||||
transform: scale(0.98);
|
||||
background-color: rgba(76, 110, 245, 0.05);
|
||||
background-color: rgba(26, 188, 156, 0.05);
|
||||
}
|
||||
|
||||
.message-item {
|
||||
@@ -118,8 +118,8 @@ page {
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
box-shadow: 0 4rpx 12rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
|
||||
.ai-avatar {
|
||||
@@ -157,10 +157,10 @@ page {
|
||||
}
|
||||
|
||||
.user-bubble {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.2);
|
||||
box-shadow: 0 4rpx 12rpx rgba(23, 195, 165, 0.2);
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
@@ -315,8 +315,8 @@ page {
|
||||
|
||||
.chat-toolbar-action:active {
|
||||
transform: scale(0.96);
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
border-color: #4c6ef5;
|
||||
background-color: rgba(26, 188, 156, 0.08);
|
||||
border-color: #1abc9c;
|
||||
}
|
||||
|
||||
.chat-toolbar-action text {
|
||||
@@ -351,8 +351,8 @@ page {
|
||||
}
|
||||
|
||||
.send-btn.active {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
box-shadow: 0 4rpx 12rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
|
||||
.send-text {
|
||||
|
||||
+12
-12
@@ -4,15 +4,15 @@
|
||||
@bg-primary: #f5f6fa;
|
||||
@bg-secondary: #ffffff;
|
||||
@bg-card: #ffffff;
|
||||
@accent-primary: #4c6ef5;
|
||||
@accent-secondary: #6b7aff;
|
||||
@accent-gradient-start: #4c6ef5;
|
||||
@accent-gradient-end: #748ffc;
|
||||
@accent-primary: #1abc9c;
|
||||
@accent-secondary: #17c3a5;
|
||||
@accent-gradient-start: #2dd36f;
|
||||
@accent-gradient-end: #17c3a5;
|
||||
@text-primary: #1a1a2e;
|
||||
@text-secondary: #6b7280;
|
||||
@text-muted: #9ca3af;
|
||||
@border-color: #e5e7eb;
|
||||
@male-color: #4c6ef5;
|
||||
@male-color: #1abc9c;
|
||||
@female-color: #ff6b6b;
|
||||
@divider-color: #f3f4f6;
|
||||
|
||||
@@ -77,7 +77,7 @@ page {
|
||||
font-weight: 500;
|
||||
border-radius: 32rpx;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
|
||||
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.25);
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
@@ -215,10 +215,10 @@ page {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
box-shadow: 0 4rpx 12rpx rgba(23, 195, 165, 0.25);
|
||||
|
||||
&.male {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
}
|
||||
|
||||
&.female {
|
||||
@@ -263,7 +263,7 @@ page {
|
||||
font-weight: 500;
|
||||
|
||||
&.male {
|
||||
background-color: rgba(76, 110, 245, 0.1);
|
||||
background-color: rgba(26, 188, 156, 0.1);
|
||||
color: @male-color;
|
||||
}
|
||||
|
||||
@@ -301,16 +301,16 @@ page {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
background-color: rgba(26, 188, 156, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(76, 110, 245, 0.15);
|
||||
background-color: rgba(26, 188, 156, 0.15);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.call {
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
background-color: rgba(26, 188, 156, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<!-- 操作按钮 -->
|
||||
<view class="customer-action">
|
||||
<view class="action-btn call" data-phone="{{item.mobile}}" catchtap="onCallPhone">
|
||||
<t-icon name="call" size="32rpx" color="#4c6ef5" />
|
||||
<t-icon name="call" size="32rpx" color="#1abc9c" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// pages/healthprofile/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
const { calcAge } = require('../../utils/format.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
totalCount: 0,
|
||||
profileList: [],
|
||||
quickMenus: [
|
||||
{ icon: 'view-list', title: '全部档案' },
|
||||
{ icon: 'add', title: '新建档案' },
|
||||
{ icon: 'search', title: '患者查询' },
|
||||
{ icon: 'time', title: '就诊记录' }
|
||||
]
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载档案总数与最近更新的档案列表(按修改时间排序,最多20条)
|
||||
*/
|
||||
loadData() {
|
||||
API.healthProfile.getProfiles({ page: 1, pageSize: 20, sortBy: 'updatetime' })
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.msg || '获取档案失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
const list = (data.list || []).map(item => {
|
||||
const age = calcAge(item.profile.birth)
|
||||
const loc = item.location || {}
|
||||
return {
|
||||
id: item._id,
|
||||
name: item.profile.name || '',
|
||||
mobile: item.profile.mobile || '',
|
||||
sexLabel: item.profile.sex === 'male' ? '男' : (item.profile.sex === 'female' ? '女' : ''),
|
||||
ageText: age ? `${age}岁` : '',
|
||||
locationText: [loc.province, loc.city].filter(Boolean).join(' ')
|
||||
}
|
||||
})
|
||||
this.setData({ totalCount: data.total || 0, profileList: list })
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取健康档案失败', err)
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
onMenuTap(e) {
|
||||
const { index } = e.currentTarget.dataset
|
||||
const item = this.data.quickMenus[index]
|
||||
if (item.title === '新建档案') {
|
||||
wx.navigateTo({ url: '/pages/healthprofile/profileInfo' })
|
||||
return
|
||||
}
|
||||
if (item.title === '全部档案') {
|
||||
// 列表已展示在本页,暂不跳转
|
||||
wx.showToast({ title: '档案列表见下方', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showToast({
|
||||
title: `${item.title} 开发中`,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击档案 → 编辑/删除
|
||||
*/
|
||||
onProfileTap(e) {
|
||||
const { id } = e.currentTarget.dataset
|
||||
wx.navigateTo({ url: `/pages/healthprofile/profileInfo?id=${id}` })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "健康档案",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"patient-info": "../../components/patient-info/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* pages/healthprofile/index.less */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.profile-page {
|
||||
padding: 24rpx;
|
||||
padding-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 头部统计卡 */
|
||||
.header-card {
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-center {
|
||||
margin-top: 36rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 8rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
font-size: 26rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 常用功能 */
|
||||
.section {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.quick-grid {
|
||||
margin-top: 16rpx;
|
||||
padding: 24rpx 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.quick-item:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: #e6f9f3;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.quick-label {
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 健康档案列表 */
|
||||
.profile-list {
|
||||
margin-top: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
|
||||
patient-info {
|
||||
display: block;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--pages/healthprofile/index.wxml-->
|
||||
<view class="profile-page">
|
||||
<!-- 头部统计卡 -->
|
||||
<view class="header-card">
|
||||
<view class="header-row">
|
||||
<text class="header-title">健康档案</text>
|
||||
</view>
|
||||
<view class="stat-center">
|
||||
<text class="stat-label">档案总数</text>
|
||||
<view class="stat-value">
|
||||
<text class="stat-num">{{totalCount}}</text>
|
||||
<text class="stat-unit">份</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 常用功能 -->
|
||||
<view class="section">
|
||||
<text class="section-title">常用功能</text>
|
||||
<view class="quick-grid card">
|
||||
<view class="quick-item" wx:for="{{quickMenus}}" wx:key="title" data-index="{{index}}" bindtap="onMenuTap">
|
||||
<view class="quick-icon">
|
||||
<t-icon name="{{item.icon}}" size="40rpx" color="#1abc9c" />
|
||||
</view>
|
||||
<text class="quick-label">{{item.title}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康档案列表 -->
|
||||
<view class="section" wx:if="{{profileList.length > 0}}">
|
||||
<text class="section-title">健康档案</text>
|
||||
<view class="profile-list card">
|
||||
<patient-info
|
||||
wx:for="{{profileList}}"
|
||||
wx:key="id"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="onProfileTap"
|
||||
name="{{item.name}}"
|
||||
sex-label="{{item.sexLabel}}"
|
||||
age-text="{{item.ageText}}"
|
||||
mobile="{{item.mobile}}"
|
||||
location-text="{{item.locationText}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,219 @@
|
||||
// pages/healthprofile/profileInfo.js
|
||||
const API = require('../../utils/api.js')
|
||||
const store = require('../../utils/store.js')
|
||||
|
||||
const SEX_MAP = [
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' }
|
||||
]
|
||||
const BLOOD_TYPES = ['A', 'B', 'O', 'AB']
|
||||
|
||||
Page({
|
||||
data: {
|
||||
isEdit: false,
|
||||
profileId: '',
|
||||
isSubmitting: false,
|
||||
form: {
|
||||
name: '',
|
||||
mobile: '',
|
||||
sexLabel: '',
|
||||
birth: '',
|
||||
idnumber: '',
|
||||
address: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
bloodType: '',
|
||||
remark: ''
|
||||
},
|
||||
region: [],
|
||||
regionLabel: '',
|
||||
sexLabels: SEX_MAP.map(item => item.label),
|
||||
bloodTypes: BLOOD_TYPES
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const now = new Date()
|
||||
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
this.setData({ today })
|
||||
if (options && options.id) {
|
||||
this.setData({ isEdit: true, profileId: options.id })
|
||||
wx.setNavigationBarTitle({ title: '档案详情' })
|
||||
this.loadProfile(options.id)
|
||||
} else {
|
||||
wx.setNavigationBarTitle({ title: '新建档案' })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载档案详情
|
||||
*/
|
||||
loadProfile(id) {
|
||||
API.healthProfile.getProfileById(id)
|
||||
.then(res => {
|
||||
if (res.code !== 0 || !res.data || !res.data.profile) {
|
||||
wx.showToast({ title: res.msg || '获取档案失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const p = res.data.profile
|
||||
const sexItem = SEX_MAP.find(item => item.value === p.profile.sex)
|
||||
const loc = p.location || {}
|
||||
this.setData({
|
||||
form: {
|
||||
name: p.profile.name || '',
|
||||
mobile: p.profile.mobile || '',
|
||||
sex: p.profile.sex || '',
|
||||
sexLabel: sexItem ? sexItem.label : '',
|
||||
birth: p.profile.birth || '',
|
||||
idnumber: p.profile.idnumber || '',
|
||||
address: loc.address || '',
|
||||
height: p.health.height || '',
|
||||
weight: p.health.weight || '',
|
||||
bloodType: p.health.bloodType || '',
|
||||
remark: p.health.remark || ''
|
||||
},
|
||||
region: [loc.province, loc.city, loc.district].filter(Boolean),
|
||||
regionLabel: [loc.province, loc.city, loc.district].filter(Boolean).join(' ')
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 文本输入统一处理
|
||||
*/
|
||||
onFieldChange(e) {
|
||||
const { field } = e.currentTarget.dataset
|
||||
this.setData({ [`form.${field}`]: e.detail.value })
|
||||
},
|
||||
|
||||
onSexChange(e) {
|
||||
const item = SEX_MAP[e.detail.value]
|
||||
if (item) {
|
||||
this.setData({ 'form.sex': item.value, 'form.sexLabel': item.label })
|
||||
}
|
||||
},
|
||||
|
||||
onBloodChange(e) {
|
||||
this.setData({ 'form.bloodType': this.data.bloodTypes[e.detail.value] })
|
||||
},
|
||||
|
||||
onBirthChange(e) {
|
||||
this.setData({ 'form.birth': e.detail.value })
|
||||
},
|
||||
|
||||
onRegionChange(e) {
|
||||
const value = e.detail.value || []
|
||||
this.setData({ region: value, regionLabel: value.join(' ') })
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验并提交
|
||||
*/
|
||||
onSave() {
|
||||
if (this.data.isSubmitting) return
|
||||
|
||||
const { form, isEdit, profileId } = this.data
|
||||
if (!form.name.trim()) {
|
||||
wx.showToast({ title: '请输入患者姓名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!form.mobile.trim()) {
|
||||
wx.showToast({ title: '请输入患者电话', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const region = this.data.region
|
||||
const payload = {
|
||||
profile: {
|
||||
name: form.name.trim(),
|
||||
mobile: form.mobile.trim(),
|
||||
sex: form.sex || '',
|
||||
birth: form.birth || '',
|
||||
idnumber: form.idnumber.trim()
|
||||
},
|
||||
location: {
|
||||
province: region[0] || '',
|
||||
city: region[1] || '',
|
||||
district: region[2] || '',
|
||||
address: form.address.trim()
|
||||
},
|
||||
health: {
|
||||
height: parseFloat(form.height) || 0,
|
||||
weight: parseFloat(form.weight) || 0,
|
||||
bloodType: form.bloodType,
|
||||
remark: form.remark.trim()
|
||||
}
|
||||
}
|
||||
|
||||
this.setData({ isSubmitting: true })
|
||||
|
||||
const request = isEdit
|
||||
? API.healthProfile.updateProfile(profileId, payload)
|
||||
: API.healthProfile.createProfile(payload)
|
||||
|
||||
request.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.msg || '保存失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showToast({
|
||||
title: isEdit ? '保存成功' : '创建成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => wx.navigateBack(), 800)
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ isSubmitting: false })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转陪诊预约(携带档案患者信息)
|
||||
*/
|
||||
onBooking() {
|
||||
const { profileId, form } = this.data
|
||||
const params = [
|
||||
`id=${profileId}`,
|
||||
`name=${encodeURIComponent(form.name)}`,
|
||||
`mobile=${form.mobile}`,
|
||||
`sex=${form.sex || ''}`,
|
||||
`birth=${form.birth || ''}`
|
||||
].join('&')
|
||||
wx.navigateTo({ url: `/pages/order/orderEdit?${params}` })
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除档案(仅编辑模式)
|
||||
*/
|
||||
onDelete() {
|
||||
const { profileId } = this.data
|
||||
wx.showModal({
|
||||
title: '删除确认',
|
||||
content: '确定要删除该健康档案吗?删除后不可恢复。',
|
||||
confirmColor: '#ff4d4f',
|
||||
success: res => {
|
||||
if (!res.confirm) return
|
||||
API.healthProfile.deleteProfile(profileId)
|
||||
.then(res2 => {
|
||||
if (res2.code !== 0) {
|
||||
wx.showToast({ title: res2.msg || '删除失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 清除缓存并通知列表页刷新
|
||||
store.removeProfile(profileId)
|
||||
store.notifyProfileChange('remove', profileId)
|
||||
wx.showToast({ title: '删除成功', icon: 'success' })
|
||||
setTimeout(() => wx.navigateBack(), 800)
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "新建档案",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* pages/healthprofile/profileInfo.less */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.info-page {
|
||||
padding: 24rpx;
|
||||
padding-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
margin-top: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 26rpx 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 170rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.picker-value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 100rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #b8b8c4;
|
||||
}
|
||||
|
||||
.form-divider {
|
||||
height: 1rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
border-radius: 44rpx;
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
|
||||
text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
box-shadow: 0 8rpx 24rpx rgba(23, 195, 165, 0.25);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #ff4d4f;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #1abc9c;
|
||||
|
||||
text {
|
||||
color: #1abc9c;
|
||||
}
|
||||
}
|
||||
|
||||
.booking-btn {
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.btn-group + .btn-group,
|
||||
.booking-btn + .btn-group {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<!--pages/healthprofile/profileInfo.wxml-->
|
||||
<view class="info-page">
|
||||
<!-- 患者信息 -->
|
||||
<view class="section">
|
||||
<text class="section-title">患者信息</text>
|
||||
<view class="form-card card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名</text>
|
||||
<input class="form-input" placeholder="请输入患者姓名" value="{{form.name}}" bindinput="onFieldChange" data-field="name" />
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">电话</text>
|
||||
<input class="form-input" type="number" placeholder="请输入患者电话" value="{{form.mobile}}" bindinput="onFieldChange" data-field="mobile" />
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">性别</text>
|
||||
<picker mode="selector" range="{{sexLabels}}" bindchange="onSexChange">
|
||||
<view class="picker-value {{form.sex ? '' : 'placeholder'}}">{{form.sexLabel || '请选择性别'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">出生年月</text>
|
||||
<picker mode="date" value="{{form.birth}}" end="{{today}}" bindchange="onBirthChange">
|
||||
<view class="picker-value {{form.birth ? '' : 'placeholder'}}">{{form.birth || '请选择出生日期'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">身份证号</text>
|
||||
<input class="form-input" placeholder="请输入身份证号(选填)" value="{{form.idnumber}}" bindinput="onFieldChange" data-field="idnumber" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 所在地 -->
|
||||
<view class="section">
|
||||
<text class="section-title">所在地</text>
|
||||
<view class="form-card card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">省市区</text>
|
||||
<picker mode="region" value="{{region}}" bindchange="onRegionChange">
|
||||
<view class="picker-value {{regionLabel ? '' : 'placeholder'}}">{{regionLabel || '请选择省市区(选填)'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">详细地址</text>
|
||||
<input class="form-input" placeholder="街道、门牌号等(选填)" value="{{form.address}}" bindinput="onFieldChange" data-field="address" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康信息 -->
|
||||
<view class="section">
|
||||
<text class="section-title">健康信息</text>
|
||||
<view class="form-card card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">身高(cm)</text>
|
||||
<input class="form-input" type="digit" placeholder="选填" value="{{form.height}}" bindinput="onFieldChange" data-field="height" />
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">体重(kg)</text>
|
||||
<input class="form-input" type="digit" placeholder="选填" value="{{form.weight}}" bindinput="onFieldChange" data-field="weight" />
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">血型</text>
|
||||
<picker mode="selector" range="{{bloodTypes}}" value="{{form.bloodIndex}}" bindchange="onBloodChange">
|
||||
<view class="picker-value {{form.bloodType ? '' : 'placeholder'}}">{{form.bloodType || '请选择血型'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-divider"></view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">备注</text>
|
||||
<textarea class="form-textarea" placeholder="病史、过敏史等(选填)" value="{{form.remark}}" bindinput="onFieldChange" data-field="remark" auto-height maxlength="200" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 陪诊预约(仅编辑已有档案时显示) -->
|
||||
<view wx:if="{{isEdit}}" class="btn btn-outline booking-btn" bindtap="onBooking">
|
||||
<text>陪诊预约</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="btn-group">
|
||||
<view class="btn btn-primary {{isSubmitting ? 'disabled' : ''}}" bindtap="onSave">
|
||||
<text>{{isEdit ? '保存修改' : '创建档案'}}</text>
|
||||
</view>
|
||||
<view wx:if="{{isEdit}}" class="btn btn-danger" bindtap="onDelete">
|
||||
<text>删除档案</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
+101
-27
@@ -4,16 +4,39 @@ const API = require('../../utils/api.js')
|
||||
Page({
|
||||
data: {
|
||||
today: '',
|
||||
todayCount: 0,
|
||||
pendingCount: 0,
|
||||
completedCount: 0,
|
||||
menuList: [
|
||||
{ icon: '/images/icon_order.png', name: '订单管理', url: '/pages/order/index' },
|
||||
{ icon: '/images/icon_patient.png', name: '患者管理', url: '/pages/patient/index' },
|
||||
{ icon: '/images/icon_escort.png', name: '陪诊员管理', url: '/pages/escort/index' },
|
||||
{ icon: '/images/icon_schedule.png', name: '排班管理', url: '/pages/schedule/index' }
|
||||
recentOrders: [],
|
||||
// 快捷入口:前3个为快捷操作(橙色系),后4个为功能入口(绿色系)
|
||||
quickEntries: [
|
||||
{ icon: 'add-circle', name: '增加预约', url: '/pages/order/orderEdit', color: '#ff8f4d' },
|
||||
{ icon: 'file-add', name: '增加档案', url: '/pages/healthprofile/profileInfo', color: '#ff8f4d' },
|
||||
{ icon: 'lightbulb-circle-filled', name: 'AI办公', url: '/pages/ai/index', isTab: true, color: '#ff8f4d' },
|
||||
{ icon: 'bill', name: '预约订单', url: '/pages/order/index', color: '#1abc9c' },
|
||||
{ icon: 'heart', name: '健康档案', url: '/pages/healthprofile/index', color: '#1abc9c' },
|
||||
{ icon: 'user', name: '注册用户', url: '/pages/customer/index', color: '#1abc9c' },
|
||||
{ icon: 'user-circle', name: '个人信息', url: '/pages/me/index', color: '#1abc9c' }
|
||||
],
|
||||
todayOrders: [],
|
||||
online: true,
|
||||
profile: {
|
||||
name: '王姐陪诊师',
|
||||
level: '初级陪诊师',
|
||||
avatar: ''
|
||||
},
|
||||
todayIncome: 168,
|
||||
todayOrderCount: 0,
|
||||
pendingOrder: {
|
||||
count: 1,
|
||||
hospital: '中医院',
|
||||
time: '10:00',
|
||||
price: 88
|
||||
},
|
||||
ongoingOrder: {
|
||||
count: 1,
|
||||
hospital: '康复医院',
|
||||
time: '15:30',
|
||||
price: 128
|
||||
},
|
||||
statusMap: {
|
||||
pending: '待确认',
|
||||
confirmed: '已确认',
|
||||
@@ -37,56 +60,107 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.loadUser()
|
||||
const now = new Date();
|
||||
const today = now.toISOString().substring(0, 10);
|
||||
this.setData({ today });
|
||||
this.getTodayOrders();
|
||||
this.getRecentOrders();
|
||||
this.getStats();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.getTodayOrders();
|
||||
this.loadUser()
|
||||
this.getRecentOrders();
|
||||
this.getStats();
|
||||
},
|
||||
|
||||
loadUser() {
|
||||
const app = getApp()
|
||||
const user = app.globalData && app.globalData.user
|
||||
if (user) {
|
||||
this.setData({
|
||||
'profile.name': user.nickname || this.data.profile.name,
|
||||
'profile.level': user.level || this.data.profile.level,
|
||||
'profile.avatar': user.avatar || ''
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
toggleOnline() {
|
||||
this.setData({
|
||||
online: !this.data.online
|
||||
})
|
||||
},
|
||||
|
||||
onOrderCardTap(e) {
|
||||
const { type } = e.currentTarget.dataset
|
||||
const title = type === 'pending' ? '待接单' : '进行中'
|
||||
wx.showToast({
|
||||
title: `${title} 订单列表`,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
async getStats() {
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
const [todayRes, pendingRes, completedRes] = await Promise.all([
|
||||
API.escort.getMyRecords({ appointmentDate: today }),
|
||||
API.escort.getMyRecords({ status: ['pending', 'confirmed'] }),
|
||||
API.escort.getMyRecords({ status: ['completed'] })
|
||||
API.escort.getAllRecords({ appointmentDate: today }),
|
||||
API.escort.getAllRecords({ status: 'pending,confirmed' }),
|
||||
API.escort.getAllRecords({ status: 'completed' })
|
||||
]);
|
||||
this.setData({
|
||||
todayCount: todayRes.code === 0 ? (todayRes.data.records || []).length : 0,
|
||||
todayOrderCount: todayRes.code === 0 ? (todayRes.data.records || []).length : 0,
|
||||
pendingCount: pendingRes.code === 0 ? (pendingRes.data.records || []).length : 0,
|
||||
completedCount: completedRes.code === 0 ? (completedRes.data.records || []).length : 0,
|
||||
});
|
||||
},
|
||||
|
||||
async getTodayOrders() {
|
||||
const res = await API.escort.getMyRecords({
|
||||
appointmentDate: new Date().toISOString().substring(0, 10),
|
||||
});
|
||||
if (res.code == 0) {
|
||||
const records = (res.data.records || []).map(item => {
|
||||
if (item.schedule && item.schedule.date) {
|
||||
/**
|
||||
* 加载近7日订单(含6天前至今及未来订单,按预约时间倒序,最多10条)
|
||||
*/
|
||||
async getRecentOrders() {
|
||||
const res = await API.escort.getAllRecords({ page: 1, pageSize: 50 });
|
||||
if (res.code !== 0) return;
|
||||
|
||||
const start = new Date();
|
||||
start.setDate(start.getDate() - 6);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const records = (res.data.records || [])
|
||||
.filter(item => item.schedule && item.schedule.date && new Date(item.schedule.date) >= start)
|
||||
.slice(0, 10)
|
||||
.map(item => {
|
||||
const d = new Date(item.schedule.date);
|
||||
item.schedule.date = d.toISOString().substring(0, 10) + ' ' + d.toTimeString().substring(0, 5);
|
||||
}
|
||||
item.schedule.date = `${d.getMonth() + 1}月${d.getDate()}日 ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
return item;
|
||||
});
|
||||
this.setData({ todayOrders: records });
|
||||
}
|
||||
this.setData({ recentOrders: records });
|
||||
},
|
||||
|
||||
navigateTo(e) {
|
||||
const url = e.currentTarget.dataset.url
|
||||
wx.navigateTo({ url })
|
||||
onEntryTap(e) {
|
||||
const { index } = e.currentTarget.dataset
|
||||
const item = this.data.quickEntries[index]
|
||||
if (item.isTab) {
|
||||
wx.switchTab({ url: item.url })
|
||||
} else {
|
||||
wx.navigateTo({ url: item.url })
|
||||
}
|
||||
},
|
||||
|
||||
viewAllOrders() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/order/index'
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击近7日订单卡片,进入订单详情
|
||||
*/
|
||||
onOrderTap(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
if (!id) return
|
||||
wx.navigateTo({
|
||||
url: `/pages/order/orderDetail?id=${id}`
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "暖橙陪诊",
|
||||
"usingComponents": {}
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"order-stats": "../../components/order-stats/index"
|
||||
}
|
||||
}
|
||||
+56
-60
@@ -1,97 +1,93 @@
|
||||
<!--pages/home/index.wxml-->
|
||||
<view class="page">
|
||||
<!-- 顶部问候 -->
|
||||
<view class="header">
|
||||
<view class="greeting">
|
||||
<text class="greeting-text">您好,管理员</text>
|
||||
<text class="greeting-sub">今天是 {{today}}</text>
|
||||
<!-- 头部信息卡 -->
|
||||
<view class="header-card">
|
||||
<view class="profile-row">
|
||||
<view class="profile-left">
|
||||
<view class="avatar">
|
||||
<image wx:if="{{profile.avatar}}" class="avatar-img" src="{{profile.avatar}}" mode="aspectFill" />
|
||||
<text wx:else class="avatar-text">{{profile.name[0]}}</text>
|
||||
</view>
|
||||
<view class="profile-info">
|
||||
<view class="name-row">
|
||||
<text class="name">{{profile.name}}</text>
|
||||
<view class="level-tag">
|
||||
<text class="level-text">{{profile.level}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<view class="stats-container">
|
||||
<view class="stats-grid">
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{todayCount}}</view>
|
||||
<view class="stat-label">新增用户</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{todayCount}}</view>
|
||||
<view class="stat-label">新增预约</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{pendingCount}}</view>
|
||||
<view class="stat-label">待处理</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{completedCount}}</view>
|
||||
<view class="stat-label">已完成</view>
|
||||
<view class="online-switch" bindtap="toggleOnline">
|
||||
<text class="switch-text">{{online ? '接单中' : '休息中'}}</text>
|
||||
<view class="switch-track {{online ? 'on' : 'off'}}">
|
||||
<view class="switch-thumb"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能入口 -->
|
||||
<view class="stats-row">
|
||||
<view class="stat">
|
||||
<text class="stat-label">今日收益</text>
|
||||
<view class="stat-value">
|
||||
<text class="stat-unit">¥</text>
|
||||
<text class="stat-num">{{todayIncome}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat stat-right">
|
||||
<text class="stat-label">今日接单</text>
|
||||
<view class="stat-value">
|
||||
<text class="stat-num">{{todayOrderCount}}</text>
|
||||
<text class="stat-unit">单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷入口 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">快捷入口</text>
|
||||
<view class="quick-grid card">
|
||||
<view class="quick-item" wx:for="{{quickEntries}}" wx:key="name" bindtap="onEntryTap" data-index="{{index}}">
|
||||
<view class="quick-icon" style="background-color: {{item.color}}1a;">
|
||||
<t-icon name="{{item.icon}}" size="40rpx" color="{{item.color}}" />
|
||||
</view>
|
||||
<view class="menu-grid">
|
||||
<view class="menu-card" wx:for="{{menuList}}" wx:key="index" bindtap="navigateTo" data-url="{{item.url}}">
|
||||
<view class="menu-icon-wrap">
|
||||
<text class="menu-icon-text">{{item.name[0]}}</text>
|
||||
</view>
|
||||
<text class="menu-name">{{item.name}}</text>
|
||||
<text class="quick-label">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日订单 -->
|
||||
<!-- 订单统计 -->
|
||||
<order-stats />
|
||||
|
||||
<!-- 近7日订单 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">今日订单</text>
|
||||
<text class="section-title">近7日订单</text>
|
||||
<view class="view-all" bindtap="viewAllOrders">
|
||||
<text>全部</text>
|
||||
<text class="arrow">→</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="order-list" wx:if="{{todayOrders.length > 0}}">
|
||||
<view class="order-card" wx:for="{{todayOrders}}" wx:key="_id">
|
||||
<view class="order-top">
|
||||
<text class="order-no">#{{item._id}}</text>
|
||||
<view class="order-list" wx:if="{{recentOrders.length > 0}}">
|
||||
<view class="order-card" wx:for="{{recentOrders}}" wx:key="_id" data-id="{{item._id}}" bindtap="onOrderTap">
|
||||
<view class="order-line1">
|
||||
<text class="order-patient">{{item.patient.name}}</text>
|
||||
<text class="order-service">{{item.escort.serviceName}}</text>
|
||||
<view class="status-tag status-{{item.status}}">
|
||||
<text>{{statusMap[item.status] || item.status}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="order-body">
|
||||
<view class="order-row">
|
||||
<text class="row-label">患者</text>
|
||||
<text class="row-value">{{item.patient.name}}</text>
|
||||
</view>
|
||||
<view class="order-row">
|
||||
<text class="row-label">医院</text>
|
||||
<text class="row-value">{{item.hospital.name}} · {{item.hospital.department}}</text>
|
||||
</view>
|
||||
<view class="order-row">
|
||||
<text class="row-label">时间</text>
|
||||
<text class="row-value">{{item.schedule.date}}</text>
|
||||
</view>
|
||||
<view class="order-row">
|
||||
<text class="row-label">服务</text>
|
||||
<text class="row-value">{{item.escort.serviceName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="order-footer">
|
||||
<text class="fee-label">服务费用</text>
|
||||
<text class="fee-value">¥{{item.payment.totalFee}}</text>
|
||||
<view class="order-line2">
|
||||
<text class="order-hospital">{{item.hospital.name}} · {{item.hospital.department}}</text>
|
||||
<text class="order-time">{{item.schedule.date}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" wx:else>
|
||||
<text class="empty-text">暂无今日订单</text>
|
||||
<text class="empty-text">暂无近7日订单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
+264
-158
@@ -1,120 +1,226 @@
|
||||
/* pages/home/index.wxss */
|
||||
|
||||
page {
|
||||
background: linear-gradient(180deg, #e8f8f2 0%, #f5f6fa 360rpx);
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fc;
|
||||
padding-bottom: 40rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 24rpx 64rpx;
|
||||
}
|
||||
|
||||
/* === 顶部问候 === */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
padding: 60rpx 40rpx 80rpx;
|
||||
/* === 头部信息卡 === */
|
||||
.header-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -60rpx;
|
||||
right: -40rpx;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -100rpx;
|
||||
right: 80rpx;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.greeting {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
border-radius: 28rpx;
|
||||
padding: 36rpx 32rpx;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2rpx;
|
||||
margin-bottom: 12rpx;
|
||||
box-shadow: 0 12rpx 32rpx rgba(23, 195, 165, 0.28);
|
||||
}
|
||||
|
||||
.greeting-sub {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-weight: 300;
|
||||
letter-spacing: 1rpx;
|
||||
/* 装饰光斑,增加层次 */
|
||||
.header-card::before,
|
||||
.header-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* === 数据统计 === */
|
||||
.stats-container {
|
||||
margin: -40rpx 30rpx 0;
|
||||
.header-card::before {
|
||||
width: 260rpx;
|
||||
height: 260rpx;
|
||||
top: -120rpx;
|
||||
right: -60rpx;
|
||||
}
|
||||
|
||||
.header-card::after {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
bottom: -80rpx;
|
||||
left: -40rpx;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.profile-row {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx 16rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s;
|
||||
.profile-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
.avatar {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 8rpx;
|
||||
font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
margin-left: 12rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
}
|
||||
|
||||
.level-text {
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.online-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 28rpx;
|
||||
padding: 6rpx 6rpx 6rpx 18rpx;
|
||||
}
|
||||
|
||||
.switch-text {
|
||||
font-size: 24rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.switch-track {
|
||||
width: 56rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.switch-track.on {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.switch-thumb {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
position: absolute;
|
||||
top: 2rpx;
|
||||
left: 2rpx;
|
||||
transition: left 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.switch-track.on .switch-thumb {
|
||||
left: 26rpx;
|
||||
background-color: #17c3a5;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #a0a3bd;
|
||||
font-weight: 400;
|
||||
letter-spacing: 1rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 8rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
font-size: 26rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* === 通用 Section === */
|
||||
.section {
|
||||
margin: 32rpx 30rpx 0;
|
||||
margin-top: 44rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
position: relative;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-left: 20rpx;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -20rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 8rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 4rpx;
|
||||
background: linear-gradient(180deg, #2dd36f, #17c3a5);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
letter-spacing: 1rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
@@ -130,44 +236,50 @@
|
||||
color: #a0a3bd;
|
||||
}
|
||||
|
||||
/* === 功能菜单 === */
|
||||
.menu-grid {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
/* === 快捷功能入口 === */
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(31, 61, 56, 0.06);
|
||||
box-shadow: 0 6rpx 20rpx rgba(31, 61, 56, 0.07);
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx 0;
|
||||
.quick-grid {
|
||||
margin-top: 16rpx;
|
||||
padding: 32rpx 8rpx 8rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 32rpx;
|
||||
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), opacity 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.menu-icon-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
.quick-item:active {
|
||||
opacity: 0.85;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
background-color: #e6f9f3;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14rpx;
|
||||
box-shadow: inset 0 2rpx 4rpx rgba(255, 255, 255, 0.8), 0 4rpx 10rpx rgba(31, 61, 56, 0.06);
|
||||
}
|
||||
|
||||
.menu-icon-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.menu-name {
|
||||
.quick-label {
|
||||
font-size: 24rpx;
|
||||
color: #5a5d7a;
|
||||
font-weight: 500;
|
||||
color: #3a3f4d;
|
||||
}
|
||||
|
||||
/* === 订单列表 === */
|
||||
@@ -179,24 +291,38 @@
|
||||
|
||||
.order-card {
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(31, 61, 56, 0.06);
|
||||
padding: 28rpx 32rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 6rpx 20rpx rgba(31, 61, 56, 0.07);
|
||||
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1), box-shadow 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.order-top {
|
||||
.order-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(31, 61, 56, 0.08);
|
||||
}
|
||||
|
||||
.order-line1 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #f0f1f5;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #a0a3bd;
|
||||
.order-patient {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.order-service {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
font-family: 'DIN Alternate', 'Helvetica Neue', monospace;
|
||||
color: #1abc9c;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
@@ -204,6 +330,7 @@
|
||||
padding: 6rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
@@ -231,58 +358,37 @@
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.order-body {
|
||||
padding: 20rpx 0;
|
||||
.order-line2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.order-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.row-label {
|
||||
font-size: 24rpx;
|
||||
color: #a0a3bd;
|
||||
width: 72rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.row-value {
|
||||
font-size: 26rpx;
|
||||
color: #3a3d5c;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.order-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #f0f1f5;
|
||||
justify-content: space-between;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.fee-label {
|
||||
.order-hospital {
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.order-time {
|
||||
font-size: 24rpx;
|
||||
color: #a0a3bd;
|
||||
}
|
||||
|
||||
.fee-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* === 空状态 === */
|
||||
.empty-state {
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid rgba(31, 61, 56, 0.06);
|
||||
padding: 80rpx 0;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 6rpx 20rpx rgba(31, 61, 56, 0.07);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// pages/me/index.js
|
||||
// 性别映射
|
||||
const SEX_MAP = {
|
||||
male: '男',
|
||||
female: '女'
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
user: {}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadUser()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载当前登录用户信息
|
||||
*/
|
||||
loadUser() {
|
||||
const app = getApp()
|
||||
app.ensureLogin()
|
||||
.then(user => {
|
||||
this.setUser(user)
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '获取用户信息失败', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理并展示用户信息
|
||||
*/
|
||||
setUser(user) {
|
||||
if (!user) return
|
||||
const profile = user.profile || {}
|
||||
const location = user.location || {}
|
||||
const meta = user.meta || {}
|
||||
|
||||
const name = profile.name || '微信用户'
|
||||
const locationText = [location.province, location.city, location.district]
|
||||
.filter(Boolean).join(' ')
|
||||
|
||||
this.setData({
|
||||
user: {
|
||||
avatar: profile.avatar || '',
|
||||
avatarText: name[0],
|
||||
name,
|
||||
mobile: profile.mobile || '',
|
||||
sexLabel: SEX_MAP[profile.sex] || '未知',
|
||||
birthText: this.formatDate(profile.birth),
|
||||
email: profile.email || '',
|
||||
idnumber: profile.idnumber || '',
|
||||
locationText,
|
||||
createText: this.formatDate(meta.createtime)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期为 yyyy-MM-dd
|
||||
*/
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return ''
|
||||
const pad = (n) => String(n).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '个人中心',
|
||||
path: '/pages/me/index'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/* pages/me/index.less */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* === 用户信息卡 === */
|
||||
.header-card {
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 44rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.user-mobile {
|
||||
margin-top: 10rpx;
|
||||
font-size: 26rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* === 详细信息卡 === */
|
||||
.info-card {
|
||||
margin-top: 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 8rpx 28rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 0;
|
||||
|
||||
& + .info-item {
|
||||
border-top: 1rpx solid #f0f1f5;
|
||||
}
|
||||
}
|
||||
|
||||
.info-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
max-width: 400rpx;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<!--pages/me/index.wxml-->
|
||||
<view class="page">
|
||||
<!-- 用户信息卡 -->
|
||||
<view class="header-card">
|
||||
<view class="avatar">
|
||||
<image wx:if="{{user.avatar}}" class="avatar-img" src="{{user.avatar}}" mode="aspectFill" />
|
||||
<text wx:else class="avatar-text">{{user.avatarText}}</text>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{user.name}}</text>
|
||||
<text class="user-mobile">{{user.mobile || '暂未绑定手机号'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<view class="info-card">
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="user" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">性别</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.sexLabel}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="calendar" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">出生日期</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.birthText || '未填写'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="mail" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">邮箱</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.email || '未填写'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="id-card" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">身份证号</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.idnumber || '未填写'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="location" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">所在地区</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.locationText || '未填写'}}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-left">
|
||||
<t-icon name="time" size="36rpx" color="#1abc9c" />
|
||||
<text class="info-label">注册时间</text>
|
||||
</view>
|
||||
<text class="info-value">{{user.createText || '未知'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
+31
-8
@@ -1,10 +1,11 @@
|
||||
// pages/order/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
const { calcAge } = require('../../utils/format.js')
|
||||
|
||||
// 状态映射配置
|
||||
const STATUS_MAP = {
|
||||
pending: { label: '待确认', text: '待确认', color: '#f59f00' },
|
||||
confirmed: { label: '已确认', text: '已确认', color: '#4c6ef5' },
|
||||
confirmed: { label: '已确认', text: '已确认', color: '#1abc9c' },
|
||||
in_progress: { label: '进行中', text: '进行中', color: '#20c997' },
|
||||
completed: { label: '已完成', text: '已完成', color: '#51cf66' },
|
||||
cancelled: { label: '已取消', text: '已取消', color: '#ff6b6b' }
|
||||
@@ -37,7 +38,7 @@ Page({
|
||||
},
|
||||
// 状态筛选
|
||||
statusFilters: STATUS_FILTERS,
|
||||
currentStatus: '',
|
||||
currentStatus: 'pending',
|
||||
// 订单列表
|
||||
orderList: [],
|
||||
// 分页
|
||||
@@ -149,15 +150,19 @@ Page({
|
||||
* 处理订单数据
|
||||
*/
|
||||
processOrders(orders) {
|
||||
return orders.map(order => ({
|
||||
return orders.map(order => {
|
||||
const age = calcAge(order.patient?.birth)
|
||||
return {
|
||||
...order,
|
||||
statusText: STATUS_MAP[order.status]?.text || order.status,
|
||||
patientFirstChar: (order.patient?.name || '?')[0],
|
||||
patientAgeText: age ? `${age}岁` : '',
|
||||
schedule: {
|
||||
...order.schedule,
|
||||
dateText: this.formatDate(order.schedule?.date)
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -176,10 +181,10 @@ Page({
|
||||
status: 'pending,confirmed,in_progress,completed,cancelled'
|
||||
};
|
||||
|
||||
API.escort.getMyRecords(params)
|
||||
API.escort.getAllRecords(params)
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '获取订单失败', icon: 'none' });
|
||||
wx.showToast({ title: res.msg || '获取订单失败', icon: 'none' });
|
||||
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
|
||||
return;
|
||||
}
|
||||
@@ -228,6 +233,24 @@ Page({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转新增预约(页面内选择健康档案)
|
||||
*/
|
||||
onAddOrder() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/order/orderEdit'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 空状态刷新
|
||||
*/
|
||||
onReload() {
|
||||
this.setData({ page: 1, hasMore: true }, () => {
|
||||
this.loadOrderList();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 状态筛选切换
|
||||
*/
|
||||
@@ -284,7 +307,7 @@ Page({
|
||||
wx.showModal({
|
||||
title: actionConfig.title,
|
||||
content: actionConfig.content,
|
||||
confirmColor: '#4c6ef5',
|
||||
confirmColor: '#1abc9c',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.updateOrderStatus(id, actionConfig.nextStatus);
|
||||
@@ -302,7 +325,7 @@ Page({
|
||||
API.escort.updateStatus(id, { status: newStatus })
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '操作失败', icon: 'none' });
|
||||
wx.showToast({ title: res.msg || '操作失败', icon: 'none' });
|
||||
wx.hideLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"t-tabs": "tdesign-miniprogram/tabs/tabs",
|
||||
"t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-divider": "tdesign-miniprogram/divider/divider"
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
|
||||
+27
-9
@@ -1,13 +1,13 @@
|
||||
/* pages/order/index.less */
|
||||
|
||||
@bg: #f7f8fc;
|
||||
@bg: #f5f6fa;
|
||||
@card: #ffffff;
|
||||
@dark: #1a1a2e;
|
||||
@text: #3a3d5c;
|
||||
@muted: #a0a3bd;
|
||||
@border: #f0f1f5;
|
||||
@accent: #667eea;
|
||||
@accent-end: #764ba2;
|
||||
@accent: #1abc9c;
|
||||
@accent-end: #17c3a5;
|
||||
|
||||
@pending: #e6a23c;
|
||||
@confirmed: #409eff;
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
page {
|
||||
background-color: @bg;
|
||||
color: @dark;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
@@ -28,10 +30,14 @@ page {
|
||||
|
||||
/* === 顶部 === */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
background: linear-gradient(135deg, #2dd36f 0%, #17c3a5 100%);
|
||||
box-shadow: 0 8rpx 24rpx rgba(23, 195, 165, 0.25);
|
||||
padding: 60rpx 40rpx 70rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@@ -78,6 +84,18 @@ page {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.header-add {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 32rpx;
|
||||
}
|
||||
|
||||
/* === 筛选栏 === */
|
||||
.filter-bar {
|
||||
background: @card;
|
||||
@@ -110,7 +128,7 @@ page {
|
||||
background: linear-gradient(135deg, @accent, @accent-end);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.3);
|
||||
|
||||
.filter-count {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
@@ -181,8 +199,8 @@ page {
|
||||
.order-card {
|
||||
margin-bottom: 20rpx;
|
||||
background: @card;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
|
||||
&:active {
|
||||
@@ -289,7 +307,7 @@ page {
|
||||
font-weight: 500;
|
||||
|
||||
&.male {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
background: rgba(26, 188, 156, 0.1);
|
||||
color: @accent;
|
||||
}
|
||||
|
||||
@@ -382,7 +400,7 @@ page {
|
||||
.btn-solid {
|
||||
background: linear-gradient(135deg, @accent, @accent-end);
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.25);
|
||||
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<text class="header-title">订单管理</text>
|
||||
<text class="header-sub">共 {{stats.total}} 个订单</text>
|
||||
</view>
|
||||
<view class="header-add" bindtap="onAddOrder">新增预约</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态筛选 -->
|
||||
@@ -45,7 +46,7 @@
|
||||
<view class="empty-box" wx:elif="{{!isLoading && orderList.length === 0}}">
|
||||
<t-empty icon="file" description="暂无订单" t-class-description="empty-text">
|
||||
<view slot="action">
|
||||
<view class="empty-btn" bindtap="loadOrderList">刷新</view>
|
||||
<view class="empty-btn" bindtap="onReload">刷新</view>
|
||||
</view>
|
||||
</t-empty>
|
||||
</view>
|
||||
@@ -76,7 +77,7 @@
|
||||
<view class="patient-name-row">
|
||||
<text class="patient-name">{{item.patient.name || '未知患者'}}</text>
|
||||
<text class="patient-sex {{item.patient.sex}}">{{item.patient.sex === 'male' ? '男' : item.patient.sex === 'female' ? '女' : ''}}</text>
|
||||
<text class="patient-age" wx:if="{{item.patient.age}}">{{item.patient.age}}岁</text>
|
||||
<text class="patient-age" wx:if="{{item.patientAgeText}}">{{item.patientAgeText}}</text>
|
||||
</view>
|
||||
<text class="patient-phone">{{item.patient.mobile || '暂无电话'}}</text>
|
||||
</view>
|
||||
|
||||
+19
-13
@@ -55,7 +55,6 @@ Page({
|
||||
// 格式化后的显示字段
|
||||
statusText: '',
|
||||
statusDesc: '',
|
||||
patientFirstChar: '',
|
||||
attendantFirstChar: '',
|
||||
scheduleDateText: '',
|
||||
scheduleStartTimeText: '',
|
||||
@@ -63,7 +62,6 @@ Page({
|
||||
createtimeText: '',
|
||||
updatetimeText: '',
|
||||
paymentStatusText: '',
|
||||
patientSexText: '',
|
||||
attendantSexText: '',
|
||||
sexRequirementText: '',
|
||||
// 操作按钮配置
|
||||
@@ -98,11 +96,11 @@ Page({
|
||||
wx.hideLoading()
|
||||
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '加载失败', icon: 'none' })
|
||||
wx.showToast({ title: res.msg || '加载失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const order = res.data || {}
|
||||
const order = res.data?.record || {}
|
||||
this.applyOrderData(order)
|
||||
} catch (err) {
|
||||
wx.hideLoading()
|
||||
@@ -113,7 +111,6 @@ Page({
|
||||
|
||||
applyOrderData(order) {
|
||||
const status = order.status || 'pending'
|
||||
const patient = order.patient || {}
|
||||
const attendant = order.attendant || {}
|
||||
const schedule = order.schedule || {}
|
||||
const payment = order.payment || {}
|
||||
@@ -123,9 +120,10 @@ Page({
|
||||
this.setData({
|
||||
loaded: true,
|
||||
order: order,
|
||||
// 有关联档案时交由 patient-info 按 ID 加载(不读缓存,以档案最新数据为准)
|
||||
healthProfileId: order.healthProfileId || '',
|
||||
statusText: STATUS_MAP[status] || status,
|
||||
statusDesc: STATUS_DESC_MAP[status] || '',
|
||||
patientFirstChar: (patient.name || '?')[0],
|
||||
attendantFirstChar: (attendant.name || '?')[0],
|
||||
scheduleDateText: formatDate(schedule.date),
|
||||
scheduleStartTimeText: schedule.startTime || '',
|
||||
@@ -133,10 +131,10 @@ Page({
|
||||
createtimeText: formatDateTime(meta.createtime),
|
||||
updatetimeText: formatDateTime(meta.updatetime),
|
||||
paymentStatusText: PAYMENT_STATUS_MAP[payment.status] || payment.status || '未支付',
|
||||
patientSexText: patient.sex === 'male' ? '男' : patient.sex === 'female' ? '女' : '',
|
||||
attendantSexText: attendant.sex === 'male' ? '男' : attendant.sex === 'female' ? '女' : '',
|
||||
sexRequirementText: escort.sexRequirement === 'male' ? '要求男陪诊' : escort.sexRequirement === 'female' ? '要求女陪诊' : '不限',
|
||||
// 操作按钮
|
||||
// 操作按钮(待确认/已确认状态下可编辑订单信息)
|
||||
showEditBtn: status === 'pending' || status === 'confirmed',
|
||||
showConfirmBtn: status === 'pending',
|
||||
showCancelBtn: status === 'pending',
|
||||
showStartBtn: status === 'confirmed',
|
||||
@@ -146,7 +144,8 @@ Page({
|
||||
|
||||
// 拨打电话
|
||||
callPhone(e) {
|
||||
const phone = e.currentTarget.dataset.phone
|
||||
// 兼容 patient-info 组件的 call 事件与 data-phone 用法
|
||||
const phone = (e.detail && e.detail.mobile) || e.currentTarget.dataset.phone
|
||||
if (!phone) {
|
||||
wx.showToast({ title: '暂无电话', icon: 'none' })
|
||||
return
|
||||
@@ -154,12 +153,19 @@ Page({
|
||||
wx.makePhoneCall({ phoneNumber: phone })
|
||||
},
|
||||
|
||||
// 编辑订单
|
||||
onEdit() {
|
||||
wx.navigateTo({
|
||||
url: `/pages/order/orderEdit?orderId=${this.data.orderId}`
|
||||
})
|
||||
},
|
||||
|
||||
// 确认订单
|
||||
onConfirm() {
|
||||
wx.showModal({
|
||||
title: '确认订单',
|
||||
content: '确认接受此订单?',
|
||||
confirmColor: '#4c6ef5',
|
||||
confirmColor: '#1abc9c',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.updateStatus('confirmed')
|
||||
@@ -187,7 +193,7 @@ Page({
|
||||
wx.showModal({
|
||||
title: '开始服务',
|
||||
content: '确认开始陪诊服务?',
|
||||
confirmColor: '#4c6ef5',
|
||||
confirmColor: '#1abc9c',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.updateStatus('in_progress')
|
||||
@@ -201,7 +207,7 @@ Page({
|
||||
wx.showModal({
|
||||
title: '完成服务',
|
||||
content: '确认陪诊服务已完成?',
|
||||
confirmColor: '#4c6ef5',
|
||||
confirmColor: '#1abc9c',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.updateStatus('completed')
|
||||
@@ -217,7 +223,7 @@ Page({
|
||||
wx.hideLoading()
|
||||
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '操作失败', icon: 'none' })
|
||||
wx.showToast({ title: res.msg || '操作失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {}
|
||||
"usingComponents": {
|
||||
"patient-info": "../../components/patient-info/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,22 +18,13 @@
|
||||
<view class="info-section">
|
||||
<text class="section-title">患者信息</text>
|
||||
<view class="info-card">
|
||||
<view class="patient-header">
|
||||
<view class="patient-avatar">
|
||||
<text class="avatar-text">{{patientFirstChar}}</text>
|
||||
</view>
|
||||
<view class="patient-info">
|
||||
<view class="patient-name-row">
|
||||
<text class="patient-name">{{order.patient.name || '未知患者'}}</text>
|
||||
<text class="patient-sex {{order.patient.sex}}" wx:if="{{patientSexText}}">{{patientSexText}}</text>
|
||||
<text class="patient-age" wx:if="{{order.patient.age}}">{{order.patient.age}}岁</text>
|
||||
</view>
|
||||
<view class="patient-phone" data-phone="{{order.patient.mobile}}" bindtap="callPhone">
|
||||
<text>{{order.patient.mobile || '暂无电话'}}</text>
|
||||
<text class="call-icon" wx:if="{{order.patient.mobile}}"> 拨打</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 订单必须关联健康档案:按档案ID加载(不读缓存,直接请求最新) -->
|
||||
<patient-info
|
||||
profile-id="{{healthProfileId}}"
|
||||
use-cache="{{false}}"
|
||||
dial="{{true}}"
|
||||
bind:call="callPhone"
|
||||
/>
|
||||
<view class="divider"></view>
|
||||
<view class="info-row" wx:if="{{order.patient.weight}}">
|
||||
<text class="info-label">体重</text>
|
||||
@@ -192,7 +183,8 @@
|
||||
</view>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<view class="bottom-actions" wx:if="{{showConfirmBtn || showCancelBtn || showStartBtn || showCompleteBtn}}">
|
||||
<view class="bottom-actions" wx:if="{{showEditBtn || showConfirmBtn || showCancelBtn || showStartBtn || showCompleteBtn}}">
|
||||
<view class="action-btn btn-ghost" wx:if="{{showEditBtn}}" bindtap="onEdit">编辑订单</view>
|
||||
<view class="action-btn btn-ghost" wx:if="{{showCancelBtn}}" bindtap="onCancel">取消订单</view>
|
||||
<view class="action-btn btn-solid" wx:if="{{showConfirmBtn}}" bindtap="onConfirm">确认订单</view>
|
||||
<view class="action-btn btn-solid" wx:if="{{showStartBtn}}" bindtap="onStart">开始服务</view>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/* pages/order/orderDetail.wxss */
|
||||
|
||||
page {
|
||||
background-color: #f7f8fc;
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fc;
|
||||
background: #f5f6fa;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
@@ -87,18 +89,19 @@ page {
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a2e;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16rpx;
|
||||
margin-left: 8rpx;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
@@ -125,45 +128,8 @@ page {
|
||||
}
|
||||
|
||||
/* === 患者信息 === */
|
||||
.patient-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.patient-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.patient-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
.info-card patient-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.patient-sex {
|
||||
@@ -174,8 +140,8 @@ page {
|
||||
}
|
||||
|
||||
.patient-sex.male {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
color: #667eea;
|
||||
background: rgba(26, 188, 156, 0.1);
|
||||
color: #1abc9c;
|
||||
}
|
||||
|
||||
.patient-sex.female {
|
||||
@@ -194,7 +160,7 @@ page {
|
||||
}
|
||||
|
||||
.call-icon {
|
||||
color: #667eea;
|
||||
color: #1abc9c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -215,13 +181,19 @@ page {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.attendant-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -297,13 +269,13 @@ page {
|
||||
}
|
||||
|
||||
.btn-solid {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.3);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: #f7f8fc;
|
||||
background: #f5f6fa;
|
||||
color: #a0a3bd;
|
||||
border: 1rpx solid #f0f1f5;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
// pages/order/orderEdit.js
|
||||
const API = require('../../utils/api.js')
|
||||
const { calcAge } = require('../../utils/format.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
isEdit: false,
|
||||
form: {
|
||||
hospital: { name: '', department: '', doctor: '' },
|
||||
schedule: { date: '' },
|
||||
escort: { serviceName: '' },
|
||||
payment: { totalFee: '' },
|
||||
notes: { patientNote: '' }
|
||||
},
|
||||
profileSummary: null,
|
||||
// 新增模式下尚未选择档案时为 true,展示选择占位
|
||||
needPickProfile: false,
|
||||
// 档案选择弹层
|
||||
showProfilePicker: false,
|
||||
profileList: [],
|
||||
filteredProfiles: [],
|
||||
profileKeyword: '',
|
||||
today: '',
|
||||
submitting: false
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
* 新增:?id=健康档案ID(患者信息取自档案),不带 id 则进入档案选择
|
||||
* 编辑:?orderId=订单ID(回填订单数据)
|
||||
*/
|
||||
onLoad(options) {
|
||||
const now = new Date()
|
||||
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
this.setData({ today })
|
||||
|
||||
if (options && options.orderId) {
|
||||
// 编辑模式
|
||||
this.orderId = options.orderId
|
||||
this.setData({ isEdit: true })
|
||||
wx.setNavigationBarTitle({ title: '编辑订单' })
|
||||
this.loadOrder(options.orderId)
|
||||
return
|
||||
}
|
||||
|
||||
wx.setNavigationBarTitle({ title: '新增预约' })
|
||||
if (!options || !options.id) {
|
||||
// 未携带档案ID:弹出档案选择
|
||||
this.setData({ needPickProfile: true })
|
||||
this.openProfilePicker()
|
||||
return
|
||||
}
|
||||
this.profileId = options.id
|
||||
// 组件根据档案ID自行加载(缓存优先)
|
||||
this.setData({ profileId: options.id })
|
||||
},
|
||||
|
||||
/**
|
||||
* 空方法:阻止弹层内容区域点击冒泡关闭
|
||||
*/
|
||||
noop() {},
|
||||
|
||||
/**
|
||||
* 档案选择弹层:打开并加载档案列表
|
||||
*/
|
||||
openProfilePicker() {
|
||||
this.setData({ showProfilePicker: true, profileKeyword: '' })
|
||||
if (this.data.profileList.length === 0) {
|
||||
this.loadProfiles()
|
||||
} else {
|
||||
this.filterProfiles()
|
||||
}
|
||||
},
|
||||
|
||||
closeProfilePicker() {
|
||||
this.setData({ showProfilePicker: false })
|
||||
},
|
||||
|
||||
loadProfiles() {
|
||||
API.healthProfile.getProfiles({ page: 1, pageSize: 100, sortBy: 'updatetime' })
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
return wx.showToast({ title: res.msg || '获取档案失败', icon: 'none' })
|
||||
}
|
||||
const list = ((res.data && res.data.list) || []).map(item => {
|
||||
const p = item.profile || {}
|
||||
const age = calcAge(p.birth)
|
||||
return {
|
||||
id: item._id,
|
||||
name: p.name || '',
|
||||
mobile: p.mobile || '',
|
||||
sexLabel: p.sex === 'male' ? '男' : (p.sex === 'female' ? '女' : ''),
|
||||
ageText: age ? `${age}岁` : ''
|
||||
}
|
||||
})
|
||||
this.setData({ profileList: list })
|
||||
this.filterProfiles()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取健康档案失败', err)
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
onProfileSearch(e) {
|
||||
this.setData({ profileKeyword: e.detail.value })
|
||||
this.filterProfiles()
|
||||
},
|
||||
|
||||
filterProfiles() {
|
||||
const kw = (this.data.profileKeyword || '').trim()
|
||||
const list = this.data.profileList
|
||||
const filtered = kw
|
||||
? list.filter(item => item.name.includes(kw) || (item.mobile || '').includes(kw))
|
||||
: list
|
||||
this.setData({ filteredProfiles: filtered })
|
||||
},
|
||||
|
||||
/**
|
||||
* 选中档案:加载档案信息并进入表单
|
||||
*/
|
||||
onProfileSelect(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
if (!id) return
|
||||
this.profileId = id
|
||||
// 组件根据档案ID自行加载(缓存优先)
|
||||
this.setData({ profileId: id, needPickProfile: false, showProfilePicker: false })
|
||||
},
|
||||
|
||||
/**
|
||||
* 编辑模式:加载订单并回填表单
|
||||
*/
|
||||
loadOrder(id) {
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
API.escort.getRecordById(id)
|
||||
.then(res => {
|
||||
wx.hideLoading()
|
||||
if (res.code !== 0 || !res.data || !res.data.record) {
|
||||
wx.showToast({ title: res.msg || '订单加载失败', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 800)
|
||||
return
|
||||
}
|
||||
const order = res.data.record
|
||||
this.profileId = order.healthProfileId || null
|
||||
// 患者信息取自订单快照(作为无关联档案时的回退)
|
||||
const p = order.patient || {}
|
||||
this.patientInfo = {
|
||||
name: p.name || '',
|
||||
mobile: p.mobile || '',
|
||||
sex: p.sex || '',
|
||||
birth: p.birth || '',
|
||||
idnumber: p.idnumber || ''
|
||||
}
|
||||
const sexLabel = p.sex === 'male' ? '男' : (p.sex === 'female' ? '女' : '')
|
||||
const age = calcAge(p.birth)
|
||||
this.setData({
|
||||
// 有关联档案时交由 patient-info 按 ID 加载(不读缓存,以档案最新数据为准)
|
||||
profileId: order.healthProfileId || '',
|
||||
profileSummary: {
|
||||
name: p.name || '',
|
||||
mobile: p.mobile || '',
|
||||
sexLabel,
|
||||
ageText: age ? `${age}岁` : '',
|
||||
bloodType: (order.health && order.health.bloodType) || '',
|
||||
height: (order.health && order.health.height) || '',
|
||||
weight: (order.health && order.health.weight) || '',
|
||||
remark: (order.health && order.health.remark) || ''
|
||||
},
|
||||
form: {
|
||||
hospital: {
|
||||
name: (order.hospital && order.hospital.name) || '',
|
||||
department: (order.hospital && order.hospital.department) || '',
|
||||
doctor: (order.hospital && order.hospital.doctor) || ''
|
||||
},
|
||||
schedule: { date: this.formatDate(order.schedule && order.schedule.date) },
|
||||
escort: { serviceName: (order.escort && order.escort.serviceName) || '' },
|
||||
payment: {
|
||||
totalFee: order.payment && order.payment.totalFee ? String(order.payment.totalFee) : '',
|
||||
paid: !!(order.payment && order.payment.status === 'paid')
|
||||
},
|
||||
notes: { patientNote: (order.notes && order.notes.patientNote) || '' }
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取订单失败', err)
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期为 YYYY-MM-DD(兼容时间戳/字符串)
|
||||
*/
|
||||
formatDate(date) {
|
||||
if (!date) return ''
|
||||
const d = new Date(date)
|
||||
if (isNaN(d.getTime())) return String(date)
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
},
|
||||
|
||||
/**
|
||||
* patient-info 组件按档案ID加载完成:保存患者原始信息(供提交)及档案附加信息(供标签展示)
|
||||
*/
|
||||
onProfileLoaded(e) {
|
||||
const profile = e.detail.profile || {}
|
||||
const p = profile.profile || {}
|
||||
this.patientInfo = {
|
||||
name: p.name || '',
|
||||
mobile: p.mobile || '',
|
||||
sex: p.sex || '',
|
||||
birth: p.birth || '',
|
||||
idnumber: p.idnumber || ''
|
||||
}
|
||||
this.setData({
|
||||
profileSummary: {
|
||||
bloodType: (profile.health && profile.health.bloodType) || '',
|
||||
height: (profile.health && profile.health.height) || '',
|
||||
weight: (profile.health && profile.health.weight) || '',
|
||||
remark: (profile.health && profile.health.remark) || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 表单输入处理
|
||||
*/
|
||||
onInput(e) {
|
||||
const key = e.currentTarget.dataset.key
|
||||
const value = e.detail.value
|
||||
this.setData({ [`form.${key}`]: value })
|
||||
},
|
||||
|
||||
/**
|
||||
* 就诊日期选择
|
||||
*/
|
||||
onDateChange(e) {
|
||||
this.setData({ 'form.schedule.date': e.detail.value })
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否已支付切换
|
||||
*/
|
||||
onPaidChange(e) {
|
||||
this.setData({ 'form.payment.paid': e.detail.value })
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交订单(新增或保存修改)
|
||||
*/
|
||||
onSubmit() {
|
||||
if (this.data.submitting) return
|
||||
|
||||
const { form } = this.data
|
||||
if (!this.profileId) return wx.showToast({ title: '请先选择健康档案', icon: 'none' })
|
||||
// 患者信息取自档案/订单
|
||||
const patient = this.patientInfo
|
||||
if (!patient || !patient.name) return wx.showToast({ title: '健康档案信息未加载', icon: 'none' })
|
||||
if (!form.hospital.name) return wx.showToast({ title: '请输入医院名称', icon: 'none' })
|
||||
if (!form.schedule.date) return wx.showToast({ title: '请选择就诊日期', icon: 'none' })
|
||||
|
||||
const app = getApp()
|
||||
const userId = app.globalData.user?._id
|
||||
if (!userId) return wx.showToast({ title: '未获取到用户信息,请重新登录', icon: 'none' })
|
||||
|
||||
if (this.data.isEdit) {
|
||||
this.updateOrder(form)
|
||||
} else {
|
||||
this.createOrder(form, userId)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增订单
|
||||
*/
|
||||
createOrder(form, userId) {
|
||||
const payload = {
|
||||
userId,
|
||||
healthProfileId: this.profileId || null,
|
||||
patient: this.patientInfo,
|
||||
hospital: {
|
||||
name: form.hospital.name,
|
||||
department: form.hospital.department,
|
||||
doctor: form.hospital.doctor
|
||||
},
|
||||
schedule: {
|
||||
date: form.schedule.date,
|
||||
duration: 60
|
||||
},
|
||||
escort: {
|
||||
serviceId: -1,
|
||||
serviceName: form.escort.serviceName
|
||||
},
|
||||
payment: {
|
||||
totalFee: form.payment.totalFee ? Number(form.payment.totalFee) : 0,
|
||||
paidFee: 0,
|
||||
status: 'unpaid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: form.notes.patientNote
|
||||
}
|
||||
}
|
||||
|
||||
this.setData({ submitting: true })
|
||||
wx.showLoading({ title: '提交中...' })
|
||||
|
||||
API.escort.createRecord(payload)
|
||||
.then(res => {
|
||||
wx.hideLoading()
|
||||
if (res.code !== 0) {
|
||||
this.setData({ submitting: false })
|
||||
return wx.showToast({ title: res.msg || '创建失败', icon: 'none' })
|
||||
}
|
||||
wx.showToast({ title: '创建成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 600)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('创建订单失败', err)
|
||||
this.setData({ submitting: false })
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 编辑模式:保存修改
|
||||
*/
|
||||
updateOrder(form) {
|
||||
const payload = {
|
||||
'hospital.name': form.hospital.name,
|
||||
'hospital.department': form.hospital.department,
|
||||
'hospital.doctor': form.hospital.doctor,
|
||||
'schedule.date': form.schedule.date,
|
||||
'escort.serviceName': form.escort.serviceName,
|
||||
'payment.totalFee': form.payment.totalFee ? Number(form.payment.totalFee) : 0,
|
||||
'payment.paidFee': form.payment.paid ? (form.payment.totalFee ? Number(form.payment.totalFee) : 0) : 0,
|
||||
'payment.status': form.payment.paid ? 'paid' : 'unpaid',
|
||||
'notes.patientNote': form.notes.patientNote
|
||||
}
|
||||
|
||||
this.setData({ submitting: true })
|
||||
wx.showLoading({ title: '保存中...' })
|
||||
|
||||
API.escort.updateRecord(this.orderId, payload)
|
||||
.then(res => {
|
||||
wx.hideLoading()
|
||||
if (res.code !== 0) {
|
||||
this.setData({ submitting: false })
|
||||
return wx.showToast({ title: res.msg || '保存失败', icon: 'none' })
|
||||
}
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
wx.navigateBack()
|
||||
}, 600)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('更新订单失败', err)
|
||||
this.setData({ submitting: false })
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "新增预约",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"patient-info": "/components/patient-info/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/* pages/order/orderEdit.wxss */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6fa;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 健康档案简要信息 */
|
||||
.profile-brief {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.brief-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.brief-tag {
|
||||
font-size: 22rpx;
|
||||
background-color: rgba(26, 188, 156, 0.1);
|
||||
color: #1abc9c;
|
||||
border-radius: 16rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
}
|
||||
|
||||
.brief-remark {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 未选择档案占位 */
|
||||
.profile-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.profile-empty-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.profile-empty-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.profile-empty-sub {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.profile-empty-btn {
|
||||
padding: 14rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: rgba(26, 188, 156, 0.1);
|
||||
color: #1abc9c;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 档案选择弹层 */
|
||||
.picker-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.picker-sheet {
|
||||
width: 100%;
|
||||
max-height: 75vh;
|
||||
background: #ffffff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.picker-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 32rpx 0;
|
||||
}
|
||||
|
||||
.picker-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.picker-close {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
.picker-search {
|
||||
padding: 24rpx 32rpx;
|
||||
}
|
||||
|
||||
.picker-search-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background: #f5f6fa;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.picker-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: 55vh;
|
||||
padding: 0 32rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f2;
|
||||
}
|
||||
|
||||
.picker-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.picker-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #e6f9f3;
|
||||
color: #17c3a5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.picker-item-main {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.picker-item-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-item-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.picker-item-sub {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.picker-item-mobile {
|
||||
display: block;
|
||||
margin-top: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
color: #c0c4cc;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.picker-empty {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 0 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
padding: 28rpx 0 12rpx;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 96rpx;
|
||||
border-bottom: 1rpx solid #f0f0f2;
|
||||
}
|
||||
|
||||
.field:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.field-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.field-switch {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
margin-right: 16rpx;
|
||||
font-size: 26rpx;
|
||||
|
||||
&.paid {
|
||||
color: #17c3a5;
|
||||
}
|
||||
|
||||
&.unpaid {
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
|
||||
.field-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.field-value.placeholder {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
color: #c0c4cc;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.field-textarea {
|
||||
align-items: flex-start;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.field-area {
|
||||
flex: 1;
|
||||
min-height: 120rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 16rpx;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, #2dd36f, #17c3a5);
|
||||
border-radius: 48rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(23, 195, 165, 0.25);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<!--pages/order/orderEdit.wxml-->
|
||||
<view class="page">
|
||||
<!-- 未选择档案时的占位提示 -->
|
||||
<view class="profile-brief profile-empty" wx:if="{{needPickProfile}}" bindtap="openProfilePicker">
|
||||
<view class="profile-empty-main">
|
||||
<text class="profile-empty-title">尚未选择健康档案</text>
|
||||
<text class="profile-empty-sub">患者信息将取自所选档案</text>
|
||||
</view>
|
||||
<view class="profile-empty-btn">选择档案</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康档案简要信息 -->
|
||||
<view class="profile-brief" wx:if="{{profileId || profileSummary}}">
|
||||
<!-- 有关联档案:统一按档案ID加载(不读缓存,直接请求最新),患者信息以档案为准 -->
|
||||
<patient-info
|
||||
wx:if="{{profileId}}"
|
||||
profile-id="{{profileId}}"
|
||||
use-cache="{{false}}"
|
||||
bindloaded="onProfileLoaded"
|
||||
/>
|
||||
<!-- 无关联档案(如订单未关联档案):使用订单快照 -->
|
||||
<patient-info
|
||||
wx:else
|
||||
name="{{profileSummary.name}}"
|
||||
sex-label="{{profileSummary.sexLabel}}"
|
||||
age-text="{{profileSummary.ageText}}"
|
||||
mobile="{{profileSummary.mobile}}"
|
||||
location-text="{{profileSummary.locationText}}"
|
||||
/>
|
||||
<view class="brief-tags" wx:if="{{profileSummary.bloodType || profileSummary.height || profileSummary.weight}}">
|
||||
<text wx:if="{{profileSummary.bloodType}}" class="brief-tag">血型 {{profileSummary.bloodType}}</text>
|
||||
<text wx:if="{{profileSummary.height}}" class="brief-tag">身高 {{profileSummary.height}}cm</text>
|
||||
<text wx:if="{{profileSummary.weight}}" class="brief-tag">体重 {{profileSummary.weight}}kg</text>
|
||||
</view>
|
||||
<text wx:if="{{profileSummary.remark}}" class="brief-remark">备注:{{profileSummary.remark}}</text>
|
||||
</view>
|
||||
|
||||
<view class="form">
|
||||
<!-- 就诊信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">就诊信息</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">医院</text>
|
||||
<input class="field-input" placeholder="请输入医院名称" value="{{form.hospital.name}}" data-key="hospital.name" bindinput="onInput" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">科室</text>
|
||||
<input class="field-input" placeholder="请输入科室" value="{{form.hospital.department}}" data-key="hospital.department" bindinput="onInput" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">医生姓名</text>
|
||||
<input class="field-input" placeholder="请输入医生姓名(选填)" value="{{form.hospital.doctor}}" data-key="hospital.doctor" bindinput="onInput" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">就诊日期</text>
|
||||
<picker class="field-picker" mode="date" value="{{form.schedule.date}}" start="{{today}}" bindchange="onDateChange">
|
||||
<view class="field-value {{form.schedule.date ? '' : 'placeholder'}}">
|
||||
{{form.schedule.date || '请选择就诊日期'}}
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">服务信息</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">服务名称</text>
|
||||
<input class="field-input" placeholder="请输入服务名称" value="{{form.escort.serviceName}}" data-key="escort.serviceName" bindinput="onInput" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">费用(元)</text>
|
||||
<input class="field-input" type="digit" placeholder="请输入服务费用" value="{{form.payment.totalFee}}" data-key="payment.totalFee" bindinput="onInput" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="field-label">是否已支付</text>
|
||||
<view class="field-switch">
|
||||
<text class="switch-label {{form.payment.paid ? 'paid' : 'unpaid'}}">{{form.payment.paid ? '已支付' : '未支付'}}</text>
|
||||
<switch checked="{{form.payment.paid}}" color="#17c3a5" bindchange="onPaidChange" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="field field-textarea">
|
||||
<text class="field-label">备注</text>
|
||||
<textarea class="field-area" placeholder="请输入备注信息(选填)" value="{{form.notes.patientNote}}" data-key="notes.patientNote" bindinput="onInput" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交 -->
|
||||
<view class="submit-btn" bindtap="onSubmit">{{isEdit ? '保存修改' : '提交订单'}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 健康档案选择弹层 -->
|
||||
<view class="picker-mask" wx:if="{{showProfilePicker}}" bindtap="closeProfilePicker">
|
||||
<view class="picker-sheet" catchtap="noop">
|
||||
<view class="picker-head">
|
||||
<text class="picker-title">选择健康档案</text>
|
||||
<text class="picker-close" bindtap="closeProfilePicker">✕</text>
|
||||
</view>
|
||||
<view class="picker-search">
|
||||
<input class="picker-search-input" placeholder="搜索姓名 / 手机号" value="{{profileKeyword}}" bindinput="onProfileSearch" />
|
||||
</view>
|
||||
<scroll-view scroll-y class="picker-list">
|
||||
<view
|
||||
class="picker-item"
|
||||
wx:for="{{filteredProfiles}}"
|
||||
wx:key="id"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="onProfileSelect"
|
||||
>
|
||||
<view class="picker-avatar">
|
||||
<text>{{item.name[0]}}</text>
|
||||
</view>
|
||||
<view class="picker-item-main">
|
||||
<view class="picker-item-name-row">
|
||||
<text class="picker-item-name">{{item.name}}</text>
|
||||
<text class="picker-item-sub">{{item.sexLabel}}{{item.sexLabel ? ' · ' : ''}}{{item.ageText}}</text>
|
||||
</view>
|
||||
<text class="picker-item-mobile">{{item.mobile || '暂无电话'}}</text>
|
||||
</view>
|
||||
<text class="picker-arrow">›</text>
|
||||
</view>
|
||||
<view class="picker-empty" wx:if="{{filteredProfiles.length === 0}}">未找到匹配的档案</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
+16
-1
@@ -3,7 +3,7 @@ const request = require('./request.js')
|
||||
const API = {
|
||||
user: {
|
||||
wxGetPhoneNumber: (data) => request.post('/user/wxgetphonenumber', data),
|
||||
wxSignin: (data) => request.post('/user/wxsignin', data),
|
||||
wxSignin: (data) => request.post('/user/wxsignin', data, { skipAuthWait: true }),
|
||||
signout: (data) => request.post('/user/signout', data),
|
||||
update: (data) => request.post('/user/update', data),
|
||||
userInfo: (data) => request.post('/user/userInfo', data),
|
||||
@@ -11,6 +11,9 @@ const API = {
|
||||
},
|
||||
|
||||
escort: {
|
||||
// 管理端:按条件查询全部记录
|
||||
getAllRecords: (params) => request.get('/health/escort-record', params),
|
||||
// 当前登录用户的记录
|
||||
getMyRecords: (params) => request.get('/health/escort-record/my', params),
|
||||
getAttendantRecords: (params) => request.get('/health/escort-record/attendant', params),
|
||||
getRecordById: (id) => request.get(`/health/escort-record/${id}`),
|
||||
@@ -20,9 +23,21 @@ const API = {
|
||||
deleteRecord: (id) => request.delete(`/health/escort-record/${id}`),
|
||||
},
|
||||
|
||||
healthProfile: {
|
||||
getProfiles: (params) => request.get('/health/health-profile', params),
|
||||
getProfileById: (id) => request.get(`/health/health-profile/${id}`),
|
||||
createProfile: (data) => request.post('/health/health-profile', data),
|
||||
updateProfile: (id, data) => request.put(`/health/health-profile/${id}`, data),
|
||||
deleteProfile: (id) => request.delete(`/health/health-profile/${id}`),
|
||||
},
|
||||
|
||||
resource: {
|
||||
getServices: (params) => request.get('/health/service', params),
|
||||
getAgreement: (params) => request.get('/health/agreement', params),
|
||||
getHospitalInfo: (params) => request.get('/health/hospital-info', params),
|
||||
getHospitalRanking: (params) => request.get('/health/hospital-ranking', params),
|
||||
getDepartmentRankings: (params) => request.get('/health/department-rankings', params),
|
||||
getAiQuickQuestions: (params) => request.get('/health/ai-quick-questions', params),
|
||||
},
|
||||
|
||||
ai: {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 根据出生年月(YYYY-MM-DD)计算年龄
|
||||
function calcAge(birth) {
|
||||
if (!birth) return 0
|
||||
const d = new Date(birth)
|
||||
if (isNaN(d.getTime())) return 0
|
||||
const now = new Date()
|
||||
let age = now.getFullYear() - d.getFullYear()
|
||||
const m = now.getMonth() - d.getMonth()
|
||||
if (m < 0 || (m === 0 && now.getDate() < d.getDate())) age--
|
||||
return age > 0 ? age : 0
|
||||
}
|
||||
|
||||
module.exports = { calcAge }
|
||||
+19
-5
@@ -1,17 +1,31 @@
|
||||
class Request {
|
||||
|
||||
constructor(baseURL = 'https://api.huashengtec.com') {
|
||||
//constructor(baseURL = 'http://127.0.0.1:9010') {
|
||||
//constructor(baseURL = 'http://127.0.0.1:9004') {
|
||||
this.baseURL = baseURL
|
||||
}
|
||||
|
||||
request(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { url, method = 'GET', data = {}, header = {}, ...rest } = options
|
||||
async request(options) {
|
||||
const { url, method = 'GET', data = {}, header = {}, skipAuthWait = false, ...rest } = options
|
||||
|
||||
// 等待登录完成后再携带 token,避免页面请求早于 app.js 登录的竞态
|
||||
// skipAuthWait: 登录请求自身使用,否则会等待自己导致死锁
|
||||
let token = ''
|
||||
try {
|
||||
const app = getApp()
|
||||
const token = app?.globalData?.user?.security?.token || ''
|
||||
if (skipAuthWait) {
|
||||
token = app?.globalData?.user?.security?.token || ''
|
||||
} else if (app && app.ensureLogin) {
|
||||
const user = await app.ensureLogin()
|
||||
token = user?.security?.token || ''
|
||||
} else {
|
||||
token = app?.globalData?.user?.security?.token || ''
|
||||
}
|
||||
} catch (e) {
|
||||
// 登录失败仍继续请求,由服务端返回未授权错误
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
data.appId = 'wxapp-escort-admin'
|
||||
|
||||
wx.request({
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// utils/store.js
|
||||
// 轻量全局缓存(内存级,冷启动自然为空)
|
||||
// - cacheProfile / getProfile / removeProfile:健康档案缓存(id → 原始文档)
|
||||
// - notifyProfileChange:档案变更通知,列表页订阅后刷新
|
||||
// 注意:拉取列表回填缓存时务必用 cacheProfile(静默),
|
||||
// 只有真实的增删改才走 notifyProfileChange,避免"回填→通知→再拉取"死循环
|
||||
const cache = {}
|
||||
const listeners = {}
|
||||
|
||||
/**
|
||||
* 订阅事件,返回取消订阅函数
|
||||
*/
|
||||
function on(event, cb) {
|
||||
if (!listeners[event]) listeners[event] = []
|
||||
listeners[event].push(cb)
|
||||
return () => off(event, cb)
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
*/
|
||||
function off(event, cb) {
|
||||
const list = listeners[event]
|
||||
if (!list) return
|
||||
const idx = list.indexOf(cb)
|
||||
if (idx > -1) list.splice(idx, 1)
|
||||
}
|
||||
|
||||
function emit(event, data) {
|
||||
;(listeners[event] || []).slice().forEach(cb => {
|
||||
try {
|
||||
cb(data)
|
||||
} catch (err) {
|
||||
console.error(`[store] ${event} 订阅回调异常`, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 通用键值 ----
|
||||
|
||||
function get(key) {
|
||||
return cache[key]
|
||||
}
|
||||
|
||||
function set(key, value) {
|
||||
cache[key] = value
|
||||
}
|
||||
|
||||
function remove(key) {
|
||||
delete cache[key]
|
||||
}
|
||||
|
||||
// ---- 健康档案缓存 ----
|
||||
|
||||
/**
|
||||
* 静默回填缓存(不触发变更通知)
|
||||
*/
|
||||
function cacheProfile(profile) {
|
||||
if (profile && profile._id) {
|
||||
cache[`profile:${profile._id}`] = profile
|
||||
}
|
||||
}
|
||||
|
||||
function getProfile(id) {
|
||||
return id ? cache[`profile:${id}`] : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*/
|
||||
function removeProfile(id) {
|
||||
if (id) delete cache[`profile:${id}`]
|
||||
}
|
||||
|
||||
/**
|
||||
* 档案变更通知(增删改成功后调用)
|
||||
* @param {string} action - 'create' | 'update' | 'remove'
|
||||
* @param {string} id - 档案ID
|
||||
*/
|
||||
function notifyProfileChange(action, id) {
|
||||
emit('profile-change', { action, id })
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
on,
|
||||
off,
|
||||
get,
|
||||
set,
|
||||
remove,
|
||||
cacheProfile,
|
||||
getProfile,
|
||||
removeProfile,
|
||||
notifyProfileChange
|
||||
}
|
||||
Reference in New Issue
Block a user