ykt-team-wxapp/pages/survey/components/survey-question.vue
2026-03-05 17:04:49 +08:00

197 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="h-full flex flex-col">
<view class="flex-shrink-0 px-10 pt-5 text-lg text-primary font-semibold text-center truncate">{{ survey.name }}
</view>
<view v-if="survey.description"
class="flex-shrink-0 px-10 mt-10 text-sm leading-normal text-gray line-clamp-2 text-center">
{{ survey.description }}
</view>
<view v-if="survey.enableScore" class="text-right mt-10 px-10 text-sm">
当前得分<span class="text-primary text-base font-semibold min-w-5 inline-block">{{ allScore }} </span>
</view>
<view v-if="quesiton" class="my-3 text-base font-semibold px-10 leading-normal ">
<span v-if="quesiton.require" class="text-xs text-danger">*</span> {{ index + 1 }}{{ quesiton.title }}
</view>
<view v-if="quesiton" class="flex-grow relative">
<scroll-view :scroll-y="true" class="absolute inset-0">
<view class="px-10">
<template v-if="quesiton.type === 'radio'">
<view v-for="(opt, idx) in quesiton.options" :key="opt.value" class="flex py-4 border-b "
:class="idx === 0 ? 'border-t' : ''" @click="changeRadio(opt.value)">
<uni-icons v-if="answer[quesiton.id] === opt.value" class="mr-2 text-primary flex-shrink-0" color=" "
type="checkbox-filled" size="24"></uni-icons>
<uni-icons v-else class="mr-2 text-gray flex-shrink-0" color=" " type="circle" size="24"></uni-icons>
<view class="flex-grow text-base leading-normal">
{{ opt.label }}
<span v-if="survey.enableScore && opt.score >= 0">
{{ opt.score }}
</span>
</view>
</view>
</template>
<textarea v-else-if="quesiton.type === 'input'" :value="answer[quesiton.id]" placeholder="请输入..."
placeholder-class="text-gray text-sm"
class="p-10 text-sm w-full box-border rounded border min-h-30 border-gray-200" @input="change($event)" />
</view>
</scroll-view>
</view>
<view v-if="list.length > 1" class="flex-shrink-0 py-3">
<view class="mx-3 bg-gray-100 h-3 rounded-lg overflow-hidden">
<view class="h-3 rounded-lg bg-primary" :style="{ width: `${progress}%` }"></view>
</view>
<view class="flex items-center justify-between px-10 py-15">
<view class="flex items-center text-primary" :class="index > 0 ? '' : 'opacity-0'" @click="prev()">
<uni-icons class="inline-block mr-10rpx" color=" " type="arrowleft"></uni-icons>
<span class="text-base font-semibold">上一题</span>
</view>
<!-- 当前得分40 -->
<view class="text-sm">{{ index + 1 }} / {{ list.length }}</view>
<view v-if="index < list.length - 1" class="flex items-center text-primary"
:class="index < list.length - 1 ? '' : 'opacity-0'" @click="next()">
<span class="text-base font-semibold">下一题</span>
<uni-icons class="inline-block ml-10rpx" color=" " type="arrowright"></uni-icons>
</view>
<view v-if="index === list.length - 1" class="flex items-center text-primary" @click="submit()">
<span class="text-base font-semibold">提交</span>
</view>
</view>
</view>
<view v-if="list.length === 1"
class="flex-shrink-0 mx-10 py-12 leading-normal text-white text-center rounded bg-primary" @click="submit()">
提交
</view>
</view>
</template>
<script>
import { toast } from '@/utils/widget'
export default {
name: 'Question',
props: {
list: { type: Array, default: () => ([]) },
survey: { type: Object, default: () => ({}) }
},
data() {
return {
index: 0,
answer: {},
waiting: false
}
},
computed: {
quesiton() {
return this.list[this.index]
},
progress() {
return (this.index + 1) / (this.list.length || 1) * 100
},
allScore() {
return this.list.reduce((score, item) => {
if (item.type === 'radio') {
const opt = item.options.find(i => i.value && i.value === this.answer[item.id])
if (opt && opt.score >= 0) {
score = Math.floor(score * 100 + opt.score * 100) / 100
}
}
return score
}, 0)
}
},
methods: {
change(e) {
this.$set(this.answer, this.quesiton.id, e.detail.value)
},
changeIndex(num) {
if (this.waiting) return;
this.waiting = true;
setTimeout(() => this.waiting = false, 1000);
const target = num + this.index;
this.index = target >= 0 && target < this.list.length ? target : this.index
},
changeRadio(value) {
this.$set(this.answer, this.quesiton.id, value)
},
prev() {
this.changeIndex(-1)
},
next() {
if (this.quesiton.require && this.quesiton.type === 'radio' && !this.quesiton.options.some(i => i.value && i.value === this.answer[this.quesiton.id])) {
toast('请完成当前题目')
} else if (this.quesiton.require && this.quesiton.type === 'input' && (typeof this.answer[this.quesiton.id] !== 'string' || this.answer[this.quesiton.id].trim() === '')) {
toast('请完成当前题目')
} else {
this.changeIndex(1)
}
},
verify() {
const index = this.list.findIndex(i => {
if (i.require && i.type === 'radio' && !i.options.some(j => j.value && j.value === this.answer[i.id])) {
return true
}
if (i.require && i.type === 'input' && (typeof this.answer[i.id] !== 'string' || this.answer[i.id].trim() === '')) {
return true
}
return false
})
if (index >= 0) {
toast('请完成第' + (index + 1) + '题')
this.index = index
return false
}
return true
},
submit() {
if (this.verify()) {
const list = this.list.map(i => {
const item = {
id: i.id,
value: this.answer[i.id] || '',
title: i.title,
type: i.type,
require: i.require,
}
if (Array.isArray(i.options)) {
item.options = i.options.map(opt => ({ ...opt }))
}
return item
})
this.$emit('submit', { list, score: this.allScore })
}
}
}
}
</script>
<style lang="scss" scoped>
.pt-5 {
padding-top: 40rpx;
}
.min-w-5 {
min-width: 40rpx;
}
.inline-block {
display: inline-block;
}
.my-3 {
margin-top: 24rpx;
margin-bottom: 24rpx;
}
.py-4 {
padding-top: 30rpx;
padding-bottom: 30rpx;
}
.border-t {
border-top: 1px solid #eee;
}
.min-h-30 {
min-height: 200rpx;
}
</style>