survey-questionnaire.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <template>
  2. <view class="container">
  3. <view class="header-card">
  4. <text class="header-title">{{ templateTitle }}</text>
  5. <text class="header-dimension" v-if="templateDimension">适用维度: {{ templateDimension }}</text>
  6. <text class="header-period" v-if="periodStart || periodEnd">周期: {{ periodStart && formatDate(periodStart) }} - {{ periodEnd && formatDate(periodEnd) }}</text>
  7. </view>
  8. <view class="question-section" v-for="(q, qIdx) in questions" :key="getQuestionKey(q)">
  9. <view class="question-header">
  10. <text class="question-num">Q{{ qIdx + 1 }}</text>
  11. <text class="question-text">{{ q.text }}</text>
  12. </view>
  13. <view class="options-list">
  14. <view class="option-item" :class="answers[qIdx] === opt ? 'option-selected' : ''" v-for="(opt, oIdx) in q.options" :key="getOptionKey(q, opt)" @click="selectOption(qIdx, opt)">
  15. <view class="option-radio" :class="answers[qIdx] === opt ? 'radio-checked' : ''"></view>
  16. <text class="option-text">{{ opt }}</text>
  17. </view>
  18. </view>
  19. </view>
  20. <view class="footer-area">
  21. <button class="btn-submit" :disabled="isSubmitting" @click="handleSubmit">{{ isSubmitting ? '提交中...' : '提交答案' }}</button>
  22. </view>
  23. </view>
  24. </template>
  25. <script>
  26. import { getActiveTemplates, submitSurvey } from '../../utils/api.js'
  27. export default {
  28. data() { return { templateId: null, templateTitle: '定期调研', templateDimension: '', questions: [], answers: {}, periodStart: '', periodEnd: '', isSubmitting: false } },
  29. onLoad(options) {
  30. if (options.templateId) this.templateId = parseInt(options.templateId)
  31. if (options.templateTitle) this.templateTitle = decodeURIComponent(options.templateTitle)
  32. if (options.templateDimension) this.templateDimension = options.templateDimension
  33. if (options.periodStart) this.periodStart = options.periodStart
  34. if (options.periodEnd) this.periodEnd = options.periodEnd
  35. if (options.questions) { try { this.questions = JSON.parse(decodeURIComponent(options.questions)) } catch(e) { this.questions = [] } }
  36. if (this.templateId && !this.questions.length) this.loadTemplate()
  37. },
  38. methods: {
  39. getQuestionKey(q) { return q && q.text ? q.text : 'q-' + Math.random() },
  40. getOptionKey(q, opt) { return q && q.text ? q.text + '-' + opt : 'opt-' + Math.random() },
  41. formatDate(s) { return s ? s.substring(0, 10) : '' },
  42. selectOption(qIdx, opt) { this.$set(this.answers, qIdx, opt) },
  43. async loadTemplate() {
  44. try {
  45. var res = await getActiveTemplates()
  46. if (res.code === 200 && res.data) {
  47. for (var i = 0; i < res.data.length; i++) {
  48. var t = res.data[i]
  49. if (t && t.id === this.templateId) {
  50. this.templateTitle = t.title || '定期调研'
  51. this.templateDimension = t.dimension || ''
  52. if (t.questionsJson) { try { this.questions = JSON.parse(t.questionsJson) } catch(e) {} }
  53. break
  54. }
  55. }
  56. }
  57. } catch(e) { console.error('加载模板失败', e) }
  58. },
  59. async handleSubmit() {
  60. var self = this
  61. var ui = uni.getStorageSync('userInfo') || {}
  62. var memberId = ui.currentMemberId || ui.memberId
  63. var answersArr = []
  64. for (var i = 0; i < this.questions.length; i++) answersArr.push(this.answers[i] || '')
  65. var answersJson = JSON.stringify(answersArr)
  66. if (!answersJson) { uni.showToast({ title: '请至少选择一道题', icon: 'none' }); return }
  67. this.isSubmitting = true
  68. try {
  69. var d = { memberId: memberId, templateId: this.templateId, answersJson: answersJson }
  70. if (this.periodStart) d.periodStart = this.periodStart
  71. if (this.periodEnd) d.periodEnd = this.periodEnd
  72. var res = await submitSurvey(d)
  73. if (res.code === 200) { uni.showToast({ title: '提交成功', icon: 'success' }); setTimeout(function(){ uni.navigateTo({ url: '/pages/health/survey-history?memberId=' + (memberId || '') }) }, 1200) }
  74. } catch(e) { uni.showToast({ title: e.message || '提交失败', icon: 'none' }) }
  75. finally { this.isSubmitting = false }
  76. }
  77. }
  78. }
  79. </script>
  80. <style scoped>
  81. .container{background:#FFF7ED;min-height:100vh;padding-bottom:120rpx}
  82. .header-card{background:#fff;margin:20rpx 24rpx;border-radius:20rpx;padding:28rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  83. .header-title{font-size:32rpx;font-weight:bold;color:#333;display:block;margin-bottom:8rpx}
  84. .header-dimension{font-size:24rpx;color:#999;display:block;margin-bottom:4rpx}
  85. .header-period{font-size:24rpx;color:#F97316;display:block}
  86. .question-section{background:#fff;margin:16rpx 24rpx;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  87. .question-header{display:flex;align-items:flex-start;margin-bottom:16rpx}
  88. .question-num{font-size:24rpx;font-weight:bold;color:#F97316;margin-right:12rpx;flex-shrink:0}
  89. .question-text{font-size:26rpx;color:#333;flex:1}
  90. .option-item{display:flex;align-items:center;padding:16rpx 0;border-bottom:1rpx solid #f0f0f0}
  91. .option-item:last-child{border-bottom:none}
  92. .option-radio{width:28rpx;height:28rpx;border-radius:50%;border:2rpx solid #ddd;margin-right:16rpx;flex-shrink:0}
  93. .radio-checked{border-color:#F97316;background:#F97316}
  94. .option-text{font-size:26rpx;color:#333}
  95. .option-selected .option-text{color:#F97316;font-weight:bold}
  96. .footer-area{padding:20rpx 24rpx}
  97. .btn-submit{background:#F97316;color:#fff;border:none;border-radius:16rpx;font-size:30rpx;font-weight:bold;height:80rpx;line-height:80rpx}
  98. .btn-submit[disabled]{background:#ffb47a}
  99. </style>