import { type Ref, ref } from 'vue' import { draftTaskContentApi, saveTaskContentApi } from '@/services/modules/task/taskFrom' import type { SaveTaskContentBody, SaveTaskContentQuery, SaveTaskContentSuccessData, } from '@/services/modules/task/taskFrom/type' import type { DynamicFormState, TaskFieldConfigViewItem } from './useTaskForm' interface UseTaskFormActionOptions { taskTypeId: Ref form: Ref taskFieldConfigList: Ref } type FormAction = 'save' | 'submit' const PACKAGE_FIELD_ALIASES = new Set(['approvalResult', 'scorePackage']) const IMAGE_FIELD_TYPE = 'img' const SUCCESS_TOAST_DURATION = 3000 const ACTION_API_MAP = { save: draftTaskContentApi, submit: saveTaskContentApi, } satisfies Record const ACTION_FAILURE_TEXT = { save: '保存失败', submit: '提交失败', } satisfies Record 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 } return Math.floor(size) } 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 isSaveTaskContentSuccessData = (data: unknown): data is SaveTaskContentSuccessData => { if (!data || typeof data !== 'object' || Array.isArray(data)) { return false } const record = data as Record return typeof record.score === 'string' && typeof record.type === 'string' } export const useTaskFormAction = ({ taskTypeId, form, taskFieldConfigList, }: UseTaskFormActionOptions) => { let actionLocked = false const duplicateImageVisible = ref(false) const duplicateImageList = ref([]) const findPackageField = (): TaskFieldConfigViewItem | undefined => { return taskFieldConfigList.value.find((field) => PACKAGE_FIELD_ALIASES.has(field.alias ?? '')) } 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 maxSize = normalizeSizeLimit(field.taskFiledMaxsize) 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 } if (maxSize !== null && imageCount > maxSize) { showMessage(`${field.taskFiledValue}最多上传${maxSize}张`) 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 } 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 (!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) { 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) { 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.taskFiledType, value: String(fieldValue?.value ?? ''), }, ], } } return payload } const clearDuplicateImages = (): void => { duplicateImageList.value = [] } const showDuplicateImages = (images: string[]): void => { duplicateImageList.value = images duplicateImageVisible.value = true } const handleActionSuccess = (action: FormAction, data: unknown): void => { if (action === 'save') { showMessage('保存成功', SUCCESS_TOAST_DURATION) } else { if (!isSaveTaskContentSuccessData(data)) { showMessage('提交结果数据异常') return } showMessage(`提交成功,若审核通过可获得${data.score}积分`, SUCCESS_TOAST_DURATION) } setTimeout(() => { uni.navigateBack() }, SUCCESS_TOAST_DURATION) } const executeAction = async (action: FormAction): Promise => { if (actionLocked) { return } actionLocked = true 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) /** * 提交时: * code === 1 且 data 为非空图片数组, * 视为图片重复,不能进入成功逻辑。 */ if (action === 'submit' && response.code === 1) { const duplicateImages = extractImageUrls(response.data) if (duplicateImages.length > 0) { showDuplicateImages(duplicateImages) return } } /** * 所有非 0 状态码均不能视为成功。 */ if (response.code !== 0) { showMessage(response.msg || ACTION_FAILURE_TEXT[action]) return } handleActionSuccess(action, response.data) } catch (error: unknown) { console.error(`[useTaskFormAction] ${action} 失败`, error) showMessage(ACTION_FAILURE_TEXT[action]) } finally { actionLocked = false } } const handleSave = (): Promise => { return executeAction('save') } const handleSubmit = (): Promise => { return executeAction('submit') } return { duplicateImageVisible, duplicateImageList, clearDuplicateImages, handleSave, handleSubmit, } }