Przeglądaj źródła

💊 按要求佐证资料改为与选项绑定

快乐的梦鱼 1 miesiąc temu
rodzic
commit
3f53800d82

+ 5 - 1
src/api/collect/AssessmentContent.ts

@@ -557,6 +557,8 @@ export interface SaveCheckAnnexPayload {
   id?: number;
   name: string;
   formId: number;
+  /** 计分项 ID */
+  itemId?: number;
   url: string;
   /** 1=图片,2=视频,3=音频,4=文档,5=其他附件,6=外部链接 */
   type: CheckAnnexTypeValue | number;
@@ -819,6 +821,7 @@ export class AssessmentContentApi extends AppServerRequestModule<DataModel> {
       id: payload.id,
       name: payload.name,
       form_id: payload.formId,
+      item_id: payload.itemId,
       url: payload.url,
       type: payload.type,
       desc: payload.desc,
@@ -832,9 +835,10 @@ export class AssessmentContentApi extends AppServerRequestModule<DataModel> {
    * 证明材料列表(按自查表)
    * POST `/ich/check/getAnnexList`
    */
-  async getAnnexList(formId: number) {
+  async getAnnexList(formId: number, itemId?: number) {
     const res = await this.post<KeyValue>('/ich/check/getAnnexList', '证明材料列表', {
       form_id: formId,
+      item_id: itemId,
     });
     return normalizePaginated<CheckAnnexListItem>(CheckAnnexListItem, res.requireData());
   }

+ 90 - 2
src/pages/collect/assessment/components/EvaluationFormBlock.vue

@@ -45,6 +45,30 @@
                 @update:modelValue="setCheckedItem(item as CheckItemInfo, child as CheckItemInfo, $event)" 
               />
             </FlexCol>
+            <FlexCol gap="gap.sm">
+              <Text
+                fontConfig="subText"
+                color="text.second"
+                :text="readonly ? '佐证资料' : '佐证材料上传'"
+              />
+              <Text
+                v-if="!currentForm.id"
+                fontConfig="subText"
+                color="text.second"
+                text="请先保存评估表后再上传佐证资料"
+              />
+              <Uploader
+                v-else
+                :ref="(el) => bindUploaderRef(item.id, el)"
+                :upload="getAnnexUpload(item.id)"
+                :max-upload-count="100"
+                :max-file-size="20 * 1024 * 1024"
+                :group-type="true"
+                chooseType="file"
+                list-type="list"
+                :readonly="readonly"
+              />
+            </FlexCol>
           </FlexCol>
         </FlexCol>
       </FlexCol>
@@ -54,7 +78,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 import DynamicForm from '@/components/dynamic/DynamicForm.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import H3 from '@/components/typography/H3.vue';
@@ -62,12 +86,16 @@ import Text from '@/components/basic/Text.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import CheckBox from '@/components/form/CheckBox.vue';
 import Stepper from '@/components/form/Stepper.vue';
-import { SelfAssessmentCheckItemAnswer, type CheckItemInfo, type SelfAssessmentDetail } from '@/api/collect/AssessmentContent';
+import Uploader, { type UploaderInstance } from '@/components/form/Uploader.vue';
+import AssessmentContentApi, { getCheckAnnexType, SelfAssessmentCheckItemAnswer, type CheckItemInfo, type SelfAssessmentDetail } from '@/api/collect/AssessmentContent';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
 import { ArrayUtils } from '@imengyu/imengyu-utils';
 import Tag from '@/components/display/Tag.vue';
 import Divider from '@/components/display/Divider.vue';
 import Height from '@/components/layout/space/Height.vue';
+import { useAliOssUploadCo } from '@/common/components/upload/AliOssUploadCo';
+import { getMimeType } from '@/common/components/upload/mimes';
+import { stringUrlToUploaderItem, type UploaderAction } from '@/components/form/Uploader';
 
 const props = withDefaults(defineProps<{
   currentForm: SelfAssessmentDetail;
@@ -81,6 +109,8 @@ const props = withDefaults(defineProps<{
 });
 
 const formRef = ref<IDynamicFormRef | null>(null);
+const uploaderRefMap = new Map<number, UploaderInstance | null>();
+const uploadCoMap = new Map<number, (action: UploaderAction) => (() => void)>();
 
 const mergedFormOptions = computed<IDynamicFormOptions>(() => ({
   ...props.formOptions,
@@ -142,11 +172,69 @@ function setCheckedItem(checkItem: CheckItemInfo, childItem: CheckItemInfo, coun
     ArrayUtils.remove(props.currentFormCheckItems, item);
 }
 
+function bindUploaderRef(itemId: number, el: unknown) {
+  uploaderRefMap.set(itemId, (el as UploaderInstance | null) || null);
+  if (el)
+    loadAnnexListByItem(itemId);
+}
+
+function getAnnexUpload(itemId: number) {
+  const cached = uploadCoMap.get(itemId);
+  if (cached)
+    return cached;
+  const uploadCo = useAliOssUploadCo('assessment/annex', async (res, item) => {
+    const formId = props.currentForm.id;
+    if (!formId)
+      return;
+    const mimetype = getMimeType(item.filePath);
+    await AssessmentContentApi.saveAnnex({
+      name: item.name,
+      formId,
+      itemId,
+      url: res,
+      type: getCheckAnnexType(mimetype),
+      mimetype,
+      fileSize: item.size
+        ? Math.max(1, Math.ceil(item.size / 1024))
+        : undefined,
+    });
+    await loadAnnexListByItem(itemId);
+  });
+  uploadCoMap.set(itemId, uploadCo);
+  return uploadCo;
+}
+
+async function loadAnnexListByItem(itemId: number) {
+  const formId = props.currentForm.id;
+  const uploaderRef = uploaderRefMap.get(itemId);
+  if (!uploaderRef)
+    return;
+  if (!formId) {
+    uploaderRef.setList([]);
+    return;
+  }
+  const annexList = await AssessmentContentApi.getAnnexList(formId, itemId);
+  uploaderRef.setList(annexList.data.map((item) => stringUrlToUploaderItem(item.url, item.name)));
+}
+
+async function reloadAllAnnexList() {
+  const itemIds = props.checkItemList.map((item) => item.id);
+  await Promise.all(itemIds.map(loadAnnexListByItem));
+}
+
 async function validate() {
   if (props.readonly)
     return;
   await formRef.value?.validate();
 }
 
+watch(
+  () => [props.currentForm.id, props.checkItemList.map((item) => item.id).join(',')],
+  () => {
+    reloadAllAnnexList();
+  },
+  { immediate: true },
+);
+
 defineExpose({ validate });
 </script>

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

@@ -19,21 +19,6 @@
               :readonly="true"
             />
             <Divider />
-            <H3>佐证资料</H3>
-            <Result v-if="!annexLinks.length" status="info" title="暂无佐证资料" />
-            <FlexCol v-else gap="gap.sm">
-              <Touchable
-                v-for="a in annexLinks"
-                :key="a.id"
-                padding="space.sm"
-                backgroundColor="white"
-                radius="radius.md"
-                @click="copyAnnexUrl(a)"
-              >
-                <Text :text="a.name" />
-              </Touchable>
-            </FlexCol>
-            <Divider />
             <H3>审核提交</H3>
             <Alert
               v-if="!canSubmitReview"
@@ -103,7 +88,6 @@ import { useImageSimpleUploadCo } from '@/common/components/upload/ImageUploadCo
 import Divider from '@/components/display/Divider.vue';
 import H3 from '@/components/typography/H3.vue';
 import Text from '@/components/basic/Text.vue';
-import Touchable from '@/components/feedback/Touchable.vue';
 import Button from '@/components/basic/Button.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Stepper from '@/components/form/Stepper.vue';
@@ -121,7 +105,6 @@ const currentUserGroups = computed(() => authStore.userInfo?.adminGroup || []);
 const currentForm = ref<SelfAssessmentDetail | null>(null);
 const currentFormCheckItems = ref<SelfAssessmentCheckItemAnswer[]>([]);
 const checkItemList = ref<CheckItemInfo[]>([]);
-const annexLinks = ref<{ id: number; name: string; url: string }[]>([]);
 
 const submitLoading = ref(false);
 const reviewOpinion = ref<number | null>(null);
@@ -229,15 +212,6 @@ async function loadCheckItems(f: SelfAssessmentDetail) {
   currentFormCheckItems.value = [...f.checkItems] as SelfAssessmentCheckItemAnswer[];
 }
 
-async function loadAnnexLinks(formId: number) {
-  const annexList = await AssessmentContentApi.getAnnexList(formId);
-  annexLinks.value = annexList.data.map((item) => ({
-    id: item.id,
-    name: item.name,
-    url: item.url,
-  }));
-}
-
 function applyPrefillFromDetail(f: SelfAssessmentDetail) {
   const t = reviewProgress.value;
   if (!canSubmitReview.value)
@@ -257,15 +231,6 @@ function applyPrefillFromDetail(f: SelfAssessmentDetail) {
   }
 }
 
-function copyAnnexUrl(a: { name: string; url: string }) {
-  uni.setClipboardData({
-    data: a.url,
-    success: () => {
-      toast(`已复制「${a.name}」链接`);
-    },
-  });
-}
-
 const loader = useSimpleDataLoader(async () => {
   const id = querys.value.id;
   const uid = querys.value.userId;
@@ -277,10 +242,6 @@ const loader = useSimpleDataLoader(async () => {
   currentForm.value = detail;
   loadEditorContent(detail);
   await loadCheckItems(detail);
-  if (detail.id)
-    await loadAnnexLinks(detail.id);
-  else
-    annexLinks.value = [];
   applyPrefillFromDetail(detail);
   return detail;
 }, false);

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

@@ -52,20 +52,6 @@
               </FlexCol>
             </FlexCol>
             <Divider />
-            <H3>佐证资料上传</H3>
-            <Result v-if="!currentForm?.id" status="info" title="请先保存评估表后再上传佐证资料" />
-            <Uploader
-              v-else
-              ref="uploaderRef"
-              :upload="assessmentAnnexUpload"
-              :max-upload-count="100"
-              :max-file-size="20 * 1024 * 1024"
-              :group-type="true"
-              chooseType="file"
-              list-type="list"
-            />
-            <Height :height="30" />
-            <Divider />
             <Button type="primary" block :loading="submitLoading" @click="saveForm">保存评估表</Button>
             <Button :loading="submitLoading" @click="downloadForm">下载评估表PDF</Button>
           </FlexCol>
@@ -81,14 +67,12 @@ import { computed, ref } from 'vue';
 import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { useAuthStore } from '@/store/auth';
 import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
-import { useAliOssUploadCo } from '@/common/components/upload/AliOssUploadCo';
 import { assertNotNull, formatError, waitTimeOut } from '@imengyu/imengyu-utils';
 import { toast, alert } from '@/components/dialog/CommonRoot';
 import AssessmentContentApi, {
   SelfAssessmentDetail,
   CheckItemInfo,
   SelfAssessmentCheckItemAnswer,
-  getCheckAnnexType,
 } from '@/api/collect/AssessmentContent';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
 import Button from '@/components/basic/Button.vue';
@@ -96,14 +80,10 @@ import Result from '@/components/feedback/Result.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Height from '@/components/layout/space/Height.vue';
 import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
-import H3 from '@/components/typography/H3.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Text from '@/components/basic/Text.vue';
 import XBarSpace from '@/components/layout/space/XBarSpace.vue';
-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';
 import Field from '@/components/form/Field.vue';
 import SelfAssessmentFormDisplay from './components/SelfAssessmentFormDisplay.vue';
@@ -135,24 +115,8 @@ const currentForm = ref<SelfAssessmentDetail | null>(null);
 const currentFormCheckItems = ref<SelfAssessmentCheckItemAnswer[]>([]);
 const authStore = useAuthStore();
 
-const assessmentAnnexUpload = useAliOssUploadCo('assessment/annex', async (res, item) => {
-  assertNotNull(currentForm.value, 'currentForm is null');
-  const mimetype = getMimeType(item.filePath);
-  await AssessmentContentApi.saveAnnex({
-    name: item.name,
-    formId: currentForm.value.id,
-    url: res,
-    type: getCheckAnnexType(mimetype),
-    mimetype: mimetype,
-    fileSize: item.size
-      ? Math.max(1, Math.ceil(item.size / 1024))
-      : undefined,
-  });
-});
-
 const currentYear = new Date().getFullYear();
 
-const uploaderRef = ref<UploaderInstance | null>(null);
 const blockRef = ref<InstanceType<typeof SelfAssessmentFormDisplay> | null>(null);
 const signUploadCo = useImageSimpleUploadCo();
 const formOptions = buildSelfAssessmentFormOptions(signUploadCo);
@@ -196,17 +160,6 @@ async function loadCheckItems() {
   currentFormCheckItems.value = currentForm.value.checkItems.concat();
 }
 
-async function loadAnnexList() {
-  assertNotNull(currentForm.value, 'currentForm is null');
-  console.log('awardTime', currentForm.value.awardTime);
-  const annexList = await AssessmentContentApi.getAnnexList(currentForm.value.id);
-  setTimeout(() => {
-    if (uploaderRef.value) {  
-      uploaderRef.value.setList(annexList.data.map((item) => stringUrlToUploaderItem(item.url, item.name)));
-    }
-  }, 1000);
-}
-
 const submitLoading = ref(false);
 
 async function createForm() {
@@ -275,7 +228,6 @@ const loader = useSimpleDataLoader(async () => {
     currentForm.value = detail;
     loadEditorContent();
     await loadCheckItems();
-    await loadAnnexList();
     return;
   }
   const basicInfo = await AssessmentContentApi.getInheritorBasic(authStore.userInfo?.id);
@@ -286,7 +238,6 @@ const loader = useSimpleDataLoader(async () => {
     );
     currentForm.value = detail;
     loadEditorContent();
-    await loadAnnexList();
     await loadCheckItems();
   } else {
     currentForm.value = null;