Parcourir la source

⚙️ 修改细节问题

快乐的梦鱼 il y a 3 jours
Parent
commit
bcdac80528

+ 2 - 0
src/components/form/Uploader.ts

@@ -31,6 +31,8 @@ export interface UploaderItem {
    * 当前上传进度,0-100
    */
   progress?: number;
+
+  isTitle?: boolean;
   /**
    * 取消上传回调
    */

+ 109 - 39
src/components/form/Uploader.vue

@@ -6,7 +6,7 @@
     <slot 
       name="uploader" 
       :onClick="onUploadPress"
-      :items="currentUpladList"
+      :items="finalUploadList"
     >
     <!-- #endif -->
       <FlexView
@@ -16,41 +16,44 @@
         :innerStyle="(props.itemListStyle as ViewStyle)"
       >
         <template
-          v-for="(item, index) in currentUpladList"
-          :key="index"
+          v-for="(item, index) in finalUploadList"
+          :key="item.isTitle ? `title-${item.filePath}-${index}` : `${item.filePath}-${index}`"
         >
-          <!-- #ifndef MP -->
-          <slot 
-            name="uploadItem"
-            :index="index"
-            :item="item"
-            :onClick="() => onItemPress(item)"
-            :onDeleteClick="() => onItemDeletePress(item)"
-            :style="props.itemStyle"
-            :imageStyle="props.itemImageStyle"
-            :itemMaskStyle="props.itemMaskStyle"
-            :itemMaskTextStyle="props.itemMaskTextStyle"
-            :itemSize="itemSize"
-            :showDelete="showDelete"
-            :defaultSource="props.itemDefaultSource"
-          >
-          <!-- #endif -->
-            <UploaderListItem
+          <Text v-if="item.isTitle" bold :text="item.filePath" />
+          <template v-else>
+            <!-- #ifndef MP -->
+            <slot 
+              name="uploadItem"
+              :index="index"
               :item="item"
-              :showDelete="showDelete && !disabled && !readonly"
-              :isListStyle="props.listType === 'list'"
-              :style="itemStyle"
-              :imageStyle="itemImageStyle"
-              :itemMaskStyle="itemMaskStyle"
-              :itemMaskTextStyle="itemMaskTextStyle"
-              :defaultSource="itemDefaultSource"
+              :onClick="() => onItemPress(item)"
+              :onDeleteClick="() => onItemDeletePress(item)"
+              :style="props.itemStyle"
+              :imageStyle="props.itemImageStyle"
+              :itemMaskStyle="props.itemMaskStyle"
+              :itemMaskTextStyle="props.itemMaskTextStyle"
               :itemSize="itemSize"
-              @click="() => onItemPress(item)"
-              @delete="() => onItemDeletePress(item)"
-            />
-          <!-- #ifndef MP -->
-          </slot>
-          <!-- #endif -->
+              :showDelete="showDelete"
+              :defaultSource="props.itemDefaultSource"
+            >
+            <!-- #endif -->
+              <UploaderListItem
+                :item="item"
+                :showDelete="showDelete && !disabled && !readonly"
+                :isListStyle="props.listType === 'list'"
+                :style="itemStyle"
+                :imageStyle="itemImageStyle"
+                :itemMaskStyle="itemMaskStyle"
+                :itemMaskTextStyle="itemMaskTextStyle"
+                :defaultSource="itemDefaultSource"
+                :itemSize="itemSize"
+                @click="() => onItemPress(item)"
+                @delete="() => onItemDeletePress(item)"
+              />
+            <!-- #ifndef MP -->
+            </slot>
+            <!-- #endif -->
+          </template>
         </template>
         <slot v-if="currentUpladList.length < maxUploadCount && !disabled && !readonly" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
           <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
@@ -70,7 +73,7 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue';
+import { computed, reactive, ref } from 'vue';
 import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
 import { Debounce, LogUtils } from '@imengyu/imengyu-utils';
 import { actionSheet } from '../dialog/CommonRoot';
@@ -183,6 +186,11 @@ export interface UploaderProps {
    * @default true
    */
   formMessage?: boolean;
+  /**
+   * 列表是否按类型分组,仅当listType为list时有效
+   * @default false
+   */
+  groupType?: boolean;
 
   /**
    * 上传处理。不提供则无法上传
@@ -285,11 +293,78 @@ const props = withDefaults(defineProps<UploaderProps>(), {
   uploadQueueMode: 'all',
   listType: 'grid',
   chooseType: 'image',
+  groupType: false,
   itemSize: () => propGetThemeVar('UploaderItemSize', { width: 750 / 4 - 15, height: 750 / 4 - 15 }),
 });
 
 const currentUpladList = ref<UploaderItem[]>(props.intitalItems?.concat() || []);
 
+type UploadDisplayKind = 'image' | 'video' | 'audio' | 'document' | 'other';
+
+const UPLOAD_GROUP_META: { kind: UploadDisplayKind; label: string }[] = [
+  { kind: 'image', label: '图片' },
+  { kind: 'video', label: '视频' },
+  { kind: 'audio', label: '音频' },
+  { kind: 'document', label: '文档' },
+  { kind: 'other', label: '其他' },
+];
+
+function isImagePath(path: string) {
+  return path.match(/\.(jpg|jpeg|png|gif|webp)$/i) !== null;
+}
+
+function getUploadItemDisplayKind(item: UploaderItem): UploadDisplayKind {
+  if (item.isTitle)
+    return 'other';
+  const path = item.previewPath || item.uploadedPath || item.filePath;
+  if (item.isImage || isImagePath(path))
+    return 'image';
+  if (/\.(mp4|m4v|mov|webm|avi|wmv|flv|mkv|3gp|3g2|ts)$/i.test(path))
+    return 'video';
+  if (/\.(mp3|wav|m4a|aac|ogg|opus|flac|wma|amr|aiff|aif)$/i.test(path))
+    return 'audio';
+  if (/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|rtf|csv|md)$/i.test(path))
+    return 'document';
+  return 'other';
+}
+
+function makeUploadTitleItem(label: string): UploaderItem {
+  return {
+    filePath: label,
+    isTitle: true,
+    state: 'success',
+  };
+}
+
+/** 列表模式 + groupType:按文件类型插入分组标题行(isTitle) */
+function buildGroupedUploadList(items: UploaderItem[]): UploaderItem[] {
+  const files = items.filter((i) => !i.isTitle);
+  if (files.length === 0)
+    return [];
+  const buckets = new Map<UploadDisplayKind, UploaderItem[]>();
+  for (const { kind } of UPLOAD_GROUP_META)
+    buckets.set(kind, []);
+  for (const item of files) {
+    const kind = getUploadItemDisplayKind(item);
+    buckets.get(kind)!.push(item);
+  }
+  const out: UploaderItem[] = [];
+  for (const { kind, label } of UPLOAD_GROUP_META) {
+    const group = buckets.get(kind)!;
+    if (group.length === 0)
+      continue;
+    out.push(makeUploadTitleItem(label));
+    out.push(...group);
+  }
+  return out;
+}
+
+const finalUploadList = computed(() => {
+  if (props.groupType && props.listType === 'list')
+    return buildGroupedUploadList(currentUpladList.value);
+  return currentUpladList.value;
+});
+
 //上传按钮点击
 function onUploadPress() {
   if (props.disabled || props.readonly)
@@ -487,11 +562,6 @@ function deleteListItem(item: UploaderItem) {
   }
 }
 
-function isImagePath(path: string) {
-  return path.match(/\.(jpg|jpeg|png|gif|webp)$/) !== null;
-}
-
-
 //开始上传条目
 function startUploadItem(item: UploaderItem) {
   if (item.state === 'uploading')

+ 43 - 0
src/pages/collect/assessment/evaluation-form.vue

@@ -18,11 +18,43 @@
               :checkItemList="(checkItemList as CheckItemInfo[])"
               :currentFormCheckItems="(currentFormCheckItems as SelfAssessmentCheckItemAnswer[])"
             />
+            <Divider />
             <FlexRow align="flex-end" justify="space-between">
               <H3>自评总分</H3>
               <Text fontSize="50rpx" color="#315816" fontFamily="HUNdin1451" :text="`${totalPoints}分`" />
             </FlexRow>
             <Divider />
+            <FlexCol gap="gap.lg">
+              <FlexCol v-for="(title, secIdx) in externalReviewSectionTitles" :key="secIdx" gap="gap.sm">
+                <Text bold :text="title" />
+                <Text text="评分:(待终审填写)" />
+                <FlexRow wrap align="center" gap="gap.md">
+                  <CheckBox
+                    v-for="(label, i) in externalReviewScoreRow1"
+                    :key="`${secIdx}-r1-${i}`"
+                    disabled
+                    :model-value="false"
+                    :text="label"
+                    :check-size="28"
+                  />
+                </FlexRow>
+                <FlexRow wrap align="center" gap="gap.md">
+                  <CheckBox
+                    v-for="(label, i) in externalReviewScoreRow2"
+                    :key="`${secIdx}-r2-${i}`"
+                    disabled
+                    :model-value="false"
+                    :text="label"
+                    :check-size="28"
+                  />
+                </FlexRow>
+                <FlexCol align="flex-end">
+                  <Text color="text.second" text="填写单位(盖章)" />
+                  <Text color="text.second" text="年 月 日" />
+                </FlexCol>
+              </FlexCol>
+            </FlexCol>
+            <Divider />
             <H3>佐证资料上传</H3>
             <Result v-if="!currentForm?.id" status="info" title="请先保存评估表后再上传佐证资料" />
             <Uploader
@@ -31,6 +63,7 @@
               :upload="assessmentAnnexUpload"
               :max-upload-count="9"
               :max-file-size="20 * 1024 * 1024"
+              :group-type="true"
               list-type="list"
             />
             <Height :height="30" />
@@ -79,6 +112,16 @@ import Uploader, { type UploaderInstance } from '@/components/form/Uploader.vue'
 import { getMimeType } from '@/common/components/upload/mimes';
 import Divider from '@/components/display/Divider.vue';
 import { stringUrlToUploaderItem } from '@/components/form/Uploader';
+import CheckBox from '@/components/form/CheckBox.vue';
+
+/** 评估表下方展示用:各级审核意见(不接数据、禁用) */
+const externalReviewSectionTitles = [
+  '1. 项目保护单位意见',
+  '2. 县(区)文旅部门审核意见',
+  '3. 设区市文旅部门、省非遗中心审核意见',
+] as const;
+const externalReviewScoreRow1 = ['优秀', '合格', '不合格'] as const;
+const externalReviewScoreRow2 = ['丧失传承能力', '取消资格'] as const;
 
 let loaded = false;