风险测评小程序前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
12 KiB

  1. <template>
  2. <view class="page__view">
  3. <navbar title="答题测评" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" />
  4. <!-- 答题完成 -->
  5. <template v-if="current === total">
  6. <view class="flex main is-finish">
  7. <view class="card">
  8. <image class="card-bg" src="@/pages_order/static/test/bg-test-finsih.png" mode="widthFix"></image>
  9. <view class="flex flex-column card-content">
  10. <view class="text">恭喜你</view>
  11. <view class="text">您已完成所有测评题目</view>
  12. <button class="btn" @click="onCreateReport">生成报告</button>
  13. </view>
  14. </view>
  15. </view>
  16. </template>
  17. <!-- 答题中 -->
  18. <template v-else-if="currentQuestion">
  19. <view class="bar">
  20. <view class="flex info">
  21. <view>答题进度</view>
  22. <view>
  23. <text class="highlight">{{ current + 1 }}</text>
  24. <text>{{ `/${total}` }}</text>
  25. </view>
  26. </view>
  27. <view class="progress">
  28. <view class="progress-bar" :style="{ width: `${progress}%` }"></view>
  29. </view>
  30. </view>
  31. <view class="main">
  32. <view class="card">
  33. <view class="card-header">
  34. <view class="flex tips">
  35. <image class="icon" src="@/pages_order/static/test/icon-warning.png" mode="widthFix"></image>
  36. <view>请根据真实情况谨慎作答点击下一题提交答案无法更改</view>
  37. </view>
  38. <view class="question">{{ currentQuestion.question }}</view>
  39. </view>
  40. <view class="card-content">
  41. <template v-if="currentQuestion.component === 'select'">
  42. <view class="select">
  43. <view
  44. v-for="item in currentQuestion.options"
  45. :key="item.id"
  46. :class="['select-option', item.id === value ? 'is-active' : '']"
  47. @click="onSelect(item.id)"
  48. >
  49. {{ item.content }}
  50. </view>
  51. </view>
  52. </template>
  53. <template v-else-if="currentQuestion.component === 'select-multiple'">
  54. <view class="select">
  55. <view
  56. v-for="item in currentQuestion.options"
  57. :key="item.id"
  58. :class="['select-option', value.includes(item.id) ? 'is-active' : '']"
  59. @click="onSelectMulitple(item.id)"
  60. >
  61. {{ item.content }}
  62. </view>
  63. </view>
  64. </template>
  65. </view>
  66. </view>
  67. </view>
  68. <view class="bottom">
  69. <button v-if="isLast" class="btn" @click="finish">提交</button>
  70. <button v-else class="btn" @click="next">下一题</button>
  71. </view>
  72. </template>
  73. </view>
  74. </template>
  75. <script>
  76. import { mapState } from 'vuex'
  77. export default {
  78. data() {
  79. return {
  80. current: null,
  81. tabs: [],
  82. value: null,
  83. answers: [],
  84. paperTags: [],
  85. batchNo: null,
  86. questionsList: [],
  87. total: 0,
  88. }
  89. },
  90. computed: {
  91. preIdx() {
  92. let index = this.tabs.findIndex(index => index === this.current)
  93. return index - 1
  94. },
  95. progress() {
  96. return 100 * (this.current + 1) / this.total
  97. },
  98. isLast() {
  99. return this.progress === 100
  100. },
  101. currentQuestion() {
  102. return this.questionsList[this.current]
  103. },
  104. },
  105. onLoad(arg) {
  106. this.fetchQuestionList()
  107. this.switchQuestion(parseInt(arg.current || 0))
  108. this.value = this.answers[this.current]
  109. console.log('paperTags', this.paperTags)
  110. console.log('currentQuestion', this.currentQuestion)
  111. },
  112. methods: {
  113. async fetchQuestionList(categories) {
  114. const result = await this.$fetch('queryQuestionList', { categories })
  115. // todo: set batchNo
  116. // todo: transfer
  117. const questionsList = new Array(23).fill(1).map((item, index) => {
  118. const id = `00${index}`
  119. return {
  120. id,
  121. text: '问题内容文字问题内容文字问题内容文字问题内容文字问题内容文字?',
  122. type: '0',
  123. options: new Array(4).fill(1).map((option, oIdx) => {
  124. return {
  125. id: `${id}${oIdx}`,
  126. content: '答案内容',
  127. }
  128. }),
  129. }
  130. })
  131. this.questionsList = questionsList.map((item, index) => {
  132. const { id, text, type, options, needTag, required, multiple: _multiple, content } = item
  133. let component = 'select'
  134. const multiple = _multiple == 'Y'
  135. switch(type) { // 0-单选题 1-填空题 2-图片单选题
  136. case '1':
  137. component = options?.length > 1 ? 'input-group' : 'input'
  138. break
  139. case '2':
  140. component = multiple ? 'select-box-multiple' : 'select-box'
  141. break
  142. default:
  143. component = multiple ? 'select-multiple' : 'select'
  144. break
  145. }
  146. return {
  147. id,
  148. question: `${index + 1}${text}`,
  149. type,
  150. component,
  151. options: options.map((option, oIdx) => ({ id: option.id, content: `${String.fromCharCode(oIdx+65)}${option.content}` })),
  152. needTag: needTag ? JSON.parse(needTag).map(config => config.tags).filter(tags => tags.length) : null,
  153. required: required == 'Y',
  154. multiple,
  155. desc: content,
  156. }
  157. })
  158. this.total = this.questionsList.length
  159. this.answers = this.questionsList.map(() => null)
  160. console.log('questionsList', this.questionsList)
  161. console.log('answers', this.answers)
  162. },
  163. switchQuestion(current) {
  164. console.log('current', this.current, 'target', current)
  165. let { needTag } = this.questionsList[current]
  166. console.log('needTag length', needTag?.length)
  167. if (!needTag?.length) {
  168. this.current = current
  169. this.tabs.push(this.current)
  170. return
  171. }
  172. const selectTags = this.paperTags.flat(1)
  173. console.log('selectTags', selectTags)
  174. console.log('needTag', needTag)
  175. let valid = needTag.some(tags => {
  176. return tags.every(tag => {
  177. const { value, exclude } = tag
  178. let include = selectTags.includes(value)
  179. return exclude ? !include : include
  180. })
  181. })
  182. console.log('valid', valid)
  183. if (valid) {
  184. this.current = current
  185. this.tabs.push(this.current)
  186. return
  187. }
  188. if (current + 1 < this.questionsList.length) {
  189. current += 1
  190. this.switchQuestion(current)
  191. return
  192. }
  193. this.fetchFinish()
  194. },
  195. async fetchAnswer() {
  196. try {
  197. const { id: questionsId, type, required, multiple, options } = this.currentQuestion
  198. console.log('currentQuestion', this.currentQuestion)
  199. console.log('value', this.value)
  200. if (!this.value) {
  201. console.log('未答题')
  202. uni.showToast({
  203. title: '请答题',
  204. icon:'none'
  205. })
  206. return false
  207. }
  208. let params = {
  209. batchNo: this.batchNo,
  210. questionsId,
  211. answerId: this.value,
  212. }
  213. await this.$fetch('updateAnswer', params)
  214. this.answers[this.current] = this.value
  215. return true
  216. } catch (err) {
  217. console.log('fetchAnswer', err)
  218. return false
  219. }
  220. },
  221. async fetchFinish() {
  222. // todo: delete
  223. // todo
  224. // await this.$fetch('submitPaper', { id: this.paperInfo.reportId })
  225. this.current = this.total
  226. console.log('fetchFinish', this.current, this.currentQuestion)
  227. // todo
  228. // uni.reLaunch({
  229. // url: '/pages/index/report'
  230. // })
  231. },
  232. async next() {
  233. let succ = await this.fetchAnswer()
  234. if (!succ) {
  235. return
  236. }
  237. this.switchQuestion(this.current + 1)
  238. this.value = this.answers[this.current]
  239. },
  240. async finish() {
  241. let succ = await this.fetchAnswer()
  242. if (!succ) {
  243. return
  244. }
  245. this.fetchFinish()
  246. },
  247. async onSelect(id) {
  248. this.value = id
  249. },
  250. async onSelectMulitple(id) {
  251. this.value = this.value.includes(id) ? this.value.filter(item => item !== id) : this.value.concat(id)
  252. },
  253. onCreateReport() {
  254. uni.navigateTo({
  255. url: `/pages_order/report/pay?batchNo=${this.batchNo}`
  256. })
  257. },
  258. },
  259. }
  260. </script>
  261. <style scoped lang="scss">
  262. .page__view {
  263. width: 100vw;
  264. min-height: 100vh;
  265. background: linear-gradient(164deg, #014FA2, #014FA2, #2E8AED);
  266. position: relative;
  267. }
  268. .bar {
  269. margin-top: 42rpx;
  270. width: 100%;
  271. padding: 0 47rpx;
  272. box-sizing: border-box;
  273. .info {
  274. justify-content: flex-start;
  275. column-gap: 13rpx;
  276. font-size: 28rpx;
  277. color: #A1D6FF;
  278. .highlight {
  279. color: #FFFFFF;
  280. margin-right: 8rpx;
  281. }
  282. }
  283. .progress {
  284. margin-top: 20rpx;
  285. width: 100%;
  286. height: 16rpx;
  287. background: rgba($color: #FFFFFF, $alpha: 0.35);
  288. border-radius: 8rpx;
  289. &-bar {
  290. height: 100%;
  291. background: #FFFFFF;
  292. border-radius: 8rpx;
  293. }
  294. }
  295. }
  296. .main {
  297. width: 100%;
  298. padding: 73rpx 33rpx;
  299. box-sizing: border-box;
  300. }
  301. .card {
  302. position: relative;
  303. width: 100%;
  304. min-height: 876rpx;
  305. padding: 27rpx 0;
  306. box-sizing: border-box;
  307. background: #FFFFFF;
  308. border-radius: 16rpx;
  309. &:after {
  310. content: ' ';
  311. position: absolute;
  312. bottom: 0;
  313. left: 50%;
  314. transform: translate(-50%, 100%);
  315. width: calc(100% - 20rpx * 2);
  316. height: 28rpx;
  317. background: rgba($color: #E9EFF2, $alpha: 0.29);
  318. border-bottom-left-radius: 16rpx;
  319. border-bottom-right-radius: 16rpx;
  320. }
  321. &-header {
  322. padding: 0 33rpx;
  323. .tips {
  324. column-gap: 3rpx;
  325. padding: 12rpx 5rpx;
  326. font-size: 22rpx;
  327. color: #DB5742;
  328. border: 3rpx solid #DB5742;
  329. border-radius: 7rpx;
  330. margin-bottom: 26rpx;
  331. .icon {
  332. width: 31rpx;
  333. height: auto;
  334. }
  335. }
  336. }
  337. &-content {
  338. padding: 0 24rpx;
  339. }
  340. }
  341. .question {
  342. font-family: PingFang SC;
  343. font-weight: 400;
  344. font-size: 28rpx;
  345. line-height: 50rpx;
  346. color: #000000;
  347. margin-bottom: 64rpx;
  348. }
  349. .select {
  350. &-option {
  351. margin-top: 44rpx;
  352. padding: 22rpx 28rpx;
  353. line-height: 1.3;
  354. font-size: 28rpx;
  355. color: #707070;
  356. background: #F3F3F3;
  357. border-radius: 28rpx;
  358. &.is-active {
  359. color: #014FA2;
  360. background: rgba($color: #014FA2, $alpha: 0.22);
  361. }
  362. }
  363. }
  364. .bottom {
  365. position: fixed;
  366. left: 0;
  367. bottom: 0;
  368. width: 100%;
  369. padding: 17rpx 72rpx;
  370. padding-bottom: calc(env(safe-area-inset-bottom) + 17rpx);
  371. background: #FFFFFF;
  372. box-sizing: border-box;
  373. .btn {
  374. width: 100%;
  375. padding: 26rpx 0;
  376. font-size: 30rpx;
  377. line-height: 1.4;
  378. color: #FFFFFF;
  379. background: #014FA2;
  380. border-radius: 42rpx;
  381. }
  382. }
  383. .desc {
  384. margin-top: 220rpx;
  385. font-family: PingFang SC;
  386. font-weight: 400;
  387. line-height: 1.4;
  388. font-size: 26rpx;
  389. color: #989898;
  390. }
  391. .main.is-finish {
  392. height: calc(100vh - (var(--status-bar-height) + 120rpx));
  393. padding: 0 33rpx;
  394. padding-bottom: calc(var(--status-bar-height) + 120rpx);
  395. .card {
  396. min-height: 745rpx;
  397. &-content {
  398. justify-content: flex-end;
  399. width: 100%;
  400. padding: 69rpx 94rpx;
  401. box-sizing: border-box;
  402. .text {
  403. font-size: 32rpx;
  404. font-weight: 600;
  405. color: #000000;
  406. }
  407. .text + .text {
  408. margin-top: 45rpx;
  409. }
  410. .btn {
  411. margin-top: 139rpx;
  412. padding: 29rpx 183rpx;
  413. box-sizing: border-box;
  414. line-height: 1.4;
  415. white-space: nowrap;
  416. color: #FFFFFF;
  417. background: #014FA2;
  418. border-radius: 50rpx;
  419. }
  420. }
  421. }
  422. }
  423. </style>