2 Commits

30 changed files with 746 additions and 720 deletions
Split View
  1. +7
    -0
      api/model/cart.js
  2. +5
    -0
      api/model/order.js
  3. +24
    -3
      api/model/paper.js
  4. +3
    -0
      common.scss
  5. +1
    -0
      components/config/popupQrCode.vue
  6. +31
    -10
      components/report/reportScoreView.vue
  7. +0
    -4
      pages/index/cart.vue
  8. +37
    -38
      pages/index/report.vue
  9. +1
    -2
      pages_order/center/accountCard.vue
  10. +2
    -0
      pages_order/components/reportCard.vue
  11. +1
    -1
      pages_order/home/recommendSwiper.vue
  12. +1
    -1
      pages_order/order/orderDetail/index.vue
  13. +1
    -1
      pages_order/product/tabDetect/index.vue
  14. +4
    -13
      pages_order/report/compare/reportRecordCard.vue
  15. +63
    -126
      pages_order/report/compare/result.vue
  16. +42
    -41
      pages_order/report/compare/select.vue
  17. +47
    -102
      pages_order/report/detail/index.vue
  18. +94
    -86
      pages_order/report/nutritionProgram/index.vue
  19. +18
    -6
      pages_order/report/nutritionProgram/productCard.vue
  20. +5
    -4
      pages_order/report/reportRecordCard.vue
  21. +14
    -14
      pages_order/report/reportSummary/index.vue
  22. +19
    -1
      pages_order/report/reportSummary/progressCircle.vue
  23. +38
    -44
      pages_order/report/result/index.vue
  24. +4
    -4
      pages_order/report/result/radarChart.vue
  25. +9
    -7
      pages_order/report/result/resultSummary.vue
  26. +14
    -5
      pages_order/report/result/tonicCard.vue
  27. +158
    -174
      pages_order/report/test/answer.vue
  28. +29
    -12
      pages_order/report/test/intro.vue
  29. +31
    -21
      pages_order/report/test/step.vue
  30. +43
    -0
      store/store.js

+ 7
- 0
api/model/cart.js View File

@ -17,6 +17,13 @@ const api = {
limit : 500,
showLoading : true,
},
// 购物车-批量添加
addCartBatch: {
url: '/cart/addList',
method: 'POST',
auth: true,
showLoading : true,
},
// 购物车-批量删除
deleteCartBatch: {
url: '/cart/deleteBatch',


+ 5
- 0
api/model/order.js View File

@ -70,6 +70,11 @@ const api = {
limit : 500,
showLoading : true,
},
// 商品评价数量
productEvaluateNum: {
url: '/order/productEvaluateNum',
method: 'POST',
},
// 商品评价
productEvaluate: {
url: '/order/productEvaluate',


+ 24
- 3
api/model/paper.js View File

@ -2,13 +2,17 @@
// 报告相关接口
const api = {
// 近期报告
getRecentReport: {
url: '/paper/recentReport',
method: 'GET',
auth: true,
},
// 试卷查询
getPaperList: {
url: '/paper/paper',
method: 'GET',
auth: true,
limit : 500,
showLoading : true,
},
// 试卷详情
getPaperDetail: {
@ -18,6 +22,24 @@ const api = {
limit : 500,
showLoading : true,
},
// 开始答题
startPaper: {
url: '/paper/start',
method: 'GET',
auth: true,
},
// 选择-输入完成答案
answerPaper: {
url: '/paper/answer',
method: 'GET',
auth: true,
},
// 提交试卷
submitPaper: {
url: '/paper/submit',
method: 'GET',
auth: true,
},
// 试卷下的报告列表
getReportByPaperId: {
url: '/paper/reportById',
@ -31,7 +53,6 @@ const api = {
url: '/paper/reportDetail',
method: 'GET',
auth: true,
limit : 500,
showLoading : true,
},
}


+ 3
- 0
common.scss View File

@ -62,6 +62,9 @@
.btn:after {
border: none;
}
.btn.is-disabled {
opacity: 0.5;
}
/deep/ .uv-modal__content {
padding: 0 !important;

+ 1
- 0
components/config/popupQrCode.vue View File

@ -4,6 +4,7 @@
:overlayOpacity="0.8"
mode="bottom"
round="20rpx"
:zIndex="1000000"
>
<view class="flex qr-popup">
<text class="tips">长按识别二维码了解更多内容</text>


+ 31
- 10
components/report/reportScoreView.vue View File

@ -1,15 +1,14 @@
<template>
<view class="flex view report-score__view">
<view class="flex flex-column left" :class="[scoreAlign == 'left' ? 'align-left' : '']">
<template v-if="data.score">
<template v-if="score">
<view class="flex">
<text class="score-value">{{ data.score }}</text><text class="score-full">/100</text>
<text class="score-value">{{ score }}</text><text class="score-full">/100</text>
</view>
<view class="flex">
<!-- todo: check key -->
<view class="flex change" v-if="data.change">
<text>{{ data.change }}</text>
<image class="change-icon" src="@/pages_order/static/report/arrow-down.png" mode="widthFix"></image>
<view class="flex change" v-if="change > 0 || change < 0">
<text>{{ change }}</text>
<image :class="['change-icon', change > 0 ? 'is-up' : '']" src="@/pages_order/static/report/arrow-down.png" mode="widthFix"></image>
</view>
<!-- todo: check key -->
<view class="tag" v-if="data.tag">
@ -36,10 +35,10 @@
export default {
props: {
data: {
type: Object,
reports: {
type: Array,
default() {
return {}
return []
}
},
scoreAlign: {
@ -48,7 +47,25 @@
}
},
computed : {
...mapState(['userInfo'])
...mapState(['userInfo']),
score() {
const [currentReport] = this.reports || []
if (!currentReport?.score) {
return null
}
return parseInt(currentReport.score)
},
change() {
const [currentReport, lastReport] = this.reports || []
if (lastReport) {
return parseInt(currentReport.score - lastReport.score)
}
return 0
},
},
}
</script>
@ -100,6 +117,10 @@
&-icon {
width: 24rpx;
height: auto;
&.is-up {
transform: rotate(180deg);
}
}
}


+ 0
- 4
pages/index/cart.vue View File

@ -472,10 +472,6 @@
line-height: 1;
background-image: linear-gradient(to right, #4B348F, #845CFA);
border-radius: 41rpx;
&.is-disabled {
opacity: 0.5;
}
}
}
}


+ 37
- 38
pages/index/report.vue View File

@ -6,7 +6,7 @@
</navbar>
<view class="content">
<report-summary></report-summary>
<report-summary :data="summaryData"></report-summary>
<view class="section">
<recommend-test></recommend-test>
@ -17,14 +17,14 @@
<view class="filter-item"
v-for="item in filters"
:key="item.value"
:class="['filter-item', item.value === type ? 'is-active' : '']"
:class="['filter-item', item.value === queryParams.type ? 'is-active' : '']"
@click="onSelectFilter(item.value)"
>
{{ item.label }}
</view>
</view>
<view>
<report-record-card v-for="item in list" :key="item.id" :data="item" :type="type"></report-record-card>
<report-record-card v-for="item in list" :key="item.id" :data="item" :type="queryParams.type"></report-record-card>
</view>
</view>
</view>
@ -35,7 +35,7 @@
</template>
<script>
// import mixinsList from '@/mixins/list.js'
import mixinsList from '@/mixins/list.js'
import tabber from '@/components/base/tabbar.vue'
import reportSummary from '@/pages_order/report/reportSummary/index.vue'
@ -43,7 +43,7 @@
import reportRecordCard from '@/pages_order/report/reportRecordCard.vue'
export default {
// mixins: [mixinsList],
mixins: [mixinsList],
components: {
reportSummary,
recommendTest,
@ -52,57 +52,56 @@
},
data() {
return {
summaryData: {},
// type 01
filters: [
{ value: 0, label: '已完成的检测' },
{ value: 1, label: '未完成的检测' },
],
type: 0,
list: [],
// queryParams: {
// pageNo: 1,
// pageSize: 10,
// type: 0,
// },
// mixinsListApi: 'getPaperList',
queryParams: {
pageNo: 1,
pageSize: 10,
type: 0,
},
mixinsListApi: 'getPaperList',
}
},
onLoad() {
onShow() {
console.log('onShow')
if(uni.getStorageSync('token')){
this.$store.commit('getUserInfo')
this.fetchRecentReport()
this.getData()
}
},
methods: {
onSelectFilter(val) {
// type 01
this.type = val
this.getData()
},
async getData() {
async fetchRecentReport() {
try {
this.list = await this.$fetch('getPaperList', { type: this.type })
const result = await this.$fetch('getRecentReport')
const [currentReport, lastReport] = result
let change = 0
if (lastReport) {
change = parseInt(currentReport.score - lastReport.score)
}
this.summaryData = {
id: currentReport.id,
paperId: currentReport.paperId,
score: parseInt(currentReport.score),
change,
}
console.log('summaryData', this.summaryData)
} catch (err) {
}
},
// todo: delete
// getData() {
// console.log('getData')
// let arr0 = [
// { id: '001', createTime: '2025-05-21', status: 0, score: null, change: null, tag: null },
// { id: '002', createTime: '2025-05-20', status: 0, score: null, change: null, tag: null },
// { id: '003', createTime: '2025-05-19', status: 0, score: null, change: null, tag: null },
// ]
// let arr1 = [
// { id: '001', createTime: '2025-05-21', status: 1, score: 65, change: 0.2, tag: '' },
// { id: '002', createTime: '2025-05-20', status: 1, score: 65, change: 0.2, tag: '' },
// { id: '003', createTime: '2025-05-19', status: 1, score: 65, change: 0.2, tag: '' },
// ]
// this.list = this.queryParams.status == 1 ? arr1 : arr0
// },
onSelectFilter(val) {
// type 01
this.queryParams.type = val
this.getData()
},
},
}
</script>


+ 1
- 2
pages_order/center/accountCard.vue View File

@ -30,8 +30,7 @@
computed: {
...mapState(['configList']),
qrCodeUrl() {
// todo: check key
return this.configList['account']
return this.configList['qrcode_wx_official_account']
},
},
methods: {


+ 2
- 0
pages_order/components/reportCard.vue View File

@ -6,10 +6,12 @@
<view class="avatar">
<!-- todo -->
<image class="avatar-img" src="@/pages_order/static/report/avatar.png" mode="scaleToFill"></image>
<!-- <image class="avatar-img" :src="userInfo.avatar" mode="scaleToFill"></image> -->
</view>
<view class="info">
<!-- todo -->
<view class="nickname">李星星</view>
<!-- <view class="nickname">{{ userInfo.name }}</view> -->
<view class="desc">
<text class="age">25</text>
<text class="gender"></text>


+ 1
- 1
pages_order/home/recommendSwiper.vue View File

@ -83,7 +83,7 @@
}
},
jumpToTest(paperId) {
this.$utils.navigateTo(`/pages_order/report/test/intro?paperId=${paperId}`)
this.$utils.navigateTo(`/pages_order/report/test/intro?id=${paperId}`)
},
onChange(e) {
this.current = e.detail.current


+ 1
- 1
pages_order/order/orderDetail/index.vue View File

@ -44,7 +44,7 @@
</view>
</view>
<view v-if="orderData.process.length" class="card service">
<view v-if="orderData.process && orderData.process.length" class="card service">
<view class="flex card-top">
<view class="title">售后信息</view>
</view>


+ 1
- 1
pages_order/product/tabDetect/index.vue View File

@ -82,7 +82,7 @@
},
jumpToRecommendDetect() {
uni.navigateTo({
url: `/pages_order/product/productList?type=1&homeRecommend=Y&title=自选检测`
url: `/pages_order/product/productList?type=1&homeRecommend=Y&title=推荐检测`
})
},
jumpToPersonalDetect() {


+ 4
- 13
pages_order/report/compare/reportRecordCard.vue View File

@ -1,18 +1,9 @@
<template>
<view class="flex card">
<view class="left">
<uv-checkbox-group
v-model="checkboxValue"
shape="circle"
@change="onChange"
>
<uv-checkbox
size="36rpx"
icon-size="36rpx"
activeColor="#7451DE"
:name="1"
></uv-checkbox>
</uv-checkbox-group>
<view class="radio">
<uv-radio :name="data.id"></uv-radio>
</view>
</view>
<view class="right">
<view class="flex top">
@ -20,7 +11,7 @@
<view class="time">{{ $dayjs(data.createTime).format('YYYY-MM-DD') }}</view>
</view>
<view class="bottom">
<reportScoreView :data="data" scoreAlign="left"></reportScoreView>
<reportScoreView :reports="[data]" scoreAlign="left"></reportScoreView>
</view>
</view>
</view>


+ 63
- 126
pages_order/report/compare/result.vue View File

@ -19,9 +19,9 @@
<text class="score-unit"></text>
</view>
<view class="score-detail">
<view class="score-detail-item" v-for="(score, sIdx) in item.scoreDetail" :key="sIdx">
<progressBar :progress="score" :total="100"></progressBar>
<view class="score-detail-item-score">{{ `${axis[sIdx]}` }}<text class="highlight">{{ score }}</text></view>
<view class="score-detail-item" v-for="(scoreItem, sIdx) in item.scoreDetail" :key="sIdx">
<progressBar :progress="scoreItem.score" :total="100"></progressBar>
<view class="score-detail-item-score">{{ scoreItem.name }}<text class="highlight">{{ scoreItem.score }}</text></view>
</view>
</view>
</view>
@ -56,42 +56,46 @@
<view class="section-item">
<view class="section-header">
<view class="section-header-zh">{{ `${sIdx + 1}.${step.name}` }}</view>
<view class="section-header-en">{{ step.nameEn }}</view>
<view class="section-header-en">{{ step.enTitle }}</view>
</view>
<view class="section-content">
<template v-if="step.key === 'improvementGoal'">
<!-- 普通标签 -->
<template v-if="step.type == '0'">
<view class="tags">
<view class="tag" v-for="(tag, tIdx) in report.children[sIdx].tags" :key="tIdx">{{ tag }}</view>
<view class="tag" v-for="(item, tIdx) in report.children[sIdx].schemes" :key="tIdx">{{ item.title }}</view>
</view>
</template>
<template v-else-if="step.key === 'topPriority'">
<!-- 暗色标签 -->
<template v-else-if="step.type === '1'">
<view class="tags">
<view class="tag highlight">{{ report.children[sIdx].tag }}</view>
<view class="tag highlight" v-for="(item, tIdx) in report.children[sIdx].schemes" :key="tIdx">{{ item.title }}</view>
</view>
</template>
<template v-else-if="step.key === 'potentialHealthIssues'">
<view class="flex section-content-tag">
<!-- 有序文本 -->
<template v-else-if="step.type === '2'">
<!-- todo -->
<!-- <view class="flex section-content-tag">
<image class="section-content-tag-icon" :src="report.children[sIdx].tag.icon" mode="widthFix"></image>
<text class="section-content-tag-text">{{ report.children[sIdx].tag.text }}</text>
</view>
</view> -->
<view>
<view class="section-content-item" v-for="(item, idx) in report.children[sIdx].content" :key="idx">
<view class="section-content-item" v-for="(item, idx) in report.children[sIdx].schemes" :key="idx">
<view class="flex section-content-item-header">
<view class="section-content-item-index" v-if="report.children[sIdx].content.length > 1">{{ `${idx + 1}.` }}</view>
<view class="section-content-item-index">{{ `${idx + 1}.` }}</view>
<view class="section-content-item-title">{{ item.title }}</view>
</view>
<view class="section-content-item-detail">{{ item.detail }}</view>
<view class="section-content-item-detail" v-if="item.info">{{ item.info }}</view>
</view>
</view>
</template>
<template v-else>
<!-- 无序文本 -->
<template v-else-if="step.type === '3'">
<view>
<view class="section-content-item" v-for="(item, idx) in report.children[sIdx].content" :key="idx">
<view class="section-content-item" v-for="(item, idx) in report.children[sIdx].schemes" :key="idx">
<view class="flex section-content-item-header">
<view class="section-content-item-index" v-if="report.children[sIdx].content.length > 1">{{ `${idx + 1}.` }}</view>
<view class="section-content-item-title">{{ item.title }}</view>
</view>
<view class="section-content-item-detail">{{ item.detail }}</view>
<view class="section-content-item-detail" v-if="item.info">{{ item.info }}</view>
</view>
</view>
</template>
@ -121,117 +125,49 @@
}
},
onLoad(arg) {
console.log('onLoad', arg)
const ids = JSON.parse(arg.ids)
// todo
const children = [
{
id: '001',
key: 'improvementGoal',
name: '本次改善目标',
nameEn: 'This improvement goal',
url: '/pages_order/static/report/report-detail-1.png',
tags: ['皮肤', '脑力/注意力', '眼睛/视力', '睡眠', '骨骼/关节']
},
{
id: '002',
key: 'topPriority',
name: '首要目标',
nameEn: 'Top priority',
url: '/pages_order/static/report/report-detail-2.png',
tag: '脑力/注意力'
},
{
id: '003',
key: 'targetSuggestion',
name: '健康目标建议',
nameEn: 'Target Suggestion',
url: '/pages_order/static/report/report-detail-3.png',
content: [
{ title: '吃一小把混合坚果对皮肤好', detail: '增加不饱和脂肪酸的摄入有益皮肤健康,可以每天食用一小把混合类坚果,例如核桃、葵花籽、开心果等。' },
]
},
{
id: '004',
key: 'dailyGoals',
name: '每日小目标',
nameEn: 'Daily Small Goals',
url: '/pages_order/static/report/report-detail-4.png',
content: [
{ title: '每天吃1个水煮鸡蛋', detail: '每天食用1个鸡蛋可以补充丰富的动物蛋白以及多种维生素和矿物质,如维生素A、维生素D、 维生素B12、胆碱、叶酸、磷和碘等。' },
{ title: '今天的饭后甜点是低糖酸奶', detail: '每天食用1个鸡蛋可以补充丰富的动物蛋白以及多种维生素和矿物质,如维生素A、维生素D、 维生素B12、胆碱、叶酸、磷和碘等。' },
]
},
{
id: '005',
key: 'potentialHealthIssues',
name: '需要注意的潜在健康问题',
nameEn: 'Potential health issues to note',
url: '/pages_order/static/report/report-detail-5.png',
tag: {
text: '心血管问题',
icon: '/pages_order/static/report/cardiovascular.png',
},
content: [
{ title: '影响因素', detail: '较少食用富含Omega3的食物(如:深海鱼,坚果)' },
{ title: '营养建议', detail: '建议每天吃一把坚果。' },
]
},
{
id: '006',
key: 'specialReminder',
name: '特殊提醒',
nameEn: 'Special Reminder',
url: '/pages_order/static/report/report-detail-6.png',
content: [
{ title: '眼睛长时间感觉到干涩可能是干眼症,一般是由于泪腺泪液分泌不足导致的,严重的话请及时就医。' },
{ title: '同时使用不同品牌的膳食补剂容易导致营养素摄入过量, 请注意服用剂量,避免造成补剂摄入过量带来的副作用。' },
]
},
{
id: '007',
key: 'exerciseRest',
name: '运动 & 作息',
nameEn: 'Exercise & Rest',
url: '/pages_order/static/report/report-detail-7.png',
content: [
{ title: '请尽量避免在阳光强烈的时段(通常是中午至下午3点)户外活动。在户外时,涂抹SPF(防晒系数)高的防晒霜在暴露的皮肤上,并穿着长袖衣物、帽子和太阳镜减少皮肤接触到紫外线的机会。' }
]
},
{
id: '008',
key: 'dietaryRecommendations',
name: '饮食建议',
nameEn: 'Dietary recommendations',
url: '/pages_order/static/report/report-detail-8.png',
content: [
{ title: '一天不要超过三杯美式咖啡', detail: '健康成年人每天摄入的咖啡因应控制在400mg以内。1杯 355mL的小杯美式咖啡,所含咖啡因为150mg,所以一天最好不要饮用超过3小杯咖啡。' },
]
},
]
this.list = [
{
id: '001',
createTime: '2025-05-01',
score: 56,
scoreDetail: [72, 94, 60, 82, 78],
BMI: 16.5,
children,
},
{
id: '002',
createTime: '2025-05-19',
score: 77,
scoreDetail: [72, 94, 60, 82, 78],
BMI: 16.5,
children,
},
]
this.getData(ids)
},
methods: {
async fetchReportData(id) {
console.log('fetchReportData', id)
try {
const result = await this.$fetch('getReportDetail', { id })
const {
createTime,
score,
scoreDetail,
json,
} = result
const obj = {
id,
createTime: this.$dayjs(createTime).format('YYYY-MM-DD'),
score: parseInt(score),
scoreDetail: JSON.parse(scoreDetail).map(item => ({ name: item.name, score: parseInt(item.score) })),
children: JSON.parse(json)
}
return obj
} catch (err) {
console.log('getReportDetail err', err)
return {}
}
},
async getData(ids) {
console.log('ids', ids)
const results = await Promise.allSettled(ids.map(id => { return this.fetchReportData(id) }))
console.log('results', results)
this.list = results.map(item => item.value)
console.log('list', this.list)
},
},
}
</script>
@ -264,6 +200,7 @@
}
.section {
align-items: flex-start;
width: 100%;
& + & {


+ 42
- 41
pages_order/report/compare/select.vue View File

@ -3,69 +3,70 @@
<navbar title="选择对比的报告" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" />
<view class="main">
<reportRecordCard
v-for="(item, index) in list"
:key="item.id"
:data="item"
@select="onSelect(index, $event)"
></reportRecordCard>
<uv-radio-group
v-model="selectedId"
placement="column"
shape="circle"
size="36rpx"
iconSize="36rpx"
activeColor="#7451DE"
>
<reportRecordCard
v-for="item in list"
:key="item.id"
:data="item"
></reportRecordCard>
</uv-radio-group>
</view>
<view class="flex bottom">
<button class="btn" @click="onCompare">开始对比</button>
<button :class="['btn', selectedId ? '' : 'is-disabled']" :disabled="!selectedId" @click="onCompare">开始对比</button>
</view>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
// import mixinsList from '@/mixins/list.js'
import reportRecordCard from './reportRecordCard.vue';
export default {
mixins: [mixinsList],
// mixins: [mixinsList],
components: {
reportRecordCard,
},
data() {
return {
queryParams: {
pageNo: 1,
pageSize: 10,
// todo
status: 1,
},
// todo
mixinsListApi: '',
paperId: null,
reportId: null,
list: [],
selectedId: null,
// queryParams: {
// pageNo: 1,
// pageSize: 10,
// // todo
// status: 1,
// },
// mixinsListApi: '',
}
},
onLoad(arg) {
console.log('onLoad', arg)
const { paperId, reportId } = arg
this.paperId = paperId
this.reportId = reportId
this.getData()
},
methods: {
// todo: delete
getData() {
console.log('getData')
this.list = [
{ id: '001', createTime: '2025-05-21', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '002', createTime: '2025-05-20', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '003', createTime: '2025-05-19', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '004', createTime: '2025-05-18', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '005', createTime: '2025-05-17', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '006', createTime: '2025-05-16', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '007', createTime: '2025-05-15', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '008', createTime: '2025-05-14', status: 1, score: 65, change: 0.2, tag: '正常' },
{ id: '009', createTime: '2025-05-13', status: 1, score: 65, change: 0.2, tag: '正常' },
]
},
onSelect(index, selected) {
console.log('onSelect', index, selected)
this.list[index].selected = selected
// todo limit two
async getData() {
try {
this.list = await this.$fetch('getReportByPaperId', { id: this.paperId })
} catch (err) {
console.log('getReportByPaperId err', err)
}
},
onCompare() {
// todo
let selectedIds = ['001', '002']
let selectedIds = [this.reportId, this.selectedId]
this.$utils.navigateTo(`/pages_order/report/compare/result?ids=${JSON.stringify(selectedIds)}`)
},
},


+ 47
- 102
pages_order/report/detail/index.vue View File

@ -11,47 +11,51 @@
<view class="flex section-header">
<view class="section-header-index">
<text>{{ index + 1 }}</text>
<image class="section-header-index-icon" :src="step.url" mode="widthFix"></image>
<image class="section-header-index-icon" :src="step.icon" mode="widthFix"></image>
</view>
<view class="section-header-title">
<view class="section-header-title-zh">{{ step.name }}</view>
<view class="section-header-title-en">{{ step.nameEn }}</view>
<view class="section-header-title-en">{{ step.enTitle }}</view>
</view>
</view>
<view class="section-content">
<template v-if="step.key === 'improvementGoal'">
<!-- 普通标签 -->
<template v-if="step.type == '0'">
<view class="tags">
<view class="tag" v-for="(item, tIdx) in step.tags" :key="tIdx">{{ item }}</view>
<view class="tag" v-for="(item, tIdx) in step.schemes" :key="tIdx">{{ item.title }}</view>
</view>
</template>
<template v-else-if="step.key === 'topPriority'">
<!-- 暗色标签 -->
<template v-else-if="step.type === '1'">
<view class="tags">
<view class="tag highlight">{{ step.tag }}</view>
<view class="tag highlight" v-for="(item, tIdx) in step.schemes" :key="tIdx">{{ item.title }}</view>
</view>
</template>
<template v-else-if="step.key === 'potentialHealthIssues'">
<view class="flex section-content-tag">
<!-- 有序文本 -->
<template v-else-if="step.type === '2'">
<!-- todo -->
<!-- <view class="flex section-content-tag">
<image class="section-content-tag-icon" :src="step.tag.icon" mode="widthFix"></image>
<text class="section-content-tag-text">{{ step.tag.text }}</text>
</view>
</view> -->
<view>
<view class="section-content-item" v-for="(item, idx) in step.content" :key="idx">
<view class="section-content-item" v-for="(item, idx) in step.schemes" :key="idx">
<view class="flex section-content-item-header">
<view class="section-content-item-index">{{ `${idx + 1}.` }}</view>
<view class="section-content-item-title">{{ item.title }}</view>
</view>
<view class="section-content-item-detail">{{ item.detail }}</view>
<view class="section-content-item-detail" v-if="item.info">{{ item.info }}</view>
</view>
</view>
</template>
<template v-else>
<!-- 无序文本 -->
<template v-else-if="step.type === '3'">
<view>
<view class="section-content-item" v-for="(item, idx) in step.content" :key="idx">
<view class="section-content-item" v-for="(item, idx) in step.schemes" :key="idx">
<view class="flex section-content-item-header">
<view class="section-content-item-index">{{ `${idx + 1}.` }}</view>
<view class="section-content-item-title">{{ item.title }}</view>
</view>
<view class="section-content-item-detail">{{ item.detail }}</view>
<view class="section-content-item-detail" v-if="item.info">{{ item.info }}</view>
</view>
</view>
</template>
@ -80,99 +84,40 @@
},
data() {
return {
BMI: null,
BMI: 0,
list: [],
}
},
onLoad() {
onLoad(arg) {
const { id } = arg
this.id = id
this.fetchReportData(id)
// todo: delete
this.BMI = 16.5
this.list = [
{
id: '001',
key: 'improvementGoal',
name: '本次改善目标',
nameEn: 'This improvement goal',
url: '/pages_order/static/report/report-detail-1.png',
tags: ['皮肤', '脑力/注意力', '眼睛/视力', '睡眠', '骨骼/关节']
},
{
id: '002',
key: 'topPriority',
name: '首要目标',
nameEn: 'Top priority',
url: '/pages_order/static/report/report-detail-2.png',
tag: '脑力/注意力'
},
{
id: '003',
key: 'targetSuggestion',
name: '健康目标建议',
nameEn: 'Target Suggestion',
url: '/pages_order/static/report/report-detail-3.png',
content: [
{ title: '吃一小把混合坚果对皮肤好', detail: '增加不饱和脂肪酸的摄入有益皮肤健康,可以每天食用一小把混合类坚果,例如核桃、葵花籽、开心果等。' },
]
},
{
id: '004',
key: 'dailyGoals',
name: '每日小目标',
nameEn: 'Daily Small Goals',
url: '/pages_order/static/report/report-detail-4.png',
content: [
{ title: '每天吃1个水煮鸡蛋', detail: '每天食用1个鸡蛋可以补充丰富的动物蛋白以及多种维生素和矿物质,如维生素A、维生素D、 维生素B12、胆碱、叶酸、磷和碘等。' },
{ title: '今天的饭后甜点是低糖酸奶', detail: '每天食用1个鸡蛋可以补充丰富的动物蛋白以及多种维生素和矿物质,如维生素A、维生素D、 维生素B12、胆碱、叶酸、磷和碘等。' },
]
},
{
id: '005',
key: 'potentialHealthIssues',
name: '需要注意的潜在健康问题',
nameEn: 'Potential health issues to note',
url: '/pages_order/static/report/report-detail-5.png',
tag: {
text: '心血管问题',
icon: '/pages_order/static/report/cardiovascular.png',
},
content: [
{ title: '影响因素', detail: '较少食用富含Omega3的食物(如:深海鱼,坚果)' },
{ title: '营养建议', detail: '建议每天吃一把坚果。' },
]
},
{
id: '006',
key: 'specialReminder',
name: '特殊提醒',
nameEn: 'Special Reminder',
url: '/pages_order/static/report/report-detail-6.png',
content: [
{ title: '眼睛长时间感觉到干涩可能是干眼症,一般是由于泪腺泪液分泌不足导致的,严重的话请及时就医。' },
{ title: '同时使用不同品牌的膳食补剂容易导致营养素摄入过量, 请注意服用剂量,避免造成补剂摄入过量带来的副作用。' },
]
},
{
id: '007',
key: 'exerciseRest',
name: '运动 & 作息',
nameEn: 'Exercise & Rest',
url: '/pages_order/static/report/report-detail-7.png',
content: [
{ title: '请尽量避免在阳光强烈的时段(通常是中午至下午3点)户外活动。在户外时,涂抹SPF(防晒系数)高的防晒霜在暴露的皮肤上,并穿着长袖衣物、帽子和太阳镜减少皮肤接触到紫外线的机会。' }
]
},
{
id: '008',
key: 'dietaryRecommendations',
name: '饮食建议',
nameEn: 'Dietary recommendations',
url: '/pages_order/static/report/report-detail-8.png',
content: [
{ title: '一天不要超过三杯美式咖啡', detail: '健康成年人每天摄入的咖啡因应控制在400mg以内。1杯 355mL的小杯美式咖啡,所含咖啡因为150mg,所以一天最好不要饮用超过3小杯咖啡。' },
]
},
]
},
methods: {
async fetchReportData(id) {
try {
const { json } = await this.$fetch('getReportDetail', { id })
this.list = JSON.parse(json).map((item, index) => {
let url = `/pages_order/static/report/report-detail-${index + 1}.png`
return {
...item,
// todo: delete
icon: item.icon || url,
}
})
console.log('list', this.list)
} catch (err) {
}
},
jumpToNutritionProgram() {
this.$utils.navigateTo(`/pages_order/report/nutritionProgram/index?id=${this.id}`)
},


+ 94
- 86
pages_order/report/nutritionProgram/index.vue View File

@ -33,7 +33,7 @@
<view class="flex bottom">
<view class="left">
<button class="btn btn-comment">
<button class="btn btn-comment" @click="jumpToComment">
<view>查看评价</view>
<view class="flex"><text class="highlight">{{ `${comment}` }}</text><uv-icon name="arrow-right" color="#C6C6C6" size="28rpx"></uv-icon></view>
</button>
@ -72,6 +72,7 @@
},
data() {
return {
productId: '',
list: [],
comment: 0,
}
@ -89,92 +90,96 @@
return count
},
},
onLoad() {
const detectionList = [
{
id: '0011',
url: '',
name: '眼科检查',
desc: '早期发现眼部疾病',
originalPrice: 168,
price: 68.00,
unit: '次',
count: 1,
selected: false,
},
]
const baseList = [
{
id: '0021',
url: '',
name: '全株印度人参',
desc: '安享睡眠情绪舒展',
originalPrice: 688,
price: 1664,
unit: '月',
count: 1,
selected: false,
customized: true,
},
{
id: '0022',
url: '',
name: '御氧虾青素',
desc: '安享睡眠情绪舒展',
originalPrice: 688,
price: 1664,
unit: '月',
count: 1,
selected: false,
customized: true,
},
{
id: '0023',
url: '',
name: '全株印度人参',
desc: '安享睡眠情绪舒展',
originalPrice: 688,
price: 1664,
unit: '月',
count: 1,
selected: false,
customized: true,
},
{
id: '0024',
url: '',
name: '全株印度人参',
desc: '安享睡眠情绪舒展',
originalPrice: 688,
price: 1664,
unit: '月',
count: 1,
selected: false,
customized: true,
},
]
this.list = [
{
id: '001',
name: '检测方案',
desc: '刚开始健康检测?专家推荐先做这几项',
url: '/pages_order/static/report/report-nutrition-1.png',
children: detectionList
},
{
id: '002',
name: '基础方案',
desc: `刚开始吃维生素?营养师建议从这${baseList.length}颗开始`,
url: '/pages_order/static/report/report-nutrition-2.png',
children: baseList
},
]
this.comment = 23898
async onLoad(arg) {
const { id } = arg
await this.fetchReportData(id)
this.fetchCommentNum(this.productId)
},
methods: {
async fetchCommentNum(productId) {
try {
this.comment = await this.$fetch('productEvaluateNum', { productId })
} catch (err) {
}
},
async fetchReportData(id) {
try {
const { productList } = await this.$fetch('getReportDetail', { id })
const detectList = []
const nutrientList = []
const courseList = []
const ids = []
productList.forEach(item => {
// 012
const { id, type, specs, currentPrice } = item
ids.push(id)
const spec = specs?.[0] || {}
const obj = {
...item,
specId: spec.id,
specName: spec.specName,
currentPrice: spec.price || currentPrice,
selected: true,
}
switch(type) {
case '0':
nutrientList.push(obj)
break;
case '1':
detectList.push(obj)
break;
case '2':
courseList.push(obj)
break;
default:
break;
}
})
this.list = [
{
id: '001',
name: '检测方案',
desc: '刚开始健康检测?专家推荐先做这几项',
url: '/pages_order/static/report/report-nutrition-1.png',
children: detectList
},
{
id: '002',
name: '基础方案',
desc: `刚开始吃维生素?营养师建议从这${nutrientList.length}颗开始`,
url: '/pages_order/static/report/report-nutrition-2.png',
children: nutrientList
},
// todo: check is need?
{
id: '003',
name: '课程方案',
desc: '',
url: '/pages_order/static/report/report-detail-3.png',
children: courseList
},
]
this.productId = ids.join(',')
console.log('list', this.list)
console.log('productId', this.productId)
} catch (err) {
}
},
onSelect(stepIdx, childIdx, selected) {
this.list[stepIdx].children[childIdx].selected = selected
},
@ -185,10 +190,13 @@
return arr.concat(selectedArr)
}, [])
},
jumpToComment() {
this.$utils.navigateTo(`/pages_order/comment/commentRecordsOfProduct?productId=${this.productId}`)
},
onAddCart() {
const selectedList = this.getSelectedList()
this.$store.dispatch('addCart', selectedList)
this.$store.dispatch('addCartBatch', selectedList)
},
onBuy() {
const selectedList = this.getSelectedList()


+ 18
- 6
pages_order/report/nutritionProgram/productCard.vue View File

@ -15,17 +15,23 @@
</uv-checkbox-group>
</view>
<view class="img-box">
<image class="img" :src="data.url" mode="aspectFit"></image>
<image class="img" :src="coverImg" mode="aspectFit"></image>
</view>
<view class="info">
<view class="title">{{ data.name }}</view>
<view class="desc">{{ data.desc }}</view>
<!-- <view class="desc">{{ data.desc }}</view> -->
<view class="flex price-box">
<view class="flex price">¥<text class="highlight">{{ data.price }}</text>/</view>
<view class="price-origin">{{ `¥${data.originalPrice}/次` }}</view>
<view class="flex price">
<text>¥</text>
<text class="highlight">{{ data.currentPrice }}</text>
<text>{{ `/${data.unit}` }}</text>
</view>
<view class="price-origin">
{{ `¥${data.originalPrice}/${data.unit}` }}
</view>
</view>
<view class="flex tool">
<view class="flex count">规格<text class="highlight">{{ `x${data.count}` }}</text></view>
<view class="flex count">规格<text class="highlight">{{ data.specName || '' }}</text></view>
<button class="flex btn" @click="jumpToProductDetail">详情</button>
</view>
</view>
@ -67,7 +73,12 @@
get() {
return this.checkboxValue[0] == 1 ? true : false
}
}
},
coverImg() {
const { image } = this.data
return image?.split(',')?.[0] || ''
},
},
watch: {
data: {
@ -174,6 +185,7 @@
.btn {
padding: 8rpx 22rpx;
font-size: 28rpx;
white-space: nowrap;
color: #252545;
border: 2rpx solid #252545;
border-radius: 30rpx;


+ 5
- 4
pages_order/report/reportRecordCard.vue View File

@ -18,7 +18,7 @@
</view>
</view>
<view class="bottom">
<reportScoreView :data="data"></reportScoreView>
<reportScoreView :reports="data.reports"></reportScoreView>
</view>
</view>
</template>
@ -48,13 +48,14 @@
},
methods: {
jumpToCompare() {
this.$utils.navigateTo('/pages_order/report/compare/select')
this.$utils.navigateTo(`/pages_order/report/compare/select?paperId=${this.data.id}&reportId=${this.data.reports[0].id}`)
},
jumpToReport() {
this.$utils.navigateTo(`/pages_order/report/result/index?id=${this.data.id}`)
this.$utils.navigateTo(`/pages_order/report/result/index?id=${this.data.reports[0].id}`)
},
jumpToContinueAnswer() {
this.$utils.navigateTo(`/pages_order/report/test/intro?paperId=${this.data.id}`)
console.log('id', this.data.id)
this.$utils.navigateTo(`/pages_order/report/test/intro?id=${this.data.id}`)
},
},
}


+ 14
- 14
pages_order/report/reportSummary/index.vue View File

@ -1,11 +1,11 @@
<template>
<view class="summary__view flex flex-column">
<progressCircle ref="progressCircle" :progress="score"></progressCircle>
<progressCircle ref="progressCircle" :progress="data.score"></progressCircle>
<view v-if="change" class="score-change flex">
<uv-icon v-if="change > 0" name="arrow-upward" color="#0DB556" size="24rpx"></uv-icon>
<view v-if="data.change > 0 || data.change < 0" class="score-change flex">
<uv-icon v-if="data.change > 0" name="arrow-upward" color="#0DB556" size="24rpx"></uv-icon>
<uv-icon v-else name="arrow-downward" color="#dd524d" size="24rpx"></uv-icon>
<text class="score-change-value">{{ change }}</text>
<text class="score-change-value">{{ data.change }}</text>
<text class="score-change-unit"></text>
</view>
@ -33,27 +33,27 @@
components: {
progressCircle,
},
props: {
data: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
score: 0,
change: 0,
}
},
computed : {
...mapState(['userInfo'])
},
mounted() {
this.score = 77
this.change = 12
},
methods: {
jumpToTest() {
this.$utils.navigateTo('/pages_order/report/test/intro')
// todo
// this.$utils.navigateTo(`/pages_order/report/test/intro?paperId=${paperId}`)
this.$utils.navigateTo(`/pages_order/report/test/intro?id=${this.data.paperId}`)
},
showFormula() {
// todo: check
this.$utils.navigateTo(`/pages_order/report/result/index?id=${this.data.id}`)
}
},
}


+ 19
- 1
pages_order/report/reportSummary/progressCircle.vue View File

@ -28,7 +28,8 @@ export default {
},
data() {
return {
code: Math.floor(Math.random() * 100).toString()
code: Math.floor(Math.random() * 100).toString(),
retry: 20,
}
},
mounted() {
@ -42,6 +43,7 @@ export default {
this.drawProgress(val)
})
},
immediate: true,
},
},
methods: {
@ -169,6 +171,22 @@ export default {
size: true
})
.exec(async (res) => {
console.log('progress_bar', res)
if (!res?.[0]?.node) {
if (this.retry) {
this.retry -= 1
console.log('retry', this.retry)
setTimeout(() => {
this.drawProgress(step)
}, 200)
}
return
}
const canvas = res[0].node
// Canvas
const width = res[0].width


+ 38
- 44
pages_order/report/result/index.vue View File

@ -14,7 +14,7 @@
</view>
</view>
<view :id="item.id" :class="['swiper-item', getIsShowTips(index, current) ? 'with-tips' : '']"
v-for="(item, index) in list"
v-for="(item, index) in productList"
:key="item.id"
>
<view class="content">
@ -28,7 +28,7 @@
</view>
</view>
<view class="bottom">
<indicator :current="current" :length="list.length + 1"></indicator>
<indicator :current="current" :length="productList.length + 1"></indicator>
<view class="flex bar">
<button class="flex btn" @click="jumpToReportDetail">详细报告</button>
<button class="flex btn highlight" @click="jumpToNutritionProgram">查看营养方案</button>
@ -56,14 +56,17 @@
},
data() {
return {
id: null,
current: 0,
summaryData: {},
list: [],
productList: [],
observer: null,
}
},
async mounted() {
await this.fetchReportData(this.id)
async onLoad(arg) {
const { id } = arg
this.id = id
await this.fetchReportData(id)
this.$nextTick(() => {
this.observeElement()
})
@ -74,45 +77,36 @@
},
methods: {
async fetchReportData(id) {
// todo: fetch
this.summaryData = {
score: 77,
gradeDesc: '良好',
scoreDetail: [77, 53, 67, 88, 98],
tags: ['皮肤', '脑力/注意力', '眼睛/视力', '睡眠', '骨骼/关节'],
topPriority: '脑力/注意力',
try {
const result = await this.$fetch('getReportDetail', { id })
const {
score,
scoreDetail,
json,
productList,
} = result
let detail = JSON.parse(json)?.filter(item => ['0', '1'].includes(item.type))
console.log('detail', detail)
this.summaryData = {
score: parseInt(score),
scoreDetail: JSON.parse(scoreDetail).map(item => ({ name: item.name, score: parseInt(item.score) })),
detail,
}
this.productList = productList
console.log('summaryData', this.summaryData)
console.log('productList', this.productList)
} catch (err) {
}
this.list = [
{
id: '001',
url: '',
name: '维生素 D',
nameEn: 'Vitamin D',
use: '推荐使用·每日一粒',
title: '缺乏相应的营养物质会导致生长发育不良',
desc: '维生素D能促进钙的吸收,促进骨骼发育,维生素D3滴剂更利于儿童吸收',
},
{
id: '002',
url: '',
name: '维生素 D',
nameEn: 'Vitamin D',
use: '推荐使用·每日一粒',
title: '缺乏相应的营养物质会导致生长发育不良',
desc: '维生素D能促进钙的吸收,促进骨骼发育,维生素D3滴剂更利于儿童吸收',
},
{
id: '003',
url: '',
name: '维生素 D',
nameEn: 'Vitamin D',
use: '推荐使用·每日一粒',
title: '缺乏相应的营养物质会导致生长发育不良',
desc: '维生素D能促进钙的吸收,促进骨骼发育,维生素D3滴剂更利于儿童吸收',
},
]
},
observeElement() {
this.observer = uni.createIntersectionObserver(this, { observeAll: true, thresholds: [0.5] });
@ -121,9 +115,9 @@
let current = 0
if (res.intersectionRatio > 0.5) {
current = res.id === 'home' ? 0 : this.list.findIndex(item => item.id === res.id)
current = res.id === 'home' ? 0 : this.productList.findIndex(item => item.id === res.id)
} else if (res.intersectionRatio > 0) {
current = res.id === 'home' ? 0 : this.list.findIndex(item => item.id === res.id) + 1
current = res.id === 'home' ? 0 : this.productList.findIndex(item => item.id === res.id) + 1
if (res.boundingClientRect.left > 0) {
current -= 1
@ -137,7 +131,7 @@
},
getIsShowTips(index, current) {
return current == index + 1 && current < this.list.length
return current == index + 1 && current < this.productList.length
},
jumpToReportDetail() {
this.$utils.navigateTo(`/pages_order/report/detail/index?id=${this.id}`)


+ 4
- 4
pages_order/report/result/radarChart.vue View File

@ -8,10 +8,10 @@
>
<div class="flex title">
<div class="line"></div>
<div class="label">{{ axis[index] }}</div>
<div class="label">{{ item.name }}</div>
</div>
<div class="flex desc">
指数<text class="highlight">{{ item }}</text>
指数<text class="highlight">{{ item.score }}</text>
</div>
</div>
</view>
@ -30,7 +30,7 @@
data() {
return {
code: Math.floor(Math.random() * 100).toString(),
axis: ['饮食', '运动', '心理', '体质', '作息']
// axis: ['', '', '', '', '']
}
},
watch: {
@ -38,7 +38,7 @@
handler(val) {
console.log('watch score', val)
this.$nextTick(() => {
this.drawChart(val)
this.drawChart(val.map(item => item.score))
})
},
deep: true


+ 9
- 7
pages_order/report/result/resultSummary.vue View File

@ -8,7 +8,8 @@
<text>的健康测评</text>
</view>
<view class="grade">
<view class="grade-text">{{ data.gradeDesc }}</view>
<!-- todo: check key -->
<view class="grade-text">良好</view>
<view class="grade-border"></view>
</view>
<view class="desc">
@ -22,16 +23,17 @@
<view class="flex">
<radarChart :score="data.scoreDetail"></radarChart>
</view>
<view class="section">
<!-- todo: use detail -->
<view class="section" v-for="item in data.detail" :key="item.id">
<div class="title">
<div class="title-zh">本次改善目标</div>
<div class="title-en">This improvement goal</div>
<div class="title-zh">{{ item.name }}</div>
<div class="title-en">{{ item.enTitle }}</div>
</div>
<div class="content">
<div class="tag" v-for="(item, tIdx) in data.tags" :key="tIdx">{{ item }}</div>
<div :class="['tag', item.type == '1' ? 'highlight' : '']" v-for="(item, tIdx) in item.schemes" :key="tIdx">{{ item.title }}</div>
</div>
</view>
<view class="flex section">
<!-- <view class="flex section">
<div class="title">
<div class="title-zh">首要目标</div>
<div class="title-en">Top priority</div>
@ -39,7 +41,7 @@
<div class="content">
<div class="tag highlight">{{ data.topPriority }}</div>
</div>
</view>
</view> -->
</view>
</template>


+ 14
- 5
pages_order/report/result/tonicCard.vue View File

@ -3,16 +3,17 @@
<view class="header">
<view class="index">{{ getNum(index) }}</view>
<view class="intro">
<view class="title">{{ data.title }}</view>
<view class="desc">{{ data.desc }}</view>
<view class="title">{{ data.info }}</view>
<view class="desc">{{ data.content }}</view>
</view>
</view>
<view class="main">
<image class="img" :src="data.url" mode="aspectFit"></image>
<image class="img" :src="coverImg" mode="aspectFit"></image>
<view class="flex flex-column">
<view class="use">{{ data.use }}</view>
<!-- todo: check key -->
<view class="use">推荐使用·每日一粒</view>
<view class="name-zh">{{ data.name }}</view>
<view class="name-en">{{ data.nameEn }}</view>
<view class="name-en">{{ data.enName }}</view>
</view>
</view>
</view>
@ -32,6 +33,14 @@
}
},
},
computed: {
coverImg() {
console.log('data', this.data)
const { image } = this.data || {}
return image?.split(',')?.[0] || ''
},
},
methods: {
getNum(index) {
let num = index + 1


+ 158
- 174
pages_order/report/test/answer.vue View File

@ -20,57 +20,57 @@
</view>
<view class="main">
<view class="question">{{ data.question }}</view>
<template v-if="data.type === 'select'">
<view class="question">{{ currentQuestion.question }}</view>
<template v-if="currentQuestion.component === 'select'">
<view class="select">
<view
v-for="item in data.options"
v-for="item in currentQuestion.options"
:key="item.id"
:class="['flex', 'select-option', item.id === value ? 'is-active' : '']"
@click="onSelect(item.id)"
>
{{ item.label }}
{{ item.content }}
</view>
</view>
</template>
<template v-else-if="data.type === 'select-box'">
<template v-else-if="currentQuestion.component === 'select-box'">
<view class="select-box">
<view
v-for="item in data.options"
v-for="item in currentQuestion.options"
:key="item.id"
:class="['flex', 'flex-column', 'select-box-option', item.id === value ? 'is-active' : '']"
@click="onSelect(item.id)"
>
<!-- todo: img switch acitve: white -->
<image class="img" :src="item.img" mode="aspectFit"></image>
<view class="text">{{ item.label }}</view>
<image class="img" :src="item.id === value ? item.imageAfter : item.image" mode="aspectFit"></image>
<view class="text">{{ item.content }}</view>
</view>
</view>
</template>
<template v-else-if="data.type === 'input'">
<template v-else-if="currentQuestion.component === 'input'">
<view class="input">
<view class="flex input-box">
<view class="input-label" v-if="currentQuestion.options[0].prefix">{{ currentQuestion.options[0].prefix }}</view>
<input class="input-doc" v-model="value" placeholder="请输入内容" placeholder-style="font-family: PingFang SC; font-weight: 400; line-height: 1.4; font-size: 32rpx; color: #AAAACA;" />
<view class="input-unit" v-if="data.unit">{{ data.unit }}</view>
<view class="input-unit" v-if="currentQuestion.options[0].suffix">{{ currentQuestion.options[0].suffix }}</view>
</view>
</view>
</template>
<template v-else-if="data.type === 'input-group'">
<template v-else-if="currentQuestion.component === 'input-group'">
<view class="input">
<view class="flex input-box" v-for="(item, index) in data.options" :key="item.id">
<view class="input-label" v-if="item.label">{{ item.label }}</view>
<view class="flex input-box" v-for="(item, index) in currentQuestion.options" :key="item.id">
<view class="input-label" v-if="item.prefix">{{ item.prefix }}</view>
<input class="input-doc" v-model="value[index]" placeholder="请输入内容" placeholder-style="font-family: PingFang SC; font-weight: 400; line-height: 1.4; font-size: 32rpx; color: #AAAACA;" />
<view class="input-unit" v-if="item.unit">{{ item.unit }}</view>
<view class="input-unit" v-if="item.suffix">{{ item.suffix }}</view>
</view>
</view>
</template>
<view class="flex btns">
<button :class="['btn', 'btn-palin', noneFlag ? 'is-active' : '']" v-if="data.showNoneBtn" @click="onSelectNone"> </button>
<button :class="['btn', 'btn-palin', noneFlag ? 'is-active' : '']" v-if="!currentQuestion.required" @click="onSelectNone"> </button>
<button class="btn" v-if="showConfirmBtn" @click="onConfirm"> </button>
<button class="btn" v-if="showSubmitBtn" @click="onSubmit"> </button>
</view>
<view class="desc" v-if="data.desc">
<uv-parse :content="data.desc"></uv-parse>
<view class="desc" v-if="currentQuestion.desc">
<uv-parse :content="currentQuestion.desc"></uv-parse>
</view>
</view>
@ -78,6 +78,8 @@
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
@ -86,11 +88,12 @@
value: null,
noneFlag: false,
answers: [],
list: [],
questionsList: [],
total: 0,
}
},
computed: {
...mapState(['paperInfo']),
showPreBtn() {
return this.current > 0
},
@ -100,15 +103,15 @@
showSubmitBtn() {
return this.current + 1 === this.total
},
data() {
return this.list[this.current]
currentQuestion() {
return this.questionsList[this.current]
},
showConfirmBtn() {
if (this.showSubmitBtn) {
return false
}
return ['input', 'input-group'].includes(this.data?.type)
return ['input', 'input-group'].includes(this.currentQuestion?.component)
},
},
onLoad(arg) {
@ -116,145 +119,58 @@
this.current = parseInt(arg.current || 0)
this.fetchQuestionList()
console.log('currentQuestion', this.currentQuestion)
},
methods: {
fetchQuestionList() {
// todo
this.list = [
{
question: '您的年龄是?',
type: 'select',
options: [
{ id: '001', label: '0-3' },
{ id: '002', label: '3-6' },
{ id: '003', label: '6-10' },
{ id: '004', label: '10-18' },
{ id: '005', label: '18-50' },
{ id: '006', label: '50岁+' },
]
},
{
question: '你的性别?',
type: 'select',
options: [
{ id: '001', label: '男' },
{ id: '002', label: '女' },
]
},
{
question: '你的是否本人?',
type: 'select',
options: [
{ id: '001', label: '本人' },
{ id: '002', label: '夫妻' },
{ id: '003', label: '子女' },
{ id: '004', label: '父母' },
{ id: '005', label: '(外)祖父母' },
]
},
{
question: '你的姓名?',
type: 'input',
},
{
question: '你的体重?',
type: 'input',
unit: 'KG',
},
{
question: '你是否在备孕?',
type: 'select',
options: [
{ id: '001', label: '是' },
{ id: '002', label: '否' },
]
},
{
question: '是否有以下问题的困扰?',
type: 'select-box',
options: [
{ id: '001', label: '高血压', img: '/pages_order/static/report/trouble-1.png' },
{ id: '002', label: '高血脂', img: '/pages_order/static/report/trouble-2.png' },
{ id: '003', label: '高血糖', img: '/pages_order/static/report/trouble-3.png' },
{ id: '004', label: '免疫问题', img: '/pages_order/static/report/trouble-4.png' },
{ id: '005', label: '胃肠道疾病', img: '/pages_order/static/report/trouble-5.png' },
{ id: '006', label: '甲状腺疾病', img: '/pages_order/static/report/trouble-6.png' },
{ id: '007', label: '头痛头晕', img: '/pages_order/static/report/trouble-7.png' },
{ id: '008', label: '肝功能不全', img: '/pages_order/static/report/trouble-8.png' },
{ id: '009', label: '肾功能不全', img: '/pages_order/static/report/trouble-9.png' },
{ id: '010', label: '关节疼痛', img: '/pages_order/static/report/trouble-10.png' },
{ id: '011', label: '精神疾病', img: '/pages_order/static/report/trouble-11.png' },
],
showNoneBtn: true,
},
{
question: '是否接受过手术?',
type: 'input',
showNoneBtn: true,
},
{
question: '您的孩子晚上能睡几个小时?',
type: 'select',
options: [
{ id: '001', label: '> 10 小时' },
{ id: '002', label: '8-10 小时' },
{ id: '003', label: '6-8 小时' },
{ id: '004', label: '<6 小时' },
]
},
{
question: '是否有饮酒?',
type: 'input-group',
options: [
{ id: '001', label: '平均每周饮酒', unit: '次' },
{ id: '002', label: '每次饮酒', unit: 'ml' },
],
showNoneBtn: true,
},
{
question: '你经常食用富含不饱和脂肪酸的食物吗?',
type: 'select',
options: [
{ id: '001', label: '基本不吃' },
{ id: '002', label: '偶尔' },
{ id: '003', label: '经常' },
],
desc: `
<p>富含不饱和脂肪酸的食物包括鱼类三文鱼鳕鱼鳟鱼等坚果和种子杏仁核桃花生等植物油橄榄油亚麻籽油等</p>
`
},
{
question: '你运动的强度如何?',
type: 'select',
options: [
{ id: '001', label: '高强度' },
{ id: '002', label: '中等强度' },
{ id: '003', label: '低强度' },
],
desc: `
<p>
低强度运动45分钟以上散步瑜伽太极拳打扫等<br/>
中强度运动30-45 分钟 慢跑骑车健身操等<br/>
高强度运动20-30 分钟跑步跳绳游泳跳舞HIT等
</p>
`
},
]
this.answers = this.list.map(item => {
const { category: categoryList } = this.paperInfo
const category = categoryList[this.step]
const { questionsList } = category
this.questionsList = questionsList.map(item => {
const { id, text, type, options, required, content } = item
let component = 'select'
switch(type) { // 0- 1- 2-
case '1':
component = options?.length > 1 ? 'input-group' : 'input'
break
case '2':
component = 'select-box'
break
default:
component = 'select'
break
}
return {
id,
question: text,
type,
component,
options,
required: required == 'Y',
desc: content,
}
})
this.total = this.questionsList.length
this.answers = this.questionsList.map(item => {
let val = null
if (this.data.type === 'input-group') {
val = new Array(this.data.options.length).fill(0).map(() => null)
if (item.component === 'input-group') {
val = new Array(item.options.length).fill(0).map(() => null)
}
return val
})
this.value = this.answers[this.current]
this.total = this.list.length
// todo: delete
this.current = 0
console.log('questionsList', this.questionsList)
console.log('answers', this.answers)
},
pre() {
this.current -= 1
@ -269,50 +185,118 @@
this.value = this.answers[this.current]
this.noneFlag = false
},
onSelect(id) {
async fetchAnswer() {
try {
const { id: questionsId, type, required, options } = this.currentQuestion
console.log('paperInfo', this.paperInfo)
console.log('currentQuestion', this.currentQuestion)
console.log('required', required, 'noneFlag', this.noneFlag, 'value', this.value)
if (required && !this.noneFlag && (!this.value || this.value?.every?.(val => !val))) {
console.log('未答题')
uni.showToast({
title: '请答题',
icon:'none'
})
return false
}
let answer
// 0- 1- 2-
if (type == 1) {
answer = options.reduce((obj, option, oIdx) => {
const { id: optionsId } = option
obj[optionsId] = this.value[oIdx]
return obj
}, {})
answer = JSON.stringify(answer)
} else {
answer = this.value
}
let params = {
id: this.paperInfo.reportId,
questionsId,
answer
}
console.log('params', params)
await this.$fetch('answerPaper', params)
this.answers[this.current] = this.value
return true
} catch (err) {
console.log('answerPaper', err)
return false
}
},
async onSelect(id) {
this.value = id
// todo: fetch submit answer of this question
if (this.showSubmitBtn) {
return
}
if (!this.showSubmitBtn) {
this.$nextTick(() => {
setTimeout(() => {
this.next()
}, 500)
})
let succ = await this.fetchAnswer()
console.log('fetchAnswer', succ)
if (!succ) {
return
}
},
onConfirm() {
// todo: check is fill
this.answers[this.current] = this.value
this.$nextTick(() => {
setTimeout(() => {
this.next()
}, 500)
})
},
async onConfirm() {
let succ = await this.fetchAnswer()
console.log('fetchAnswer', succ)
// todo: fetch submit answer of this question
if (!succ) {
return
}
this.next()
},
onSelectNone() {
async onSelectNone() {
this.noneFlag = true
let val = null
if (this.data.type === 'input-group') {
val = new Array(this.data.options.length).fill(0).map(() => null)
if (this.currentQuestion.component === 'input-group') {
val = new Array(this.currentQuestion.options.length).fill(0).map(() => '')
}
this.answers[this.current] = val
this.value = val
// todo: fetch submit answer of this question
let succ = await this.fetchAnswer()
console.log('fetchAnswer', succ)
if (!succ) {
return
}
this.next()
},
onSubmit() {
// todo
async onSubmit() {
let succ = await this.fetchAnswer()
console.log('fetchAnswer', succ)
if (!succ) {
return
}
const { category } = this.paperInfo
// todo: fetch submit answer of this step
if (this.step == category.length - 1) {
if (this.step == 3) {
// todo: submit all answer and jump to the report
await this.$fetch('submitPaper', { id: this.paperInfo.reportId })
uni.reLaunch({
url: '/pages/index/report'


+ 29
- 12
pages_order/report/test/intro.vue View File

@ -4,11 +4,11 @@
<view class="main">
<view class="title">
<view class="title-zh">个人营养问卷</view>
<view class="title-en">Personal Nutrition Survey</view>
<view class="title-zh">{{ detail.title }}</view>
<view class="title-en">{{ detail.enTitle }}</view>
</view>
<view class="flex desc">
<view class="dot"></view>完成问卷大概需要3~5分钟
<view class="dot"></view>{{ detail.tips }}
</view>
</view>
@ -44,6 +44,7 @@
</template>
<script>
import { mapState } from 'vuex'
import agreementModal from '@/pages_order/components/agreementModal.vue'
export default {
@ -52,34 +53,49 @@
},
data() {
return {
paperId: null,
id: null,
detail: {},
checkboxValue : []
}
},
computed: {
...mapState(['paperInfo']),
},
onLoad(arg) {
const { paperId } = arg
console.log('onLoad', arg)
const { id } = arg
this.paperId = paperId
this.id = id
this.getData(paperId)
this.getData(id)
},
methods: {
async getData(paperId) {
async getData(id) {
try {
this.detail = await this.$fetch('getPaperDetail', { paperId })
this.detail = await this.$fetch('getPaperDetail', { id })
} catch (err) {
}
},
onStart(){
async onStart(){
if(!this.checkboxValue.length){
return uni.showToast({
title: '请先同意《用户协议》《隐私协议》',
icon:'none'
})
}
this.$utils.navigateTo('/pages_order/report/test/step?step=0')
try {
const reportId = await this.$fetch('startPaper', { id: this.id })
this.detail.reportId = reportId
this.$store.commit('setPaperInfo', this.detail)
this.$utils.navigateTo('/pages_order/report/test/step?step=0')
} catch (err) {
}
},
onConfirmAgreement(confirm) {
if (confirm) {
@ -171,8 +187,9 @@
.bar {
padding: 24rpx 40rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 24rpx);
box-sizing: border-box;
height: 200rpx;
// height: 200rpx;
background: #FFFFFF;
.btn {


+ 31
- 21
pages_order/report/test/step.vue View File

@ -3,35 +3,45 @@
<navbar title="问卷测评" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="transparent" />
<view class="main">
<view :class="['step', item.step === current ? 'is-active' : '']" v-for="item in steps" :key="item.step">
<view class="step-zh">{{ `{ ${item.zh} }` }}</view>
<view class="step-en">{{ item.en }}</view>
<view :class="['step', step === current ? 'is-active' : '']" v-for="(item, step) in steps" :key="item.id">
<view class="step-zh">{{ `{ ${item.title} }` }}</view>
<view class="step-en">{{ item.enTitle }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
current: 0,
steps: [
{ step: 0, zh: '基本信息', en: 'Personal Nutrition Survey' },
{ step: 1, zh: '营养目标', en: 'Nutritional Goal' },
{ step: 2, zh: '生活习惯', en: 'Living habits' },
{ step: 3, zh: '身体状况', en: 'Physical condition' },
],
}
},
onLoad(arg) {
this.current = parseInt(arg.step)
import { mapState } from 'vuex'
export default {
data() {
return {
current: 0,
}
},
computed: {
...mapState(['paperInfo']),
steps() {
console.log('paperInfo', this.paperInfo)
const { category } = this.paperInfo
return category?.map?.(item => {
const { id, title, enTitle } = item
setTimeout(() => {
this.$utils.redirectTo(`/pages_order/report/test/answer?step=${this.current}`)
}, 1500)
return { id, title, enTitle }
})
},
},
onLoad(arg) {
console.log('onLoad', arg)
this.current = parseInt(arg.step || 0)
console.log('current', this.current)
setTimeout(() => {
this.$utils.redirectTo(`/pages_order/report/test/answer?step=${this.current}`)
}, 1500)
}
}
}
</script>
<style scoped lang="scss">


+ 43
- 0
store/store.js View File

@ -15,6 +15,7 @@ const store = new Vuex.Store({
payOrderProduct: [], //支付订单中的商品applyServiceProduct
applyServiceProduct: [], // 售后服务商品
addressInfo: null,
paperInfo: null,
},
getters: {
// 角色 true为水洗店 false为酒店 : 身份判断如果不需要,可以删除
@ -131,6 +132,9 @@ const store = new Vuex.Store({
setApplyServiceProduct(state, data) {
state.applyServiceProduct = data
},
setPaperInfo(state, data) {
state.paperInfo = data
},
},
actions: {
async addCart(state, data) {
@ -157,6 +161,45 @@ const store = new Vuex.Store({
title: '成功加入购物车',
});
return true
} catch (err) {
console.log('addCart err', err)
return false
}
},
async addCartBatch(state, arr) {
console.log('addCartBatch', arr)
try {
const { id: productId, specId, specs } = data
let skuId
if (specId) {
skuId = specId
} else {
let arr = specs?.length ? specs : await fetch('getProductSpec', { productId })
arr?.sort?.((a, b) => a.sortOrder - b.sortOrder)
skuId = arr?.[0]?.id
}
const list = arr.map(item => {
const { id: productId, specId, specs } = item
return {
productId,
skuId: specId || specs?.[0]?.id
}
})
await fetch('addCartBatch', { list: JSON.stringify(list) })
uni.showToast({
icon: 'success',
title: '成功加入购物车',
});
return true
} catch (err) {
console.log('addCart err', err)


Loading…
Cancel
Save