|
@@ -3,7 +3,12 @@ import { ref } from 'vue'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
|
|
import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom'
|
|
import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom'
|
|
|
-import type { TaskFieldConfigItem, TaskFieldDictMap } from '@/services/modules/task/taskFrom/type'
|
|
|
|
|
|
|
+import type {
|
|
|
|
|
+ TaskContentConfig,
|
|
|
|
|
+ TaskContentConfigDeriveFrom,
|
|
|
|
|
+ TaskFieldConfigItem,
|
|
|
|
|
+ TaskFieldDictMap,
|
|
|
|
|
+} from '@/services/modules/task/taskFrom/type'
|
|
|
|
|
|
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
|
|
|
|
|
@@ -58,6 +63,12 @@ export interface DynamicFormState {
|
|
|
value: Record<string, DynamicFormFieldValue>
|
|
value: Record<string, DynamicFormFieldValue>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+export interface DerivedFieldChangeEvent {
|
|
|
|
|
+ previousValue: FieldValue
|
|
|
|
|
+ value: string
|
|
|
|
|
+ label: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const createEmptyFieldValue = (): DynamicFormFieldValue => ({
|
|
const createEmptyFieldValue = (): DynamicFormFieldValue => ({
|
|
|
label: '',
|
|
label: '',
|
|
|
value: '',
|
|
value: '',
|
|
@@ -198,6 +209,57 @@ const serializeTaskFieldConfig = (
|
|
|
}))
|
|
}))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const isRecord = (source: unknown): source is Record<string, unknown> => {
|
|
|
|
|
+ return Boolean(source) && typeof source === 'object' && !Array.isArray(source)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const serializeTaskContentConfigResponse = (source: unknown): TaskContentConfig | null => {
|
|
|
|
|
+ if (!isRecord(source)) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!Array.isArray(source.config)) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ config: source.config as TaskFieldConfigItem[],
|
|
|
|
|
+ dict: isRecord(source.dict) ? (source.dict as TaskFieldDictMap) : {},
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const serializeDeriveFrom = (source: unknown): TaskContentConfigDeriveFrom | null => {
|
|
|
|
|
+ if (!isRecord(source) || typeof source.field !== 'string' || !isRecord(source.valueMapping)) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const field = source.field.trim()
|
|
|
|
|
+ const valueMapping = Object.entries(source.valueMapping).reduce<Record<string, string>>(
|
|
|
|
|
+ (result, [value, taskTypeId]) => {
|
|
|
|
|
+ const normalizedTaskTypeId =
|
|
|
|
|
+ typeof taskTypeId === 'string' || typeof taskTypeId === 'number'
|
|
|
|
|
+ ? String(taskTypeId).trim()
|
|
|
|
|
+ : ''
|
|
|
|
|
+
|
|
|
|
|
+ if (normalizedTaskTypeId) {
|
|
|
|
|
+ result[String(value)] = normalizedTaskTypeId
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+ },
|
|
|
|
|
+ {}
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!field || !Object.keys(valueMapping).length) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ field,
|
|
|
|
|
+ valueMapping,
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 负责:
|
|
* 负责:
|
|
|
* 1. 获取动态表单配置
|
|
* 1. 获取动态表单配置
|
|
@@ -214,10 +276,14 @@ export const useTaskForm = () => {
|
|
|
|
|
|
|
|
const deptId = userStore.currentUserInfo?.deptId ?? ''
|
|
const deptId = userStore.currentUserInfo?.deptId ?? ''
|
|
|
const form = ref<DynamicFormState>(createEmptyFormState())
|
|
const form = ref<DynamicFormState>(createEmptyFormState())
|
|
|
-
|
|
|
|
|
const taskFieldConfigList = ref<TaskFieldConfigViewItem[]>([])
|
|
const taskFieldConfigList = ref<TaskFieldConfigViewItem[]>([])
|
|
|
|
|
+ const activeTaskTypeId = ref('')
|
|
|
|
|
|
|
|
- const initFormState = (config: TaskFieldConfigViewItem[]): void => {
|
|
|
|
|
|
|
+ const taskFormConfigMap = new Map<string, TaskFieldConfigViewItem[]>()
|
|
|
|
|
+
|
|
|
|
|
+ let deriveFrom: TaskContentConfigDeriveFrom | null = null
|
|
|
|
|
+
|
|
|
|
|
+ const createFormState = (config: TaskFieldConfigViewItem[]): DynamicFormState => {
|
|
|
const formValue: DynamicFormState['value'] = {}
|
|
const formValue: DynamicFormState['value'] = {}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -233,7 +299,7 @@ export const useTaskForm = () => {
|
|
|
formValue[field.id] = createInitialFieldValue(field, initialDateTime)
|
|
formValue[field.id] = createInitialFieldValue(field, initialDateTime)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- form.value = {
|
|
|
|
|
|
|
+ return {
|
|
|
value: formValue,
|
|
value: formValue,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -241,6 +307,148 @@ export const useTaskForm = () => {
|
|
|
const resetTaskForm = (): void => {
|
|
const resetTaskForm = (): void => {
|
|
|
form.value = createEmptyFormState()
|
|
form.value = createEmptyFormState()
|
|
|
taskFieldConfigList.value = []
|
|
taskFieldConfigList.value = []
|
|
|
|
|
+ activeTaskTypeId.value = ''
|
|
|
|
|
+ taskFormConfigMap.clear()
|
|
|
|
|
+ deriveFrom = null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const activateTaskForm = (
|
|
|
|
|
+ taskTypeId: string,
|
|
|
|
|
+ derivedFieldValue?: DynamicFormFieldValue
|
|
|
|
|
+ ): boolean => {
|
|
|
|
|
+ const normalizedTaskTypeId = taskTypeId.trim()
|
|
|
|
|
+ const targetConfig = taskFormConfigMap.get(normalizedTaskTypeId)
|
|
|
|
|
+
|
|
|
|
|
+ if (!targetConfig) {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 每次切换都重新创建目标表单状态:
|
|
|
|
|
+ * - 不保留当前表单已经输入的内容
|
|
|
|
|
+ * - 不恢复目标表单上一次输入的内容
|
|
|
|
|
+ * - datetime 等字段继续使用原有初始化规则
|
|
|
|
|
+ */
|
|
|
|
|
+ const targetFormState = createFormState(targetConfig)
|
|
|
|
|
+
|
|
|
|
|
+ if (deriveFrom && derivedFieldValue) {
|
|
|
|
|
+ const derivedFieldName = deriveFrom.field
|
|
|
|
|
+ const targetDerivedField = targetConfig.find(
|
|
|
|
|
+ (field) => field.taskFiledValue.trim() === derivedFieldName
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (targetDerivedField) {
|
|
|
|
|
+ targetFormState.value[targetDerivedField.id] = {
|
|
|
|
|
+ label: derivedFieldValue.label,
|
|
|
|
|
+ value: derivedFieldValue.value,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ activeTaskTypeId.value = normalizedTaskTypeId
|
|
|
|
|
+ taskFieldConfigList.value = targetConfig
|
|
|
|
|
+ form.value = targetFormState
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const registerTaskForm = (taskTypeId: string, taskContentConfig: TaskContentConfig): void => {
|
|
|
|
|
+ const normalizedTaskTypeId = taskTypeId.trim()
|
|
|
|
|
+
|
|
|
|
|
+ if (!normalizedTaskTypeId) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const config = serializeTaskFieldConfig(taskContentConfig.config, taskContentConfig.dict)
|
|
|
|
|
+
|
|
|
|
|
+ taskFormConfigMap.set(normalizedTaskTypeId, config)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const loadSingleTaskForm = (taskTypeId: string, taskContentConfig: TaskContentConfig): void => {
|
|
|
|
|
+ registerTaskForm(taskTypeId, taskContentConfig)
|
|
|
|
|
+ activateTaskForm(taskTypeId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const loadMultipleTaskForms = (
|
|
|
|
|
+ source: Record<string, unknown>,
|
|
|
|
|
+ requestedTaskTypeId: string
|
|
|
|
|
+ ): boolean => {
|
|
|
|
|
+ deriveFrom = serializeDeriveFrom(source.deriveFrom)
|
|
|
|
|
+
|
|
|
|
|
+ if (!deriveFrom) {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (const [taskTypeId, taskContentConfig] of Object.entries(source)) {
|
|
|
|
|
+ const serializedTaskContentConfig = serializeTaskContentConfigResponse(taskContentConfig)
|
|
|
|
|
+
|
|
|
|
|
+ if (taskTypeId !== 'deriveFrom' && serializedTaskContentConfig) {
|
|
|
|
|
+ registerTaskForm(taskTypeId, serializedTaskContentConfig)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const mappedTaskTypeIds = Object.values(deriveFrom.valueMapping)
|
|
|
|
|
+ const initialTaskTypeId =
|
|
|
|
|
+ (taskFormConfigMap.has(requestedTaskTypeId) ? requestedTaskTypeId : undefined) ??
|
|
|
|
|
+ mappedTaskTypeIds.find((taskTypeId) =>
|
|
|
|
|
+ taskFormConfigMap
|
|
|
|
|
+ .get(taskTypeId)
|
|
|
|
|
+ ?.some((field) => field.taskFiledValue.trim() === deriveFrom?.field)
|
|
|
|
|
+ ) ??
|
|
|
|
|
+ mappedTaskTypeIds.find((taskTypeId) => taskFormConfigMap.has(taskTypeId)) ??
|
|
|
|
|
+ taskFormConfigMap.keys().next().value
|
|
|
|
|
+
|
|
|
|
|
+ return typeof initialTaskTypeId === 'string' && activateTaskForm(initialTaskTypeId)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const restoreDerivedFieldValue = (
|
|
|
|
|
+ field: TaskFieldConfigViewItem,
|
|
|
|
|
+ previousValue: FieldValue
|
|
|
|
|
+ ): void => {
|
|
|
|
|
+ const currentFieldValue = form.value.value[field.id]
|
|
|
|
|
+
|
|
|
|
|
+ if (!currentFieldValue) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const previousValueString =
|
|
|
|
|
+ previousValue === undefined || previousValue === '' ? '' : String(previousValue)
|
|
|
|
|
+ const previousOption = field.options?.find((option) => option.value === previousValueString)
|
|
|
|
|
+
|
|
|
|
|
+ currentFieldValue.value = previousValueString
|
|
|
|
|
+ currentFieldValue.label = previousOption?.label ?? ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 多表单模式下,根据 deriveFrom 指定字段的值切换实际表单。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 切换时重新初始化目标表单,仅把派生字段当前选择同步到目标表单。
|
|
|
|
|
+ */
|
|
|
|
|
+ const handleDerivedFieldChange = (
|
|
|
|
|
+ field: TaskFieldConfigViewItem,
|
|
|
|
|
+ event: DerivedFieldChangeEvent
|
|
|
|
|
+ ): void => {
|
|
|
|
|
+ if (!deriveFrom || field.taskFiledValue.trim() !== deriveFrom.field) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const targetTaskTypeId = deriveFrom.valueMapping[event.value]
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ !targetTaskTypeId ||
|
|
|
|
|
+ !activateTaskForm(targetTaskTypeId, {
|
|
|
|
|
+ label: event.label,
|
|
|
|
|
+ value: event.value,
|
|
|
|
|
+ })
|
|
|
|
|
+ ) {
|
|
|
|
|
+ restoreDerivedFieldValue(field, event.previousValue)
|
|
|
|
|
+
|
|
|
|
|
+ console.warn(`[useTaskForm] 未找到派生值 ${event.value} 对应的表单配置`)
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: '未找到对应的表单配置',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const loadTaskForm = async (taskTypeId: string): Promise<void> => {
|
|
const loadTaskForm = async (taskTypeId: string): Promise<void> => {
|
|
@@ -252,17 +460,19 @@ export const useTaskForm = () => {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const res = await getTaskContentConfigByTaskTypeIdApi(normalizedTaskTypeId)
|
|
const res = await getTaskContentConfigByTaskTypeIdApi(normalizedTaskTypeId)
|
|
|
|
|
+ const responseData: unknown = res.data
|
|
|
|
|
+ const singleTaskContentConfig = serializeTaskContentConfigResponse(responseData)
|
|
|
|
|
|
|
|
- const rawConfig: TaskFieldConfigItem[] = Array.isArray(res.data?.config)
|
|
|
|
|
- ? res.data.config
|
|
|
|
|
- : []
|
|
|
|
|
-
|
|
|
|
|
- const dict: TaskFieldDictMap = res.data?.dict ?? {}
|
|
|
|
|
|
|
+ resetTaskForm()
|
|
|
|
|
|
|
|
- const config = serializeTaskFieldConfig(rawConfig, dict)
|
|
|
|
|
|
|
+ if (singleTaskContentConfig) {
|
|
|
|
|
+ loadSingleTaskForm(normalizedTaskTypeId, singleTaskContentConfig)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- taskFieldConfigList.value = config
|
|
|
|
|
- initFormState(config)
|
|
|
|
|
|
|
+ if (!isRecord(responseData) || !loadMultipleTaskForms(responseData, normalizedTaskTypeId)) {
|
|
|
|
|
+ throw new Error('动态表单配置格式无效')
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
resetTaskForm()
|
|
resetTaskForm()
|
|
|
throw error
|
|
throw error
|
|
@@ -272,7 +482,9 @@ export const useTaskForm = () => {
|
|
|
return {
|
|
return {
|
|
|
form,
|
|
form,
|
|
|
taskFieldConfigList,
|
|
taskFieldConfigList,
|
|
|
|
|
+ activeTaskTypeId,
|
|
|
loadTaskForm,
|
|
loadTaskForm,
|
|
|
resetTaskForm,
|
|
resetTaskForm,
|
|
|
|
|
+ handleDerivedFieldChange,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|