From c5b6bf8931e80e5b097af0f93c95ee82164cf872 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 14 八月 2026 13:38:54 +0800
Subject: [PATCH] feat(srm): 添加供应商证书和招标管理附件功能
---
src/api/srm/tender/index.ts | 4 +
src/views/erp/purchase/invoice/index.vue | 42 ----------
src/views/bpm/model/index.vue | 7 +
src/views/srm/statistics/index.vue | 9 ++
src/views/crm/receivable/data.ts | 10 --
src/views/erp/purchase/invoice/data.ts | 22 -----
src/views/srm/supplierCertificate/modules/form.vue | 11 ++
src/components/form-create/helpers.ts | 9 +
src/views/srm/supplierCertificate/data.ts | 20 +++++
src/views/srm/tender/detail.vue | 28 +++++-
src/views/srm/supplierCertificate/index.vue | 17 ++++
src/api/srm/supplier/index.ts | 5 +
src/views/crm/receivable/components/data.ts | 10 --
src/api/srm/supplierCertificate/index.ts | 3
src/api/erp/purchase/invoice/index.ts | 17 ----
src/views/srm/tender/data.ts | 13 +++
src/views/srm/tender/modules/form.vue | 12 +++
17 files changed, 129 insertions(+), 110 deletions(-)
diff --git a/src/api/erp/purchase/invoice/index.ts b/src/api/erp/purchase/invoice/index.ts
index bf69aaa..305230c 100644
--- a/src/api/erp/purchase/invoice/index.ts
+++ b/src/api/erp/purchase/invoice/index.ts
@@ -26,8 +26,6 @@
invoiceTitle?: string;
price?: number;
invoiceTime?: string;
- auditStatus?: number;
- processInstanceId?: string;
remark?: string;
attachmentList?: { id: number; name?: string; url?: string }[];
blobIds?: number[];
@@ -48,7 +46,6 @@
price?: number;
supplierId?: number;
purchaseOrderId?: number;
- auditStatus?: number;
hasAttachment?: boolean;
remainingInvoicePrice?: number;
}
@@ -89,19 +86,7 @@
return requestClient.download('/erp/purchase-invoice/export-excel', { params });
}
-/** 鏇存柊鏉ョエ鐨勭姸鎬侊紙瀹℃牳/鍙嶅鏍革級 */
-export function updatePurchaseInvoiceStatus(id: number, status: number) {
- return requestClient.put('/erp/purchase-invoice/update-status', null, {
- params: { id, status },
- });
-}
-
-/** 鑾峰緱寰呭鏍告潵绁ㄦ暟閲� */
-export function getAuditPurchaseInvoiceCount() {
- return requestClient.get<number>('/erp/purchase-invoice/audit-count');
-}
-
-/** 鏌ヨ鏉ョエ涓嬫媺鍒楄〃锛堟寜渚涘簲鍟嗗彲閫夋潵绁紝浠呭鏍搁�氳繃涓斿凡涓婁紶鍙戠エ闄勪欢锛� */
+/** 鏌ヨ鏉ョエ涓嬫媺鍒楄〃锛堟寜渚涘簲鍟嗗彲閫夋潵绁紝宸蹭笂浼犲彂绁ㄩ檮浠朵笖鍓╀綑鍙粯閲戦澶т簬 0锛� */
export function getPurchaseInvoiceSimpleList(
supplierId: number,
purchaseOrderId?: number,
diff --git a/src/api/srm/supplier/index.ts b/src/api/srm/supplier/index.ts
index 60b176a..e75a362 100644
--- a/src/api/srm/supplier/index.ts
+++ b/src/api/srm/supplier/index.ts
@@ -31,6 +31,11 @@
return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/simple-list');
}
+/** 鑾峰彇渚涘簲鍟嗘。妗堝垪琛紙涓嶈繃婊ゅ噯鍏ョ姸鎬侊級 */
+export function getSupplierList() {
+ return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/list');
+}
+
/** 鑾峰彇渚涘簲鍟嗚鎯� */
export function getSupplier(id: number) {
return requestClient.get<SrmSupplierApi.SupplierVO>(`/srm/supplier/get?id=${id}`);
diff --git a/src/api/srm/supplierCertificate/index.ts b/src/api/srm/supplierCertificate/index.ts
index 9908b1c..2504662 100644
--- a/src/api/srm/supplierCertificate/index.ts
+++ b/src/api/srm/supplierCertificate/index.ts
@@ -1,5 +1,6 @@
import type { PageParam, PageResult } from '@vben/request';
+import type { SystemStorageApi } from '#/api/system/storage';
import { requestClient } from '#/api/request';
export namespace SrmSupplierCertificateApi {
@@ -16,6 +17,8 @@
status?: number;
remark?: string;
createTime?: string;
+ blobIds?: number[];
+ attachmentList?: SystemStorageApi.StorageBlob[];
}
}
diff --git a/src/api/srm/tender/index.ts b/src/api/srm/tender/index.ts
index a44c5b9..cefefdb 100644
--- a/src/api/srm/tender/index.ts
+++ b/src/api/srm/tender/index.ts
@@ -1,5 +1,6 @@
import type { PageParam, PageResult } from '@vben/request';
+import type { SystemStorageApi } from '#/api/system/storage';
import { requestClient } from '#/api/request';
export namespace SrmTenderApi {
@@ -23,6 +24,8 @@
purchaseOrderNo?: string;
biddingMode?: string;
createTime?: string;
+ blobIds?: number[];
+ attachmentList?: SystemStorageApi.StorageBlob[];
}
export interface TenderMaterialVO {
@@ -105,6 +108,7 @@
id: number;
tenderDesc?: string;
qualificationRequirements?: string;
+ blobIds?: number[];
}
}
diff --git a/src/components/form-create/helpers.ts b/src/components/form-create/helpers.ts
index 12d07a6..9e827de 100644
--- a/src/components/form-create/helpers.ts
+++ b/src/components/form-create/helpers.ts
@@ -28,7 +28,10 @@
}
/** 瑙g爜琛ㄥ崟 Conf */
-export function decodeConf(conf: string) {
+export function decodeConf(conf?: string | null) {
+ if (!conf) {
+ return {};
+ }
return formCreate.parseJson(conf);
}
@@ -43,9 +46,9 @@
}
/** 瑙g爜琛ㄥ崟 Fields */
-export function decodeFields(fields: string[]) {
+export function decodeFields(fields?: string[] | null) {
const rule: Rule[] = [];
- fields.forEach((item) => {
+ (fields || []).forEach((item) => {
rule.push(formCreate.parseJson(item));
});
return rule;
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index 91c504f..d7b2b7d 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { ModelCategoryInfo } from '#/api/bpm/model';
-import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
+import { onActivated, onMounted, reactive, ref, useTemplateRef, watch } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
@@ -76,6 +76,11 @@
}
/** 鍒濆鍖� */
+onMounted(() => {
+ getList();
+});
+
+/** 婵�娲绘椂鍒锋柊锛坘eep-alive 缂撳瓨鍦烘櫙涓嬩粠缂栬緫椤佃繑鍥炴椂瑙﹀彂锛� */
onActivated(() => {
getList();
});
diff --git a/src/views/crm/receivable/components/data.ts b/src/views/crm/receivable/components/data.ts
index 4f3f0e1..4aa273b 100644
--- a/src/views/crm/receivable/components/data.ts
+++ b/src/views/crm/receivable/components/data.ts
@@ -53,16 +53,6 @@
minWidth: 150,
},
{
- title: '鍥炴鐘舵��',
- field: 'auditStatus',
- minWidth: 100,
- fixed: 'right',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.CRM_AUDIT_STATUS },
- },
- },
- {
title: '鎿嶄綔',
field: 'actions',
width: 130,
diff --git a/src/views/crm/receivable/data.ts b/src/views/crm/receivable/data.ts
index 5dc6c1a..e401e1f 100644
--- a/src/views/crm/receivable/data.ts
+++ b/src/views/crm/receivable/data.ts
@@ -357,16 +357,6 @@
minWidth: 150,
},
{
- title: '鍥炴鐘舵��',
- field: 'auditStatus',
- minWidth: 100,
- fixed: 'right',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.CRM_AUDIT_STATUS },
- },
- },
- {
title: '鎿嶄綔',
field: 'actions',
minWidth: 200,
diff --git a/src/views/erp/purchase/invoice/data.ts b/src/views/erp/purchase/invoice/data.ts
index c70e185..a371839 100644
--- a/src/views/erp/purchase/invoice/data.ts
+++ b/src/views/erp/purchase/invoice/data.ts
@@ -148,19 +148,6 @@
valueField: 'id',
},
},
- {
- fieldName: 'auditStatus',
- label: '瀹℃牳鐘舵��',
- component: 'Select',
- componentProps: {
- options: [
- { label: '鏈鏍�', value: 10 },
- { label: '宸插鏍�', value: 20 },
- ],
- placeholder: '璇烽�夋嫨瀹℃牳鐘舵��',
- allowClear: true,
- },
- },
];
}
@@ -221,16 +208,9 @@
slots: { default: 'attachment' },
},
{
- title: '瀹℃壒鐘舵��',
- field: 'auditStatus',
- minWidth: 100,
- fixed: 'right',
- slots: { default: 'auditStatus' },
- },
- {
title: '鎿嶄綔',
field: 'actions',
- minWidth: 280,
+ minWidth: 200,
fixed: 'right',
slots: { default: 'actions' },
},
diff --git a/src/views/erp/purchase/invoice/index.vue b/src/views/erp/purchase/invoice/index.vue
index c0cbd7b..38fc81b 100644
--- a/src/views/erp/purchase/invoice/index.vue
+++ b/src/views/erp/purchase/invoice/index.vue
@@ -12,7 +12,6 @@
deletePurchaseInvoice,
exportPurchaseInvoice,
getPurchaseInvoicePage,
- updatePurchaseInvoiceStatus,
} from '#/api/erp/purchase/invoice';
import { $t } from '#/locales';
@@ -62,24 +61,6 @@
try {
await deletePurchaseInvoice(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.no]));
- handleRefresh();
- } finally {
- hideLoading();
- }
-}
-
-/** 鏇存柊鏉ョエ鐘舵�侊紙瀹℃牳/鍙嶅鏍革級 */
-async function handleUpdateStatus(
- row: ErpPurchaseInvoiceApi.PurchaseInvoice,
- status: number,
-) {
- const hideLoading = message.loading({
- content: '鏇存柊鐘舵�佷腑...',
- duration: 0,
- });
- try {
- await updatePurchaseInvoiceStatus(row.id!, status);
- message.success('鏇存柊鐘舵�佹垚鍔�');
handleRefresh();
} finally {
hideLoading();
@@ -153,11 +134,6 @@
<Tag v-if="!row.hasAttachment" color="warning">鏈笂浼�</Tag>
<Tag v-else color="success">宸蹭笂浼�</Tag>
</template>
- <template #auditStatus="{ row }">
- <Tag v-if="row.auditStatus === 10" color="warning">鏈鏍�</Tag>
- <Tag v-else-if="row.auditStatus === 20" color="success">宸插鏍�</Tag>
- <Tag v-else color="default">--</Tag>
- </template>
<template #actions="{ row }">
<TableAction
:actions="[
@@ -173,24 +149,7 @@
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['erp:purchase-invoice:update'],
- ifShow: row.auditStatus === 10,
onClick: handleEdit.bind(null, row),
- },
- {
- label: '瀹℃牳',
- type: 'link',
- icon: ACTION_ICON.AUDIT,
- auth: ['erp:purchase-invoice:update-status'],
- ifShow: row.auditStatus === 10,
- onClick: handleUpdateStatus.bind(null, row, 20),
- },
- {
- label: '鍙嶅鏍�',
- type: 'link',
- icon: ACTION_ICON.VIEW,
- auth: ['erp:purchase-invoice:update-status'],
- ifShow: row.auditStatus === 20,
- onClick: handleUpdateStatus.bind(null, row, 10),
},
{
label: $t('common.delete'),
@@ -198,7 +157,6 @@
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:purchase-invoice:delete'],
- ifShow: row.auditStatus !== 20,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),
confirm: handleDelete.bind(null, row),
diff --git a/src/views/srm/statistics/index.vue b/src/views/srm/statistics/index.vue
index 439ae78..5f9652a 100644
--- a/src/views/srm/statistics/index.vue
+++ b/src/views/srm/statistics/index.vue
@@ -96,7 +96,14 @@
</div>
<div class="rounded-lg bg-purple-50 p-6 text-center shadow-sm">
<div class="text-3xl font-bold text-purple-600">{{ stats.savedAmount?.toLocaleString() ?? 0 }}</div>
- <div class="mt-2 text-gray-500">鑺傜害閲戦(鍏�)</div>
+ <div class="mt-2 flex items-center justify-center gap-1 text-gray-500">
+ 鑺傜害閲戦(鍏�)
+ <a-tooltip title="鑺傜害閲戦 = 鎷涙爣鎬婚噾棰�(棰勭畻閲戦) 鈭� 瀹氭爣鎬婚噾棰�(涓爣閲戦)">
+ <span
+ class="inline-flex h-3.5 w-3.5 cursor-help items-center justify-center rounded-full bg-gray-400 text-[10px] leading-none text-white"
+ >?</span>
+ </a-tooltip>
+ </div>
</div>
<div class="rounded-lg bg-teal-50 p-6 text-center shadow-sm">
<div class="text-3xl font-bold text-teal-600">{{ stats.winRate ?? 0 }}%</div>
diff --git a/src/views/srm/supplierCertificate/data.ts b/src/views/srm/supplierCertificate/data.ts
index 1472c53..c956f2d 100644
--- a/src/views/srm/supplierCertificate/data.ts
+++ b/src/views/srm/supplierCertificate/data.ts
@@ -83,6 +83,19 @@
rules: 'required',
},
{
+ fieldName: 'blobIds',
+ label: '璇佷功闄勪欢',
+ component: 'FileUpload',
+ componentProps: {
+ valueKey: 'id',
+ maxNumber: 10,
+ multiple: true,
+ accept: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
+ helpText: '鏀寔jpg銆乯peg銆乸ng銆乸df銆乨oc銆乨ocx鏍煎紡锛屽崟涓枃浠朵笉瓒呰繃2MB',
+ },
+ formItemClass: 'md:col-span-2',
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
@@ -144,6 +157,13 @@
{ field: 'validEndDate', title: '鍒版湡鏃ユ湡', minWidth: 120 },
{ field: 'createTime', title: '鍒涘缓鏃堕棿', minWidth: 160 },
{
+ field: 'attachmentList',
+ title: '闄勪欢',
+ minWidth: 100,
+ fixed: 'right',
+ slots: { default: 'attachment' },
+ },
+ {
title: '鎿嶄綔',
width: 130,
fixed: 'right',
diff --git a/src/views/srm/supplierCertificate/index.vue b/src/views/srm/supplierCertificate/index.vue
index 29ea03f..a26aa75 100644
--- a/src/views/srm/supplierCertificate/index.vue
+++ b/src/views/srm/supplierCertificate/index.vue
@@ -4,7 +4,7 @@
import { Page, useVbenModal } from '@vben/common-ui';
-import { message } from 'ant-design-vue';
+import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -112,6 +112,21 @@
]"
/>
</template>
+ <template #attachment="{ row }">
+ <div v-if="row.attachmentList && row.attachmentList.length" class="flex flex-col gap-1">
+ <a
+ v-for="file in row.attachmentList"
+ :key="file.id"
+ :href="file.url"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-blue-500 hover:underline"
+ >
+ {{ file.name || '闄勪欢-' + file.id }}
+ </a>
+ </div>
+ <Tag v-else color="default">--</Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/srm/supplierCertificate/modules/form.vue b/src/views/srm/supplierCertificate/modules/form.vue
index d0bc05b..041e5e0 100644
--- a/src/views/srm/supplierCertificate/modules/form.vue
+++ b/src/views/srm/supplierCertificate/modules/form.vue
@@ -65,6 +65,17 @@
modalApi.lock();
try {
formData.value = await getCertificate(data.id);
+ if (formData.value?.attachmentList?.length) {
+ // 杞负涓婁紶缁勪欢鐨勬枃浠跺璞★紱鎻愪氦鏃� formApi 浼氭寜 valueKey 鎻愬彇闄勪欢 ID
+ (formData.value as Record<string, unknown>).blobIds =
+ formData.value.attachmentList.map((item) => ({
+ uid: String(item.id),
+ name: item.name || '',
+ url: item.url || '',
+ status: 'done',
+ id: item.id,
+ }));
+ }
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
diff --git a/src/views/srm/tender/data.ts b/src/views/srm/tender/data.ts
index a3b1005..475eae7 100644
--- a/src/views/srm/tender/data.ts
+++ b/src/views/srm/tender/data.ts
@@ -180,6 +180,19 @@
componentProps: { placeholder: '璇疯緭鍏ヨ祫璐ㄨ姹�', rows: 3 },
formItemClass: 'col-span-2',
},
+ {
+ fieldName: 'blobIds',
+ label: '璧勮川瑕佹眰闄勪欢',
+ component: 'FileUpload',
+ componentProps: {
+ valueKey: 'id',
+ maxNumber: 10,
+ multiple: true,
+ accept: ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'],
+ helpText: '鏀寔jpg銆乯peg銆乸ng銆乸df銆乨oc銆乨ocx鏍煎紡锛屽崟涓枃浠朵笉瓒呰繃2MB',
+ },
+ formItemClass: 'col-span-2',
+ },
];
}
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index b7f5e2c..83cd7b8 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -19,7 +19,7 @@
createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder,
confirmTenderProject,
} from '#/api/srm/tender';
-import { getSupplierSimpleList } from '#/api/srm/supplier';
+import { getSupplierList } from '#/api/srm/supplier';
import { getItemSimpleList } from '#/api/mdm/item';
import type { MdmItemApi } from '#/api/mdm/item';
import {
@@ -51,7 +51,7 @@
onMounted(async () => {
const [items, suppliers] = await Promise.all([
getItemSimpleList(),
- getSupplierSimpleList(),
+ getSupplierList(),
]);
mdmItemList.value = items;
supplierList.value = suppliers;
@@ -538,14 +538,32 @@
</div>
<!-- 鎷涙爣璇存槑 / 璧勮川瑕佹眰 -->
- <template v-if="project.tenderDesc || project.qualificationRequirements">
+ <template
+ v-if="project.tenderDesc || project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)"
+ >
<a-divider class="!my-2" />
<a-collapse :bordered="false" ghost expand-icon-position="end">
<a-collapse-panel v-if="project.tenderDesc" key="desc" header="鎷涙爣璇存槑">
<p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.tenderDesc }}</p>
</a-collapse-panel>
- <a-collapse-panel v-if="project.qualificationRequirements" key="req" header="璧勮川瑕佹眰">
- <p class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p>
+ <a-collapse-panel
+ v-if="project.qualificationRequirements || (project.attachmentList && project.attachmentList.length)"
+ key="req"
+ header="璧勮川瑕佹眰"
+ >
+ <p v-if="project.qualificationRequirements" class="text-gray-500 text-xs whitespace-pre-wrap">{{ project.qualificationRequirements }}</p>
+ <div v-if="project.attachmentList && project.attachmentList.length" class="mt-2 flex flex-col gap-1">
+ <a
+ v-for="file in project.attachmentList"
+ :key="file.id"
+ :href="file.url"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-blue-500 text-xs hover:underline"
+ >
+ {{ file.name || '闄勪欢-' + file.id }}
+ </a>
+ </div>
</a-collapse-panel>
</a-collapse>
</template>
diff --git a/src/views/srm/tender/modules/form.vue b/src/views/srm/tender/modules/form.vue
index bcdc8d2..652d017 100644
--- a/src/views/srm/tender/modules/form.vue
+++ b/src/views/srm/tender/modules/form.vue
@@ -58,6 +58,7 @@
id: data.id!,
tenderDesc: data.tenderDesc,
qualificationRequirements: data.qualificationRequirements,
+ blobIds: data.blobIds,
});
} else {
await (formData.value?.id
@@ -92,6 +93,17 @@
modalApi.lock();
try {
formData.value = await getTenderProject(data.id);
+ if (formData.value?.attachmentList?.length) {
+ // 杞负涓婁紶缁勪欢鐨勬枃浠跺璞★紱鎻愪氦鏃� formApi 浼氭寜 valueKey 鎻愬彇闄勪欢 ID
+ (formData.value as Record<string, unknown>).blobIds =
+ formData.value.attachmentList.map((item) => ({
+ uid: String(item.id),
+ name: item.name || '',
+ url: item.url || '',
+ status: 'done',
+ id: item.id,
+ }));
+ }
const partial = formData.value.tenderStatus !== undefined
&& formData.value.tenderStatus !== TENDER_STATUS.DRAFT;
await formApi.setValues({ ...formData.value, _partial: partial || undefined });
--
Gitblit v1.9.3