survey.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <template>
  2. <view class="sv-wrap">
  3. <view class="sv-header">
  4. <text class="sv-title">专项调研</text>
  5. <text class="sv-sub">回答以下问题,我们为你推荐最适合的方案</text>
  6. </view>
  7. <view class="sv-loading" v-if="loading">加载中...</view>
  8. <view class="sv-empty" v-else-if="!questions || questions.length === 0">暂无问卷题目</view>
  9. <view class="sv-form" v-else>
  10. <view class="sv-q" v-for="(q, qi) in questions" :key="getQKey(qi)">
  11. <text class="sv-q-label">{{ qi + 1 }}. {{ q.text }}</text>
  12. <view class="sv-options">
  13. <view
  14. class="sv-opt"
  15. v-for="(o, oi) in q.options"
  16. :key="getOptKey(qi, oi)"
  17. :class="{ 'sv-opt-active': isSelected(qi, oi) }"
  18. @click="toggleOpt(qi, oi, q)">
  19. <text class="sv-opt-text">{{ o.text }}</text>
  20. <text class="sv-opt-check" v-if="isSelected(qi, oi)">✓</text>
  21. </view>
  22. </view>
  23. </view>
  24. <view class="sv-btn" @click="submitSurvey">
  25. <text class="sv-btn-text">提交调研</text>
  26. </view>
  27. </view>
  28. </view>
  29. </template>
  30. <script>
  31. import { submitProblemSurvey, getProblemSurveyQuestions } from '../../utils/api.js'
  32. export default {
  33. data() {
  34. return {
  35. code: '',
  36. templateId: '',
  37. questions: [],
  38. answers: [],
  39. loading: false,
  40. submitting: false
  41. }
  42. },
  43. onLoad(options) {
  44. this.code = (options && options.code) || ''
  45. this.templateId = (options && options.templateId) || ''
  46. this.loadQuestions()
  47. },
  48. methods: {
  49. getQKey: function(qi) { return 'q' + qi },
  50. getOptKey: function(qi, oi) { return 'o' + qi + '_' + oi },
  51. getOptId: function(q, oi) {
  52. var opt = q.options && q.options[oi]
  53. return opt ? (opt.id != null ? opt.id : oi) : oi
  54. },
  55. isSelected(qi, oi) {
  56. var ans = this.answers[qi]
  57. if (!ans) return false
  58. var q = this.questions[qi]
  59. return ans.indexOf(this.getOptId(q, oi)) !== -1
  60. },
  61. toggleOpt(qi, oi, q) {
  62. var optId = this.getOptId(q, oi)
  63. var isMulti = q.multi !== false
  64. if (!this.answers[qi]) {
  65. this.answers[qi] = []
  66. }
  67. var arr = this.answers[qi]
  68. var idx = arr.indexOf(optId)
  69. if (idx !== -1) {
  70. arr.splice(idx, 1)
  71. } else {
  72. if (isMulti) {
  73. arr.push(optId)
  74. } else {
  75. this.answers[qi] = [optId]
  76. }
  77. }
  78. this.$forceUpdate()
  79. },
  80. loadQuestions() {
  81. var self = this
  82. self.loading = true
  83. uni.showLoading({ title: '加载中...', mask: true })
  84. // 优先按问题域 code 拉取问卷(后端未绑定模板时返回内置默认问卷)
  85. if (self.code) {
  86. getProblemSurveyQuestions(self.code).then(function(res) {
  87. uni.hideLoading()
  88. self.loading = false
  89. var data = res && res.data
  90. if (data && Array.isArray(data.questions) && data.questions.length > 0) {
  91. self.questions = data.questions
  92. if (data.templateId) self.templateId = data.templateId
  93. } else {
  94. self.questions = self.getFallbackQuestions()
  95. }
  96. }).catch(function() {
  97. uni.hideLoading()
  98. self.loading = false
  99. self.questions = self.getFallbackQuestions()
  100. })
  101. } else {
  102. uni.hideLoading()
  103. self.loading = false
  104. self.questions = self.getFallbackQuestions()
  105. }
  106. },
  107. getFallbackQuestions() {
  108. return [
  109. { id: 'q1', text: '请简单描述你的情况', multi: false, options: [{ id: 'o1', text: '了解中' }, { id: 'o2', text: '已有初步了解' }] },
  110. { id: 'q2', text: '你希望获得什么帮助?', multi: true, options: [{ id: 'o1', text: '了解方案' }, { id: 'o2', text: '购买产品' }, { id: 'o3', text: '咨询专家' }] }
  111. ]
  112. },
  113. submitSurvey() {
  114. var self = this
  115. // 检查是否至少答了一题
  116. var hasAnswer = false
  117. for (var i = 0; i < this.answers.length; i++) {
  118. if (this.answers[i] && this.answers[i].length > 0) {
  119. hasAnswer = true
  120. break
  121. }
  122. }
  123. if (!hasAnswer) {
  124. uni.showToast({ title: '请至少回答一题', icon: 'none' })
  125. return
  126. }
  127. if (self.submitting) return
  128. self.submitting = true
  129. uni.showLoading({ title: '提交中...', mask: true })
  130. // 统一契约: [{questionId, optionIds}]
  131. var answerList = []
  132. for (var qi = 0; qi < this.questions.length; qi++) {
  133. var q = this.questions[qi]
  134. var selected = this.answers[qi] || []
  135. if (selected.length > 0) {
  136. answerList.push({
  137. questionId: q.id != null ? q.id : ('q' + (qi + 1)),
  138. optionIds: selected
  139. })
  140. }
  141. }
  142. submitProblemSurvey({
  143. problemDomainCode: self.code,
  144. surveyTemplateId: self.templateId || undefined,
  145. answers: answerList
  146. }).then(function(res) {
  147. uni.hideLoading()
  148. self.submitting = false
  149. uni.showToast({ title: '提交成功', icon: 'success' })
  150. setTimeout(function() {
  151. uni.navigateTo({
  152. url: '/pages/problem/package-match?code=' + self.code
  153. })
  154. }, 500)
  155. }).catch(function() {
  156. uni.hideLoading()
  157. self.submitting = false
  158. uni.showToast({ title: '提交失败,请重试', icon: 'none' })
  159. })
  160. }
  161. }
  162. }
  163. </script>
  164. <style scoped>
  165. .sv-wrap {
  166. min-height: 100vh;
  167. background: #FFF7ED;
  168. padding: 40rpx 32rpx 120rpx;
  169. box-sizing: border-box;
  170. }
  171. .sv-header {
  172. margin-bottom: 32rpx;
  173. }
  174. .sv-title {
  175. display: block;
  176. font-size: 38rpx;
  177. font-weight: bold;
  178. color: #333;
  179. text-align: center;
  180. }
  181. .sv-sub {
  182. display: block;
  183. font-size: 26rpx;
  184. color: #999;
  185. text-align: center;
  186. margin-top: 10rpx;
  187. }
  188. .sv-loading, .sv-empty {
  189. text-align: center;
  190. font-size: 28rpx;
  191. color: #999;
  192. padding: 80rpx 0;
  193. }
  194. .sv-q {
  195. margin-bottom: 36rpx;
  196. }
  197. .sv-q-label {
  198. display: block;
  199. font-size: 30rpx;
  200. font-weight: 600;
  201. color: #333;
  202. margin-bottom: 16rpx;
  203. }
  204. .sv-options {
  205. display: flex;
  206. flex-wrap: wrap;
  207. gap: 16rpx;
  208. }
  209. .sv-opt {
  210. display: flex;
  211. align-items: center;
  212. background: #fff;
  213. border: 2rpx solid #E5E7EB;
  214. border-radius: 16rpx;
  215. padding: 18rpx 24rpx;
  216. margin-right: 16rpx;
  217. margin-bottom: 12rpx;
  218. }
  219. .sv-opt-active {
  220. border-color: #F97316;
  221. background: #FFF7ED;
  222. }
  223. .sv-opt-text {
  224. font-size: 28rpx;
  225. color: #333;
  226. }
  227. .sv-opt-check {
  228. color: #F97316;
  229. font-size: 28rpx;
  230. font-weight: bold;
  231. margin-left: 8rpx;
  232. }
  233. .sv-btn {
  234. background: linear-gradient(135deg, #F97316, #FB923C);
  235. border-radius: 48rpx;
  236. padding: 26rpx 0;
  237. text-align: center;
  238. margin-top: 32rpx;
  239. }
  240. .sv-btn:active {
  241. opacity: 0.9;
  242. }
  243. .sv-btn-text {
  244. color: #fff;
  245. font-size: 32rpx;
  246. font-weight: 600;
  247. }
  248. </style>