|
@@ -65,6 +65,51 @@
|
|
|
{{ child.name }} ({{ child.points }}分)
|
|
{{ child.name }} ({{ child.points }}分)
|
|
|
</a-checkbox>
|
|
</a-checkbox>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+ <div class="annex-upload-block">
|
|
|
|
|
+ <a-typography-text type="secondary">{{ readonly ? '佐证资料' : '佐证材料上传' }}</a-typography-text>
|
|
|
|
|
+ <a-alert
|
|
|
|
|
+ v-if="!currentForm.id"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ class="mt-2"
|
|
|
|
|
+ message="请先保存评估表后再上传佐证资料"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ />
|
|
|
|
|
+ <template v-else-if="readonly">
|
|
|
|
|
+ <a-empty v-if="!getAnnexFileList(item.id).length" description="暂无佐证资料" />
|
|
|
|
|
+ <ul v-else class="annex-list pl-5 mt-2">
|
|
|
|
|
+ <li v-for="file in getAnnexFileList(item.id)" :key="file.uid">
|
|
|
|
|
+ <a :href="file.url" target="_blank" rel="noopener noreferrer">({{ (file.response as any).desc ?? '暂无说明' }}){{ file.name }}</a>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <a-upload
|
|
|
|
|
+ v-else
|
|
|
|
|
+ class="flex flex-row items-center gap-2 mt-2"
|
|
|
|
|
+ :file-list="getAnnexFileList(item.id)"
|
|
|
|
|
+ :multiple="true"
|
|
|
|
|
+ :custom-request="(options: any) => annexCustomRequest(item.id, options)"
|
|
|
|
|
+ :before-upload="beforeAnnexUpload"
|
|
|
|
|
+ @update:file-list="(list: any) => setAnnexFileList(item.id, list)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #itemRender="{ originNode, file }">
|
|
|
|
|
+ <div class="flex flex-row items-center gap-2">
|
|
|
|
|
+ <div class="flex-1 min-w-0">
|
|
|
|
|
+ <component :is="originNode" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <a-button
|
|
|
|
|
+ type="link"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ class="mt-2!"
|
|
|
|
|
+ :disabled="file?.status !== 'done'"
|
|
|
|
|
+ @click.prevent.stop="editAnnexDesc(item.id, file)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <a-button type="default" size="small" class="ml-3">选择文件上传</a-button>
|
|
|
|
|
+ </a-upload>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
<a-divider />
|
|
<a-divider />
|
|
|
<DynamicForm
|
|
<DynamicForm
|
|
@@ -76,16 +121,21 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, ref } from 'vue';
|
|
|
|
|
|
|
+import { computed, h, ref, watch } from 'vue';
|
|
|
import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@imengyu/vue-dynamic-form';
|
|
import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@imengyu/vue-dynamic-form';
|
|
|
import { ArrayUtils } from '@imengyu/imengyu-utils';
|
|
import { ArrayUtils } from '@imengyu/imengyu-utils';
|
|
|
|
|
+import type { UploadProps } from 'ant-design-vue';
|
|
|
|
|
+import { Input, message, Modal } from 'ant-design-vue';
|
|
|
import type { FormInstance } from 'ant-design-vue';
|
|
import type { FormInstance } from 'ant-design-vue';
|
|
|
import {
|
|
import {
|
|
|
|
|
+ getCheckAnnexType,
|
|
|
|
|
+ default as AssessmentContentApi,
|
|
|
SelfAssessmentCheckItemAnswer,
|
|
SelfAssessmentCheckItemAnswer,
|
|
|
type CheckItemInfo,
|
|
type CheckItemInfo,
|
|
|
type SelfAssessmentDetail,
|
|
type SelfAssessmentDetail,
|
|
|
} from '@/api/collect/AssessmentContent';
|
|
} from '@/api/collect/AssessmentContent';
|
|
|
import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
|
|
import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
|
|
|
|
|
+import { useAliOssUploadCo } from '@/common/upload/AliOssUploadCo';
|
|
|
import type { RadioValueFormItemProps, SelectIdProps } from '@imengyu/vue-dynamic-form-ant';
|
|
import type { RadioValueFormItemProps, SelectIdProps } from '@imengyu/vue-dynamic-form-ant';
|
|
|
import type { SignProps } from '@imengyu/vue-dynamic-form-rich';
|
|
import type { SignProps } from '@imengyu/vue-dynamic-form-rich';
|
|
|
|
|
|
|
@@ -101,6 +151,8 @@ const props = withDefaults(defineProps<{
|
|
|
|
|
|
|
|
const mainFormRef = ref<IDynamicFormRef | null>(null);
|
|
const mainFormRef = ref<IDynamicFormRef | null>(null);
|
|
|
const tailFormRef = ref<IDynamicFormRef | null>(null);
|
|
const tailFormRef = ref<IDynamicFormRef | null>(null);
|
|
|
|
|
+const annexUploadCo = useAliOssUploadCo('assessment/annex');
|
|
|
|
|
+const annexFileMap = ref<Record<number, UploadProps['fileList']>>({});
|
|
|
|
|
|
|
|
const contentItemLabels = [
|
|
const contentItemLabels = [
|
|
|
'(一)开展传承活动,培养后继人才情况;',
|
|
'(一)开展传承活动,培养后继人才情况;',
|
|
@@ -333,6 +385,174 @@ function setCheckedItem(checkItem: CheckItemInfo, childItem: CheckItemInfo, coun
|
|
|
ArrayUtils.remove(props.currentFormCheckItems, item);
|
|
ArrayUtils.remove(props.currentFormCheckItems, item);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function getAnnexFileList(itemId: number) {
|
|
|
|
|
+ return annexFileMap.value[itemId] ?? [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function setAnnexFileList(itemId: number, list: UploadProps['fileList']) {
|
|
|
|
|
+ annexFileMap.value[itemId] = list ?? [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatAnnexDisplayName(desc: string | null | undefined, name: string) {
|
|
|
|
|
+ const descText = (desc ?? '').trim();
|
|
|
|
|
+ if (!descText)
|
|
|
|
|
+ return name;
|
|
|
|
|
+ return `${descText}(${name})`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function mapAnnexListItemToUploadFile(item: any) {
|
|
|
|
|
+ const rawName = String(item.name ?? '');
|
|
|
|
|
+ const desc = String(item.desc ?? '');
|
|
|
|
|
+ return {
|
|
|
|
|
+ uid: String(item.id),
|
|
|
|
|
+ name: formatAnnexDisplayName(desc, rawName),
|
|
|
|
|
+ status: 'done' as const,
|
|
|
|
|
+ url: item.url,
|
|
|
|
|
+ response: {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ rawName,
|
|
|
|
|
+ desc,
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const beforeAnnexUpload: UploadProps['beforeUpload'] = () => {
|
|
|
|
|
+ if (!props.currentForm.id) {
|
|
|
|
|
+ message.warning('请先保存评估表');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+async function loadAnnexListByItem(itemId: number) {
|
|
|
|
|
+ const formId = props.currentForm.id;
|
|
|
|
|
+ if (!formId) {
|
|
|
|
|
+ setAnnexFileList(itemId, []);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const annexList = await AssessmentContentApi.getAnnexList(formId, itemId);
|
|
|
|
|
+ setAnnexFileList(itemId, annexList.data.map(mapAnnexListItemToUploadFile));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function promptAnnexDesc(initialDesc: string, title: string) {
|
|
|
|
|
+ const draft = ref(initialDesc);
|
|
|
|
|
+ return new Promise<string | null>((resolve) => {
|
|
|
|
|
+ Modal.confirm({
|
|
|
|
|
+ title,
|
|
|
|
|
+ okText: '保存',
|
|
|
|
|
+ cancelText: '取消',
|
|
|
|
|
+ content: () => h(Input.TextArea, {
|
|
|
|
|
+ value: draft.value,
|
|
|
|
|
+ rows: 4,
|
|
|
|
|
+ maxlength: 200,
|
|
|
|
|
+ showCount: true,
|
|
|
|
|
+ placeholder: '请输入说明(可选)',
|
|
|
|
|
+ 'onUpdate:value': (v: string) => {
|
|
|
|
|
+ draft.value = v ?? '';
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ onOk: () => {
|
|
|
|
|
+ resolve(draft.value.trim());
|
|
|
|
|
+ },
|
|
|
|
|
+ onCancel: () => {
|
|
|
|
|
+ resolve(null);
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function editAnnexDesc(itemId: number, file: any, isAfterUpload = false) {
|
|
|
|
|
+ const formId = props.currentForm.id;
|
|
|
|
|
+ if (!formId)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const res = file?.response ?? {};
|
|
|
|
|
+ const annexId = Number(res.id ?? file.uid);
|
|
|
|
|
+ const rawName = String(res.rawName ?? res.name ?? file.name ?? '').trim();
|
|
|
|
|
+ const url = String(res.url ?? file.url ?? '').trim();
|
|
|
|
|
+ if (!annexId || !rawName || !url) {
|
|
|
|
|
+ message.warning('附件信息不完整,暂无法编辑说明');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const currentDesc = String(res.desc ?? '').trim();
|
|
|
|
|
+ const nextDesc = await promptAnnexDesc(
|
|
|
|
|
+ currentDesc,
|
|
|
|
|
+ isAfterUpload ? '上传成功,请补充佐证资料说明' : '编辑佐证资料说明',
|
|
|
|
|
+ );
|
|
|
|
|
+ if (nextDesc === null)
|
|
|
|
|
+ return;
|
|
|
|
|
+ await AssessmentContentApi.saveAnnex({
|
|
|
|
|
+ id: annexId,
|
|
|
|
|
+ name: rawName,
|
|
|
|
|
+ formId,
|
|
|
|
|
+ itemId,
|
|
|
|
|
+ url,
|
|
|
|
|
+ type: Number(res.type ?? 5),
|
|
|
|
|
+ desc: nextDesc,
|
|
|
|
|
+ mimetype: res.mimetype,
|
|
|
|
|
+ attachId: res.attachId,
|
|
|
|
|
+ fileSize: res.fileSize,
|
|
|
|
|
+ });
|
|
|
|
|
+ await loadAnnexListByItem(itemId);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function annexCustomRequest(itemId: number, options: Parameters<NonNullable<UploadProps['customRequest']>>[0]) {
|
|
|
|
|
+ const { file, onSuccess, onError } = options;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const formId = props.currentForm.id;
|
|
|
|
|
+ if (!formId)
|
|
|
|
|
+ return;
|
|
|
|
|
+ const raw = file as File;
|
|
|
|
|
+ const mimetype = raw.type || 'application/octet-stream';
|
|
|
|
|
+ let uploadedUrl = '';
|
|
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
|
|
+ annexUploadCo.uploadRequest({
|
|
|
|
|
+ action: 'upload',
|
|
|
|
|
+ filename: raw.name,
|
|
|
|
|
+ data: {},
|
|
|
|
|
+ headers: {},
|
|
|
|
|
+ file: raw,
|
|
|
|
|
+ withCredentials: false,
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ onProgress: () => {},
|
|
|
|
|
+ onSuccess: (body: unknown) => {
|
|
|
|
|
+ uploadedUrl = annexUploadCo.getUrlByUploadResponse?.(body) ?? '';
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (err) => reject(err),
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ await AssessmentContentApi.saveAnnex({
|
|
|
|
|
+ name: raw.name,
|
|
|
|
|
+ formId,
|
|
|
|
|
+ itemId,
|
|
|
|
|
+ url: uploadedUrl,
|
|
|
|
|
+ type: getCheckAnnexType(mimetype),
|
|
|
|
|
+ mimetype,
|
|
|
|
|
+ fileSize: raw.size ? Math.max(1, Math.ceil(raw.size / 1024)) : undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+ onSuccess?.({ url: uploadedUrl }, raw as never);
|
|
|
|
|
+ await loadAnnexListByItem(itemId);
|
|
|
|
|
+ const uploadedFile = getAnnexFileList(itemId).find((f: any) => {
|
|
|
|
|
+ const fileUrl = String(f?.response?.url ?? f?.url ?? '');
|
|
|
|
|
+ return fileUrl === uploadedUrl;
|
|
|
|
|
+ });
|
|
|
|
|
+ if (uploadedFile)
|
|
|
|
|
+ await editAnnexDesc(itemId, uploadedFile, true);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ onError?.(err as Error);
|
|
|
|
|
+ message.error(err instanceof Error ? err.message : String(err));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => [props.currentForm.id, props.checkItemList.map((item) => item.id).join(',')],
|
|
|
|
|
+ async () => {
|
|
|
|
|
+ const itemIds = props.checkItemList.map((item) => item.id);
|
|
|
|
|
+ await Promise.all(itemIds.map(loadAnnexListByItem));
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true },
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
async function validate() {
|
|
async function validate() {
|
|
|
if (props.readonly)
|
|
if (props.readonly)
|
|
|
return;
|
|
return;
|