鸿宇研学生前端代码
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.

391 lines
10 KiB

  1. <template>
  2. <view>
  3. <uv-popup ref="popup" mode="bottom" bgColor="none" :zIndex="1000000" @change="onPopupChange">
  4. <view class="popup__view" v-if="isShow">
  5. <view class="flex header">
  6. <view class="title">新增记录</view>
  7. <button class="btn" @click="close">关闭</button>
  8. </view>
  9. <view class="form">
  10. <uv-form
  11. ref="form"
  12. :model="form"
  13. errorType="toast"
  14. >
  15. <view class="form-item">
  16. <uv-form-item prop="activityId" :customStyle="formItemStyle">
  17. <view class="form-item-label">
  18. <image class="icon" src="@/pages_order/static/icon-require.png" mode="widthFix"></image>
  19. 关联项目
  20. </view>
  21. <view class="form-item-content">
  22. <view class="flex row" @click="openRelatePojectPicker">
  23. <view v-if="form.activityId" class="text">{{ projectDesc }}</view>
  24. <view v-else class="text placeholder">请选择关联项目</view>
  25. <uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon>
  26. </view>
  27. <reloateProjectPopup ref="reloateProjectPopup" :options="projects" @confirm="onRelateProjectChange"></reloateProjectPopup>
  28. </view>
  29. </uv-form-item>
  30. </view>
  31. <view class="form-item">
  32. <uv-form-item prop="processScore" :customStyle="formItemStyle">
  33. <view class="flex row">
  34. <view class="form-item-label">行程</view>
  35. <view class="form-item-content">
  36. <formRate v-model="form.processScore"></formRate>
  37. </view>
  38. </view>
  39. </uv-form-item>
  40. </view>
  41. <view class="form-item">
  42. <uv-form-item prop="spotScore" :customStyle="formItemStyle">
  43. <view class="flex row">
  44. <view class="form-item-label">景点</view>
  45. <view class="form-item-content">
  46. <formRate v-model="form.spotScore"></formRate>
  47. </view>
  48. </view>
  49. </uv-form-item>
  50. </view>
  51. <view class="form-item">
  52. <uv-form-item prop="teacherScore" :customStyle="formItemStyle">
  53. <view class="flex row">
  54. <view class="form-item-label">导师</view>
  55. <view class="form-item-content">
  56. <formRate v-model="form.teacherScore"></formRate>
  57. </view>
  58. </view>
  59. </uv-form-item>
  60. </view>
  61. <view class="form-item">
  62. <uv-form-item prop="images" :customStyle="formItemStyle">
  63. <view class="form-item-label">上传图片</view>
  64. <view class="form-item-content">
  65. <formUpload v-model="form.images"></formUpload>
  66. </view>
  67. </uv-form-item>
  68. </view>
  69. <view class="form-item" v-for="(item, index) in questions" :key="item.id">
  70. <uv-form-item :prop="`texts[${index}]`" :customStyle="formItemStyle">
  71. <view class="form-item-label">
  72. <image class="icon" src="@/pages_order/static/icon-require.png" mode="widthFix"></image>
  73. {{ item.label }}
  74. </view>
  75. <view class="form-item-content">
  76. <formTextarea v-model="form.texts[index]"></formTextarea>
  77. </view>
  78. </uv-form-item>
  79. </view>
  80. </uv-form>
  81. </view>
  82. <view class="footer">
  83. <button class="flex btn" @click="onPublish">发布</button>
  84. </view>
  85. </view>
  86. </uv-popup>
  87. </view>
  88. </template>
  89. <script>
  90. import { mapState } from 'vuex'
  91. import reloateProjectPopup from '@/pages_order/components/reloateProjectPopup.vue'
  92. import formTextarea from '@/pages_order/components/formTextarea.vue'
  93. import formUpload from '@/pages_order/components/formUpload.vue'
  94. import formRate from '@/pages_order/components/formRate.vue'
  95. export default {
  96. components: {
  97. reloateProjectPopup,
  98. formTextarea,
  99. formUpload,
  100. formRate,
  101. },
  102. data() {
  103. return {
  104. isShow: false,
  105. form: {
  106. activityId: null,
  107. processScore: null,
  108. spotScore: null,
  109. teacherScore: null,
  110. images: [],
  111. texts: [],
  112. },
  113. projects: [],
  114. questions: [],
  115. }
  116. },
  117. computed: {
  118. ...mapState(['userInfo']),
  119. projectDesc() {
  120. const { activityId } = this.form
  121. const target = this.projects?.find?.(item => item.id === activityId)
  122. return target?.name || ''
  123. },
  124. },
  125. methods: {
  126. getData() {
  127. // todo
  128. this.projects = [
  129. {
  130. id: '001',
  131. name: '亲子•坝上双草原6日 |乌兰布统+锡林郭勒+长城',
  132. },
  133. {
  134. id: '002',
  135. name: '青青草原•云中岭 |5-10公里AB线强度可选',
  136. },
  137. {
  138. id: '003',
  139. name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
  140. },
  141. {
  142. id: '004',
  143. name: '九色甘南|人间净土6日/7日深度游',
  144. },
  145. {
  146. id: '005',
  147. name: '北疆全景12日| 入疆首推!阿勒泰+伊犁+吐鲁番',
  148. },
  149. {
  150. id: '006',
  151. name: '塞上江南•神奇宁夏5日|穿越大漠与历史对话',
  152. },
  153. {
  154. id: '007',
  155. name: '尊享•天山环线9日| 伊犁全景+独库,头等舱大巴',
  156. },
  157. ]
  158. this.questions = [
  159. {
  160. id: '001',
  161. label: '这次研学之旅,整体给你留下了怎样的印象?用几个词或几句话简单概括一下',
  162. },
  163. {
  164. id: '002',
  165. label: '在整个行程中,你最喜欢的部分是哪里?为什么?',
  166. },
  167. {
  168. id: '003',
  169. label: '你觉得这次研学的行程安排是否合理?有没有哪些地方让你觉得特别满意或需要改进的?',
  170. },
  171. ]
  172. },
  173. setRules() {
  174. const rules = {
  175. 'activityId': {
  176. type: 'string',
  177. required: true,
  178. message: '请选择关联项目',
  179. },
  180. }
  181. // todo: check
  182. this.questions.forEach((item, index) => {
  183. rules[`texts[${index}]`] = {
  184. type: 'string',
  185. required: true,
  186. message: `请回答“${item.label}`,
  187. }
  188. })
  189. this.$refs.form.setRules(this.rules)
  190. },
  191. async open() {
  192. await this.getData()
  193. const texts = this.questions.map(() => '')
  194. this.form = {
  195. activityId: null,
  196. processScore: null,
  197. spotScore: null,
  198. teacherScore: null,
  199. images: [],
  200. texts,
  201. }
  202. this.$refs.popup.open()
  203. },
  204. close() {
  205. this.$refs.popup.close()
  206. },
  207. onPopupChange(e) {
  208. this.isShow = e.show
  209. // todo: need settimeout?
  210. this.setRules()
  211. },
  212. openRelatePojectPicker() {
  213. this.$refs.reloateProjectPopup.open(this.form.activityId?.id || null)
  214. },
  215. onRelateProjectChange(id) {
  216. this.form.activityId = id
  217. },
  218. async onPublish() {
  219. try {
  220. await this.$refs.form.validate()
  221. const {
  222. activityId,
  223. processScore,
  224. spotScore,
  225. teacherScore,
  226. images,
  227. texts,
  228. } = this.form
  229. const params = {
  230. // todo: check
  231. userId: this.userInfo.id,
  232. activityId,
  233. processScore,
  234. spotScore,
  235. teacherScore,
  236. image: images.join(','),
  237. content: texts.join('\r\n') // todo: check
  238. }
  239. await this.$fetch('addExperience', params)
  240. uni.showToast({
  241. icon: 'success',
  242. title: '发布成功',
  243. });
  244. this.$emit('submitted')
  245. this.close()
  246. } catch (err) {
  247. console.log('onSave err', err)
  248. }
  249. },
  250. },
  251. }
  252. </script>
  253. <style lang="scss" scoped>
  254. .popup__view {
  255. width: 100vw;
  256. display: flex;
  257. flex-direction: column;
  258. box-sizing: border-box;
  259. background: #FFFFFF;
  260. border-top-left-radius: 32rpx;
  261. border-top-right-radius: 32rpx;
  262. }
  263. .header {
  264. position: relative;
  265. width: 100%;
  266. padding: 24rpx 0;
  267. box-sizing: border-box;
  268. border-bottom: 2rpx solid #EEEEEE;
  269. .title {
  270. font-family: PingFang SC;
  271. font-weight: 500;
  272. font-size: 34rpx;
  273. line-height: 1.4;
  274. color: #181818;
  275. }
  276. .btn {
  277. font-family: PingFang SC;
  278. font-weight: 500;
  279. font-size: 32rpx;
  280. line-height: 1.4;
  281. color: #8B8B8B;
  282. position: absolute;
  283. top: 26rpx;
  284. left: 40rpx;
  285. }
  286. }
  287. .form {
  288. max-height: 75vh;
  289. padding: 32rpx 40rpx;
  290. box-sizing: border-box;
  291. overflow-y: auto;
  292. &-item {
  293. padding: 8rpx 0 6rpx 0;
  294. & + & {
  295. padding-top: 24rpx;
  296. border-top: 2rpx solid #EEEEEE;
  297. }
  298. &-label {
  299. margin-bottom: 14rpx;
  300. display: flex;
  301. align-items: center;
  302. font-family: PingFang SC;
  303. font-weight: 400;
  304. font-size: 26rpx;
  305. line-height: 1.4;
  306. color: #181818;
  307. .icon {
  308. margin-right: 8rpx;
  309. width: 16rpx;
  310. height: auto;
  311. }
  312. }
  313. &-content {
  314. .text {
  315. padding: 2rpx 0;
  316. font-family: PingFang SC;
  317. font-weight: 400;
  318. font-size: 32rpx;
  319. line-height: 1.4;
  320. &.placeholder {
  321. color: #C6C6C6;
  322. }
  323. }
  324. }
  325. }
  326. }
  327. .row {
  328. justify-content: space-between;
  329. .form-label {
  330. margin: 0;
  331. }
  332. }
  333. .footer {
  334. width: 100%;
  335. padding: 32rpx 40rpx;
  336. box-sizing: border-box;
  337. border-top: 2rpx solid #F1F1F1;
  338. .btn {
  339. width: 100%;
  340. padding: 14rpx 0;
  341. box-sizing: border-box;
  342. font-family: PingFang SC;
  343. font-weight: 500;
  344. font-size: 36rpx;
  345. line-height: 1.4;
  346. color: #FFFFFF;
  347. background-image: linear-gradient(to right, #21FEEC, #019AF9);
  348. border: 2rpx solid #00A9FF;
  349. border-radius: 41rpx;
  350. }
  351. }
  352. </style>