فهرست منبع

重构任务表单组件,支持医院和药店选择,优化数据请求和类型定义

yuanmingze 1 ماه پیش
والد
کامیت
3f1e29dd52

+ 52 - 21
src/pages-common/entity-selector/index.vue

@@ -25,7 +25,7 @@
         </view>
 
         <view class="item-content">
-          <text class="item-name">{{ item.pharmacyName }}</text>
+          <text class="item-name">{{ getItemName(item) }}</text>
           <text v-if="item.address" class="item-address">
             {{ item.address }}
           </text>
@@ -104,21 +104,30 @@ import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
 import { debounce } from '@/plugins/debounce'
 
 import {
+  getHospitalByNameApi,
   getWmDaPharmacyByNameApi,
+  listHospitalByNameApi,
   listWmDaPharmacyByNameApi,
 } from '@/services/modules/task/entitySelector'
 import type {
-  ListWmDaPharmacyByNameRequest,
+  EntitySelectorRequest,
+  HospitalItem,
   PharmacyItem,
 } from '@/services/modules/task/entitySelector/type'
 
 const aliasConfig = {
   pharmacyName: {
     entityName: '药店',
+    eventName: 'pharmacyNameSelect',
+  },
+  yymc: {
+    entityName: '医院',
+    eventName: 'yymcSelect',
   },
 } as const
 
 type PageAlias = keyof typeof aliasConfig
+type EntityItem = PharmacyItem | HospitalItem
 
 interface PageQuery {
   alias?: string
@@ -126,8 +135,8 @@ interface PageQuery {
 
 const pageAlias = ref<PageAlias>('pharmacyName')
 const keyword = ref('')
-const list = ref<PharmacyItem[]>([])
-const selected = ref<PharmacyItem>()
+const list = ref<EntityItem[]>([])
+const selected = ref<EntityItem>()
 const loading = ref(false)
 const finished = ref(false)
 
@@ -147,6 +156,24 @@ const searchPlaceholder = computed(() => `搜索${entityName.value}名称`)
 const addTitle = computed(() => `新增${entityName.value}`)
 const addPlaceholder = computed(() => `请输入${entityName.value}名称`)
 
+const getItemName = (item: EntityItem): string => {
+  return 'yymc' in item ? item.yymc : item.pharmacyName
+}
+
+const requestEntityPage = async (
+  params: EntitySelectorRequest
+): Promise<{ records: EntityItem[]; pages: number }> => {
+  if (pageAlias.value === 'pharmacyName') {
+    const { data } = await listWmDaPharmacyByNameApi(params)
+
+    return data
+  }
+
+  const { data } = await listHospitalByNameApi(params)
+
+  return data
+}
+
 const loadList = async (reset = false) => {
   if (!reset && (loading.value || finished.value)) {
     return
@@ -165,23 +192,21 @@ const loadList = async (reset = false) => {
   loading.value = true
 
   try {
-    const params: ListWmDaPharmacyByNameRequest = {
+    const params: EntitySelectorRequest = {
       current: currentPage,
       size: pagination.size,
       name: keyword.value.trim(),
     }
 
-    const { data } = await listWmDaPharmacyByNameApi(params)
+    const { records = [], pages } = await requestEntityPage(params)
 
     if (version !== requestVersion) {
       return
     }
 
-    const records = data.records ?? []
-
     list.value = reset ? records : [...list.value, ...records]
 
-    finished.value = currentPage >= data.pages || records.length < pagination.size
+    finished.value = currentPage >= pages || records.length < pagination.size
 
     if (!finished.value) {
       pagination.current = currentPage + 1
@@ -226,11 +251,16 @@ const getOpenerEventChannel = () => {
   return proxy?.getOpenerEventChannel?.()
 }
 
-const selectItem = (item: PharmacyItem) => {
+const emitSelectedItem = (item: EntityItem | string) => {
   const eventChannel = getOpenerEventChannel()
+
+  eventChannel?.emit(aliasConfig[pageAlias.value].eventName, item)
+}
+
+const selectItem = (item: EntityItem) => {
   selected.value = item
 
-  eventChannel?.emit('pharmacyNameSelect', item)
+  emitSelectedItem(item)
   uni.navigateBack()
 }
 
@@ -262,31 +292,32 @@ const confirmAdd = () => {
     addError.value = `${entityName.value}名称不能为空`
     return
   }
-  if (pageAlias.value === 'pharmacyName') {
-    pharmacyAddSubmit(name)
-  }
+  void entityAddSubmit(name)
 
   showAddDialog.value = false
 }
 
-const pharmacyAddSubmit = async (name: string) => {
-  const res = await getWmDaPharmacyByNameApi(name)
-  const eventChannel = getOpenerEventChannel()
-  if (res.data) {
+const entityAddSubmit = async (name: string) => {
+  const existingItem =
+    pageAlias.value === 'pharmacyName'
+      ? (await getWmDaPharmacyByNameApi(name)).data
+      : (await getHospitalByNameApi(name)).data
+
+  if (existingItem) {
     uni.showToast({
-      title: '药店已存在',
+      title: `${entityName.value}已存在`,
       icon: 'none',
       duration: 1000,
     })
 
-    eventChannel?.emit('pharmacyNameSelect', res.data)
+    emitSelectedItem(existingItem)
   } else {
     uni.showToast({
       title: '请完善信息',
       icon: 'none',
       duration: 1000,
     })
-    eventChannel?.emit('pharmacyNameSelect', name)
+    emitSelectedItem(name)
   }
   setTimeout(() => {
     uni.navigateBack()

+ 2 - 2
src/pages-task/task-form/components/Area.vue

@@ -102,8 +102,8 @@ const handleOpen = async () => {
 /**
  * 选择确认
  */
-const handleConfirm = (event: { selectedItems?: AreaOption[] }) => {
-  const items = event.selectedItems ?? []
+const handleConfirm = (event: { selectedOptions?: AreaOption[] }) => {
+  const items = event.selectedOptions ?? []
 
   if (!items.length) return
 

+ 23 - 4
src/pages-task/task-form/components/InputAutoSelect.vue

@@ -15,21 +15,40 @@
 <script setup lang="ts">
 import { computed } from 'vue'
 
-import type { TaskFieldConfigItem, TaskFieldDictMap } from '@/services/modules/task/taskFrom/type'
+import type { TaskFieldConfigItem } from '@/services/modules/task/taskFrom/type'
 
 interface Props {
   taskFieldConfig: TaskFieldConfigItem
-  taskFieldDict: TaskFieldDictMap
   disabled?: boolean
+  beforeOpen?: () => boolean | Promise<boolean>
 }
 
 const props = withDefaults(defineProps<Props>(), {
   disabled: false,
+  beforeOpen: undefined,
+})
+
+const label = defineModel<FieldValue>('label')
+const value = defineModel<FieldValue>('value')
+
+const cellValue = computed(() => {
+  return label.value === undefined ? '' : String(label.value)
 })
 
 const isDisabled = computed(() => props.disabled)
 
-const pickerClick = () => {
-  console.log('qaq')
+const pickerClick = async () => {
+  if (props.disabled) {
+    return
+  }
+
+  try {
+    const canOpen = await props.beforeOpen?.()
+    if (canOpen === false) {
+      return
+    }
+  } catch (error) {
+    console.error(`打开单选字段失败:${props.taskFieldConfig.taskFiledValue}`, error)
+  }
 }
 </script>

+ 224 - 12
src/pages-task/task-form/composables/useTaskForm.ts

@@ -3,7 +3,12 @@ import { ref } from 'vue'
 import dayjs from 'dayjs'
 
 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'
 
@@ -58,6 +63,12 @@ export interface DynamicFormState {
   value: Record<string, DynamicFormFieldValue>
 }
 
+export interface DerivedFieldChangeEvent {
+  previousValue: FieldValue
+  value: string
+  label: string
+}
+
 const createEmptyFieldValue = (): DynamicFormFieldValue => ({
   label: '',
   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. 获取动态表单配置
@@ -214,10 +276,14 @@ export const useTaskForm = () => {
 
   const deptId = userStore.currentUserInfo?.deptId ?? ''
   const form = ref<DynamicFormState>(createEmptyFormState())
-
   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'] = {}
 
     /**
@@ -233,7 +299,7 @@ export const useTaskForm = () => {
       formValue[field.id] = createInitialFieldValue(field, initialDateTime)
     }
 
-    form.value = {
+    return {
       value: formValue,
     }
   }
@@ -241,6 +307,148 @@ export const useTaskForm = () => {
   const resetTaskForm = (): void => {
     form.value = createEmptyFormState()
     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> => {
@@ -252,17 +460,19 @@ export const useTaskForm = () => {
 
     try {
       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) {
       resetTaskForm()
       throw error
@@ -272,7 +482,9 @@ export const useTaskForm = () => {
   return {
     form,
     taskFieldConfigList,
+    activeTaskTypeId,
     loadTaskForm,
     resetTaskForm,
+    handleDerivedFieldChange,
   }
 }

+ 39 - 3
src/pages-task/task-form/composables/useTaskSelectHandlers.ts

@@ -1,6 +1,6 @@
 import type { Ref } from 'vue'
 
-import type { PharmacyItem } from '@/services/modules/task/entitySelector/type'
+import type { HospitalItem, PharmacyItem } from '@/services/modules/task/entitySelector/type'
 import { getListDrugTableApi } from '@/services/modules/task/taskFrom/index'
 
 import type {
@@ -18,6 +18,11 @@ export interface SingleSelectChangeEvent {
   selectedItem: TaskFieldOption
 }
 
+const HOSPITAL_FIELD_SOURCE_MAP: Readonly<Record<string, keyof HospitalItem>> = {
+  yycs: 'city',
+  yydz: 'address',
+}
+
 interface UseTaskSelectHandlersOptions {
   taskTypeId: Ref<string>
   form: Ref<DynamicFormState>
@@ -164,8 +169,6 @@ export const useTaskSelectHandlers = ({
    * 特殊接口请求和业务校验写在这里。
    */
   const handleSingleSelectBeforeOpen = async (field: TaskFieldConfigViewItem): Promise<boolean> => {
-    console.log('field', field)
-
     if (field.taskTypeId === '18' && field.alias === 'pharmacyName') {
       uni.navigateTo({
         url: `/pages-common/entity-selector/index?alias=pharmacyName`,
@@ -177,6 +180,17 @@ export const useTaskSelectHandlers = ({
       })
       return false
     }
+    if (field.taskTypeId === '15' && field.alias === 'yymc') {
+      uni.navigateTo({
+        url: `/pages-common/entity-selector/index?alias=yymc`,
+        events: {
+          yymcSelect: (selectedHospitalItem: HospitalItem | string) => {
+            setHospitalNameSelectEvent(selectedHospitalItem)
+          },
+        },
+      })
+      return false
+    }
     if (taskTypeId.value === '47') {
       /**
        * OTC 推广任务示例。
@@ -320,9 +334,31 @@ export const useTaskSelectHandlers = ({
     const address = `${selectedPharmacyItem.province}-${selectedPharmacyItem.city}-${selectedPharmacyItem.area}`
     setFieldFormValueByAlias('shengshiqu', {
       label: address,
+      value: address,
+    })
+  }
+
+  const setHospitalNameSelectEvent = (selectedHospitalItem: HospitalItem | string) => {
+    if (typeof selectedHospitalItem === 'string') {
+      setFieldFormValueByAlias('yymc', {
+        label: selectedHospitalItem,
+        value: selectedHospitalItem,
+      })
+      return
+    }
 
+    setFieldFormValueByAlias('yymc', {
+      label: selectedHospitalItem.yymc,
+      value: selectedHospitalItem.id,
+    })
+
+    const address = `${selectedHospitalItem.province}-${selectedHospitalItem.city}-${selectedHospitalItem.area}`
+    setFieldFormValueByAlias('address', {
+      label: address,
       value: address,
     })
+
+    console.log('setHospitalNameSelectEvent', selectedHospitalItem)
   }
 
   return {

+ 26 - 12
src/pages-task/task-form/index.vue

@@ -28,6 +28,15 @@
                 v-model:value="form.value[item.id].value"
                 :task-field-config="item"
                 :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"
+                :before-open="() => handleSingleSelectBeforeOpen(item)"
                 @change="handleSingleSelectChange(item, $event)"
               />
 
@@ -81,13 +90,6 @@
                 :before-open="() => handleMultipleSelectBeforeOpen(item)"
               />
 
-              <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"
-              />
-
               <DataTimeRange
                 v-else-if="isDateTimeRange(item.taskFiledType)"
                 v-model:label="form.value[item.id].label"
@@ -164,11 +166,14 @@ import SingleSelect from './components/SingleSelect.vue'
 import SingleText from './components/SingleText.vue'
 import TaskLocation from './components/TaskLocation.vue'
 import { useTaskFieldType } from './composables/useTaskFieldType'
-import { useTaskForm } from './composables/useTaskForm'
+import { type TaskFieldConfigViewItem, useTaskForm } from './composables/useTaskForm'
 import { useTaskFormAction } from './composables/useTaskFormAction'
 import { useTaskFormPageFeatures } from './composables/useTaskFormPageFeatures'
 import { useTaskFormSafeArea } from './composables/useTaskFormSafeArea'
-import { useTaskSelectHandlers } from './composables/useTaskSelectHandlers'
+import {
+  type SingleSelectChangeEvent,
+  useTaskSelectHandlers,
+} from './composables/useTaskSelectHandlers'
 
 interface PageLoadOptions {
   taskTypeId?: string
@@ -177,11 +182,12 @@ interface PageLoadOptions {
 
 const taskTypeId = ref('')
 
-const { form, taskFieldConfigList, loadTaskForm } = useTaskForm()
+const { form, taskFieldConfigList, activeTaskTypeId, loadTaskForm, handleDerivedFieldChange } =
+  useTaskForm()
 
 const { handleSingleSelectBeforeOpen, handleSingleSelectChange, handleMultipleSelectBeforeOpen } =
   useTaskSelectHandlers({
-    taskTypeId,
+    taskTypeId: activeTaskTypeId,
     form,
     taskFieldConfigList,
   })
@@ -193,7 +199,7 @@ const {
   handleSave,
   handleSubmit,
 } = useTaskFormAction({
-  taskTypeId,
+  taskTypeId: activeTaskTypeId,
   form,
   taskFieldConfigList,
 })
@@ -226,6 +232,14 @@ const decodeRouteParam = (value?: string): string => {
   }
 }
 
+const handleTaskSingleSelectChange = (
+  field: TaskFieldConfigViewItem,
+  event: SingleSelectChangeEvent
+) => {
+  handleSingleSelectChange(field, event)
+  handleDerivedFieldChange(field, event)
+}
+
 onLoad(async (options: PageLoadOptions = {}) => {
   initSafeArea()
 

+ 15 - 2
src/services/modules/task/entitySelector/index.ts

@@ -1,11 +1,13 @@
 import http from '../../../index'
 import type {
-  ListWmDaPharmacyByNameRequest,
+  EntitySelectorRequest,
+  HospitalItem,
+  ListHospitalByNameResponse,
   ListWmDaPharmacyByNameResponse,
   PharmacyItem,
 } from './type'
 
-export const listWmDaPharmacyByNameApi = (params: ListWmDaPharmacyByNameRequest) => {
+export const listWmDaPharmacyByNameApi = (params: EntitySelectorRequest) => {
   return http.get<ListWmDaPharmacyByNameResponse>(
     `/admin/wmdapharmacy/listWmDaPharmacyByName?`,
     params,
@@ -18,3 +20,14 @@ export const listWmDaPharmacyByNameApi = (params: ListWmDaPharmacyByNameRequest)
 export const getWmDaPharmacyByNameApi = (name: string) => {
   return http.get<PharmacyItem | null>(`/admin/wmdapharmacy/getWmDaPharmacyByName?name=${name}`)
 }
+
+export const listHospitalByNameApi = (params: EntitySelectorRequest) => {
+  return http.get<ListHospitalByNameResponse>(`/admin/wmdahospital/listHospitalByName`, params, {
+    loading: true,
+    loadingText: '加载中...',
+  })
+}
+
+export const getHospitalByNameApi = (name: string) => {
+  return http.get<HospitalItem | null>(`/admin/wmdahospital/getHospitalByName?name=${name}`)
+}

+ 140 - 3
src/services/modules/task/entitySelector/type.d.ts

@@ -1,21 +1,25 @@
-export interface ListWmDaPharmacyByNameRequest {
+export interface EntitySelectorRequest {
   current: number
   size: number
   name: string
 }
 
-export interface ListWmDaPharmacyByNameResponse {
+export interface EntitySelectorPageResponse<T> {
   current: number
   hitCount: boolean
   optimizeCountSql: boolean
   orders: unknown[]
   pages: number
-  records: PharmacyItem[]
+  records: T[]
   searchCount: boolean
   size: number
   total: number
 }
 
+export type ListWmDaPharmacyByNameResponse = EntitySelectorPageResponse<PharmacyItem>
+
+export type ListHospitalByNameResponse = EntitySelectorPageResponse<HospitalItem>
+
 export interface PharmacyItem {
   /**
    * 主键 ID
@@ -112,3 +116,136 @@ export interface PharmacyItem {
    */
   updateUser: string | null
 }
+
+export interface HospitalItem {
+  /**
+   * 主键 ID
+   */
+  id: string
+
+  /**
+   * 医院名称
+   */
+  yymc: string
+
+  /**
+   * 省
+   */
+  province: string
+
+  /**
+   * 市
+   */
+  city: string
+
+  /**
+   * 区
+   */
+  area: string
+
+  /**
+   * 详细地址
+   */
+  address: string
+
+  /**
+   * 纬度
+   */
+  latitude: string
+
+  /**
+   * 经度
+   */
+  longitude: string
+
+  /**
+   * 医院性质
+   */
+  yyxz: string | null
+
+  /**
+   * 医院规模
+   */
+  yygm: string | null
+
+  /**
+   * 医院病床总数
+   */
+  ybczs: string | null
+
+  /**
+   * 医院官网
+   */
+  website: string | null
+
+  /**
+   * 院长姓名、联系方式
+   */
+  yzxm: string | null
+  yzlxfs: string | null
+
+  /**
+   * 药委会常务主任姓名、联系方式
+   */
+  ywcczxm: string | null
+  ywcczlxfs: string | null
+
+  /**
+   * 药剂科主任姓名、联系方式
+   */
+  yjkzrxm: string | null
+  yjkzrlsfs: string | null
+
+  /**
+   * 分院院长姓名、联系方式
+   */
+  fyzxm: string | null
+  fyzlxfs: string | null
+
+  /**
+   * 纪委处处长姓名、联系方式
+   */
+  jwcczxm: string | null
+  jwcczlxfs: string | null
+
+  /**
+   * 配送品种是否放入、配送品种配送商业
+   */
+  pspzsfry: string | null
+  pspzpssy: string | null
+
+  /**
+   * 临床专家
+   */
+  lxzj: string | null
+
+  /**
+   * 供货商业
+   */
+  ggyx: string | null
+
+  /**
+   * 治疗供货商业
+   */
+  zlghsy: string | null
+
+  /**
+   * 数据清洗备注
+   */
+  cleanRemark: string | null
+
+  /**
+   * 数据清洗标识(接口字段名保留原拼写)
+   */
+  dataCleaing: boolean
+
+  remark: string | null
+  tenantId: number | null
+  deptId: string | number | null
+  enableFlag: string
+  delFlag: string
+  createTime: string | null
+  updateTime: string | null
+  createUser: string | number | null
+  updateUser: string | number | null
+}

+ 13 - 1
src/services/modules/task/taskFrom/type.d.ts

@@ -1,10 +1,22 @@
 import type { DictItem } from '@/services/modules/common/type'
 
-export interface TaskContentConfigByTaskTypeIdResponse {
+export interface TaskContentConfig {
   config: TaskFieldConfigItem[]
   dict: TaskFieldDictMap
 }
 
+export interface TaskContentConfigDeriveFrom {
+  field: string
+  valueMapping: Record<string, string>
+}
+
+export type MultipleTaskContentConfig = {
+  deriveFrom: TaskContentConfigDeriveFrom
+  [taskTypeId: string]: TaskContentConfig | TaskContentConfigDeriveFrom
+}
+
+export type TaskContentConfigByTaskTypeIdResponse = TaskContentConfig | MultipleTaskContentConfig
+
 export interface TaskFieldConfigItem {
   id: number | string
   alias: string | null