ykt-team-wxapp/pages/survey/components/survey-question.vue

197 lines
6.6 KiB
Vue
Raw Normal View History

2026-02-28 11:18:20 +08:00
<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"
2026-03-05 17:04:49 +08:00
class="flex-shrink-0 mx-10 py-12 leading-normal text-white text-center rounded bg-primary" @click="submit()">
2026-02-28 11:18:20 +08:00
提交
</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>