| @ -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> | |||||