| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- <template>
- <DynamicForm
- ref="formRef"
- :model="currentForm"
- :options="mergedFormOptions"
- />
-
- <FlexCol>
- <Divider />
- <H3>自查项目选择</H3>
- <Height :height="30" />
- <FlexCol gap="gap.md">
- <FlexCol v-for="(item, index) in checkItemList" :key="item.id" gap="gap.md">
- <FlexRow justify="space-between" align="center">
- <Text fontConfig="subTitleText" :text="`${index + 1}. ${item.name}`" />
- <Tag :text="getCheckModeText(item.checkType)" />
- </FlexRow>
- <FlexCol v-if="item.checkType == 3" gap="gap.sm">
- <FlexRow v-for="child in item.children" :key="child.id" justify="space-between">
- <CheckBox
- :disabled="readonly"
- :text="`${child.name} (${child.points}分)`"
- :modelValue="hasCheckedItem(child.id)"
- @update:modelValue="setCheckedItem(item as CheckItemInfo, child as CheckItemInfo, $event)"
- />
- <Stepper
- v-if="hasCheckedItem(child.id)"
- :disabled="readonly"
- :min="0"
- :max="20"
- :step="1"
- :modelValue="getCheckedItemCount(child.id) ?? 0"
- addonAfter="次"
- @update:modelValue="setCheckedItem(item as CheckItemInfo, child as CheckItemInfo, $event)"
- />
- <view v-else></view>
- </FlexRow>
- </FlexCol>
- <FlexCol v-else gap="gap.sm">
- <CheckBox
- v-for="child in item.children" :key="child.id"
- :disabled="readonly"
- :text="`${child.name} (${child.points}分)`"
- :modelValue="hasCheckedItem(child.id)"
- @update:modelValue="setCheckedItem(item as CheckItemInfo, child as CheckItemInfo, $event)"
- />
- </FlexCol>
- <FlexCol gap="gap.sm" border="1px solid #e0e0e0" radius="radius.md" padding="space.md">
- <Text
- fontConfig="subText"
- color="text.second"
- :text="readonly ? '佐证资料' : '佐证材料上传,支持图片、视频、音频、文档(word、pdf、excel、ppt)等格式'"
- />
- <Text
- v-if="!currentForm.id"
- fontConfig="subText"
- color="text.second"
- text="请先保存评估表后再上传佐证资料"
- />
- <Uploader
- v-show="currentForm.id"
- :ref="(el) => bindUploaderRef(item.id, el)"
- :upload="getAnnexUpload(item.id)"
- :onDeleteClick="(file) => handleAnnexRemove(item.id, file)"
- :max-upload-count="100"
- :max-file-size="20 * 1024 * 1024"
- :group-type="true"
- chooseType="file"
- list-type="list"
- :itemExtraButtons="readonly ? [] : [{ icon: 'edit', onClick: (item2) => editAnnexDesc(item.id, item2) }]"
- :readonly="readonly"
- />
- </FlexCol>
- </FlexCol>
- </FlexCol>
- </FlexCol>
- <FlexCol>
- <Divider />
- <FlexRow justify="space-between" align="center">
- <H3>自评总分</H3>
- <Text fontConfig="subTitleText" color="text.primary" :text="`${totalPoints} 分`" />
- </FlexRow>
- </FlexCol>
- <DynamicForm
- ref="formRef2"
- :model="currentForm"
- :options="mergedFormOptionsEnd"
- />
- </template>
- <script setup lang="ts">
- 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';
- 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 Uploader, { type UploaderInstance } from '@/components/form/Uploader.vue';
- import AssessmentContentApi, { getCheckAnnexType, SelfAssessmentCheckItemAnswer, type CheckItemInfo, type SelfAssessmentDetail } from '@/api/collect/AssessmentContent';
- import AgentWorkApi from '@/api/agent/AgentWorks';
- 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, type UploaderItem } from '@/components/form/Uploader';
- const props = withDefaults(defineProps<{
- currentForm: SelfAssessmentDetail;
- formOptions: IDynamicFormOptions;
- formOptionsEnd: IDynamicFormOptions;
- checkItemList: CheckItemInfo[];
- currentFormCheckItems: SelfAssessmentCheckItemAnswer[];
- /** 管理员只读查看 / 审核页 */
- readonly?: boolean;
- }>(), {
- readonly: false,
- });
- const formRef = ref<IDynamicFormRef | null>(null);
- const formRef2 = 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,
- formAdditionaProps: {
- ...props.formOptions.formAdditionaProps,
- disabled: props.readonly,
- },
- }));
- const mergedFormOptionsEnd = computed<IDynamicFormOptions>(() => ({
- ...props.formOptionsEnd,
- formAdditionaProps: {
- ...props.formOptionsEnd.formAdditionaProps,
- disabled: props.readonly,
- },
- }));
- function getCheckModeText(checkMode: number) {
- switch (checkMode) {
- case 1:
- return '单选';
- case 2:
- return '多选';
- case 3:
- return '可多次';
- }
- }
- function hasCheckedItem(id: number) {
- return props.currentFormCheckItems.some(item => item.id === id);
- }
- function getCheckedItemCount(id: number) {
- return props.currentFormCheckItems.find(item => item.id === id)?.count;
- }
- function setCheckedItem(checkItem: CheckItemInfo, childItem: CheckItemInfo, count: number|boolean) {
- if (props.readonly)
- return;
- if (typeof count === 'boolean') {
- count = count ? 1 : 0;
- }
- let item = props.currentFormCheckItems.find(item => item.id === childItem.id);
- if (!item) {
- item = new SelfAssessmentCheckItemAnswer();
- props.currentFormCheckItems.push(item);
- }
- if (item.count === count)
- return;
- item.id = childItem.id;
- item.points = childItem.points;
- item.count = count;
- switch (checkItem.checkType) {
- case 1: {
- /** 单选,清除其他选项 */
- const allChildren = checkItem.children.map(child => child.id);
- props.currentFormCheckItems.forEach(item => {
- if (allChildren.includes(item.id) && item.id !== childItem.id)
- item.count = 0;
- });
- for (let i = props.currentFormCheckItems.length - 1; i >= 0; i--) {
- if (props.currentFormCheckItems[i].count === 0)
- props.currentFormCheckItems.splice(i, 1);
- }
- break;
- }
- }
- if (item.count === 0)
- ArrayUtils.remove(props.currentFormCheckItems, item);
- }
- function formatAnnexDisplayName(desc: string | null | undefined, name: string) {
- const descText = (desc ?? '').trim();
- if (!descText)
- return name;
- return `${descText}(${name})`;
- }
- function buildAnnexDescInputDoc() {
- const content = props.currentForm.content;
- if (!content)
- return '';
- const lines: string[] = [];
- for (let i = 0; i <= 6; i++) {
- const value = String(content[`item${i}`] ?? '').trim();
- if (!value)
- continue;
- lines.push(`第${i + 1}项:${value}`);
- }
- return lines.join('\n');
- }
- type AnnexUploaderItem = UploaderItem & {
- annexMeta?: {
- id: number;
- rawName: string;
- desc: string;
- url: string;
- type: number;
- mimetype?: string|null;
- attachId?: number|null;
- fileSize?: number|null;
- };
- };
- function createAnnexUploaderItem(item: {
- id: number;
- name: string;
- desc?: string|null;
- url: string;
- type: number;
- mimetype?: string|null;
- attachId?: number|null;
- fileSize?: number|null;
- }) {
- const displayName = formatAnnexDisplayName(item.desc, item.name);
- const uploaderItem = stringUrlToUploaderItem(item.url, displayName) as AnnexUploaderItem;
- uploaderItem.annexMeta = {
- id: item.id,
- rawName: item.name,
- desc: item.desc ?? '',
- url: item.url,
- type: item.type,
- mimetype: item.mimetype,
- attachId: item.attachId,
- fileSize: item.fileSize,
- };
- return uploaderItem;
- }
- function promptAnnexDesc(initialDesc: string, title: string) {
- return new Promise<string|null>((resolve) => {
- uni.showModal({
- title,
- editable: true,
- placeholderText: '请输入佐证资料说明(可选)',
- content: initialDesc,
- success: (res) => {
- if (!res.confirm) {
- resolve(null);
- return;
- }
- resolve((res.content ?? '').trim());
- },
- fail: () => resolve(null),
- });
- });
- }
- async function editAnnexDesc(itemId: number, item: UploaderItem, isAfterUpload = false) {
- const formId = props.currentForm.id;
- if (!formId)
- return;
- const annexItem = item as AnnexUploaderItem;
- const meta = annexItem.annexMeta;
- if (!meta?.id) {
- uni.showToast({ title: '附件信息不完整,无法编辑说明', icon: 'none' });
- return;
- }
- const nextDesc = await promptAnnexDesc(
- meta.desc ?? '',
- isAfterUpload ? '上传成功,请补充附件说明' : '编辑附件说明',
- );
- if (nextDesc === null)
- return;
- item.name = nextDesc;
- await AssessmentContentApi.saveAnnex({
- id: meta.id,
- name: meta.rawName,
- formId,
- itemId,
- url: meta.url,
- type: meta.type,
- desc: nextDesc,
- mimetype: meta.mimetype ?? undefined,
- attachId: meta.attachId ?? undefined,
- fileSize: meta.fileSize ?? undefined,
- });
- await loadAnnexListByItem(itemId);
- }
- function bindUploaderRef(itemId: number, el: unknown) {
- uploaderRefMap.set(itemId, (el as UploaderInstance | null) || null);
- if (el)
- loadAnnexListByItem(itemId);
- }
- function handleAnnexRemove(itemId: number, item: UploaderItem): Promise<void> {
- const annexItem = item as AnnexUploaderItem;
- const meta = annexItem.annexMeta;
- if (!meta?.id || item.state !== 'success')
- return Promise.resolve();
- return new Promise((resolve, reject) => {
- uni.showModal({
- title: '确认删除',
- content: '确定要删除该佐证材料吗?',
- confirmText: '删除',
- confirmColor: '#ff4d4f',
- success: async (res) => {
- if (!res.confirm) {
- reject(new Error('cancel'));
- return;
- }
- try {
- await AssessmentContentApi.delAnnex(meta.id);
- await loadAnnexListByItem(itemId);
- uni.showToast({ title: '删除成功', icon: 'success' });
- resolve();
- } catch (err) {
- uni.showToast({
- title: err instanceof Error ? err.message : '删除失败',
- icon: 'none',
- });
- reject(err);
- }
- },
- fail: () => reject(new Error('cancel')),
- });
- });
- }
- 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);
- const inputDoc = buildAnnexDescInputDoc();
- const desc = /*inputDoc
- ? await AgentWorkApi.generateFileDescByInputDoc(inputDoc, item.name)
- :*/ '';
- await AssessmentContentApi.saveAnnex({
- name: item.name,
- formId,
- itemId,
- desc,
- url: res,
- type: getCheckAnnexType(mimetype),
- mimetype,
- fileSize: item.size
- ? Math.max(1, Math.ceil(item.size / 1024))
- : undefined,
- });
- await loadAnnexListByItem(itemId);
- const uploaded = uploaderRefMap.get(itemId)?.getList().find((listItem) => {
- const meta = (listItem as AnnexUploaderItem).annexMeta;
- return meta?.url === res;
- });
- if (uploaded)
- await editAnnexDesc(itemId, uploaded, true);
- });
- 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) => createAnnexUploaderItem(item)));
- }
- async function reloadAllAnnexList() {
- const itemIds = props.checkItemList.map((item) => item.id);
- await Promise.all(itemIds.map(loadAnnexListByItem));
- }
- const checkItemMap = computed(() => {
- const m = new Map<number, CheckItemInfo & { parent?: CheckItemInfo }>();
- for (const n of props.checkItemList) {
- m.set(n.id, n);
- if (n.children?.length) {
- for (const child of n.children) {
- (child as any).parent = n;
- m.set(child.id, child as CheckItemInfo & { parent?: CheckItemInfo });
- }
- }
- }
- return m;
- });
- const totalPoints = computed(() => {
- const groupMap = new Map<number, number>();
- for (const item of props.currentFormCheckItems) {
- const checkItem = checkItemMap.value.get(item.id);
- if (!checkItem || !checkItem.parent)
- continue;
- const parentId = checkItem.parent.id;
- const score = (item.points ?? 0) * (item.count ?? 1);
- groupMap.set(parentId, (groupMap.get(parentId) ?? 0) + score);
- }
- let total = 0;
- for (const [parentId, groupScore] of groupMap) {
- const parent = checkItemMap.value.get(parentId);
- const maxPoints = parent?.points ?? Infinity;
- total += Math.min(groupScore, maxPoints);
- }
- total -= (props.currentForm.deductPoints ?? 0);
- return total;
- });
- async function validate() {
- if (props.readonly)
- return;
- await formRef.value?.validate();
- await formRef2.value?.validate();
- }
- watch(
- () => [props.currentForm.id, props.checkItemList.map((item) => item.id).join(',')],
- () => {
- setTimeout(() => {
- reloadAllAnnexList();
- }, 2000);
- },
- { immediate: true },
- );
- defineExpose({ validate });
- </script>
|