index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. <template>
  2. <view class="task-form">
  3. <scroll-view
  4. class="task-form__scroll"
  5. scroll-y
  6. enhanced
  7. :show-scrollbar="false"
  8. :bounces="false"
  9. >
  10. <view class="task-form__content" :style="isQuestionnaireTask ? undefined : contentStyle">
  11. <QuestionnaireTask v-if="isQuestionnaireTask" />
  12. <template v-else>
  13. <view v-if="showNotice" class="workbench-notice">
  14. <wd-notice-bar
  15. :text="noticeText"
  16. prefix="warn-bold"
  17. type="info"
  18. color="#f90"
  19. background-color="#fdf6ec"
  20. custom-style="border-radius: 12rpx; min-height: 64rpx; padding: 0 20rpx;"
  21. />
  22. </view>
  23. <view class="task-form-fields">
  24. <template v-for="item in taskFieldConfigList" :key="item.id">
  25. <view v-if="item.show && form.value[item.id]" class="task-form-field">
  26. <SingleSelect
  27. v-if="isSingleSelect(item.taskFiledType)"
  28. v-model:label="form.value[item.id].label"
  29. v-model:value="form.value[item.id].value"
  30. :task-field-config="item"
  31. :disabled="item.readonly"
  32. :before-open="() => handleSingleSelectBeforeOpen(item)"
  33. @change="handleTaskSingleSelectChange(item, $event)"
  34. />
  35. <InputAutoSelect
  36. v-else-if="isInputAutoSelect(item.taskFiledType)"
  37. v-model:label="form.value[item.id].label"
  38. v-model:value="form.value[item.id].value"
  39. :task-field-config="item"
  40. :disabled="item.readonly"
  41. :before-open="() => handleSingleSelectBeforeOpen(item)"
  42. @change="handleSingleSelectChange(item, $event)"
  43. />
  44. <SingleText
  45. v-else-if="isTextInput(item.taskFiledType)"
  46. v-model:label="form.value[item.id].label"
  47. v-model:value="form.value[item.id].value"
  48. :task-field-config="item"
  49. :readonly="item.readonly || isReadonlyText(item.taskFiledType)"
  50. />
  51. <DateTimePicker
  52. v-else-if="isDateTime(item.taskFiledType)"
  53. v-model:label="form.value[item.id].label"
  54. v-model:value="form.value[item.id].value"
  55. :task-field-config="item"
  56. :disabled="item.readonly"
  57. />
  58. <TaskLocation
  59. v-else-if="isLocation(item.taskFiledType)"
  60. v-model:label="form.value[item.id].label"
  61. v-model:value="form.value[item.id].value"
  62. :task-field-config="item"
  63. :disabled="item.readonly"
  64. />
  65. <template v-else-if="isImgUpload(item.taskFiledType)">
  66. <view class="line" />
  67. <ImgUpload
  68. v-model:label="form.value[item.id].label"
  69. v-model:value="form.value[item.id].value"
  70. :task-field-config="item"
  71. :disabled="item.readonly"
  72. />
  73. </template>
  74. <template v-else-if="isSign(item.taskFiledType)">
  75. <view class="line" />
  76. <Sign
  77. v-model:label="form.value[item.id].label"
  78. v-model:value="form.value[item.id].value"
  79. :task-field-config="item"
  80. :disabled="item.readonly"
  81. />
  82. </template>
  83. <MultipleSelect
  84. v-else-if="isMultipleSelect(item.taskFiledType)"
  85. v-model:label="form.value[item.id].label"
  86. v-model:value="form.value[item.id].value"
  87. :task-field-config="item"
  88. :disabled="item.readonly"
  89. :before-open="() => handleMultipleSelectBeforeOpen(item)"
  90. />
  91. <DataTimeRange
  92. v-else-if="isDateTimeRange(item.taskFiledType)"
  93. v-model:label="form.value[item.id].label"
  94. v-model:value="form.value[item.id].value"
  95. :task-field-config="item"
  96. :disabled="item.readonly"
  97. />
  98. <template v-else-if="isLongText(item.taskFiledType)">
  99. <view class="line" />
  100. <LongText
  101. v-model:label="form.value[item.id].label"
  102. v-model:value="form.value[item.id].value"
  103. :task-field-config="item"
  104. :disabled="item.readonly"
  105. />
  106. <view class="line" />
  107. </template>
  108. <Area
  109. v-else-if="isArea(item.taskFiledType)"
  110. v-model:label="form.value[item.id].label"
  111. v-model:value="form.value[item.id].value"
  112. :task-field-config="item"
  113. :disabled="item.readonly"
  114. :select-level="selectLevel"
  115. />
  116. </view>
  117. </template>
  118. </view>
  119. <view v-if="activeTaskTypeId === '47'" class="otc-estimated-score">
  120. <view class="otc-estimated-score__value">预计积分:{{ otcEstimatedScore }}</view>
  121. <view v-if="otcIsScoreOverLimit" class="otc-estimated-score__limit">
  122. 该服务获取积分上限为{{ otcLargestTotal }}分
  123. </view>
  124. </view>
  125. </template>
  126. </view>
  127. </scroll-view>
  128. <DuplicateImageDialog
  129. v-if="!isQuestionnaireTask"
  130. v-model="duplicateImageVisible"
  131. :images="duplicateImageList"
  132. @confirm="clearDuplicateImages"
  133. />
  134. <view
  135. v-if="!isQuestionnaireTask && taskFieldConfigList.length"
  136. class="task-form-footer"
  137. :style="footerStyle"
  138. >
  139. <button
  140. class="task-form-footer__button task-form-footer__button--save"
  141. hover-class="task-form-footer__button--hover"
  142. :disabled="isActionPending"
  143. @click="handleSave"
  144. >
  145. 保存
  146. </button>
  147. <button
  148. class="task-form-footer__button task-form-footer__button--submit"
  149. hover-class="task-form-footer__button--hover"
  150. :disabled="isActionPending"
  151. @click="handleSubmit"
  152. >
  153. 提交
  154. </button>
  155. </view>
  156. </view>
  157. </template>
  158. <script setup lang="ts">
  159. import { computed, ref } from 'vue'
  160. import { onLoad } from '@dcloudio/uni-app'
  161. import Area from './components/Area.vue'
  162. import DataTimeRange from './components/DataTimeRange.vue'
  163. import DateTimePicker from './components/DateTimePicker.vue'
  164. import DuplicateImageDialog from './components/DuplicateImageDialog.vue'
  165. import ImgUpload from './components/ImgUpload.vue'
  166. import InputAutoSelect from './components/InputAutoSelect.vue'
  167. import LongText from './components/LongText.vue'
  168. import MultipleSelect from './components/MultipleSelect.vue'
  169. import QuestionnaireTask from './components/QuestionnaireTask.vue'
  170. import Sign from './components/Sign.vue'
  171. import SingleSelect from './components/SingleSelect.vue'
  172. import SingleText from './components/SingleText.vue'
  173. import TaskLocation from './components/TaskLocation.vue'
  174. import { useTaskFieldType } from './composables/useTaskFieldType'
  175. import { type TaskFieldConfigViewItem, useTaskForm } from './composables/useTaskForm'
  176. import { useTaskFormAction } from './composables/useTaskFormAction'
  177. import { useTaskFormPageFeatures } from './composables/useTaskFormPageFeatures'
  178. import { useTaskFormSafeArea } from './composables/useTaskFormSafeArea'
  179. import {
  180. type SingleSelectChangeEvent,
  181. useTaskSelectHandlers,
  182. } from './composables/useTaskSelectHandlers'
  183. interface PageLoadOptions {
  184. taskTypeId?: string
  185. taskTitle?: string
  186. }
  187. const taskTypeId = ref('')
  188. const {
  189. form,
  190. taskFieldConfigList,
  191. activeTaskTypeId,
  192. taskTypes,
  193. loadTaskForm,
  194. handleDerivedFieldChange,
  195. } = useTaskForm()
  196. const {
  197. handleSingleSelectBeforeOpen,
  198. handleSingleSelectChange,
  199. handleMultipleSelectBeforeOpen,
  200. otcEstimatedScore,
  201. otcLargestTotal,
  202. otcIsScoreOverLimit,
  203. } = useTaskSelectHandlers({
  204. taskTypeId: activeTaskTypeId,
  205. form,
  206. taskFieldConfigList,
  207. taskTypes,
  208. })
  209. const {
  210. isActionPending,
  211. duplicateImageVisible,
  212. duplicateImageList,
  213. clearDuplicateImages,
  214. handleSave,
  215. handleSubmit,
  216. } = useTaskFormAction({
  217. taskTypeId: activeTaskTypeId,
  218. form,
  219. taskFieldConfigList,
  220. })
  221. const {
  222. isSingleSelect,
  223. isTextInput,
  224. isReadonlyText,
  225. isDateTime,
  226. isLocation,
  227. isImgUpload,
  228. isSign,
  229. isMultipleSelect,
  230. isInputAutoSelect,
  231. isDateTimeRange,
  232. isLongText,
  233. isArea,
  234. } = useTaskFieldType()
  235. const { showNotice, noticeText, selectLevel, initializeTaskFormPage } = useTaskFormPageFeatures()
  236. const { contentStyle, footerStyle, initSafeArea } = useTaskFormSafeArea()
  237. const isQuestionnaireTask = computed(() => taskTypeId.value === '801')
  238. const decodeRouteParam = (value?: string): string => {
  239. if (!value) return ''
  240. try {
  241. return decodeURIComponent(value)
  242. } catch {
  243. return value
  244. }
  245. }
  246. const handleTaskSingleSelectChange = (
  247. field: TaskFieldConfigViewItem,
  248. event: SingleSelectChangeEvent
  249. ) => {
  250. void handleSingleSelectChange(field, event)
  251. handleDerivedFieldChange(field, event)
  252. }
  253. onLoad(async (options: PageLoadOptions = {}) => {
  254. initSafeArea()
  255. taskTypeId.value = options.taskTypeId?.trim() ?? ''
  256. if (!taskTypeId.value) {
  257. uni.showToast({
  258. title: '缺少 taskTypeId',
  259. icon: 'none',
  260. })
  261. return
  262. }
  263. void initializeTaskFormPage({
  264. taskTypeId: taskTypeId.value,
  265. taskTitle: decodeRouteParam(options.taskTitle),
  266. })
  267. if (isQuestionnaireTask.value) {
  268. return
  269. }
  270. try {
  271. await loadTaskForm(taskTypeId.value)
  272. } catch (error) {
  273. console.error('[task-form] 表单配置加载失败', error)
  274. uni.showToast({
  275. title: '表单加载失败,请稍后重试',
  276. icon: 'none',
  277. })
  278. }
  279. })
  280. </script>
  281. <style lang="scss" scoped>
  282. .task-form {
  283. position: relative;
  284. width: 100%;
  285. min-height: 100vh;
  286. overflow: hidden;
  287. background: #f2f2f2;
  288. }
  289. .task-form__scroll {
  290. width: 100%;
  291. height: 100vh;
  292. }
  293. .task-form__content {
  294. min-height: 100vh;
  295. box-sizing: border-box;
  296. }
  297. .workbench-notice {
  298. padding: 20rpx 20rpx 0;
  299. background: #f2f2f2;
  300. box-sizing: border-box;
  301. }
  302. .task-form-fields {
  303. padding-top: 20rpx;
  304. }
  305. .task-form-field {
  306. background: #fff;
  307. }
  308. .otc-estimated-score {
  309. min-height: 120rpx;
  310. padding: 28rpx;
  311. box-sizing: border-box;
  312. font-size: 30rpx;
  313. line-height: 44rpx;
  314. color: #333;
  315. }
  316. .otc-estimated-score__limit {
  317. margin-top: 12rpx;
  318. color: #cd423e;
  319. }
  320. .line {
  321. width: 100%;
  322. height: 20rpx;
  323. background: #f4f4f4;
  324. }
  325. .task-form-footer {
  326. position: fixed;
  327. right: 0;
  328. left: 0;
  329. z-index: 9;
  330. display: flex;
  331. gap: 16rpx;
  332. padding: 20rpx 28rpx;
  333. background: #fff;
  334. box-sizing: border-box;
  335. box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  336. }
  337. .task-form-footer__button {
  338. flex: 1;
  339. height: 88rpx;
  340. margin: 0;
  341. padding: 0;
  342. border: 0;
  343. border-radius: 12rpx;
  344. font-size: 32rpx;
  345. line-height: 88rpx;
  346. }
  347. .task-form-footer__button::after {
  348. border: 0;
  349. }
  350. .task-form-footer__button--hover {
  351. opacity: 0.85;
  352. }
  353. .task-form-footer__button--save {
  354. color: #222;
  355. background: #f7f7f7;
  356. }
  357. .task-form-footer__button--submit {
  358. color: #fff;
  359. background: #3b9bed;
  360. }
  361. </style>