普兆健康管家前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

654 lines
18 KiB

<template>
<view class="page__view">
<navbar :title="`问题 ${current + 1}/${total}`" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="transparent" />
<template v-if="currentQuestion">
<view class="bar">
<view class="flex tools">
<view class="left">
<button class="flex btn" v-if="showPreBtn" @click="pre">
<image class="btn-icon" src="@/pages_order/static/report/icon-wrapper.png" mode="widthFix"></image>
<text>上一题</text>
</button>
</view>
<view class="right">
选择你的答案进入下一题
</view>
</view>
<view class="progress">
<view class="progress-bar" :style="{ width: `${progress}%` }"></view>
</view>
</view>
<view class="main">
<view class="question">{{ currentQuestion.question }}</view>
<template v-if="currentQuestion.component === 'select'">
<view class="select">
<view
v-for="item in currentQuestion.options"
:key="item.id"
:class="['flex', 'select-option', item.id === value ? 'is-active' : '']"
@click="onSelect(item.id)"
>
{{ item.content }}
</view>
</view>
</template>
<template v-else-if="currentQuestion.component === 'select-multiple'">
<view class="select">
<view
v-for="item in currentQuestion.options"
:key="item.id"
:class="['flex', 'select-option', value.includes(item.id) ? 'is-active' : '']"
@click="onSelectMulitple(item.id)"
>
{{ item.content }}
</view>
</view>
</template>
<template v-else-if="currentQuestion.component === 'select-box'">
<view class="select-box">
<view
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)"
>
<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="currentQuestion.component === 'select-box-multiple'">
<view class="select-box">
<view
v-for="item in currentQuestion.options"
:key="item.id"
:class="['flex', 'flex-column', 'select-box-option', value.includes(item.id) ? 'is-active' : '']"
@click="onSelectMulitple(item.id)"
>
<image class="img" :src="value.includes(item.id) ? item.imageAfter : item.image" mode="aspectFit"></image>
<view class="text">{{ item.content }}</view>
</view>
</view>
</template>
<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" :type="currentQuestion.options[0].inputType || 'text'" 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="currentQuestion.options[0].suffix">{{ currentQuestion.options[0].suffix }}</view>
</view>
</view>
</template>
<template v-else-if="currentQuestion.component === 'input-group'">
<view class="input">
<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]" :type="currentQuestion.options[index].inputType || 'text'" 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.suffix">{{ item.suffix }}</view>
</view>
</view>
</template>
<view class="flex btns">
<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="currentQuestion.desc">
<uv-parse :content="currentQuestion.desc"></uv-parse>
</view>
</view>
</template>
</view>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
step: 0,
current: null,
tabs: [],
value: null,
noneFlag: false,
answers: [],
// tags: [],
questionsList: [],
total: 0,
}
},
computed: {
...mapState(['paperInfo', 'paperTags']),
preIdx() {
let index = this.tabs.findIndex(index => index === this.current)
return index - 1
},
showPreBtn() {
return this.preIdx >= 0
},
progress() {
return 100 * (this.current + 1) / this.total
},
showSubmitBtn() {
return this.current + 1 === this.total
},
currentQuestion() {
return this.questionsList[this.current]
},
showConfirmBtn() {
if (this.showSubmitBtn) {
return false
}
return ['select-box-multiple', 'select-multiple','input', 'input-group'].includes(this.currentQuestion?.component)
},
},
onLoad(arg) {
this.step = parseInt(arg.step)
// this.current = parseInt(arg.current || 0)
console.log('paperTags', this.paperTags)
this.fetchQuestionList()
this.switchQuestion(parseInt(arg.current || 0))
this.value = this.answers[this.current]
console.log('currentQuestion', this.currentQuestion)
},
methods: {
fetchQuestionList() {
const { category: categoryList } = this.paperInfo
const category = categoryList[this.step]
const { questionsList } = category
this.questionsList = questionsList.map(item => {
const { id, text, type, options, needTag, required, multiple: _multiple, content } = item
let component = 'select'
const multiple = _multiple == 'Y'
switch(type) { // 0-单选题 1-填空题 2-图片单选题
case '1':
component = options?.length > 1 ? 'input-group' : 'input'
break
case '2':
component = multiple ? 'select-box-multiple' : 'select-box'
break
default:
component = multiple ? 'select-multiple' : 'select'
break
}
return {
id,
question: text,
type,
component,
options,
needTag: needTag ? JSON.parse(needTag).map(config => config.tags).filter(tags => tags.length) : null,
required: required == 'Y',
multiple,
desc: content,
}
})
this.total = this.questionsList.length
this.answers = this.questionsList.map(item => {
let val = null
switch (item.component) {
case 'select-box-multiple':
case 'select-multiple':
val = []
break
case 'input-group':
val = new Array(item.options.length).fill(0).map(() => '')
break;
default:
val = null
break;
}
return val
})
// this.tags = this.paperTags[this.step]
// this.value = this.answers[this.current]
console.log('questionsList', this.questionsList)
console.log('answers', this.answers)
},
switchQuestion(current) {
let { needTag } = this.questionsList[current]
if (!needTag) {
this.current = current
this.tabs.push(this.current)
return
}
const selectTags = this.paperTags.flat(2)
console.log('selectTags', selectTags)
console.log('needTag', needTag)
let valid = needTag.some(tags => {
return tags.every(tag => {
const { value, exclude } = tag
let include = selectTags.includes(value)
return exclude ? !include : include
})
})
console.log('valid', valid)
if (valid) {
this.current = current
this.tabs.push(this.current)
return
}
if (current + 1 < this.questionsList.length) {
current += 1
this.switchQuestion(current)
return
}
this.$utils.redirectTo(`/pages_order/report/test/step?step=${this.step + 1}`)
},
pre() {
// this.current -= 1
this.current = this.preIdx
this.value = this.answers[this.current]
this.noneFlag = false
},
next() {
if (this.showSubmitBtn) {
return
}
// this.current += 1
this.switchQuestion(this.current + 1)
this.value = this.answers[this.current]
this.noneFlag = false
},
async fetchAnswer() {
try {
const { id: questionsId, type, required, multiple, 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?.length || 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 if (multiple && Array.isArray(this.value)) { // 多选题
answer = this.value.join(',')
}
let params = {
id: this.paperInfo.reportId,
questionsId,
answer
}
console.log('params', params)
await this.$fetch('answerPaper', params)
this.answers[this.current] = this.value
let paperTags = [...this.paperTags]
if (type == 1) {
paperTags[this.step][this.current] = []
} else {
let selectedIdArr = Array.isArray(this.value) ? this.value : [this.value]
console.log('selectedIdArr', selectedIdArr, 'options', options)
paperTags[this.step][this.current] = selectedIdArr.reduce((arr, id) => {
const { settingTag } = options.find(item => item.id === id) || {}
if (settingTag) {
const tags = settingTag?.split?.(',')
tags?.length && (arr = arr.concat(tags))
}
return arr
}, [])
}
console.log('paperTags', paperTags)
this.$store.commit('setPaperTags', paperTags)
return true
} catch (err) {
console.log('answerPaper', err)
return false
}
},
async onSelect(id) {
this.value = id
if (this.showSubmitBtn) {
return
}
let succ = await this.fetchAnswer()
if (!succ) {
return
}
this.$nextTick(() => {
setTimeout(() => {
this.next()
}, 500)
})
},
async onSelectMulitple(id) {
this.value = this.value.includes(id) ? this.value.filter(item => item !== id) : this.value.concat(id)
},
async onConfirm() {
let succ = await this.fetchAnswer()
if (!succ) {
return
}
this.next()
},
async onSelectNone() {
this.noneFlag = true
let val = null
switch (this.currentQuestion.component) {
case 'select-box-multiple':
case 'select-multiple':
val = []
break
case 'input-group':
val = new Array(this.currentQuestion.options.length).fill(0).map(() => '')
break;
default:
val = null
break;
}
this.value = val
let succ = await this.fetchAnswer()
if (!succ) {
return
}
this.next()
},
async onSubmit() {
let succ = await this.fetchAnswer()
if (!succ) {
return
}
const { category } = this.paperInfo
if (this.step == category.length - 1) {
await this.$fetch('submitPaper', { id: this.paperInfo.reportId })
uni.reLaunch({
url: '/pages/index/report'
})
return
}
this.$utils.redirectTo(`/pages_order/report/test/step?step=${this.step + 1}`)
},
},
}
</script>
<style scoped lang="scss">
.page__view {
width: 100vw;
min-height: 100vh;
background-color: $uni-bg-color;
position: relative;
}
.bar {
margin-top: 24rpx;
width: 100%;
padding: 0 32rpx;
box-sizing: border-box;
.left {
text-align: left;
.btn {
display: inline-flex;
font-family: PingFang SC;
font-weight: 400;
font-size: 30rpx;
line-height: 1.4;
color: #7451DE;
&-icon {
width: 32rpx;
height: auto;
margin-right: 8rpx;
}
}
}
.right {
flex: 1;
text-align: right;
font-family: PingFang SC;
font-weight: 400;
font-size: 26rpx;
line-height: 1.4;
color: #989898;
}
}
.progress {
margin-top: 24rpx;
width: 100%;
height: 24rpx;
background: #E5E5E5;
border-radius: 12rpx;
&-bar {
height: 100%;
background-image: linear-gradient(to right, #4B348F, #845CFA);
border-radius: 12rpx;
}
}
.main {
width: 100%;
padding: 70rpx 104rpx 0 104rpx;
box-sizing: border-box;
}
.question {
text-align: center;
font-family: PingFang SC;
font-weight: 400;
font-size: 40rpx;
line-height: 1.4;
color: #000000;
margin-bottom: 64rpx;
}
.select {
&-option {
padding: 30rpx 0;
font-family: PingFang SC;
font-weight: 400;
line-height: 1.4;
font-size: 32rpx;
color: #252545;
background-image: linear-gradient(to right, #FFFFFF, #F4F4F6, #F2F2F4);
border-radius: 52rpx;
box-shadow: -2rpx -2rpx 10rpx 0 #FFFFFFC4,
4rpx 4rpx 20rpx 0 #AAAACC1F,
2rpx 2rpx 4rpx 0 #AAAACC40,
-1rpx -1rpx 2rpx 0 #FFFFFF;
& + & {
margin-top: 40rpx;
}
&.is-active {
color: #FFFFFF;
background: #7451DE;
}
}
}
.select-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32rpx;
&-option {
padding: 24rpx 0;
border-radius: 24rpx;
background-image: linear-gradient(to right, #FFFFFF, #F2F2F4);
box-shadow: -2rpx -2rpx 10rpx 0 #FFFFFFC4,
4rpx 4rpx 20rpx 0 #AAAACC1F,
2rpx 2rpx 4rpx 0 #AAAACC40,
-1rpx -1rpx 2rpx 0 #FFFFFF;
.img {
width: 100%;
height: 64rpx;
}
.text {
margin-top: 8rpx;
font-family: PingFang SC;
font-weight: 400;
font-size: 28rpx;
line-height: 1.4;
color: #969696;
}
&.is-active {
background: #7451DE;
.text {
color: #FFFFFFC4;
}
}
}
}
.input {
&-box {
width: 100%;
height: 104rpx;
padding: 0 40rpx;
box-sizing: border-box;
border-radius: 52rpx;
background-image: linear-gradient(to right, #FFFFFF, #F4F4F6, #F2F2F4);
box-shadow: -2rpx -2rpx 10rpx 0 #FFFFFFC4,
4rpx 4rpx 20rpx 0 #AAAACC1F,
2rpx 2rpx 4rpx 0 #AAAACC40,
-1rpx -1rpx 2rpx 0 #FFFFFF;
& + & {
margin-top: 40rpx;
}
}
&-doc {
text-align: center;
flex: 1;
}
&-label {
padding: 21rpx 40rpx 21rpx 0;
font-family: PingFang SC;
font-weight: 600;
font-size: 30rpx;
line-height: 1.4;
color: #252545;
border-right: 2rpx solid #E0DFF0;
}
&-unit {
padding: 21rpx 0 21rpx 40rpx;
font-family: PingFang SC;
font-weight: 600;
font-size: 30rpx;
line-height: 1.4;
color: #252545;
border-left: 2rpx solid #E0DFF0;
}
}
.btns {
margin-top: 64rpx;
column-gap: 36rpx;
.btn {
flex: 1;
padding: 24rpx 0;
font-family: PingFang SC;
font-weight: 600;
font-size: 30rpx;
line-height: 1.4;
color: #FFFFFF;
background-image: linear-gradient(to right, #4B348F, #845CFA);
border-radius: 45rpx;
&-palin {
padding: 22rpx 0;
font-weight: 400;
color: #252545;
background: transparent;
border: 2rpx solid #252545;
&.is-active {
color: $uni-color;
border-color: $uni-color;
}
}
}
}
.desc {
margin-top: 220rpx;
font-family: PingFang SC;
font-weight: 400;
line-height: 1.4;
font-size: 26rpx;
color: #989898;
}
</style>