| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- import { onScopeDispose, type Ref, ref } from 'vue'
- import { draftTaskContentApi, saveTaskContentApi } from '@/services/modules/task/taskFrom'
- import type {
- SaveTaskContentBody,
- SaveTaskContentQuery,
- } from '@/services/modules/task/taskFrom/type'
- import {
- type DynamicFormState,
- isMultipleSelectDrugField,
- type TaskFieldConfigViewItem,
- } from './useTaskForm'
- interface UseTaskFormActionOptions {
- taskTypeId: Ref<string>
- form: Ref<DynamicFormState>
- taskFieldConfigList: Ref<TaskFieldConfigViewItem[]>
- }
- type FormAction = 'save' | 'submit'
- const PACKAGE_FIELD_ALIASES = ['scorePackage', 'approvalResult'] as const
- const IMAGE_FIELD_TYPE = 'img'
- 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 ACTION_API_MAP = {
- save: draftTaskContentApi,
- submit: saveTaskContentApi,
- } satisfies Record<FormAction, typeof saveTaskContentApi>
- const isEmptyValue = (value: unknown): boolean => {
- return value === undefined || value === null || String(value).trim() === ''
- }
- const normalizeSizeLimit = (value: unknown): number | null => {
- if (value === undefined || value === null || value === '') {
- return null
- }
- const size = Number(value)
- if (!Number.isFinite(size) || size <= 0) {
- return null
- }
- const normalizedSize = Math.floor(size)
- return normalizedSize > 0 ? normalizedSize : null
- }
- const getContentLength = (value: unknown): number => {
- if (value === undefined || value === null) {
- return 0
- }
- return Array.from(String(value).trim()).length
- }
- const getImageCount = (value: unknown): number => {
- if (typeof value !== 'string') {
- return 0
- }
- return value
- .split(',')
- .map((item) => item.trim())
- .filter(Boolean).length
- }
- const showMessage = (title: string, duration?: number): void => {
- uni.showToast({
- title,
- icon: 'none',
- ...(duration === undefined ? {} : { duration }),
- })
- }
- /**
- * 从接口 data 中提取有效图片地址。
- */
- const extractImageUrls = (data: unknown): string[] => {
- if (!Array.isArray(data)) {
- return []
- }
- const imageUrls = data
- .filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
- .map((item) => item.trim())
- return [...new Set(imageUrls)]
- }
- const getSuccessScore = (data: unknown): string | null => {
- if (!data || typeof data !== 'object' || Array.isArray(data)) {
- return null
- }
- const score = (data as Record<string, unknown>).score
- return typeof score === 'string' || typeof score === 'number' ? String(score) : null
- }
- export const useTaskFormAction = ({
- taskTypeId,
- form,
- taskFieldConfigList,
- }: UseTaskFormActionOptions) => {
- const isActionPending = ref(false)
- const duplicateImageVisible = ref(false)
- const duplicateImageList = ref<string[]>([])
- let navigateBackTimer: ReturnType<typeof setTimeout> | null = null
- let disposed = false
- onScopeDispose(() => {
- disposed = true
- if (navigateBackTimer) {
- clearTimeout(navigateBackTimer)
- navigateBackTimer = null
- }
- })
- const findPackageField = (): TaskFieldConfigViewItem | undefined => {
- for (const alias of PACKAGE_FIELD_ALIASES) {
- const field = taskFieldConfigList.value.find((item) => item.alias === alias)
- if (field) return field
- }
- return undefined
- }
- const getPackageId = (): string => {
- const packageField = findPackageField()
- if (!packageField) {
- return ''
- }
- return String(form.value.value[packageField.id]?.value ?? '').trim()
- }
- const validateImageField = (field: TaskFieldConfigViewItem, value: unknown): boolean => {
- const imageCount = getImageCount(value)
- const configuredMinSize = normalizeSizeLimit(field.taskFiledMinsize)
- const minSize = field.isMustfill === '1' ? (configuredMinSize ?? 1) : configuredMinSize
- if (imageCount === 0 && field.isMustfill !== '1') {
- return true
- }
- if (minSize !== null && imageCount < minSize) {
- showMessage(`${field.taskFiledValue}最少需要${minSize}张`)
- return false
- }
- return true
- }
- const validateContentField = (field: TaskFieldConfigViewItem, value: unknown): boolean => {
- if (isEmptyValue(value)) {
- if (field.isMustfill === '1') {
- showMessage(`请完善${field.taskFiledValue}`)
- return false
- }
- return true
- }
- if (!TEXT_LENGTH_FIELD_TYPES.has(field.taskFiledType)) {
- return true
- }
- const contentLength = getContentLength(value)
- const minSize = normalizeSizeLimit(field.taskFiledMinsize)
- const maxSize = normalizeSizeLimit(field.taskFiledMaxsize)
- if (minSize !== null && contentLength < minSize) {
- showMessage(`${field.taskFiledValue}最少输入${minSize}个字符`)
- return false
- }
- if (maxSize !== null && contentLength > maxSize) {
- showMessage(`${field.taskFiledValue}最多输入${maxSize}个字符`)
- return false
- }
- return true
- }
- const validateField = (field: TaskFieldConfigViewItem): boolean => {
- const value = form.value.value[field.id]?.value
- if (field.taskFiledType === IMAGE_FIELD_TYPE) {
- return validateImageField(field, value)
- }
- return validateContentField(field, value)
- }
- const validateSubmitForm = (): boolean => {
- for (const field of taskFieldConfigList.value) {
- if (!field.show) continue
- if (!validateField(field)) {
- return false
- }
- }
- return true
- }
- const buildActionQuery = (action: FormAction): SaveTaskContentQuery | null => {
- const normalizedTaskTypeId = taskTypeId.value.trim()
- if (!normalizedTaskTypeId) {
- showMessage('缺少 taskTypeId')
- return null
- }
- const packageId = getPackageId()
- if (action === 'submit' && !packageId) {
- if (taskFieldConfigList.value.some(isMultipleSelectDrugField)) {
- showMessage('请先选择积分包')
- return null
- }
- const packageField = findPackageField()
- showMessage(
- packageField ? `请完善${packageField.taskFiledValue}` : '表单配置中未找到关联积分包字段'
- )
- return null
- }
- return {
- taskTypeId: normalizedTaskTypeId,
- packageId,
- }
- }
- const buildSubmitPayload = (): SaveTaskContentBody => {
- const payload: SaveTaskContentBody = {}
- for (const field of taskFieldConfigList.value) {
- if (!field.show) continue
- const fieldValue = form.value.value[field.id]
- payload[field.taskFiledValue] = {
- seq: field.seq ?? 0,
- ...(field.isMustfill === '1'
- ? {
- required: true,
- }
- : {}),
- items: [
- {
- label: String(fieldValue?.label ?? ''),
- type: field.originalTaskFiledType,
- value: String(fieldValue?.value ?? ''),
- },
- ],
- }
- }
- return payload
- }
- const clearDuplicateImages = (): void => {
- duplicateImageVisible.value = false
- duplicateImageList.value = []
- }
- const showDuplicateImages = (images: string[]): void => {
- duplicateImageList.value = images
- duplicateImageVisible.value = true
- }
- const handleActionSuccess = (action: FormAction, data: unknown): boolean => {
- if (action === 'save') {
- showMessage('保存成功', SUCCESS_TOAST_DURATION)
- } else {
- const score = getSuccessScore(data)
- if (score === null) {
- showMessage('提交结果数据异常')
- return false
- }
- showMessage(`提交成功,若审核通过可获得${score}积分`, SUCCESS_TOAST_DURATION)
- }
- if (disposed) return false
- navigateBackTimer = setTimeout(() => {
- navigateBackTimer = null
- if (disposed) return
- uni.navigateBack({
- complete: () => {
- if (!disposed) {
- isActionPending.value = false
- }
- },
- })
- }, SUCCESS_TOAST_DURATION)
- return true
- }
- const executeAction = async (action: FormAction): Promise<void> => {
- if (isActionPending.value) {
- return
- }
- isActionPending.value = true
- let navigationScheduled = false
- try {
- if (action === 'submit' && !validateSubmitForm()) {
- return
- }
- const query = buildActionQuery(action)
- if (!query) {
- return
- }
- const payload = buildSubmitPayload()
- const requestApi = ACTION_API_MAP[action]
- const response = await requestApi(query, payload)
- if (disposed) return
- if (response.code !== API_SUCCESS_CODE) {
- const duplicateImages =
- action === 'submit' && response.code === DUPLICATE_IMAGE_CODE
- ? extractImageUrls(response.data)
- : []
- if (duplicateImages.length > 0) {
- showDuplicateImages(duplicateImages)
- return
- }
- uni.showModal({
- title: '提示',
- content: response.msg || '操作失败,请稍后重试',
- showCancel: false,
- })
- return
- }
- navigationScheduled = handleActionSuccess(action, response.data)
- } catch (error: unknown) {
- if (!disposed) {
- console.error(`[useTaskFormAction] ${action} 失败`, error)
- }
- } finally {
- if (!disposed && !navigationScheduled) {
- isActionPending.value = false
- }
- }
- }
- const handleSave = (): Promise<void> => {
- return executeAction('save')
- }
- const handleSubmit = (): Promise<void> => {
- return executeAction('submit')
- }
- return {
- isActionPending,
- duplicateImageVisible,
- duplicateImageList,
- clearDuplicateImages,
- handleSave,
- handleSubmit,
- }
- }
|