浏览代码

小程序 任务详情功能实现完成

yuanmingze 3 周之前
父节点
当前提交
8c251dac47

+ 73 - 21
src/pages-task/task-detail/components/NormalTaskDetail.vue

@@ -45,43 +45,83 @@
 import { computed } from 'vue'
 
 import type {
-  TaskDetailContentField,
   TaskDetailContentItem,
   TaskFormDetailResponse,
 } from '@/services/modules/task/taskDetail/type'
+import type {
+  TaskContentConfig,
+  TaskContentConfigByTaskTypeIdResponse,
+} from '@/services/modules/task/taskFrom/type'
 
 import { previewImages, toFullUrl } from '../utils'
 import TaskDetailSummary from './TaskDetailSummary.vue'
 
-interface TaskDetailContentDisplayItem extends Omit<TaskDetailContentItem, 'label'> {
-  label: string
-}
-
-interface TaskDetailContentDisplayField extends Omit<TaskDetailContentField, 'items'> {
-  name: string
-  items: TaskDetailContentDisplayItem[]
-  imageUrls: string[]
-  text: string
-}
-
 const IMAGE_FIELD_TYPES = new Set(['img', 'sign'])
+const DICT_FIELD_TYPES = new Set(['select', 'domain', 'multiple_select'])
 
 const props = defineProps<{
   taskDetail: TaskFormDetailResponse
+  taskConfig: TaskContentConfigByTaskTypeIdResponse | null
 }>()
 
+const taskConfigs = computed<TaskContentConfig[]>(() => {
+  const source = props.taskConfig
+  if (!source) return []
+
+  const configs =
+    'dict' in source ? [source] : Object.values(source).filter((item) => 'dict' in item)
+
+  return configs as TaskContentConfig[]
+})
+
+/** 将所有表单配置预先整理为「字段名 -> value -> label」索引。 */
+const dictLabelsByField = computed(() => {
+  const result = new Map<string, Map<string, string>>()
+
+  for (const { config, dict } of taskConfigs.value) {
+    if (!Array.isArray(config) || !dict) continue
+
+    for (const field of config) {
+      const dictGroupName = field.dictGroupName?.trim()
+      const dictItems = dictGroupName ? dict[dictGroupName] : undefined
+      if (!Array.isArray(dictItems) || !dictItems.length) continue
+
+      const fieldName = field.taskFiledValue.trim()
+      const labels = result.get(fieldName) ?? new Map<string, string>()
+
+      for (const item of dictItems) {
+        const value = String(item.value).trim()
+        const label = String(item.label).trim()
+
+        if (value && label && !labels.has(value)) {
+          labels.set(value, label)
+        }
+      }
+
+      if (labels.size) result.set(fieldName, labels)
+    }
+  }
+
+  return result
+})
+
 /**
- * wmTaskContent 按 seq 排序;item.label 为空时用 value 回显。
+ * wmTaskContent 按 seq 排序;select、domain、multiple_select 类型优先从字段配置
+ * 对应的字典中获取 label,字典缺失或值无法完整匹配时直接回显 value。
+ * 其余情况在 item.label 为空时用 value 回显。
  * img、sign 类型转成图片列表,其余类型显示标准化后的 label。
  */
-const contentFields = computed<TaskDetailContentDisplayField[]>(() => {
+const contentFields = computed(() => {
   const content = props.taskDetail.wmTaskContent
   if (!content) return []
 
   return Object.entries(content)
     .sort(([, left], [, right]) => left.seq - right.seq)
     .map(([name, field]) => {
-      const items = field.items.map(normalizeContentItem)
+      const items = field.items.map((item) => ({
+        ...item,
+        label: resolveItemLabel(name, item),
+      }))
       const imageUrls = items
         .filter((item) => isImageFieldType(item.type))
         .flatMap((item) => parseImageUrls(item.label))
@@ -95,18 +135,30 @@ const contentFields = computed<TaskDetailContentDisplayField[]>(() => {
         name,
         seq: field.seq,
         required: field.required,
-        items,
         imageUrls: [...new Set(imageUrls)],
         text,
       }
     })
 })
 
-const normalizeContentItem = (item: TaskDetailContentItem): TaskDetailContentDisplayItem => {
-  return {
-    ...item,
-    label: item.label?.trim() || item.value,
-  }
+const resolveItemLabel = (fieldName: string, item: TaskDetailContentItem): string => {
+  const fieldType = item.type.trim().toLowerCase()
+  if (!DICT_FIELD_TYPES.has(fieldType)) return item.label?.trim() || item.value
+  if (fieldType !== 'multiple_select' && item.label?.trim()) return item.label.trim()
+
+  const values =
+    fieldType === 'multiple_select'
+      ? item.value
+          .split(',')
+          .map((value) => value.trim())
+          .filter(Boolean)
+      : [item.value.trim()]
+  const labels = dictLabelsByField.value.get(fieldName.trim())
+
+  if (!values.length || !labels) return item.value
+
+  const resolvedLabels = values.map((value) => labels.get(value))
+  return resolvedLabels.every(Boolean) ? resolvedLabels.join('、') : item.value
 }
 
 const isImageFieldType = (type: string) => {

+ 17 - 1
src/pages-task/task-detail/index.vue

@@ -15,7 +15,11 @@
     <template v-else-if="taskDetail">
       <SignTaskDetail v-if="signTaskDetail" :task-detail="signTaskDetail" :records="signRecords" />
       <ShareTaskDetail v-else-if="shareTaskDetail" :task-detail="shareTaskDetail" />
-      <NormalTaskDetail v-else-if="normalTaskDetail" :task-detail="normalTaskDetail" />
+      <NormalTaskDetail
+        v-else-if="normalTaskDetail"
+        :task-detail="normalTaskDetail"
+        :task-config="taskContentConfig"
+      />
     </template>
 
     <view v-else class="task-detail-state">
@@ -37,6 +41,8 @@ import type {
   TaskDetailResponse,
   TaskFormDetailResponse,
 } from '@/services/modules/task/taskDetail/type'
+import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom'
+import type { TaskContentConfigByTaskTypeIdResponse } from '@/services/modules/task/taskFrom/type'
 import { getSignInfoByTaskIdApi } from '@/services/modules/task/taskVisit'
 import type { VisitSignRecordItem } from '@/services/modules/task/taskVisit/type'
 
@@ -56,6 +62,7 @@ const taskId = ref('')
 const loading = ref(false)
 const errorMessage = ref('')
 const taskDetail = ref<TaskDetailResponse | null>(null)
+const taskContentConfig = ref<TaskContentConfigByTaskTypeIdResponse | null>(null)
 const signRecords = ref<VisitSignRecordItem[]>([])
 
 const isShareTaskDetail = (detail: TaskDetailResponse): detail is ShareTaskDetailResponse => {
@@ -87,6 +94,7 @@ const loadTaskDetail = async () => {
 
   loading.value = true
   errorMessage.value = ''
+  taskContentConfig.value = null
   signRecords.value = []
 
   try {
@@ -102,10 +110,18 @@ const loadTaskDetail = async () => {
     if (isSignTaskDetail(detail)) {
       const signRes = await getSignInfoByTaskIdApi({ id: taskId.value })
       signRecords.value = Array.isArray(signRes.data) ? signRes.data : []
+    } else if (!isShareTaskDetail(detail)) {
+      try {
+        const configRes = await getTaskContentConfigByTaskTypeIdApi(String(detail.taskType))
+        taskContentConfig.value = configRes.data ?? null
+      } catch (error) {
+        console.warn('[task-detail] 任务表单配置加载失败,将使用详情原始值', error)
+      }
     }
   } catch (error) {
     console.error('[task-detail] 任务详情加载失败', error)
     taskDetail.value = null
+    taskContentConfig.value = null
     signRecords.value = []
     errorMessage.value = '请检查网络后重试'
   } finally {

+ 1 - 10
src/pages-task/task-form/components/DynamicTaskFormFields.vue

@@ -107,9 +107,6 @@
           :select-level="selectLevel"
         />
 
-        <view v-else-if="!isSupported(item.taskFiledType)" class="unsupported-field">
-          {{ item.taskFiledValue }}:暂不支持字段类型“{{ item.taskFiledType }}”
-        </view>
       </view>
     </template>
   </view>
@@ -213,6 +210,7 @@ const {
 const shouldRenderField = (field: TaskFieldConfigViewItem) => {
   return (
     field.show &&
+    isSupported(field.taskFiledType) &&
     !hiddenFieldTypeSet.value.has(field.taskFiledType) &&
     !hiddenFieldKeySet.value.has(field.taskFiledKey) &&
     !hiddenFieldAliasSet.value.has(field.alias ?? '')
@@ -261,11 +259,4 @@ const handleTaskSingleSelectChange = (
   background: #f4f4f4;
 }
 
-.unsupported-field {
-  padding: 28rpx;
-  color: #b45309;
-  font-size: 28rpx;
-  line-height: 40rpx;
-  background: #fff8ec;
-}
 </style>