Prechádzať zdrojové kódy

完成保存 回显

yuanmingze 2 týždňov pred
rodič
commit
4f36ab715c

+ 6 - 0
src/pages-task/task-form/components/DynamicTaskFormFields.vue

@@ -54,6 +54,9 @@
             v-model:value="form.value[item.id].value"
             :task-field-config="item"
             :disabled="isFieldDisabled(item)"
+            @keyboard-focus="emit('long-text-keyboard-focus')"
+            @keyboard-blur="emit('long-text-keyboard-blur')"
+            @keyboard-height-change="emit('long-text-keyboard-height-change', $event)"
           />
         </template>
 
@@ -176,6 +179,9 @@ const props = withDefaults(
 const emit = defineEmits<{
   (event: 'derived-change', field: TaskFieldConfigViewItem, change: DerivedFieldChangeEvent): void
   (event: 'uploading-change', fieldId: string, uploading: boolean): void
+  (event: 'long-text-keyboard-focus'): void
+  (event: 'long-text-keyboard-blur'): void
+  (event: 'long-text-keyboard-height-change', result: { height?: number }): void
 }>()
 
 const form = defineModel<DynamicFormState>({ required: true })

+ 9 - 0
src/pages-task/task-form/components/LongText.vue

@@ -13,6 +13,9 @@
           :disabled="props.disabled"
           clearable
           custom-style="--wot-textarea-inner-min-height: 220rpx; min-height: 300rpx; width: 100%; padding-bottom: 56rpx; box-sizing: border-box;"
+          @focus="emit('keyboard-focus')"
+          @blur="emit('keyboard-blur')"
+          @keyboardheightchange="emit('keyboard-height-change', $event)"
         />
 
         <text class="word-limit"> {{ currentLength }}/{{ maxLength }} </text>
@@ -37,6 +40,12 @@ const props = withDefaults(defineProps<Props>(), {
   disabled: false,
 })
 
+const emit = defineEmits<{
+  (event: 'keyboard-focus'): void
+  (event: 'keyboard-blur'): void
+  (event: 'keyboard-height-change', result: { height?: number }): void
+}>()
+
 const value = defineModel<FieldValue>('value', {
   default: '',
 })

+ 63 - 0
src/pages-task/task-form/composables/useLongTextKeyboardVisibility.ts

@@ -0,0 +1,63 @@
+import { ref } from 'vue'
+
+import { onShow, onUnload } from '@dcloudio/uni-app'
+
+type KeyboardHeightChangeResult = {
+  height?: number
+}
+
+const KEYBOARD_BLUR_FALLBACK_DELAY_MS = 350
+
+export const useLongTextKeyboardVisibility = () => {
+  const longTextKeyboardVisible = ref(false)
+  let keyboardBlurFallbackTimer: ReturnType<typeof setTimeout> | undefined
+
+  const clearKeyboardBlurFallback = () => {
+    if (keyboardBlurFallbackTimer === undefined) return
+
+    clearTimeout(keyboardBlurFallbackTimer)
+    keyboardBlurFallbackTimer = undefined
+  }
+
+  const resetLongTextKeyboardState = () => {
+    clearKeyboardBlurFallback()
+    longTextKeyboardVisible.value = false
+  }
+
+  const handleLongTextKeyboardFocus = () => {
+    clearKeyboardBlurFallback()
+    longTextKeyboardVisible.value = true
+  }
+
+  const handleLongTextKeyboardBlur = () => {
+    clearKeyboardBlurFallback()
+
+    // 长文本之间切换时会先 blur 再 focus,延迟恢复可避免底部操作栏闪现。
+    keyboardBlurFallbackTimer = setTimeout(() => {
+      longTextKeyboardVisible.value = false
+      keyboardBlurFallbackTimer = undefined
+    }, KEYBOARD_BLUR_FALLBACK_DELAY_MS)
+  }
+
+  const handleLongTextKeyboardHeightChange = (result: KeyboardHeightChangeResult) => {
+    const height = Number(result.height ?? 0)
+
+    if (Number.isFinite(height) && height > 0) {
+      clearKeyboardBlurFallback()
+      longTextKeyboardVisible.value = true
+      return
+    }
+
+    resetLongTextKeyboardState()
+  }
+
+  onShow(resetLongTextKeyboardState)
+  onUnload(resetLongTextKeyboardState)
+
+  return {
+    longTextKeyboardVisible,
+    handleLongTextKeyboardFocus,
+    handleLongTextKeyboardBlur,
+    handleLongTextKeyboardHeightChange,
+  }
+}

+ 21 - 0
src/pages-task/task-form/composables/useTaskForm.ts

@@ -4,6 +4,7 @@ import dayjs from 'dayjs'
 
 import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom'
 import type {
+  DraftTaskContentResponse,
   TaskContentConfig,
   TaskContentConfigDeriveFrom,
   TaskFieldConfigItem,
@@ -448,6 +449,25 @@ export const useTaskForm = () => {
     clearTaskFormState()
   }
 
+  const fillFormFromDraft = (draftTaskContent: DraftTaskContentResponse): number => {
+    let filledFieldCount = 0
+
+    for (const field of taskFieldConfigList.value) {
+      const draftField = draftTaskContent[field.taskFiledValue]
+      const draftItem = Array.isArray(draftField?.items) ? draftField.items[0] : undefined
+
+      if (!draftItem) continue
+
+      form.value.value[field.id] = {
+        label: draftItem.label == null ? '' : String(draftItem.label),
+        value: draftItem.value == null ? '' : String(draftItem.value),
+      }
+      filledFieldCount += 1
+    }
+
+    return filledFieldCount
+  }
+
   const activateTaskForm = (
     taskTypeId: string,
     derivedFieldValue?: DynamicFormFieldValue
@@ -649,6 +669,7 @@ export const useTaskForm = () => {
     activeTaskTypeId,
     taskTypes,
     loadTaskForm,
+    fillFormFromDraft,
     resetTaskForm,
     handleDerivedFieldChange,
   }

+ 2 - 1
src/pages-task/task-form/composables/useTaskFormAction.ts

@@ -28,6 +28,7 @@ const TEXT_LENGTH_FIELD_TYPES = new Set(['text', 'readonlytext', 'longtext'])
 const SUCCESS_TOAST_DURATION = 3000
 const API_SUCCESS_CODE = 0
 const DUPLICATE_IMAGE_CODE = 1
+const DEFAULT_DRAFT_PACKAGE_ID = '1'
 
 const ACTION_API_MAP = {
   save: draftTaskContentApi,
@@ -280,7 +281,7 @@ export const useTaskFormAction = ({
       return null
     }
 
-    const packageId = getPackageId()
+    const packageId = action === 'save' ? DEFAULT_DRAFT_PACKAGE_ID : getPackageId()
 
     if (action === 'submit' && !packageId) {
       if (taskFieldConfigList.value.some(isMultipleSelectDrugField)) {

+ 38 - 1
src/pages-task/task-form/index.vue

@@ -44,6 +44,9 @@
             :disabled="isActionPending"
             @derived-change="handleDerivedFieldChange"
             @uploading-change="handleFieldUploadingChange"
+            @long-text-keyboard-focus="handleLongTextKeyboardFocus"
+            @long-text-keyboard-blur="handleLongTextKeyboardBlur"
+            @long-text-keyboard-height-change="handleLongTextKeyboardHeightChange"
           />
         </template>
       </view>
@@ -58,6 +61,7 @@
 
     <view
       v-if="!isQuestionnaireTask && taskFieldConfigList.length"
+      v-show="!longTextKeyboardVisible"
       class="task-form-footer"
       :style="footerStyle"
     >
@@ -87,10 +91,14 @@ import { computed, ref } from 'vue'
 
 import { onLoad } from '@dcloudio/uni-app'
 
+import { getDraftTaskContentApi } from '@/services/modules/task/taskFrom'
+import type { DraftTaskContentResponse } from '@/services/modules/task/taskFrom/type'
+
 import DuplicateImageDialog from './components/DuplicateImageDialog.vue'
 import DynamicTaskFormFields from './components/DynamicTaskFormFields.vue'
 import QuestionnaireTask from './components/QuestionnaireTask.vue'
 import TaskFormSkeleton from './components/TaskFormSkeleton.vue'
+import { useLongTextKeyboardVisibility } from './composables/useLongTextKeyboardVisibility'
 import { useTaskForm } from './composables/useTaskForm'
 import { useTaskFormAction } from './composables/useTaskFormAction'
 import { useTaskFormPageFeatures } from './composables/useTaskFormPageFeatures'
@@ -112,6 +120,7 @@ const {
   activeTaskTypeId,
   taskTypes,
   loadTaskForm,
+  fillFormFromDraft,
   handleDerivedFieldChange,
 } = useTaskForm()
 
@@ -130,6 +139,12 @@ const {
 const { showNotice, noticeText, selectLevel, initializeTaskFormPage } = useTaskFormPageFeatures()
 
 const { contentStyle, footerStyle, initSafeArea } = useTaskFormSafeArea()
+const {
+  longTextKeyboardVisible,
+  handleLongTextKeyboardFocus,
+  handleLongTextKeyboardBlur,
+  handleLongTextKeyboardHeightChange,
+} = useLongTextKeyboardVisibility()
 
 const isQuestionnaireTask = computed(() => taskTypeId.value === '801')
 const isAttachmentUploading = computed(() => uploadingFieldIds.value.size > 0)
@@ -159,6 +174,18 @@ const decodeRouteParam = (value?: string): string => {
   }
 }
 
+const loadDraftTaskContent = async (): Promise<DraftTaskContentResponse | null> => {
+  try {
+    const response = await getDraftTaskContentApi(taskTypeId.value)
+
+    console.log('[task-form] 当前保存的表单内容', response.data)
+    return response.data
+  } catch (error) {
+    console.error('[task-form] 获取当前保存的表单内容失败', error)
+    return null
+  }
+}
+
 const loadDynamicTaskForm = async (pageInitialization?: Promise<void>): Promise<void> => {
   if (!taskTypeId.value || formLoading.value) return
 
@@ -167,7 +194,17 @@ const loadDynamicTaskForm = async (pageInitialization?: Promise<void>): Promise<
   uploadingFieldIds.value = new Set()
 
   try {
-    await Promise.all([pageInitialization ?? Promise.resolve(), loadTaskForm(taskTypeId.value)])
+    const [, , draftTaskContent] = await Promise.all([
+      pageInitialization ?? Promise.resolve(),
+      loadTaskForm(taskTypeId.value),
+      loadDraftTaskContent(),
+    ])
+
+    if (draftTaskContent) {
+      const filledFieldCount = fillFormFromDraft(draftTaskContent)
+
+      console.log(`[task-form] 已回填 ${filledFieldCount} 个草稿字段`)
+    }
   } catch (error) {
     console.error('[task-form] 表单配置加载失败', error)
     formErrorMessage.value = '表单加载失败,请稍后重试'

+ 8 - 1
src/services/modules/task/taskFrom/index.ts

@@ -1,5 +1,6 @@
 import http from '../../../index'
 import type {
+  DraftTaskContentResponse,
   PackageRuleTaskTypeResponse,
   QuestionnaireListItem,
   QuestionnaireTaskContentConfigResponse,
@@ -48,11 +49,17 @@ export const draftTaskContentApi = (
   saveTaskContentBody: SaveTaskContentBody
 ) => {
   return http.post<SaveTaskContentResponse>(
-    `/admin/api/task-content/draft?packageId=${saveTaskContentQuery.packageId}&taskTypeId=${saveTaskContentQuery.taskTypeId}`,
+    `/admin/api/task-content/draft/${saveTaskContentQuery.taskTypeId}/1`,
     saveTaskContentBody
   )
 }
 
+export const getDraftTaskContentApi = (taskTypeId: string, packageId: string | number = 1) => {
+  return http.get<DraftTaskContentResponse | null>(
+    `/admin/api/task-content/draft/${taskTypeId}/${packageId}`
+  )
+}
+
 export const checkAreaTypeApi = (deptId: string) => {
   return http.get<number>(`/admin/dept/check-area-type?deptId=${deptId}`)
 }

+ 14 - 0
src/services/modules/task/taskFrom/type.d.ts

@@ -99,6 +99,20 @@ export interface SaveTaskContentField {
 
 export type SaveTaskContentBody = Record<string, SaveTaskContentField>
 
+export interface DraftTaskContentItem {
+  label: string
+  type: string
+  value: string
+}
+
+export interface DraftTaskContentField {
+  seq: number
+  required: boolean | null
+  items: DraftTaskContentItem[]
+}
+
+export type DraftTaskContentResponse = Record<string, DraftTaskContentField>
+
 export interface SaveTaskContentSuccessData {
   score: string
   type: string