Files
property-resident-side/property-uniapp-project/pageSubPack/service-list/questionnaireFillout.vue
2026-05-11 17:29:57 +08:00

402 lines
8.2 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="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="问卷调查" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y>
<view class="content-wrap">
<!-- 问卷标题 -->
<view class="survey-title">{{ detailItem.title || '' }}</view>
<!-- 问卷说明 -->
<view class="survey-desc">{{ detailItem.description || '' }}</view>
<!-- 空状态 -->
<view v-if="questionList.length === 0" class="empty-tip">
<text>问卷内容加载中请稍后...</text>
</view>
<!-- 动态问题列表 -->
<view v-for="(q, idx) in questionList" :key="q.cid" class="question-item">
<view class="question-title">
<text v-if="q.required" class="required">*</text>
<text>{{ idx + 1 }}.{{ q.title || '未命名问题' }}</text>
</view>
<!-- 单选 -->
<view v-if="q.type === 'radio'" class="radio-group">
<view
v-for="(opt, oIdx) in q.options"
:key="oIdx"
class="radio-item"
:class="{ checked: answers[q.cid] === opt.label }"
@click="selectRadio(q.cid, opt.label)"
>
<view class="radio-circle" :class="{ active: answers[q.cid] === opt.label }">
<text v-if="answers[q.cid] === opt.label" class="check-icon"></text>
</view>
<text class="radio-label">{{ opt.label }}</text>
</view>
</view>
<!-- 多选 -->
<view v-if="q.type === 'checkbox'" class="checkbox-group">
<view
v-for="(opt, oIdx) in q.options"
:key="oIdx"
class="checkbox-item"
:class="{ checked: isChecked(q.cid, opt.label) }"
@click="toggleCheckbox(q.cid, opt.label)"
>
<view class="checkbox-box" :class="{ active: isChecked(q.cid, opt.label) }">
<text v-if="isChecked(q.cid, opt.label)" class="check-icon"></text>
</view>
<text class="checkbox-label">{{ opt.label }}</text>
</view>
</view>
<!-- 单行输入 -->
<view v-if="q.type === 'input'">
<input
class="text-input"
v-model="answers[q.cid]"
placeholder="请输入"
/>
</view>
<!-- 多行输入 -->
<view v-if="q.type === 'textarea'">
<textarea
class="textarea-input"
v-model="answers[q.cid]"
placeholder="请输入"
maxlength="500"
/>
</view>
</view>
</view>
</scroll-view>
<!-- 固定底部提交按钮 -->
<view class="submit-btn-wrap">
<button class="submit-btn" @click="handleSubmit">提交</button>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getQuestionnaireDetail, submitQuestionnair } from '../api/apiSub.js'
const detailItem = ref({})
const questionList = computed(() => {
const content = detailItem.value?.content
if (!content) return []
try {
const list = JSON.parse(content)
return Array.isArray(list) ? list : []
} catch (e) {
return []
}
})
const answers = ref({})
// 获取问卷调查
const getDetailData = async (id) => {
try {
const res = await getQuestionnaireDetail({ id })
// console.log('接口返回:', JSON.stringify(res))
if (res.code === 200) {
const data = Array.isArray(res.data) ? res.data[0] : res.data
detailItem.value = data || {}
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
}
onLoad((option) => {
if (option.id) {
getDetailData(option.id)
} else {
uni.showToast({ title: '缺少问卷ID', icon: 'none' })
}
})
const selectRadio = (cid, label) => {
answers.value[cid] = label
}
const isChecked = (cid, label) => {
const arr = answers.value[cid]
if (!Array.isArray(arr)) return false
return arr.includes(label)
}
const toggleCheckbox = (cid, label) => {
if (!Array.isArray(answers.value[cid])) {
answers.value[cid] = []
}
const arr = answers.value[cid]
const pos = arr.indexOf(label)
if (pos > -1) {
arr.splice(pos, 1)
} else {
arr.push(label)
}
}
const handleSubmit = async () => {
for (const q of questionList.value) {
if (q.required) {
const ans = answers.value[q.cid]
if (ans === undefined || ans === null || ans === '' || (Array.isArray(ans) && ans.length === 0)) {
uni.showToast({ title: `请填写第${questionList.value.indexOf(q) + 1}`, icon: 'none' })
return
}
}
}
const answerList = []
for (const q of questionList.value) {
const val = answers.value[q.cid]
if (val !== undefined && val !== null && val !== '' && !(Array.isArray(val) && val.length === 0)) {
answerList.push({ cid: q.cid, value: val })
}
}
uni.showLoading({ title: '提交中...', mask: true })
try {
const res = await submitQuestionnair(detailItem.value.questionnaireId, answerList)
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
setTimeout(() => { goBack() }, 1500)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (err) {
console.log('errrr',err)
uni.hideLoading()
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
}
const goBack = () => {
uni.navigateBack()
}
</script>
<style lang="scss">
page {
background: #f2f1f6;
}
</style>
<style scoped lang="scss">
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
}
.content-wrap {
padding-bottom: 40rpx;
}
.survey-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
text-align: center;
margin: 0px 0 20rpx 0;
}
.survey-desc {
font-size: 24rpx;
color: #4A4A4A;
line-height: 1.6;
margin-bottom: 20rpx;
text-align: justify;
}
.empty-tip {
text-align: center;
padding: 100rpx 0;
font-size: 28rpx;
color: #999;
}
.question-item {
margin-bottom: 30rpx;
.question-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 10rpx;
line-height: 1.4;
.required {
color: #ff3b30;
font-size: 36rpx;
margin-right: 8rpx;
}
}
}
/* 单选 */
.radio-group {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-left: 10rpx;
}
.radio-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx 0;
.radio-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s;
&.active {
background-color: #007aff;
border-color: #007aff;
}
}
.check-icon {
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
.radio-label {
font-size: 32rpx;
color: #333;
}
}
/* 多选 */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-left: 10rpx;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx 0;
.checkbox-box {
width: 40rpx;
height: 40rpx;
border-radius: 8rpx;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s;
&.active {
background-color: #007aff;
border-color: #007aff;
}
}
.check-icon {
color: #fff;
font-size: 22rpx;
font-weight: bold;
}
.checkbox-label {
font-size: 32rpx;
color: #333;
}
}
/* 文本输入 */
.text-input {
width: calc(100% - 30rpx);
height: 80rpx;
padding: 0 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
margin-left: 30rpx;
}
.textarea-input {
width: calc(100% - 30rpx);
min-height: 240rpx;
padding: 20rpx;
border: 2rpx solid #e5e5e5;
border-radius: 8rpx;
background-color: #fff;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
margin-left: 30rpx;
}
/* 提交按钮 */
.submit-btn-wrap {
padding: 20rpx 30rpx 40rpx;
.submit-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background-color: #007aff;
color: #fff;
font-size: 36rpx;
font-weight: 500;
border-radius: 12rpx;
border: none;
padding: 0;
margin: 0;
&::after {
border: none;
}
}
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>