| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- <template>
- <view class="task-form">
- <scroll-view
- class="task-form__scroll"
- scroll-y
- enhanced
- :show-scrollbar="false"
- :bounces="false"
- >
- <view class="task-form__content" :style="isQuestionnaireTask ? undefined : contentStyle">
- <QuestionnaireTask v-if="isQuestionnaireTask" />
- <template v-else>
- <view v-if="showNotice" class="workbench-notice">
- <wd-notice-bar
- :text="noticeText"
- prefix="warn-bold"
- type="info"
- color="#f90"
- background-color="#fdf6ec"
- custom-style="border-radius: 12rpx; min-height: 64rpx; padding: 0 20rpx;"
- />
- </view>
- <view class="task-form-fields">
- <template v-for="item in taskFieldConfigList" :key="item.id">
- <view v-if="item.show && form.value[item.id]" class="task-form-field">
- <SingleSelect
- v-if="isSingleSelect(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- :before-open="() => handleSingleSelectBeforeOpen(item)"
- @change="handleTaskSingleSelectChange(item, $event)"
- />
- <InputAutoSelect
- v-else-if="isInputAutoSelect(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- :before-open="() => handleSingleSelectBeforeOpen(item)"
- @change="handleSingleSelectChange(item, $event)"
- />
- <SingleText
- v-else-if="isTextInput(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :readonly="item.readonly || isReadonlyText(item.taskFiledType)"
- />
- <DateTimePicker
- v-else-if="isDateTime(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- <TaskLocation
- v-else-if="isLocation(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- <template v-else-if="isImgUpload(item.taskFiledType)">
- <view class="line" />
- <ImgUpload
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- </template>
- <template v-else-if="isSign(item.taskFiledType)">
- <view class="line" />
- <Sign
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- </template>
- <MultipleSelect
- v-else-if="isMultipleSelect(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- :before-open="() => handleMultipleSelectBeforeOpen(item)"
- />
- <DataTimeRange
- v-else-if="isDateTimeRange(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- <template v-else-if="isLongText(item.taskFiledType)">
- <view class="line" />
- <LongText
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- />
- <view class="line" />
- </template>
- <Area
- v-else-if="isArea(item.taskFiledType)"
- v-model:label="form.value[item.id].label"
- v-model:value="form.value[item.id].value"
- :task-field-config="item"
- :disabled="item.readonly"
- :select-level="selectLevel"
- />
- </view>
- </template>
- </view>
- <view v-if="activeTaskTypeId === '47'" class="otc-estimated-score">
- <view class="otc-estimated-score__value">预计积分:{{ otcEstimatedScore }}</view>
- <view v-if="otcIsScoreOverLimit" class="otc-estimated-score__limit">
- 该服务获取积分上限为{{ otcLargestTotal }}分
- </view>
- </view>
- </template>
- </view>
- </scroll-view>
- <DuplicateImageDialog
- v-if="!isQuestionnaireTask"
- v-model="duplicateImageVisible"
- :images="duplicateImageList"
- @confirm="clearDuplicateImages"
- />
- <view
- v-if="!isQuestionnaireTask && taskFieldConfigList.length"
- class="task-form-footer"
- :style="footerStyle"
- >
- <button
- class="task-form-footer__button task-form-footer__button--save"
- hover-class="task-form-footer__button--hover"
- :disabled="isActionPending"
- @click="handleSave"
- >
- 保存
- </button>
- <button
- class="task-form-footer__button task-form-footer__button--submit"
- hover-class="task-form-footer__button--hover"
- :disabled="isActionPending"
- @click="handleSubmit"
- >
- 提交
- </button>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { computed, ref } from 'vue'
- import { onLoad } from '@dcloudio/uni-app'
- import Area from './components/Area.vue'
- import DataTimeRange from './components/DataTimeRange.vue'
- import DateTimePicker from './components/DateTimePicker.vue'
- import DuplicateImageDialog from './components/DuplicateImageDialog.vue'
- import ImgUpload from './components/ImgUpload.vue'
- import InputAutoSelect from './components/InputAutoSelect.vue'
- import LongText from './components/LongText.vue'
- import MultipleSelect from './components/MultipleSelect.vue'
- import QuestionnaireTask from './components/QuestionnaireTask.vue'
- import Sign from './components/Sign.vue'
- import SingleSelect from './components/SingleSelect.vue'
- import SingleText from './components/SingleText.vue'
- import TaskLocation from './components/TaskLocation.vue'
- import { useTaskFieldType } from './composables/useTaskFieldType'
- import { type TaskFieldConfigViewItem, useTaskForm } from './composables/useTaskForm'
- import { useTaskFormAction } from './composables/useTaskFormAction'
- import { useTaskFormPageFeatures } from './composables/useTaskFormPageFeatures'
- import { useTaskFormSafeArea } from './composables/useTaskFormSafeArea'
- import {
- type SingleSelectChangeEvent,
- useTaskSelectHandlers,
- } from './composables/useTaskSelectHandlers'
- interface PageLoadOptions {
- taskTypeId?: string
- taskTitle?: string
- }
- const taskTypeId = ref('')
- const {
- form,
- taskFieldConfigList,
- activeTaskTypeId,
- taskTypes,
- loadTaskForm,
- handleDerivedFieldChange,
- } = useTaskForm()
- const {
- handleSingleSelectBeforeOpen,
- handleSingleSelectChange,
- handleMultipleSelectBeforeOpen,
- otcEstimatedScore,
- otcLargestTotal,
- otcIsScoreOverLimit,
- } = useTaskSelectHandlers({
- taskTypeId: activeTaskTypeId,
- form,
- taskFieldConfigList,
- taskTypes,
- })
- const {
- isActionPending,
- duplicateImageVisible,
- duplicateImageList,
- clearDuplicateImages,
- handleSave,
- handleSubmit,
- } = useTaskFormAction({
- taskTypeId: activeTaskTypeId,
- form,
- taskFieldConfigList,
- })
- const {
- isSingleSelect,
- isTextInput,
- isReadonlyText,
- isDateTime,
- isLocation,
- isImgUpload,
- isSign,
- isMultipleSelect,
- isInputAutoSelect,
- isDateTimeRange,
- isLongText,
- isArea,
- } = useTaskFieldType()
- const { showNotice, noticeText, selectLevel, initializeTaskFormPage } = useTaskFormPageFeatures()
- const { contentStyle, footerStyle, initSafeArea } = useTaskFormSafeArea()
- const isQuestionnaireTask = computed(() => taskTypeId.value === '801')
- const decodeRouteParam = (value?: string): string => {
- if (!value) return ''
- try {
- return decodeURIComponent(value)
- } catch {
- return value
- }
- }
- const handleTaskSingleSelectChange = (
- field: TaskFieldConfigViewItem,
- event: SingleSelectChangeEvent
- ) => {
- void handleSingleSelectChange(field, event)
- handleDerivedFieldChange(field, event)
- }
- onLoad(async (options: PageLoadOptions = {}) => {
- initSafeArea()
- taskTypeId.value = options.taskTypeId?.trim() ?? ''
- if (!taskTypeId.value) {
- uni.showToast({
- title: '缺少 taskTypeId',
- icon: 'none',
- })
- return
- }
- void initializeTaskFormPage({
- taskTypeId: taskTypeId.value,
- taskTitle: decodeRouteParam(options.taskTitle),
- })
- if (isQuestionnaireTask.value) {
- return
- }
- try {
- await loadTaskForm(taskTypeId.value)
- } catch (error) {
- console.error('[task-form] 表单配置加载失败', error)
- uni.showToast({
- title: '表单加载失败,请稍后重试',
- icon: 'none',
- })
- }
- })
- </script>
- <style lang="scss" scoped>
- .task-form {
- position: relative;
- width: 100%;
- min-height: 100vh;
- overflow: hidden;
- background: #f2f2f2;
- }
- .task-form__scroll {
- width: 100%;
- height: 100vh;
- }
- .task-form__content {
- min-height: 100vh;
- box-sizing: border-box;
- }
- .workbench-notice {
- padding: 20rpx 20rpx 0;
- background: #f2f2f2;
- box-sizing: border-box;
- }
- .task-form-fields {
- padding-top: 20rpx;
- }
- .task-form-field {
- background: #fff;
- }
- .otc-estimated-score {
- min-height: 120rpx;
- padding: 28rpx;
- box-sizing: border-box;
- font-size: 30rpx;
- line-height: 44rpx;
- color: #333;
- }
- .otc-estimated-score__limit {
- margin-top: 12rpx;
- color: #cd423e;
- }
- .line {
- width: 100%;
- height: 20rpx;
- background: #f4f4f4;
- }
- .task-form-footer {
- position: fixed;
- right: 0;
- left: 0;
- z-index: 9;
- display: flex;
- gap: 16rpx;
- padding: 20rpx 28rpx;
- background: #fff;
- box-sizing: border-box;
- box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
- }
- .task-form-footer__button {
- flex: 1;
- height: 88rpx;
- margin: 0;
- padding: 0;
- border: 0;
- border-radius: 12rpx;
- font-size: 32rpx;
- line-height: 88rpx;
- }
- .task-form-footer__button::after {
- border: 0;
- }
- .task-form-footer__button--hover {
- opacity: 0.85;
- }
- .task-form-footer__button--save {
- color: #222;
- background: #f7f7f7;
- }
- .task-form-footer__button--submit {
- color: #fff;
- background: #3b9bed;
- }
- </style>
|