@ -0,0 +1,533 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="检测预约" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#F3F2F7" /> | |||
<view class="main"> | |||
<view class="card"> | |||
<view class="card-header">{{ typeDesc }}</view> | |||
<view class="flex row"> | |||
<view class="row-label">联系电话</view> | |||
<view class="row-content">{{ info.phone || '-' }}</view> | |||
</view> | |||
</view> | |||
<view class="card"> | |||
<view class="card-header">预约信息</view> | |||
<view class="form"> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
errorType="toast" | |||
> | |||
<!-- 自采 --> | |||
<template v-if="type == 1"> | |||
<view class="form-item"> | |||
<uv-form-item prop="addressData" :customStyle="formItemStyle"> | |||
<view class="form-item-label">寄送地址</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="jumpToSelectAddress"> | |||
<view v-if="form.addressData" class="text">{{ getAddressDesc(form.addressData) }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</template> | |||
<!-- 上门 --> | |||
<template v-else-if="type == 2"> | |||
<view class="form-item"> | |||
<uv-form-item prop="addressData" :customStyle="formItemStyle"> | |||
<view class="form-item-label">体检地址</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="jumpToSelectAddress"> | |||
<view v-if="form.addressData" class="text">{{ getAddressDesc(form.addressData) }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="date" :customStyle="formItemStyle"> | |||
<view class="form-item-label">预约日期</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="openDatePicker"> | |||
<view v-if="form.date" class="text">{{ $dayjs(form.date).format('YYYY-MM-DD') }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
<uv-datetime-picker | |||
ref="datetimePicker" | |||
v-model="form.date" | |||
mode="date" | |||
title="预约日期" | |||
confirmColor="#7451DE" | |||
cancelColor="#8B8B8B" | |||
round="32rpx" | |||
:minDate="minTime" | |||
@confirm="onDateChange" | |||
></uv-datetime-picker> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="timeRange" :customStyle="formItemStyle"> | |||
<view class="form-item-label">预约时段</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="openTimePicker"> | |||
<view v-if="form.timeRange" class="text">{{ form.timeRange.join('~') }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
<uv-picker | |||
ref="timeRangePicker" | |||
:columns="timeColumns" | |||
:defaultIndex="[0, 0]" | |||
title="预约时段" | |||
confirmColor="#7451DE" | |||
cancelColor="#8B8B8B" | |||
round="32rpx" | |||
@change="onTimeRangeColChange" | |||
@confirm="onTimeRangeChange" | |||
></uv-picker> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</template> | |||
<!-- 到店 --> | |||
<template v-else-if="type == 3"> | |||
<view class="form-item"> | |||
<uv-form-item prop="addressData" :customStyle="formItemStyle"> | |||
<view class="form-item-label">预约日期</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="openDatePicker"> | |||
<view v-if="form.date" class="text">{{ $dayjs(form.date).format('YYYY-MM-DD') }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
<uv-datetime-picker | |||
ref="datetimePicker" | |||
v-model="form.date" | |||
mode="date" | |||
title="预约日期" | |||
confirmColor="#7451DE" | |||
cancelColor="#8B8B8B" | |||
round="32rpx" | |||
:minDate="minTime" | |||
@confirm="onDateChange" | |||
></uv-datetime-picker> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="timeRange" :customStyle="formItemStyle"> | |||
<view class="form-item-label">预约时段</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="openTimePicker"> | |||
<view v-if="form.timeRange" class="text">{{ form.timeRange.join('~') }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
<uv-picker | |||
ref="timeRangePicker" | |||
:columns="timeColumns" | |||
:defaultIndex="[0, 0]" | |||
title="预约时段" | |||
confirmColor="#7451DE" | |||
cancelColor="#8B8B8B" | |||
round="32rpx" | |||
@change="onTimeRangeColChange" | |||
@confirm="onTimeRangeChange" | |||
></uv-picker> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="hospital" :customStyle="formItemStyle"> | |||
<view class="form-item-label">预约医院</view> | |||
<view class="form-item-content"> | |||
<view class="flex row" @click="openHospitalPicker"> | |||
<view v-if="form.hospital" class="text">{{ getHospitalDesc(form.hospital) }}</view> | |||
<view v-else class="text placeholder">请选择内容</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon> | |||
</view> | |||
<uv-picker | |||
ref="hospitalPicker" | |||
:columns="[hospitalOptions]" | |||
keyName="label" | |||
title="预约医院" | |||
confirmColor="#7451DE" | |||
cancelColor="#8B8B8B" | |||
round="32rpx" | |||
@confirm="onHospitalChange" | |||
></uv-picker> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="name" :customStyle="formItemStyle"> | |||
<view class="form-item-label">姓名</view> | |||
<view class="form-item-content input"> | |||
<formInput v-model="form.name" placeholder="请输入内容"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="phone" :customStyle="formItemStyle"> | |||
<view class="form-item-label">联系方式</view> | |||
<view class="form-item-content input"> | |||
<formInput v-model="form.phone" placeholder="请输入内容"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</template> | |||
</uv-form> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="bottom"> | |||
<button class="btn" @click="onSubmit">提交预约</button> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import { mapState } from 'vuex' | |||
import formInput from '@/pages_order/components/formInput.vue' | |||
const TYPE_AND_DESC_MAPPING = { | |||
1: '自采', | |||
2: '上门', | |||
3: '到店', | |||
} | |||
export default { | |||
components: { | |||
formInput, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
type: null, | |||
info: {}, | |||
form: { | |||
addressData: null, | |||
date: null, | |||
timeRange: null, | |||
hospital: null, | |||
name: null, | |||
phone: null, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
minTime: new Date().getTime(), | |||
hours: [], | |||
timeColumns: [], | |||
hospitalOptions: [] | |||
} | |||
}, | |||
computed: { | |||
...mapState(['configList', 'userInfo', 'payOrderProduct', 'addressInfo']), | |||
typeDesc() { | |||
const desc = TYPE_AND_DESC_MAPPING[this.type] || '' | |||
return `${desc}检测` | |||
}, | |||
}, | |||
onShow() { | |||
console.log('onShow') | |||
console.log('address', this.addressInfo) | |||
this.form.addressData = this.addressInfo || null | |||
}, | |||
onLoad(arg) { | |||
const { id, type } = arg | |||
this.id = id | |||
this.type = parseInt(type) | |||
// todo: fetch data | |||
this.fetchCheckUpPackageInfo() | |||
this.fetchCheckUpBookInfo() | |||
}, | |||
onReady() { | |||
this.setRules() | |||
}, | |||
onUnload() { | |||
this.$store.commit('setAddressInfo', null) | |||
}, | |||
methods: { | |||
setRules() { | |||
let rules = {} | |||
switch(this.type) { | |||
case 1: // 自采 | |||
rules = { | |||
'addressData': { | |||
type: 'object', | |||
required: true, | |||
message: '请选择寄送地址', | |||
}, | |||
} | |||
break | |||
case 2: // 上门 | |||
rules = { | |||
'addressData': { | |||
type: 'object', | |||
required: true, | |||
message: '请选择体检地址', | |||
}, | |||
'date': { | |||
type: 'string', | |||
required: true, | |||
message: '请选择预约日期', | |||
}, | |||
'timeRange': { | |||
type: 'array', | |||
required: true, | |||
message: '请选择预约时段', | |||
}, | |||
} | |||
this.setTimeColumns() | |||
break | |||
case 3: // 到店 | |||
rules = { | |||
'date': { | |||
type: 'string', | |||
required: true, | |||
message: '请选择预约日期', | |||
}, | |||
'timeRange': { | |||
type: 'array', | |||
required: true, | |||
message: '请选择预约时段', | |||
}, | |||
'hospital': { | |||
type: 'string', | |||
required: true, | |||
message: '请选择预约医院', | |||
}, | |||
'name': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入姓名', | |||
}, | |||
'phone': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入联系方式', | |||
}, | |||
} | |||
this.setTimeColumns() | |||
this.hospitalOptions = [ | |||
{ id: '001', label: '湖南省长沙市湘雅第一医院' }, | |||
{ id: '002', label: '湖南省长沙市湘雅第二医院' }, | |||
{ id: '003', label: '湖南省长沙市湘雅第三医院' }, | |||
{ id: '004', label: '湖南省长沙市湘雅第四医院' }, | |||
] | |||
break | |||
default: | |||
break | |||
} | |||
this.$refs.form.setRules(rules); | |||
}, | |||
getAddressDesc(data) { | |||
if (!data) { | |||
return '' | |||
} | |||
const { area, address } = data | |||
return `${area.join('')}${address}` | |||
}, | |||
getHospitalDesc() { | |||
}, | |||
fixedZero(num) { | |||
return `${num < 10 ? '0' + num : num}` | |||
}, | |||
setTimeColumns(date) { | |||
let currentTime = this.$dayjs() | |||
let startHour = 8 | |||
let endHour = 22 | |||
if (date && this.$dayjs(date).isSame(currentTime, 'day')) { | |||
let currentHour = currentTime.hour() | |||
console.log('currentHour', currentHour) | |||
console.log('startHour', startHour) | |||
console.log('endHour', endHour) | |||
console.log('date', this.$dayjs(date)) | |||
console.log('currentTime', currentTime) | |||
console.log('isToday', this.$dayjs(date).isSame(currentTime, 'day')) | |||
if (currentHour > endHour) { | |||
this.timeColumns = [] | |||
return | |||
} | |||
if (currentHour > startHour) { | |||
// todo: check | |||
startHour = currentHour | |||
} | |||
if (startHour == endHour) { | |||
this.timeColumns = [] | |||
return | |||
} | |||
} | |||
this.hours = new Array(endHour - startHour + 1).fill(startHour).map((val, idx) => val + idx) | |||
let startCols = this.hours.slice(0, this.hours.length - 1).map(hour => `${this.fixedZero(hour)}:00`) | |||
let endCols = this.hours.slice(1).map(hour => `${this.fixedZero(hour)}:00`) | |||
this.timeColumns = [startCols, endCols] | |||
this.$refs.timeRangePicker.setColumnValues(0, startCols) | |||
this.$refs.timeRangePicker.setColumnValues(1, endCols) | |||
}, | |||
openTimePicker() { | |||
this.$refs.timeRangePicker.open(); | |||
}, | |||
onTimeRangeColChange(e) { | |||
console.log('onTimeRangeColChange', e) | |||
if (e.columnIndex == 0) { | |||
let startIdx = e.indexs[0] | |||
let endCols = startIdx == this.hours.length - 1 ? [] : this.hours.slice(startIdx + 1).map(hour => `${this.fixedZero(hour)}:00`) | |||
console.log('endCols', endCols) | |||
this.timeColumns[1] = endCols | |||
this.$refs.timeRangePicker.setColumnValues(1, endCols) | |||
} | |||
}, | |||
onTimeRangeChange(e) { | |||
console.log('onTimeRangeChange', e) | |||
this.form.timeRange = e.value | |||
const [startTime, endTime] = this.form.timeRange | |||
let startIdx = startTime ? this.timeColumns[0].findIndex(item => item === startTime) : 0 | |||
let endIdx = endTime ? this.timeColumns[1].findIndex(item => item === endTime) : 0 | |||
console.log('setIndexs', [startIdx, endIdx]) | |||
this.$refs.timeRangePicker.setIndexs([startIdx, endIdx], true); | |||
}, | |||
openDatePicker() { | |||
this.$refs.datetimePicker.open(); | |||
}, | |||
onDateChange(e) { | |||
const date = e.value | |||
this.setTimeColumns(date) | |||
const [startTime] = this.form.timeRange || [] | |||
let dateStr = this.$dayjs(date).format('YYYY-MM-DD') | |||
if (startTime && this.$dayjs(`${dateStr} ${startTime}`).isBefore(this.$dayjs())) { | |||
this.form.timeRange = null | |||
this.$refs.datetimePicker.setIndexs([0, 0]); | |||
} | |||
}, | |||
openHospitalPicker() { | |||
this.$refs.hospitalPicker.open(); | |||
}, | |||
onHospitalChange(e) { | |||
console.log('onHospitalChange', e) | |||
this.form.hospital = e.value[0].id | |||
console.log('form.hospital', this.form.hospital) | |||
}, | |||
getHospitalDesc(id) { | |||
console.log('getHospitalDesc', id) | |||
return this.hospitalOptions.find(item => item.id === id)?.label | |||
}, | |||
fetchCheckUpPackageInfo() { | |||
// todo: fetch | |||
this.info = { | |||
phone: '0745-5982433', | |||
} | |||
}, | |||
fetchCheckUpBookInfo() { | |||
// todo: fetch | |||
const detail = { | |||
id: '003', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '孕产妇体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
area: ['海南省', '海口市', '秀英区'], | |||
address: '秀英街道5单元183室', | |||
hospital: '001', | |||
createTime: '2025-04-28 08:14', | |||
appointmentDate: '2025-04-28', | |||
appointmentTime: ['08:00', '09:00'], | |||
type: 2, | |||
status: 2, | |||
} | |||
const { | |||
userName, | |||
phone, | |||
area, | |||
address, | |||
appointmentDate, | |||
appointmentTime, | |||
hospital, | |||
} = detail | |||
this.form = { | |||
addressData: { | |||
id: '001', | |||
name: userName, | |||
phone, | |||
area, | |||
address, | |||
}, | |||
date: appointmentDate, | |||
timeRange: appointmentTime, | |||
hospital, | |||
name: userName, | |||
phone, | |||
} | |||
}, | |||
jumpToSelectAddress() { | |||
this.$utils.navigateTo('/pages_order/address/addressList') | |||
}, | |||
async onSubmit() { | |||
try { | |||
const res = await this.$refs.form.validate() | |||
console.log('onSubmit res', res) | |||
// todo | |||
setTimeout(() => { | |||
// todo: check | |||
// this.$utils.navigateBack() | |||
let id = '001' | |||
this.$utils.redirectTo(`/pages_order/checkup/checkupBook/detail?id=${id}`) | |||
}, 800) | |||
} catch (err) { | |||
console.log('onSubmit err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import './style.scss'; | |||
</style> |
@ -0,0 +1,286 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="我的预约" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#F3F2F7" /> | |||
<template v-if="detail"> | |||
<view class="main"> | |||
<view class="flex flex-column status"> | |||
<template v-if="orderSuccess"> | |||
<image class="icon" src="@/pages_order/static/checkup/icon-success.png" mode="widthFix"></image> | |||
</template> | |||
<template v-else> | |||
<image class="icon" src="@/pages_order/static/checkup/icon-error.png" mode="widthFix"></image> | |||
</template> | |||
<view>{{ statusDesc }}</view> | |||
</view> | |||
<!-- 自采 --> | |||
<template v-if="detail.type == 1"> | |||
<view class="card"> | |||
<view class="card-header">自采检测预约信息</view> | |||
<view class="flex row"> | |||
<view class="row-label">姓名</view> | |||
<view class="row-content">{{ detail.userName }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">寄送地址</view> | |||
<view class="row-content">{{ detail.addressDesc }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">电话</view> | |||
<view class="row-content">{{ detail.phone }}</view> | |||
</view> | |||
</view> | |||
</template> | |||
<!-- 上门 --> | |||
<template v-else-if="detail.type == 2"> | |||
<view class="card"> | |||
<view class="card-header">上门检测预约信息</view> | |||
<view class="flex row"> | |||
<view class="row-label">姓名</view> | |||
<view class="row-content">{{ detail.userName }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">体检地址</view> | |||
<view class="row-content">{{ detail.addressDesc }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">时间</view> | |||
<view class="row-content highlight">{{ detail.timeDesc }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">电话</view> | |||
<view class="row-content">{{ detail.phone }}</view> | |||
</view> | |||
</view> | |||
</template> | |||
<!-- 到店 --> | |||
<template v-else-if="detail.type == 3"> | |||
<view class="card"> | |||
<view class="card-header">到店检测预约信息</view> | |||
<view class="flex row"> | |||
<view class="row-label">姓名</view> | |||
<view class="row-content">{{ detail.userName }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">时间</view> | |||
<view class="row-content highlight">{{ detail.timeDesc }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">电话</view> | |||
<view class="row-content">{{ detail.phone }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">医院</view> | |||
<view class="row-content">{{ detail.hospitalDesc }}</view> | |||
</view> | |||
</view> | |||
</template> | |||
</view> | |||
<view class="flex bottom"> | |||
<!-- 自采 --> | |||
<template v-if="detail.status == 1"> | |||
<button class="btn btn-plain" open-type="contact">客服</button> | |||
<button class="btn" @click="openTrackingNoPopup">已回寄</button> | |||
</template> | |||
<!-- 上门, 到店, 预约失败 --> | |||
<template v-else-if="[2, 3, 6].includes(detail.status)"> | |||
<button class="btn btn-plain" @click="onCancel">取消</button> | |||
<button class="btn" @click="onEdit">修改</button> | |||
</template> | |||
<!-- 已完成 --> | |||
<template v-else-if="detail.status == 4"> | |||
<button class="btn" @click="jumpToReportDetail">报告查看</button> | |||
</template> | |||
</view> | |||
</template> | |||
<checkupServicePopup ref="checkupServicePopup"></checkupServicePopup> | |||
<checkupTrackingNoPopup ref="checkupTrackingNoPopup" @submitted="getData"></checkupTrackingNoPopup> | |||
</view> | |||
</template> | |||
<script> | |||
import checkupServicePopup from '@/pages_order/checkup/checkupServicePopup.vue' | |||
import checkupTrackingNoPopup from '@/pages_order/checkup/checkupTrackingNoPopup.vue' | |||
export default { | |||
components: { | |||
checkupServicePopup, | |||
checkupTrackingNoPopup, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
detail: null, | |||
hospitalOptions: [ | |||
{ id: '001', label: '湖南省长沙市湘雅第一医院' }, | |||
{ id: '002', label: '湖南省长沙市湘雅第二医院' }, | |||
{ id: '003', label: '湖南省长沙市湘雅第三医院' }, | |||
{ id: '004', label: '湖南省长沙市湘雅第四医院' }, | |||
] | |||
} | |||
}, | |||
onLoad(arg) { | |||
this.id = arg.id | |||
this.getData() | |||
}, | |||
computed: { | |||
orderSuccess() { | |||
// todo: check status | |||
return this.detail?.status < 5 | |||
}, | |||
statusDesc() { | |||
const status = this.detail?.status | |||
if (status < 4) { | |||
return '预约成功' | |||
} | |||
if (status == 4) { | |||
return '已完成' | |||
} | |||
if (status == 6) { | |||
return '预约失败' | |||
} | |||
return '' | |||
}, | |||
}, | |||
methods: { | |||
getData() { | |||
// todo: fetch by id | |||
let detail = { | |||
id: '003', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '孕产妇体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
area: ['海南省', '海口市', '秀英区'], | |||
address: '秀英街道5单元183室', | |||
hospital: '001', | |||
createTime: '2025-04-28 08:14', | |||
appointmentDate: '2025-04-28', | |||
appointmentTime: ['08:00', '09:00'], | |||
type: 2, | |||
status: 4, | |||
} | |||
detail.addressDesc = this.getAddressDesc(detail) | |||
detail.timeDesc = this.getTimeDesc(detail) | |||
detail.hospitalDesc = this.getHospitalDesc(detail) | |||
this.detail = detail | |||
if ([2, 3].includes(this.detail.status)) { | |||
this.$refs.checkupServicePopup.open() | |||
} | |||
}, | |||
getAddressDesc(data) { | |||
if (!data) { | |||
return '' | |||
} | |||
console.log('detail', data) | |||
const { area, address } = data | |||
console.log('area', area, 'address', address) | |||
return `${(area || []).join('')}${address}` | |||
}, | |||
getTimeDesc(data) { | |||
if (!data) { | |||
return '' | |||
} | |||
const { appointmentDate, appointmentTime } = data | |||
return `${appointmentDate} ${(appointmentTime || []).join('~')}` | |||
}, | |||
getHospitalDesc(data) { | |||
const { hospital } = data | |||
return this.hospitalOptions.find(item => item.id == hospital)?.label | |||
}, | |||
openTrackingNoPopup() { | |||
this.$refs.checkupTrackingNoPopup.open(this.id) | |||
}, | |||
onEdit() { | |||
this.$utils.navigateTo(`/pages_order/checkup/checkupBook/apply?id=${this.detail.id}&type=${this.detail.type}`) | |||
}, | |||
onCancel() { | |||
// todo: fetch | |||
uni.showToast({ | |||
title: '取消成功', | |||
}); | |||
setTimeout(() => { | |||
this.$utils.navigateBack() | |||
}, 800) | |||
}, | |||
jumpToReportDetail() { | |||
this.$utils.navigateTo(`/pages_order/checkup/checkupReport/index?id=${this.detail.id}`) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import './style.scss'; | |||
.status { | |||
margin-bottom: 40rpx; | |||
row-gap: 4rpx; | |||
width: 100%; | |||
padding: 24rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
background: #FFFFFF; | |||
border-radius: 24rpx; | |||
.icon { | |||
width: 84rpx; | |||
height: auto; | |||
} | |||
} | |||
.row { | |||
&-content { | |||
text-align: right; | |||
font-size: 28rpx; | |||
color: #393939; | |||
&.highlight { | |||
font-weight: 500; | |||
color: #7451DE; | |||
} | |||
} | |||
} | |||
.bottom { | |||
align-items: flex-start; | |||
column-gap: 32rpx; | |||
.btn { | |||
flex: 1; | |||
&-plain { | |||
padding: 14rpx 0; | |||
color: #252545; | |||
background: transparent; | |||
border: 2rpx solid #252545; | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,126 @@ | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background-color: $uni-bg-color; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
padding: calc(var(--status-bar-height) + 144rpx) 32rpx 224rpx 32rpx; | |||
} | |||
.card { | |||
padding: 32rpx; | |||
background: #FAFAFF; | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
&-header { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
margin-bottom: 32rpx; | |||
} | |||
} | |||
.row { | |||
justify-content: space-between; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
column-gap: 24rpx; | |||
& + & { | |||
margin-top: 32rpx; | |||
} | |||
&-label { | |||
flex: none; | |||
font-size: 26rpx; | |||
color: #8B8B8B; | |||
} | |||
&-content { | |||
font-size: 32rpx; | |||
color: #181818; | |||
} | |||
} | |||
.form { | |||
padding: 8rpx 0 0 0; | |||
&-item { | |||
border-bottom: 2rpx solid #EEEEEE; | |||
&:last-child { | |||
border: none; | |||
} | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
&-label { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
&-content { | |||
margin-top: 14rpx; | |||
padding: 6rpx 0; | |||
.text { | |||
padding: 2rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 32rpx; | |||
line-height: 1.4; | |||
&.placeholder { | |||
color: #C6C6C6; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.bottom { | |||
position: fixed; | |||
left: 0; | |||
bottom: 0; | |||
width: 100vw; | |||
height: 200rpx; | |||
padding: 24rpx 40rpx; | |||
background: #FFFFFF; | |||
box-sizing: border-box; | |||
.btn { | |||
width: 100%; | |||
padding: 16rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
border-radius: 41rpx; | |||
} | |||
} |
@ -0,0 +1,241 @@ | |||
<template> | |||
<view class="card" @click="jumpToProductDetail"> | |||
<view class="flex top"> | |||
<view class="title">{{ data.title }}</view> | |||
<view :class="['flex', 'status', `status-${data.status}`]">{{ statusDesc }}</view> | |||
</view> | |||
<view class="flex main"> | |||
<image class="img" :src="data.url" mode="scaleToFill"></image> | |||
<view class="info"> | |||
<view class="flex row"> | |||
<view class="row-label">客户姓名:</view> | |||
<view class="row-content">{{ data.userName }}</view> | |||
</view> | |||
<template v-if="data.status == 0"> | |||
<view class="flex row"> | |||
<view class="row-label">下单时间:</view> | |||
<view class="row-content">{{ data.createTime }}</view> | |||
</view> | |||
</template> | |||
<template v-else> | |||
<view class="flex row"> | |||
<view class="row-label">检测类型:</view> | |||
<view class="row-content">{{ data.typeDesc }}</view> | |||
</view> | |||
<view class="flex row"> | |||
<view class="row-label">预约时间:</view> | |||
<view class="row-content">{{ data.appointmentTime || '-' }}</view> | |||
</view> | |||
</template> | |||
<view class="flex row"> | |||
<view class="row-label">联系电话:</view> | |||
<view class="row-content">{{ data.phone }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="flex bottom"> | |||
<view class="flex price"> | |||
<text class="price-label">总价格:</text> | |||
<text class="price-unit">¥</text><text class="price-value">{{ data.amount.toFixed(2) }}</text> | |||
</view> | |||
<view class="flex btns"> | |||
<!-- 待预约 --> | |||
<template v-if="data.status == 0"> | |||
<button class="btn" @click.stop="onBook">检测预约</button> | |||
</template> | |||
<!-- 自采 --> | |||
<template v-else-if="data.status == 1"> | |||
<button class="btn" @click.stop="onSendBackOnline">线上回寄试剂盒</button> | |||
</template> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
const STATUS_AND_DESC_MAPPING = { | |||
0: '待预约', | |||
1: '自采', | |||
2: '上门', | |||
3: '到店', | |||
4: '已完成', | |||
5: '已取消', | |||
6: '预约失败', | |||
} | |||
export default { | |||
props: { | |||
data: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
} | |||
}, | |||
computed: { | |||
statusDesc() { | |||
return STATUS_AND_DESC_MAPPING[this.data.status] | |||
}, | |||
}, | |||
methods: { | |||
onBook() { | |||
this.$utils.navigateTo(`/pages_order/checkup/checkupBook/apply?id=${this.data.id}&type=${this.data.type}`) | |||
}, | |||
onSendBackOnline() { | |||
this.$emit('sendBack') | |||
}, | |||
jumpToProductDetail() { | |||
console.log(this.data.id, 'jumpToProductDetail') | |||
if ([0, 5].includes(this.data.status) == 5) { // 0-待预约 5-已取消 | |||
this.$utils.navigateTo(`/pages_order/order/orderDetail/index?id=${this.data.id}`) | |||
// todo: check product id | |||
// this.$utils.navigateTo(`/pages_order/product/productDetail?id=${this.data.id}`) | |||
} else { | |||
this.$utils.navigateTo(`/pages_order/checkup/checkupBook/detail?id=${this.data.id}`) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.card { | |||
width: 100%; | |||
padding: 32rpx; | |||
box-sizing: border-box; | |||
background: #FAFAFF; | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
} | |||
.top { | |||
justify-content: space-between; | |||
.title { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
} | |||
.status { | |||
display: inline-flex; | |||
min-width: 120rpx; | |||
padding: 6rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
background: #F3F3F3; | |||
border-radius: 12rpx; | |||
&-0 { | |||
color: #7D27E0; | |||
background: #F5EEFD; | |||
} | |||
&-1 { | |||
color: #2799E0; | |||
background: #EEF7FD; | |||
} | |||
&-2 { | |||
color: #E53C29; | |||
background: #FDE7E5; | |||
} | |||
&-3 { | |||
color: #10A934; | |||
background: #E2FDE9; | |||
} | |||
} | |||
} | |||
.main { | |||
margin: 24rpx 0; | |||
column-gap: 24rpx; | |||
.img { | |||
flex: none; | |||
width: 120rpx; | |||
height: 120rpx; | |||
} | |||
.info { | |||
flex: 1; | |||
padding: 24rpx; | |||
background: #FFFFFF; | |||
border-radius: 32rpx; | |||
} | |||
.row { | |||
justify-content: flex-start; | |||
column-gap: 4rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.4; | |||
&-label { | |||
flex: none; | |||
color: #8B8B8B; | |||
} | |||
&-content { | |||
color: #393939; | |||
} | |||
} | |||
.row + .row { | |||
margin-top: 16rpx; | |||
} | |||
} | |||
.bottom { | |||
justify-content: space-between; | |||
.price { | |||
column-gap: 8rpx; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
line-height: 1.4; | |||
&-label { | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
color: #8B8B8B; | |||
} | |||
&-unit { | |||
font-size: 24rpx; | |||
color: #7451DE; | |||
} | |||
&-value { | |||
font-size: 32rpx; | |||
color: #7451DE; | |||
} | |||
} | |||
.btns { | |||
flex: 1; | |||
justify-content: flex-end; | |||
column-gap: 16rpx; | |||
} | |||
.btn { | |||
padding: 10rpx 22rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.4; | |||
color: #393939; | |||
border: 2rpx solid #252545; | |||
border-radius: 32rpx; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,196 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="检测预约" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||
<view class="main"> | |||
<view class="tabs"> | |||
<uv-tabs | |||
:list="tabs" | |||
:scrollable="false" | |||
lineColor="#7451DE" | |||
lineWidth="48rpx" | |||
lineHeight="4rpx" | |||
:activeStyle="{ | |||
'font-family': 'PingFang SC', | |||
'font-weight': 500, | |||
'font-size': '32rpx', | |||
'line-height': 1.4, | |||
'color': '#7451DE', | |||
}" | |||
:inactiveStyle="{ | |||
'font-family': 'PingFang SC', | |||
'font-weight': 400, | |||
'font-size': '32rpx', | |||
'line-height': 1.4, | |||
'color': '#181818', | |||
}" | |||
@click="clickTabs" | |||
></uv-tabs> | |||
</view> | |||
<view class="list"> | |||
<view class="list-item" v-for="item in list" :key="item.id"> | |||
<checkupCard :data="item" @sendBack="openTrackingNoPopup(item.id)"></checkupCard> | |||
</view> | |||
</view> | |||
</view> | |||
<checkupTrackingNoPopup ref="checkupTrackingNoPopup" @submitted="getData"></checkupTrackingNoPopup> | |||
</view> | |||
</template> | |||
<script> | |||
import mixinsList from '@/mixins/list.js' | |||
import checkupCard from './checkupCard.vue' | |||
import checkupTrackingNoPopup from '@/pages_order/checkup/checkupTrackingNoPopup.vue' | |||
export default { | |||
mixins: [mixinsList], | |||
components: { | |||
checkupCard, | |||
checkupTrackingNoPopup, | |||
}, | |||
data() { | |||
return { | |||
tabs: [ | |||
{ name: '全部' }, | |||
{ name: '自采' }, | |||
{ name: '上门' }, | |||
{ name: '到店' }, | |||
{ name: '已取消' }, | |||
], | |||
mixinsListApi: '', | |||
} | |||
}, | |||
methods: { | |||
//点击tab栏 | |||
clickTabs({ index }) { | |||
if (index == 0) { | |||
delete this.queryParams.status | |||
} else { | |||
this.queryParams.status = index - 1 | |||
} | |||
this.getData() | |||
}, | |||
// todo: delete | |||
getData() { | |||
this.list = [ | |||
{ | |||
id: '001', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '月度装定制营养包', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
appointmentTime: '2025-04-28 08:14', | |||
type: 3, | |||
typeDesc: '到店检测', | |||
status: 5, | |||
}, | |||
{ | |||
id: '002', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '青少年体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
appointmentTime: '2025-04-28 08:00~09:00', | |||
type: 3, | |||
typeDesc: '到店检测', | |||
status: 3, | |||
}, | |||
{ | |||
id: '003', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '孕产妇体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
createTime: '2025-04-28 08:14', | |||
type: 1, | |||
status: 0, | |||
}, | |||
{ | |||
id: '004', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '青少年体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
appointmentTime: null, | |||
type: 1, | |||
typeDesc: '自采检测', | |||
status: 1, | |||
}, | |||
{ | |||
id: '005', | |||
url: '/pages_order/static/product/detect-8.png', | |||
title: '青少年体检套餐', | |||
userName: '周小艺', | |||
phone: '15558661691', | |||
amount: 688, | |||
appointmentTime: '2025-04-28 08:00~09:00', | |||
type: 2, | |||
typeDesc: '上门检测', | |||
status: 2, | |||
}, | |||
] | |||
this.total = this.list.length | |||
}, | |||
openTrackingNoPopup(id) { | |||
this.$refs.checkupTrackingNoPopup.open(id) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background-color: $uni-bg-color; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
width: 100vw; | |||
padding: calc(var(--status-bar-height) + 244rpx) 32rpx 40rpx 32rpx; | |||
box-sizing: border-box; | |||
.tabs { | |||
position: fixed; | |||
top: calc(var(--status-bar-height) + 120rpx); | |||
left: 0; | |||
width: 100%; | |||
height: 84rpx; | |||
background: #FFFFFF; | |||
/deep/ .uv-tabs__wrapper__nav__line { | |||
border-radius: 2rpx; | |||
bottom: 0; | |||
} | |||
} | |||
} | |||
.list { | |||
&-item { | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,331 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="体检报告" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||
<view class="main" v-if="detail"> | |||
<view class="section"> | |||
<reportSummaryView :data="detail"></reportSummaryView> | |||
</view> | |||
<view class="section"> | |||
<reportMainIndexView :list="detail.mainIndexList"></reportMainIndexView> | |||
</view> | |||
<view class="section"> | |||
<reportAbnormalView :list="detail.abnormalList"></reportAbnormalView> | |||
</view> | |||
<view class="section"> | |||
<reportCommonView :data="detail"></reportCommonView> | |||
</view> | |||
</view> | |||
<view class="bottom"> | |||
<button class="flex btn" @click="jumpToNutritionProgram">查看营养方案</button> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import reportSummaryView from './reportSummaryView.vue' | |||
import reportMainIndexView from './reportMainIndexView.vue' | |||
import reportAbnormalView from './reportAbnormalView.vue' | |||
import reportCommonView from './reportCommonView.vue' | |||
export default { | |||
components: { | |||
reportSummaryView, | |||
reportMainIndexView, | |||
reportAbnormalView, | |||
reportCommonView, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
detail: null, | |||
} | |||
}, | |||
onLoad(arg) { | |||
this.id = arg.id | |||
this.getData() | |||
}, | |||
methods: { | |||
getData() { | |||
this.detail = { | |||
score: 77, | |||
BMI: 20.3, | |||
BMIchange: -0.2, | |||
BMItag: '正常', | |||
height: 164, | |||
weight: 46, | |||
mainIndexList: [ | |||
{ | |||
id: '001', | |||
label: '心率', | |||
value: '75', | |||
unit: 'bpm(次/分钟)', | |||
standrad: '60-100bpm', | |||
status: 1, | |||
}, | |||
{ | |||
id: '002', | |||
label: '血氧', | |||
value: '99', | |||
unit: '%', | |||
standrad: '≥94%', | |||
status: 1, | |||
}, | |||
{ | |||
id: '003', | |||
label: '空腹血糖', | |||
value: '4.0', | |||
unit: 'mmol/L', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '004', | |||
label: '血压', | |||
value: '110/80', | |||
unit: 'mmHg', | |||
standrad: '140/90mmHg~90/60 mmHg', | |||
status: 1, | |||
}, | |||
], | |||
abnormalList: [ | |||
{ | |||
id: '001', | |||
title: '甲状腺结节(甲状腺结节或钙化)', | |||
desc: '建议进一步监测;常规测甲状腺功能全套,甲状腺。', | |||
}, | |||
{ | |||
id: '002', | |||
title: '甲状腺结节(甲状腺结节或钙化)', | |||
desc: '建议进一步监测;常规测甲状腺功能全套,甲状腺。', | |||
}, | |||
{ | |||
id: '003', | |||
title: '甲状腺结节(甲状腺结节或钙化)', | |||
desc: '建议进一步监测;常规测甲状腺功能全套,甲状腺。', | |||
}, | |||
{ | |||
id: '004', | |||
title: '甲状腺结节(甲状腺结节或钙化)', | |||
desc: '建议进一步监测;常规测甲状腺功能全套,甲状腺。', | |||
}, | |||
], | |||
chronicFoodAllergyList: [ | |||
{ | |||
id: '001', | |||
label: '血小板', | |||
value: '5.2', | |||
standrad: '125~350×10^9/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '002', | |||
label: '血红蛋白', | |||
value: '4.8', | |||
standrad: '110 ~ 150g/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '003', | |||
label: '白细胞', | |||
value: '3.0', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '004', | |||
label: '转氨酶', | |||
value: '7.0', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '005', | |||
label: '胆红素', | |||
value: '17', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '006', | |||
label: '白蛋白', | |||
value: '15', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '007', | |||
label: '肌酐', | |||
value: '4', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '008', | |||
label: '尿酸', | |||
value: '556', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
{ | |||
id: '009', | |||
label: '血压', | |||
value: '23', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
{ | |||
id: '010', | |||
label: '甘油三酯', | |||
value: '56', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
], | |||
gutMicrobiomeList: [ | |||
{ | |||
id: '001', | |||
label: '血小板', | |||
value: '5.2', | |||
standrad: '125~350×10^9/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '002', | |||
label: '血红蛋白', | |||
value: '4.8', | |||
standrad: '110 ~ 150g/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '003', | |||
label: '白细胞', | |||
value: '3.0', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '004', | |||
label: '转氨酶', | |||
value: '7.0', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '005', | |||
label: '胆红素', | |||
value: '17', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '006', | |||
label: '白蛋白', | |||
value: '15', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '007', | |||
label: '肌酐', | |||
value: '4', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 1, | |||
}, | |||
{ | |||
id: '008', | |||
label: '尿酸', | |||
value: '556', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
{ | |||
id: '009', | |||
label: '血压', | |||
value: '23', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
{ | |||
id: '010', | |||
label: '甘油三酯', | |||
value: '56', | |||
standrad: '3.9-6.1mmol/L', | |||
status: 0, | |||
}, | |||
], | |||
scoreList: [ | |||
{ id: '001', label: '饮食', value: 23 }, | |||
{ id: '002', label: '饮食', value: 23 }, | |||
{ id: '003', label: '作息', value: 44 }, | |||
{ id: '004', label: '作息', value: 44 }, | |||
{ id: '005', label: '运动', value: 88 }, | |||
{ id: '006', label: '运动', value: 88 }, | |||
{ id: '007', label: '体质', value: 56 }, | |||
{ id: '008', label: '体质', value: 56 }, | |||
{ id: '009', label: '心理', value: 78 }, | |||
{ id: '010', label: '心理', value: 100 }, | |||
], | |||
} | |||
console.log('detail', this.detail) | |||
}, | |||
jumpToNutritionProgram() { | |||
this.$utils.navigateTo(`/pages_order/report/nutritionProgram/index?id=${this.id}`) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background-color: $uni-bg-color; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
padding: calc(var(--status-bar-height) + 168rpx) 32rpx 280rpx 32rpx; | |||
} | |||
.section { | |||
& + & { | |||
margin-top: 40rpx; | |||
} | |||
} | |||
.bottom { | |||
width: 100%; | |||
height: 200rpx; | |||
position: fixed; | |||
left: 0; | |||
bottom: 0; | |||
padding: 24rpx 40rpx; | |||
box-sizing: border-box; | |||
background: #FFFFFF; | |||
.btn { | |||
padding: 16rpx 0; | |||
font-family: PingFang SC; | |||
font-size: 36rpx; | |||
font-weight: 500; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,336 @@ | |||
<template> | |||
<view class="progress_box"> | |||
<div class="bg-outer"></div> | |||
<canvas class="progress progress-outer" canvas-id="cpouterline" type="2d"></canvas> | |||
<div class="bg-inner"></div> | |||
<canvas class="progress progress-inner" canvas-id="cpinnerline" type="2d"></canvas> | |||
<div class="bg-score bg-score-min">0分</div> | |||
<div class="bg-score bg-score-max">100分</div> | |||
<div class="progress progress-info"> | |||
<div class="flex"> | |||
<div class="progress-score">{{ progress }}</div> | |||
<div class="progress-unit">分</div> | |||
</div> | |||
<div class="progress-desc">体检分数</div> | |||
</div> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
progress: { | |||
type: Number, | |||
default: 0 | |||
} | |||
}, | |||
data() { | |||
return { | |||
outerCanvas: {}, | |||
innerCanvas: {}, | |||
ratio: 1, | |||
dpr: 1, | |||
} | |||
}, | |||
mounted() { | |||
this.init() | |||
}, | |||
methods: { | |||
init() { | |||
uni.createSelectorQuery().in(this) | |||
.select('.progress-outer') | |||
.fields({ | |||
node: true, | |||
size: true | |||
}) | |||
.exec(async (res) => { | |||
// Canvas 画布的实际绘制宽高 | |||
const width = res[0].width | |||
const dpr = wx.getWindowInfo().pixelRatio | |||
let Ratio = width / 446 | |||
this.outerCanvas = res[0] | |||
this.ratio = Ratio | |||
this.dpr = dpr | |||
uni.createSelectorQuery().in(this) | |||
.select('.progress-inner') | |||
.fields({ | |||
node: true, | |||
size: true | |||
}) | |||
.exec(async (res) => { | |||
this.innerCanvas = res[0] | |||
this.drawOuterLine(this.progress) | |||
this.drawInnerLine(this.progress) | |||
}) | |||
}) | |||
}, | |||
drawOuterLine(step) { | |||
uni.createSelectorQuery().in(this) | |||
.select('.progress-outer') | |||
.fields({ | |||
node: true, | |||
size: true | |||
}) | |||
.exec(async (res) => { | |||
const canvas = res[0].node | |||
// Canvas 画布的实际绘制宽高 | |||
const width = res[0].width | |||
const height = res[0].height | |||
//根据dpr调整 | |||
const dpr = wx.getWindowInfo().pixelRatio | |||
canvas.width = width * dpr | |||
canvas.height = height * dpr | |||
let Ratio = width / 446 | |||
// 渲染上下文 | |||
const ctx = canvas.getContext('2d') | |||
ctx.scale(dpr, dpr) | |||
ctx.clearRect(0, 0, width, height) | |||
let r = 210 * Ratio | |||
let x = this.outerCanvas.width / 2 | |||
let y = this.outerCanvas.height - this.innerCanvas.height / 2 | |||
let lineWidth = 10 * Ratio; | |||
let lineHeight = 26 * Ratio; | |||
let startAngle = 0 | |||
// 进度条的渐变(中心x坐标-半径-边宽,中心Y坐标,中心x坐标+半径+边宽,中心Y坐标) | |||
let gradient = ctx.createLinearGradient(x - r, y, x + r, y); | |||
gradient.addColorStop('0', '#7451DE'); | |||
gradient.addColorStop('1.0', '#B1A4FF'); | |||
ctx.strokeStyle = '#E5E5E5'; | |||
ctx.save() | |||
for (let i = 0; i < 11; i++) { | |||
ctx.beginPath(); | |||
if (i % 5) { | |||
lineWidth = 6 * Ratio; | |||
lineHeight = 15 * Ratio; | |||
} else { | |||
lineWidth = 10 * Ratio; | |||
lineHeight = 18 * Ratio; | |||
} | |||
if (step >= i * 10) { | |||
ctx.strokeStyle = gradient; | |||
} else { | |||
ctx.restore(); | |||
} | |||
ctx.lineWidth = lineWidth; | |||
ctx.lineCap = 'round'; | |||
let angle = startAngle - Math.PI * i / 10 | |||
let x0 = x - r * Math.cos(angle) | |||
let y0 = y + r * Math.sin(angle) | |||
let x1 = x0 + lineHeight * Math.cos(angle) | |||
let y1 = y0 - lineHeight * Math.sin(angle) | |||
ctx.moveTo(x0, y0); | |||
ctx.lineTo(x1, y1); | |||
ctx.stroke(); | |||
ctx.closePath(); | |||
} | |||
}) | |||
}, | |||
drawInnerLine(step) { | |||
uni.createSelectorQuery().in(this) | |||
.select('.progress-inner') | |||
.fields({ | |||
node: true, | |||
size: true | |||
}) | |||
.exec(async (res) => { | |||
const canvas = res[0].node | |||
// Canvas 画布的实际绘制宽高 | |||
const width = res[0].width | |||
const height = res[0].height | |||
//根据dpr调整 | |||
const dpr = wx.getWindowInfo().pixelRatio | |||
canvas.width = width * dpr | |||
canvas.height = height * dpr | |||
let Ratio = width / 356 | |||
// 渲染上下文 | |||
const ctx = canvas.getContext('2d') | |||
ctx.scale(dpr, dpr) | |||
ctx.clearRect(0, 0, width, height) | |||
let r = 140 * Ratio | |||
let x = this.innerCanvas.width / 2 | |||
let y = this.innerCanvas.height / 2 | |||
let lineWidth = 5 * Ratio; | |||
let lineHeight = 28 * Ratio; | |||
// 进度条的渐变(中心x坐标-半径-边宽,中心Y坐标,中心x坐标+半径+边宽,中心Y坐标) | |||
let gradient = ctx.createLinearGradient(x - r, y, x + r, y); | |||
gradient.addColorStop('0', '#E81717'); | |||
gradient.addColorStop('0.5', '#ECBD00'); | |||
gradient.addColorStop('1.0', '#0DB556'); | |||
ctx.lineWidth = lineWidth; | |||
ctx.strokeStyle = '#989898'; | |||
ctx.save() | |||
let angle = 0 | |||
let i = 0 | |||
while (angle > - Math.PI) { | |||
ctx.beginPath(); | |||
if (i % 2) { | |||
angle -= Math.PI / 50 | |||
i++ | |||
continue | |||
} | |||
if (step * Math.PI / 100 >= -angle) { | |||
ctx.strokeStyle = gradient; | |||
} else { | |||
ctx.restore(); | |||
} | |||
let x0 = x - r * Math.cos(angle) | |||
let y0 = y + r * Math.sin(angle) | |||
let x1 = x0 + lineHeight * Math.cos(angle) | |||
let y1 = y0 - lineHeight * Math.sin(angle) | |||
ctx.moveTo(x0, y0); | |||
ctx.lineTo(x1, y1); | |||
ctx.stroke(); | |||
ctx.closePath(); | |||
angle -= Math.PI / 100 | |||
i++ | |||
} | |||
}) | |||
}, | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
$size: 356rpx; | |||
.progress_box { | |||
position: relative; | |||
width: 100vw; | |||
height: 402rpx; | |||
} | |||
.progress { | |||
position: absolute; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
&-outer { | |||
width: 446rpx; | |||
height: 100%; | |||
} | |||
&-inner { | |||
width: $size; | |||
height: $size; | |||
bottom: 0; | |||
} | |||
&-info { | |||
position: absolute; | |||
left: 50%; | |||
bottom: calc(#{$size} / 2); | |||
transform: translate(-50%, calc(50% + 5rpx)); | |||
} | |||
&-score { | |||
font-size: 48rpx; | |||
font-weight: 600; | |||
font-family: PingFang SC; | |||
color: #000000; | |||
} | |||
&-unit { | |||
font-size: 28rpx; | |||
font-weight: 600; | |||
font-family: PingFang SC; | |||
color: #000000; | |||
margin-left: 8rpx; | |||
} | |||
&-desc { | |||
font-size: 26rpx; | |||
font-weight: 400; | |||
font-family: PingFang SC; | |||
color: #989898; | |||
text-align: center; | |||
} | |||
} | |||
.bg { | |||
&-outer { | |||
position: absolute; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
bottom: 0; | |||
width: $size; | |||
height: $size; | |||
border-radius: 50%; | |||
box-shadow: inset 4px 4px 16px #8D96B466, | |||
4px 4px 16px 0 rgba(141, 150, 180, 0.3), | |||
// 2px 2px 2px 0 #8D96B466, | |||
-2px -2px 8px 0 #FFFFFF, | |||
-1px -1px 14px 0 #FFFFFF; | |||
} | |||
&-inner { | |||
position: absolute; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
bottom: 28rpx; | |||
width: 300rpx; | |||
height: 300rpx; | |||
background-color: #EEF0F6; | |||
border-radius: 50%; | |||
box-shadow: 6px 6px 6px 0 #0000000F, | |||
-4px -4px 13px 0 #FFFFFF, | |||
4px 4px 7px 0 #00000012, | |||
} | |||
&-score { | |||
color: #989898; | |||
font-size: 26rpx; | |||
font-weight: 400; | |||
position: absolute; | |||
left: 50%; | |||
top: 50%; | |||
&-min { | |||
transform: translate(-303rpx, 0); | |||
} | |||
&-max { | |||
transform: translate(263rpx, 0); | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,86 @@ | |||
<template> | |||
<view class="view" | |||
:style="style" | |||
> | |||
<view class="bar bg"> | |||
<view | |||
v-for="(item, index) in list" | |||
:key="index" | |||
:class="['bar-item', index % 2 == 0 ? 'is-active' : '']" | |||
></view> | |||
</view> | |||
<view class="bar fg"> | |||
<view | |||
v-for="(item, index) in fgList" | |||
:key="index" | |||
:class="['bar-item', index % 2 == 0 ? 'is-active' : '']" | |||
></view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
progress: { | |||
type: Number, | |||
default: 0, | |||
}, | |||
activeColor: { | |||
type: String, | |||
default: '#7451DE', | |||
}, | |||
}, | |||
data() { | |||
return { | |||
list: new Array(51).fill(1), | |||
} | |||
}, | |||
computed: { | |||
fgList() { | |||
const num = Math.floor(51 * this.progress / 100) | |||
return this.list.slice(0, num + 1) | |||
}, | |||
style() { | |||
return `--color: ${this.activeColor}` | |||
} | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.view { | |||
position: relative; | |||
width: 100%; | |||
height: 28rpx; | |||
} | |||
.bar { | |||
width: 100%; | |||
height: 28rpx; | |||
display: grid; | |||
grid-template-columns: repeat(51, 1fr); | |||
&-item { | |||
} | |||
&.bg { | |||
.is-active { | |||
background: #989898; | |||
} | |||
} | |||
&.fg { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
.is-active { | |||
background: #7451DE; | |||
background: var(--color); | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,80 @@ | |||
<template> | |||
<view class="card"> | |||
<view class="card-header"> | |||
<view class="title">体检异常项</view> | |||
<view class="desc">Abnormal items from health check-up</view> | |||
</view> | |||
<view class="flex row" v-for="item in list" :key="item.id" @click="onClick(item)"> | |||
<view> | |||
<view class="title">{{ item.title }}</view> | |||
<view class="desc">{{ item.desc }}</view> | |||
</view> | |||
<uv-icon name="arrow-right" color="#C6C6C6" size="24rpx"></uv-icon> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
list: { | |||
type: Array, | |||
default() { | |||
return [] | |||
} | |||
} | |||
}, | |||
methods: { | |||
onClick(data) { | |||
// todo | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.card { | |||
padding: 32rpx; | |||
background: #FAFAFF; | |||
border-radius: 24rpx; | |||
&-header { | |||
font-family: PingFang SC; | |||
line-height: 1.4; | |||
color: #252545; | |||
.title { | |||
font-weight: 600; | |||
font-size: 32rpx; | |||
} | |||
.desc { | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
} | |||
} | |||
.row { | |||
margin-top: 24rpx; | |||
justify-content: space-between; | |||
padding: 24rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
background: #F3F2F7; | |||
border-radius: 16rpx; | |||
.title { | |||
font-size: 30rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
} | |||
.desc { | |||
margin-top: 4rpx; | |||
font-size: 22rpx; | |||
line-height: 1.6; | |||
color: #989898; | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,239 @@ | |||
<template> | |||
<view class="card"> | |||
<view class="flex card-header"> | |||
<view class="title">检测数据</view> | |||
</view> | |||
<view class="section"> | |||
<view class="section-header"> | |||
<view class="title">慢性食物过敏检测</view> | |||
<view class="desc">Chronic food allergy testing</view> | |||
</view> | |||
<view class="section-content index"> | |||
<view | |||
class="index-item" | |||
v-for="item in data.chronicFoodAllergyList" | |||
:key="item.id" | |||
> | |||
<view class="flex top"> | |||
<view class="label">{{ item.label }}</view> | |||
<view class="tag is-error" v-if="item.status === 0">{{ item.label }}</view> | |||
</view> | |||
<view class="flex main"> | |||
<text>当前:</text><text class="value">{{ item.value }}</text> | |||
</view> | |||
<view class="bottom desc">{{ `* 标准值:${item.standrad}` }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="section"> | |||
<view class="section-header"> | |||
<view class="title">肠道菌群检测</view> | |||
<view class="desc">Gut microbiome testing</view> | |||
</view> | |||
<view class="section-content index"> | |||
<view | |||
class="index-item" | |||
v-for="item in data.gutMicrobiomeList" | |||
:key="item.id" | |||
> | |||
<view class="flex top"> | |||
<view class="label">{{ item.label }}</view> | |||
<view class="tag is-error" v-if="item.status === 0">{{ item.label }}</view> | |||
</view> | |||
<view class="flex main"> | |||
<text>当前:</text><text class="value">{{ item.value }}</text> | |||
</view> | |||
<view class="bottom desc">{{ `* 标准值:${item.standrad}` }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="section"> | |||
<view class="section-header"> | |||
<view class="title">肠道菌群检测</view> | |||
<view class="desc">Gut microbiome testing</view> | |||
</view> | |||
<view class="section-content score"> | |||
<view | |||
class="score-item" | |||
v-for="(item, index) in data.scoreList" | |||
:key="item.id" | |||
> | |||
<view> | |||
<progressLine :progress="item.value" :activeColor="getColor(index)"></progressLine> | |||
</view> | |||
<view class="flex info"> | |||
<view class="label">{{ `${item.label}:` }}</view> | |||
<view class="value">{{ item.value }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import progressLine from './progressLine.vue'; | |||
const COLORS = [ | |||
'#43B741', | |||
'#43B741', | |||
'#ECB501', | |||
'#ECB501', | |||
'#EB7F09', | |||
'#EB7F09', | |||
'#009CEF', | |||
'#009CEF', | |||
'#7451DE', | |||
'#7451DE', | |||
] | |||
export default { | |||
components: { | |||
progressLine, | |||
}, | |||
props: { | |||
data: { | |||
type: Object, | |||
default() { | |||
return { | |||
chronicFoodAllergyList: [], | |||
gutMicrobiomeList: [], | |||
scoreList: [], | |||
} | |||
} | |||
}, | |||
}, | |||
data() { | |||
return { | |||
} | |||
}, | |||
onLoad() { | |||
console.log('onLoad', this.data) | |||
}, | |||
methods: { | |||
getColor(index) { | |||
return COLORS[index % 10] | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import './style.scss'; | |||
.card { | |||
padding: 32rpx 24rpx; | |||
background-image: linear-gradient(#F2EDFF, #FCFEFE); | |||
border: 8rpx solid #F9F7FF; | |||
border-radius: 64rpx; | |||
&-header { | |||
.title { | |||
padding: 6rpx 14rpx; | |||
display: inline-flex; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.5; | |||
color: #252545; | |||
border: 2rpx solid #252545; | |||
border-radius: 30rpx; | |||
} | |||
} | |||
.section { | |||
margin-top: 40rpx; | |||
&-header { | |||
font-family: PingFang SC; | |||
line-height: 1.4; | |||
color: #252545; | |||
.title { | |||
font-weight: 600; | |||
font-size: 32rpx; | |||
} | |||
.desc { | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
} | |||
} | |||
&-content { | |||
margin-top: 32rpx; | |||
} | |||
} | |||
} | |||
.index { | |||
display: grid; | |||
grid-template-columns: repeat(2, 1fr); | |||
gap: 32rpx; | |||
&-item { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
.top { | |||
justify-content: space-between; | |||
} | |||
.main { | |||
justify-content: flex-start; | |||
column-gap: 12rpx; | |||
font-size: 24rpx; | |||
color: #8B8B8B; | |||
} | |||
.label { | |||
font-size: 30rpx; | |||
color: #000000; | |||
} | |||
.value { | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
color: #000000; | |||
} | |||
.desc { | |||
font-size: 22rpx; | |||
line-height: 1.6; | |||
color: #989898; | |||
} | |||
} | |||
} | |||
.score { | |||
display: grid; | |||
grid-template-columns: repeat(2, 1fr); | |||
column-gap: 32rpx; | |||
row-gap: 36rpx; | |||
&-item { | |||
.info { | |||
margin-top: 18rpx; | |||
justify-content: flex-start; | |||
column-gap: 12rpx; | |||
font-family: PingFang SC; | |||
line-height: 1.4; | |||
.label { | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
} | |||
.value { | |||
font-weight: 500; | |||
font-size: 28rpx; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@ -0,0 +1,86 @@ | |||
<template> | |||
<view class="view"> | |||
<view class="card" v-for="item in list" :key="item.id"> | |||
<view class="flex top"> | |||
<view class="label">{{ item.label }}</view> | |||
<view :class="['tag', item.status === 0 ? 'is-error' : '' ]">{{ item.status == 0 ? '异常' : '正常' }}</view> | |||
</view> | |||
<view class="flex main"> | |||
<view class="value">{{ item.value }}</view> | |||
<view class="unit">{{ item.unit }}</view> | |||
</view> | |||
<view class="desc">{{ `* 标准值:${item.standrad}` }}</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
list: { | |||
type: Array, | |||
default() { | |||
return [] | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import './style.scss'; | |||
.view { | |||
display: grid; | |||
grid-template-columns: repeat(2, 1fr); | |||
gap: 16rpx; | |||
} | |||
.card { | |||
flex: 1; | |||
padding: 24rpx; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
background-image: linear-gradient(#FAFAFF, #F3F3F3); | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
.top { | |||
justify-content: space-between; | |||
} | |||
.main { | |||
margin: 4rpx 0; | |||
justify-content: flex-start; | |||
column-gap: 8rpx; | |||
} | |||
} | |||
.label { | |||
font-size: 30rpx; | |||
color: #000000; | |||
} | |||
.value { | |||
font-weight: 600; | |||
font-size: 56rpx; | |||
color: transparent; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
background-clip: text; | |||
display: inline-block; | |||
} | |||
.unit { | |||
font-size: 24rpx; | |||
color: #252545CC; | |||
} | |||
.desc { | |||
font-size: 22rpx; | |||
line-height: 1.6; | |||
color: #989898; | |||
} | |||
</style> |
@ -0,0 +1,241 @@ | |||
<template> | |||
<view class="card"> | |||
<view class="card-header"> | |||
<image class="card-header-bg" src="@/pages_order/static/report/report-card-header-bg.png" mode="scaleToFill"></image> | |||
<view class="flex card-header-content"> | |||
<view class="avatar"> | |||
<!-- todo --> | |||
<image class="avatar-img" src="@/pages_order/static/report/avatar.png" mode="scaleToFill"></image> | |||
</view> | |||
<view class="info"> | |||
<!-- todo --> | |||
<view class="nickname">李星星</view> | |||
<view class="desc"> | |||
<text class="age">25岁</text> | |||
<text class="gender">男</text> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="card-content"> | |||
<view class="score-comprehensive"> | |||
<progressCircleLine :progress="data.score"></progressCircleLine> | |||
</view> | |||
<view class="flex cols"> | |||
<view class="flex flex-column col"> | |||
<view class="flex"> | |||
<view class="value">{{ data.BMI || '--' }}</view> | |||
<view v-if="data.BMIchange < 0" class="flex change"> | |||
<text>{{ data.BMIchange }}</text> | |||
<image class="change-icon" src="@/pages_order/static/report/arrow-down.png" mode="widthFix"></image> | |||
</view> | |||
<view v-else-if="data.BMIchange > 0" class="flex change rise"> | |||
<text>{{ data.BMIchange }}</text> | |||
<image class="change-icon" src="@/pages_order/static/report/arrow-down.png" mode="widthFix"></image> | |||
</view> | |||
</view> | |||
<view class="flex"> | |||
<view class="label">BMI</view> | |||
<view class="tag"> | |||
{{ data.BMItag || '-' }} | |||
</view> | |||
</view> | |||
</view> | |||
<view class="divider"></view> | |||
<view class="flex flex-column col"> | |||
<view class="flex"> | |||
<view class="value">{{ data.height || '--' }}</view> | |||
<view class="unit">cm</view> | |||
</view> | |||
<view class="label">身高</view> | |||
</view> | |||
<view class="divider"></view> | |||
<view class="flex flex-column col"> | |||
<view class="flex"> | |||
<view class="value">{{ data.weight || '--' }}</view> | |||
<view class="unit">kg</view> | |||
</view> | |||
<view class="label">体重</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import progressCircleLine from './progressCircleLine.vue' | |||
export default { | |||
components: { | |||
progressCircleLine, | |||
}, | |||
props: { | |||
data: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
} | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import './style.scss'; | |||
$header-height: 240rpx; | |||
$score-bar-height: 402rpx; | |||
.card { | |||
width: 100%; | |||
background-image: linear-gradient(#FAFAFF, #F3F3F3); | |||
border: 2rpx solid #FFFFFF; | |||
border-bottom-left-radius: 64rpx; | |||
border-bottom-right-radius: 64rpx; | |||
box-sizing: border-box; | |||
position: relative; | |||
&-header { | |||
background-color: $uni-bg-color; | |||
width: calc(100% + 2rpx * 2); | |||
height: $header-height; | |||
position: absolute; | |||
top: -2rpx; | |||
left: -2rpx; | |||
&-bg { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
&-content { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
padding: 0 32rpx; | |||
box-sizing: border-box; | |||
justify-content: flex-start; | |||
.avatar { | |||
width: 104rpx; | |||
height: 104rpx; | |||
border: 4rpx solid #FFFFFF; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
&-img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.info { | |||
margin-left: 24rpx; | |||
.nickname { | |||
font-family: PingFang SC; | |||
font-weight: 600; | |||
font-size: 40rpx; | |||
line-height: 1.1; | |||
color: #252545; | |||
} | |||
.desc { | |||
margin-top: 8rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 36rpx; | |||
line-height: 1.2; | |||
color: #252545CC; | |||
.gender { | |||
margin-left: 16rpx; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
&-content { | |||
position: relative; | |||
width: 100%; | |||
padding: calc(#{$header-height} + 32rpx + #{$score-bar-height} + 24rpx) 0 32rpx 0; | |||
box-sizing: border-box; | |||
} | |||
} | |||
.score-comprehensive { | |||
position: absolute; | |||
top: calc(#{$header-height} + 32rpx); | |||
left: 50%; | |||
transform: translateX(-50%); | |||
} | |||
.cols { | |||
padding: 0 32rpx 6rpx 32rpx; | |||
align-items: flex-start; | |||
.col { | |||
flex: 1; | |||
} | |||
.divider { | |||
width: 2rpx; | |||
height: 32rpx; | |||
background: #B5B8CE; | |||
margin: 0 15rpx; | |||
} | |||
.label { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #6D6D6D; | |||
} | |||
.unit { | |||
margin-left: 8rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
} | |||
.value { | |||
font-family: PingFang SC; | |||
line-height: 1.4; | |||
font-weight: 600; | |||
font-size: 40rpx; | |||
color: $uni-color; | |||
} | |||
.change { | |||
margin-left: 8rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 24rpx; | |||
line-height: 1.4; | |||
color: #F79205; | |||
&-icon { | |||
width: 24rpx; | |||
height: auto; | |||
} | |||
&.rise { | |||
.change-icon { | |||
transform: rotate(180deg); | |||
} | |||
} | |||
} | |||
.tag { | |||
margin-left: 16rpx; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,17 @@ | |||
.tag { | |||
padding: 6rpx 16rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 20rpx; | |||
line-height: 1.4; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(90deg, #4B348F, #845CFA); | |||
border-top-left-radius: 24rpx; | |||
border-bottom-right-radius: 24rpx; | |||
&.is-error { | |||
background-image: linear-gradient(90deg, #8F3434, #FA5C5C); | |||
} | |||
} |
@ -0,0 +1,67 @@ | |||
<template> | |||
<view> | |||
<uv-popup ref="popup" mode="center" bgColor="none" > | |||
<view class="flex flex-column popup__view"> | |||
<view class="header">添加客服微信</view> | |||
<view class="content"> | |||
<image class="qrcode" src="@/pages_order/static/checkup/qrcode.png" mode="widthFix"></image> | |||
</view> | |||
</view> | |||
</uv-popup> | |||
</view> | |||
</template> | |||
<script> | |||
import { mapState } from 'vuex' | |||
export default { | |||
data() { | |||
return { | |||
} | |||
}, | |||
computed : { | |||
...mapState(['configList']) | |||
}, | |||
methods: { | |||
open() { | |||
this.$refs.popup.open() | |||
}, | |||
close() { | |||
this.$refs.popup.close() | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.popup__view { | |||
width: 550rpx; | |||
padding: 32rpx 0; | |||
box-sizing: border-box; | |||
background: #F3F2F7; | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 64rpx; | |||
} | |||
.header { | |||
margin-bottom: 40rpx; | |||
text-align: center; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 40rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
.content { | |||
width: 486rpx; | |||
height: auto; | |||
.qrcode { | |||
width: 100%; | |||
height: auto; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,184 @@ | |||
<template> | |||
<view> | |||
<uv-popup ref="popup" mode="bottom" bgColor="none" > | |||
<view class="popup__view"> | |||
<view class="flex header"> | |||
<view class="title">线上试剂盒回寄</view> | |||
<button class="btn" @click="close">关闭</button> | |||
</view> | |||
<view class="form"> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
:rules="rules" | |||
errorType="toast" | |||
> | |||
<view class="form-item"> | |||
<uv-form-item prop="trackingNo" :customStyle="formItemStyle"> | |||
<view class="form-item-label">回寄订单号</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.trackingNo" inputAlign="right"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</uv-form> | |||
</view> | |||
<view class="footer"> | |||
<button class="flex btn" @click="close">取消</button> | |||
<button class="flex btn" @click="onConfirm">确认</button> | |||
</view> | |||
</view> | |||
</uv-popup> | |||
</view> | |||
</template> | |||
<script> | |||
import formInput from '@/pages_order/components/formInput.vue' | |||
export default { | |||
components: { | |||
formInput, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
form: { | |||
trackingNo: null, | |||
}, | |||
rules: { | |||
'trackingNo': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入联系人', | |||
}, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
} | |||
}, | |||
methods: { | |||
open(id) { | |||
this.id = id | |||
this.$refs.popup.open() | |||
}, | |||
close() { | |||
this.$refs.popup.close() | |||
}, | |||
async onConfirm() { | |||
try { | |||
const res = await this.$refs.form.validate() | |||
console.log('onConfirm res', res) | |||
// todo: save | |||
this.$emit('submitted') | |||
this.close() | |||
} catch (err) { | |||
console.log('onConfirm err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.popup__view { | |||
width: 100vw; | |||
display: flex; | |||
flex-direction: column; | |||
box-sizing: border-box; | |||
background: #FFFFFF; | |||
border-top-left-radius: 32rpx; | |||
border-top-right-radius: 32rpx; | |||
} | |||
.header { | |||
position: relative; | |||
width: 100%; | |||
padding: 24rpx 0; | |||
box-sizing: border-box; | |||
border-bottom: 2rpx solid #EEEEEE; | |||
.title { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 34rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
.btn { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 32rpx; | |||
line-height: 1.4; | |||
color: #8B8B8B; | |||
position: absolute; | |||
top: 26rpx; | |||
left: 40rpx; | |||
} | |||
} | |||
.form { | |||
padding: 32rpx 40rpx; | |||
&-item { | |||
padding: 8rpx 0 6rpx 0; | |||
& + & { | |||
padding-top: 24rpx; | |||
border-top: 2rpx solid #EEEEEE; | |||
} | |||
&-label { | |||
margin-bottom: 14rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
&-content { | |||
.placeholder { | |||
color: #C6C6C6; | |||
font-size: 32rpx; | |||
font-weight: 400; | |||
} | |||
.region { | |||
min-height: 44rpx; | |||
justify-content: flex-start; | |||
} | |||
} | |||
} | |||
} | |||
.footer { | |||
width: 100%; | |||
// todo:check | |||
// height: 214rpx; | |||
padding: 32rpx 40rpx; | |||
box-sizing: border-box; | |||
border-top: 2rpx solid #F1F1F1; | |||
.btn { | |||
width: 100%; | |||
padding: 16rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |