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