From 908fae82bbf0476df66c9213b49a8199571e8333 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 29 七月 2026 11:05:38 +0800
Subject: [PATCH] fix(datepicker): 修复日期选择器时间格式化问题
---
src/views/mes/pro/mps/data.ts | 2
src/views/erp/finance/payment/data.ts | 4
src/views/srm/statistics/index.vue | 128 +
src/views/system/tenant/data.ts | 2
src/views/wms/order/shipment/data.ts | 2
src/views/infra/demo/demo01/data.ts | 2
src/adapter/vxe-table.ts | 33
src/views/srm/tender/index.vue | 223 +++
src/views/srm/tender/modules/form.vue | 84 +
src/views/wms/order/movement/data.ts | 2
src/views/erp/stock/move/data.ts | 2
src/views/srm/enums.ts | 263 +++
src/api/srm/supplierApply/index.ts | 78 +
src/views/infra/demo/demo03/inner/data.ts | 2
src/views/mes/pro/workorder/data.ts | 2
src/views/erp/stock/check/data.ts | 2
src/views/wls/outsourceissue/data.ts | 2
src/views/crm/saleQuotation/data.ts | 4
src/views/infra/demo/demo03/erp/data.ts | 2
src/views/srm/supplier/modules/form.vue | 82 +
src/views/wls/stocktaking/task/data.ts | 4
src/views/mes/pro/feedback/data.ts | 2
src/views/crm/clue/data.ts | 2
src/views/wls/productreceipt/data.ts | 2
src/views/erp/purchase/in/data.ts | 4
src/views/mes/workbench/components/tabs/ReturnIssueTab.vue | 2
src/views/mes/pd/project/data.ts | 2
src/views/mes/qc/iqc/data.ts | 4
src/views/mes/pro/card/data.ts | 4
src/views/crm/contract/data.ts | 6
src/views/wms/order/check/data.ts | 2
src/views/srm/supplier/components/index.ts | 2
src/views/srm/supplier/data.ts | 178 ++
src/views/srm/tender/detail.vue | 811 ++++++++++
src/views/wls/arrivalnotice/data.ts | 2
src/views/erp/purchase/order/data.ts | 4
src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue | 2
src/views/mes/dv/maintenrecord/data.ts | 2
src/views/erp/purchase/request/data.ts | 4
src/views/erp/sale/return/data.ts | 2
src/views/srm/tender/data.ts | 188 ++
src/api/mdm/item/index.ts | 11
src/api/srm/tender/index.ts | 232 +++
src/views/bpm/oa/leave/data.ts | 4
src/views/srm/supplierCertificate/modules/form.vue | 82 +
src/views/erp/purchase/statistics/supplier.vue | 2
src/views/wls/salesnotice/data.ts | 2
src/views/mes/dv/checkplan/data.ts | 4
src/views/mes/dv/machinery/data.ts | 4
src/views/srm/supplierEvaluation/data.ts | 132 +
src/views/crm/receivable/plan/data.ts | 2
src/views/wls/returnsales/data.ts | 2
src/views/mes/qc/oqc/data.ts | 4
src/views/crm/customer/data.ts | 2
src/views/erp/stock/in/data.ts | 4
src/views/wls/productsales/data.ts | 2
src/views/mes/qc/rqc/data.ts | 2
src/views/crm/business/data.ts | 2
src/views/mes/dv/checkrecord/data.ts | 2
src/views/mes/qc/ipqc/data.ts | 2
src/views/srm/supplierCertificate/data.ts | 153 ++
src/views/srm/supplierApply/modules/form.vue | 84 +
src/api/srm/supplierCertificate/index.ts | 55
src/views/srm/supplier/components/select-dialog.vue | 259 +++
src/views/wls/outsourcereceipt/data.ts | 6
src/views/erp/purchase/supplier/data.ts | 2
src/views/infra/demo/demo03/normal/data.ts | 2
src/views/erp/purchase/request/modules/order-form.vue | 2
src/views/srm/supplierEvaluation/index.vue | 120 +
src/router/routes/modules/srm.ts | 27
src/api/srm/supplierEvaluation/index.ts | 47
src/views/erp/finance/receipt/data.ts | 2
src/views/mes/pd/project/modules/task/data.ts | 4
src/views/mes/workbench/components/tabs/ProductIssueTab.vue | 2
src/views/erp/purchase/statistics/uncompleted.vue | 2
src/views/wls/productissue/data.ts | 2
src/views/erp/stock/out/data.ts | 2
src/api/srm/statistics/index.ts | 18
src/views/wls/miscissue/data.ts | 2
src/views/wls/packages/data.ts | 4
src/views/wls/stocktaking/plan/data.ts | 4
src/views/mes/pro/task/data.ts | 6
src/views/mes/cal/plan/data.ts | 4
src/views/crm/receivable/data.ts | 2
src/views/wls/miscreceipt/data.ts | 2
src/views/mes/dv/repair/data.ts | 6
src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue | 2
src/views/wls/returnissue/data.ts | 2
src/views/srm/supplierApply/data.ts | 160 ++
src/views/mes/pro/andon/record/data.ts | 2
src/views/crm/followup/data.ts | 2
src/views/erp/purchase/return/data.ts | 4
src/views/erp/sale/order/data.ts | 2
src/views/crm/contact/data.ts | 2
src/views/wls/itemreceipt/data.ts | 6
src/views/srm/supplierEvaluation/modules/form.vue | 82 +
src/views/srm/supplierCertificate/index.vue | 141 +
src/api/srm/supplier/index.ts | 52
src/views/srm/supplier/components/select.vue | 117 +
src/views/srm/supplier/index.vue | 120 +
src/views/erp/sale/out/data.ts | 2
src/views/wls/returnvendor/data.ts | 2
src/views/srm/supplierApply/index.vue | 264 +++
src/views/wms/order/receipt/data.ts | 2
src/views/mes/tm/tool/data.ts | 2
105 files changed, 4,325 insertions(+), 107 deletions(-)
diff --git a/src/adapter/vxe-table.ts b/src/adapter/vxe-table.ts
index 554ae3a..01f7374 100644
--- a/src/adapter/vxe-table.ts
+++ b/src/adapter/vxe-table.ts
@@ -120,11 +120,42 @@
});
// 琛ㄦ牸閰嶇疆椤瑰彲浠ョ敤 cellRender: { name: 'CellTag' },
+ // props: { data: [{value, color}], labels?: {[value]: label} }
+ // 鏀寔涓ょ鍖归厤妯″紡:
+ // 1. 绮剧‘鍖归厤: data 鍜� labels 涓兘鏄簿纭�� (鐘舵��/绫诲瀷鏋氫妇)
+ // 2. 闃堝�煎尮閰�: data 涓洪檷搴忛槇鍊�, label 鏄剧ず鍘熷鏁板�� (璇勫垎)
vxeUI.renderer.add('CellTag', {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
- return h(Tag, { color: props?.color }, () => row[column.field]);
+ const rawValue = row[column.field];
+
+ // 鏌ユ壘鏄剧ず鏂囨湰
+ let label = rawValue;
+ if (props?.labels && rawValue !== undefined && rawValue !== null) {
+ label = props.labels[rawValue] ?? rawValue;
+ }
+
+ // 鏌ユ壘棰滆壊: 浼樺厛绮剧‘鍖归厤锛屽惁鍒欓槇鍊煎尮閰�
+ let color: string | undefined;
+ if (props?.data) {
+ const exact = props.data.find(
+ (item: any) => item.value === rawValue,
+ );
+ if (exact) {
+ color = exact.color;
+ } else {
+ const sorted = [...props.data]
+ .filter((item: any) => typeof item.value === 'number')
+ .sort((a: any, b: any) => b.value - a.value);
+ const threshold = sorted.find(
+ (item: any) => Number(rawValue) >= item.value,
+ );
+ if (threshold) color = threshold.color;
+ }
+ }
+
+ return h(Tag, { color }, () => label);
},
});
diff --git a/src/api/mdm/item/index.ts b/src/api/mdm/item/index.ts
index 2f40e36..f169a1f 100644
--- a/src/api/mdm/item/index.ts
+++ b/src/api/mdm/item/index.ts
@@ -67,9 +67,14 @@
});
}
-/** 鏌ヨ鐗╂枡鍒楄〃 */
-export function getItemList(params?: Partial<MdmItemApi.Item>) {
- return requestClient.get<MdmItemApi.Item[]>('/mdm/item/list', { params });
+/** 鏌ヨ鐗╂枡鍒楄〃锛堟寜ID鍒楄〃锛� */
+export function getItemList(ids: number[]) {
+ return requestClient.get<MdmItemApi.Item[]>('/mdm/item/list-by-ids', { params: { ids: ids.join(',') } });
+}
+
+/** 鑾峰彇鐗╂枡绮剧畝鍒楄〃锛堝惎鐢ㄧ姸鎬侊紝鐢ㄤ簬涓嬫媺閫夋嫨锛� */
+export function getItemSimpleList() {
+ return requestClient.get<MdmItemApi.Item[]>('/mdm/item/simple-list');
}
/** 鏌ヨ鐗╂枡璇︽儏 */
diff --git a/src/api/srm/statistics/index.ts b/src/api/srm/statistics/index.ts
new file mode 100644
index 0000000..36525c0
--- /dev/null
+++ b/src/api/srm/statistics/index.ts
@@ -0,0 +1,18 @@
+import { requestClient } from '#/api/request';
+
+export namespace SrmStatisticsApi {
+ export interface TenderStatisticsVO {
+ totalTenderCount?: number;
+ totalTenderAmount?: number;
+ totalAwardAmount?: number;
+ savedAmount?: number;
+ winRate?: number;
+ supplierCount?: number;
+ period?: string;
+ }
+}
+
+/** 鑾峰彇鎷涙爣缁熻 */
+export function getTenderStatistics(year: string) {
+ return requestClient.get<SrmStatisticsApi.TenderStatisticsVO>(`/srm/statistics/tender?year=${year}`);
+}
diff --git a/src/api/srm/supplier/index.ts b/src/api/srm/supplier/index.ts
new file mode 100644
index 0000000..27da1c2
--- /dev/null
+++ b/src/api/srm/supplier/index.ts
@@ -0,0 +1,52 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace SrmSupplierApi {
+ export interface SupplierVO {
+ id?: number;
+ code: string;
+ name: string;
+ type: number;
+ status: number;
+ contactName?: string;
+ contactMobile?: string;
+ contactEmail?: string;
+ address?: string;
+ bankName?: string;
+ bankAccount?: string;
+ taxNo?: string;
+ remark?: string;
+ createTime?: string;
+ }
+}
+
+/** 鍒嗛〉鏌ヨ渚涘簲鍟� */
+export function getSupplierPage(params: PageParam) {
+ return requestClient.get<PageResult<SrmSupplierApi.SupplierVO>>('/srm/supplier/page', { params });
+}
+
+/** 鑾峰彇渚涘簲鍟嗙簿绠�鍒楄〃锛堜粎杩斿洖宸查�氳繃鍑嗗叆瀹℃壒鐨勪緵搴斿晢锛� */
+export function getSupplierSimpleList() {
+ return requestClient.get<SrmSupplierApi.SupplierVO[]>('/srm/supplier/simple-list');
+}
+
+/** 鑾峰彇渚涘簲鍟嗚鎯� */
+export function getSupplier(id: number) {
+ return requestClient.get<SrmSupplierApi.SupplierVO>(`/srm/supplier/get?id=${id}`);
+}
+
+/** 鍒涘缓渚涘簲鍟� */
+export function createSupplier(data: SrmSupplierApi.SupplierVO) {
+ return requestClient.post('/srm/supplier/create', data);
+}
+
+/** 鏇存柊渚涘簲鍟� */
+export function updateSupplier(data: SrmSupplierApi.SupplierVO) {
+ return requestClient.put('/srm/supplier/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟� */
+export function deleteSupplier(id: number) {
+ return requestClient.delete(`/srm/supplier/delete?id=${id}`);
+}
diff --git a/src/api/srm/supplierApply/index.ts b/src/api/srm/supplierApply/index.ts
new file mode 100644
index 0000000..35a48fa
--- /dev/null
+++ b/src/api/srm/supplierApply/index.ts
@@ -0,0 +1,78 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace SrmSupplierApplyApi {
+ export interface ApplyVO {
+ id?: number;
+ applyNo?: string;
+ supplierId?: number;
+ supplierName: string;
+ contactName?: string;
+ contactMobile?: string;
+ contactEmail?: string;
+ address?: string;
+ bankName?: string;
+ bankAccount?: string;
+ taxNo?: string;
+ businessLicense?: string;
+ taxCertificate?: string;
+ orgCodeCertificate?: string;
+ applyStatus?: number;
+ applyStatusLabel?: string;
+ auditComment?: string;
+ remark?: string;
+ createTime?: string;
+ }
+
+ export interface ReviewVO {
+ id: number;
+ passed: boolean;
+ comment?: string;
+ }
+}
+
+/** 鍒嗛〉鏌ヨ鍑嗗叆鐢宠 */
+export function getApplyPage(params: PageParam) {
+ return requestClient.get<PageResult<SrmSupplierApplyApi.ApplyVO>>('/srm/supplier-apply/page', { params });
+}
+
+/** 鑾峰彇鍑嗗叆鐢宠璇︽儏 */
+export function getApply(id: number) {
+ return requestClient.get<SrmSupplierApplyApi.ApplyVO>(`/srm/supplier-apply/get?id=${id}`);
+}
+
+/** 鍒涘缓鍑嗗叆鐢宠 */
+export function createApply(data: SrmSupplierApplyApi.ApplyVO) {
+ return requestClient.post('/srm/supplier-apply/create', data);
+}
+
+/** 鏇存柊鍑嗗叆鐢宠 */
+export function updateApply(data: SrmSupplierApplyApi.ApplyVO) {
+ return requestClient.put('/srm/supplier-apply/update', data);
+}
+
+/** 鍒犻櫎鍑嗗叆鐢宠 */
+export function deleteApply(id: number) {
+ return requestClient.delete(`/srm/supplier-apply/delete?id=${id}`);
+}
+
+/** 鎻愪氦瀹℃牳 */
+export function submitApply(id: number) {
+ return requestClient.put(`/srm/supplier-apply/submit?id=${id}`);
+}
+
+/** 閲囪喘瀹℃牳 */
+export function reviewPurchase(data: SrmSupplierApplyApi.ReviewVO) {
+ return requestClient.put('/srm/supplier-apply/review-purchase', data);
+}
+
+/** 璐ㄩ噺瀹℃牳 */
+export function reviewQuality(data: SrmSupplierApplyApi.ReviewVO) {
+ return requestClient.put('/srm/supplier-apply/review-quality', data);
+}
+
+/** 璐㈠姟瀹℃牳 */
+export function reviewFinance(data: SrmSupplierApplyApi.ReviewVO) {
+ return requestClient.put('/srm/supplier-apply/review-finance', data);
+}
diff --git a/src/api/srm/supplierCertificate/index.ts b/src/api/srm/supplierCertificate/index.ts
new file mode 100644
index 0000000..3b775cf
--- /dev/null
+++ b/src/api/srm/supplierCertificate/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace SrmSupplierCertificateApi {
+ export interface CertificateVO {
+ id?: number;
+ supplierId: number;
+ supplierName?: string;
+ certificateName: string;
+ certificateNo?: string;
+ certificateType: number;
+ issuingAuthority?: string;
+ issueDate: string;
+ expireDate: string;
+ expireStatus?: number;
+ remark?: string;
+ createTime?: string;
+ }
+}
+
+/** 鍒嗛〉鏌ヨ璧勮川 */
+export function getCertificatePage(params: PageParam) {
+ return requestClient.get<PageResult<SrmSupplierCertificateApi.CertificateVO>>('/srm/supplier-certificate/page', { params });
+}
+
+/** 鑾峰彇璧勮川璇︽儏 */
+export function getCertificate(id: number) {
+ return requestClient.get<SrmSupplierCertificateApi.CertificateVO>(`/srm/supplier-certificate/get?id=${id}`);
+}
+
+/** 鍒涘缓璧勮川 */
+export function createCertificate(data: SrmSupplierCertificateApi.CertificateVO) {
+ return requestClient.post('/srm/supplier-certificate/create', data);
+}
+
+/** 鏇存柊璧勮川 */
+export function updateCertificate(data: SrmSupplierCertificateApi.CertificateVO) {
+ return requestClient.put('/srm/supplier-certificate/update', data);
+}
+
+/** 鍒犻櫎璧勮川 */
+export function deleteCertificate(id: number) {
+ return requestClient.delete(`/srm/supplier-certificate/delete?id=${id}`);
+}
+
+/** 鎸変緵搴斿晢鑾峰彇璧勮川鍒楄〃 */
+export function getCertificateListBySupplier(supplierId: number) {
+ return requestClient.get<SrmSupplierCertificateApi.CertificateVO[]>(`/srm/supplier-certificate/list-by-supplier?supplierId=${supplierId}`);
+}
+
+/** 鑾峰彇鍗冲皢鍒版湡鐨勮祫璐ㄥ垪琛� */
+export function getExpiringCertificates() {
+ return requestClient.get<SrmSupplierCertificateApi.CertificateVO[]>('/srm/supplier-certificate/expiring-list');
+}
diff --git a/src/api/srm/supplierEvaluation/index.ts b/src/api/srm/supplierEvaluation/index.ts
new file mode 100644
index 0000000..c7ed017
--- /dev/null
+++ b/src/api/srm/supplierEvaluation/index.ts
@@ -0,0 +1,47 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace SrmSupplierEvaluationApi {
+ export interface EvaluationVO {
+ id?: number;
+ supplierId: number;
+ supplierName?: string;
+ evaluationPeriod: string;
+ qualityScore: number;
+ deliveryScore: number;
+ priceScore: number;
+ serviceScore: number;
+ compositeScore?: number;
+ grade?: string;
+ evaluatorId?: number;
+ evaluatorName?: string;
+ remark?: string;
+ createTime?: string;
+ }
+}
+
+/** 鍒嗛〉鏌ヨ缁╂晥璇勪环 */
+export function getEvaluationPage(params: PageParam) {
+ return requestClient.get<PageResult<SrmSupplierEvaluationApi.EvaluationVO>>('/srm/supplier-evaluation/page', { params });
+}
+
+/** 鑾峰彇缁╂晥璇勪环璇︽儏 */
+export function getEvaluation(id: number) {
+ return requestClient.get<SrmSupplierEvaluationApi.EvaluationVO>(`/srm/supplier-evaluation/get?id=${id}`);
+}
+
+/** 鍒涘缓缁╂晥璇勪环 */
+export function createEvaluation(data: SrmSupplierEvaluationApi.EvaluationVO) {
+ return requestClient.post('/srm/supplier-evaluation/create', data);
+}
+
+/** 鏇存柊缁╂晥璇勪环 */
+export function updateEvaluation(data: SrmSupplierEvaluationApi.EvaluationVO) {
+ return requestClient.put('/srm/supplier-evaluation/update', data);
+}
+
+/** 鍒犻櫎缁╂晥璇勪环 */
+export function deleteEvaluation(id: number) {
+ return requestClient.delete(`/srm/supplier-evaluation/delete?id=${id}`);
+}
diff --git a/src/api/srm/tender/index.ts b/src/api/srm/tender/index.ts
new file mode 100644
index 0000000..965eba4
--- /dev/null
+++ b/src/api/srm/tender/index.ts
@@ -0,0 +1,232 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace SrmTenderApi {
+ export interface TenderProjectVO {
+ id?: number;
+ tenderNo?: string;
+ tenderName: string;
+ tenderType: number;
+ purchaseOrg?: string;
+ projectLeaderId?: number;
+ projectLeaderName?: string;
+ tenderStatus?: number;
+ budgetAmount?: number;
+ startTime?: string;
+ endTime?: string;
+ bidOpenTime?: string;
+ tenderDesc?: string;
+ qualificationRequirements?: string;
+ purchaseRequestId?: number;
+ awardId?: number;
+ purchaseOrderNo?: string;
+ createTime?: string;
+ }
+
+ export interface TenderMaterialVO {
+ id?: number;
+ tenderProjectId: number;
+ productId?: number;
+ productCode?: string;
+ productName: string;
+ productSpec?: string;
+ unit?: string;
+ quantity?: number;
+ techRequirement?: string;
+ estimatedPrice?: number;
+ sort?: number;
+ }
+
+ export interface TenderBidVO {
+ id?: number;
+ tenderProjectId: number;
+ supplierId: number;
+ supplierName?: string;
+ bidNo: string;
+ bidStatus?: number;
+ bidTime?: string;
+ bidTotalAmount?: number;
+ bidFileName?: string;
+ bidFileUrl?: string;
+ createTime?: string;
+ }
+
+ export interface QuoteVO {
+ id?: number;
+ tenderProjectId?: number;
+ supplierId?: number;
+ bidId: number;
+ tenderMaterialId: number;
+ materialName?: string;
+ quotePrice?: number;
+ taxRate?: number;
+ deliveryCycle?: number;
+ paymentTerms?: string;
+ warrantyPeriod?: number;
+ remark?: string;
+ }
+
+ export interface BidEvaluationVO {
+ id?: number;
+ tenderProjectId: number;
+ supplierId: number;
+ supplierName?: string;
+ bidId: number;
+ priceScore?: number;
+ techScore?: number;
+ deliveryScore?: number;
+ serviceScore?: number;
+ compositeScore?: number;
+ rank?: number;
+ evaluatorId?: number;
+ evaluatorName?: string;
+ evaluationOpinion?: string;
+ }
+
+ export interface BidAwardVO {
+ id?: number;
+ tenderProjectId: number;
+ supplierId: number;
+ supplierName?: string;
+ bidId: number;
+ awardNo: string;
+ awardStatus?: number;
+ awardAmount?: number;
+ awardTime?: string;
+ noticeFileName?: string;
+ noticeFileUrl?: string;
+ purchaseOrderId?: number;
+ remark?: string;
+ }
+}
+
+// ========== 鎷涙爣椤圭洰 ==========
+
+export function getTenderProjectPage(params: PageParam) {
+ return requestClient.get<PageResult<SrmTenderApi.TenderProjectVO>>('/srm/tender-project/page', { params });
+}
+
+export function getTenderProject(id: number) {
+ return requestClient.get<SrmTenderApi.TenderProjectVO>(`/srm/tender-project/get?id=${id}`);
+}
+
+export function createTenderProject(data: SrmTenderApi.TenderProjectVO) {
+ return requestClient.post('/srm/tender-project/create', data);
+}
+
+export function updateTenderProject(data: SrmTenderApi.TenderProjectVO) {
+ return requestClient.put('/srm/tender-project/update', data);
+}
+
+export function deleteTenderProject(id: number) {
+ return requestClient.delete(`/srm/tender-project/delete?id=${id}`);
+}
+
+export function publishTender(id: number) {
+ return requestClient.put(`/srm/tender-project/publish?id=${id}`);
+}
+
+export function closeTender(id: number) {
+ return requestClient.put(`/srm/tender-project/close?id=${id}`);
+}
+
+export function startBidding(id: number) {
+ return requestClient.put(`/srm/tender-project/start-bidding?id=${id}`);
+}
+
+export function startEvaluation(id: number) {
+ return requestClient.put(`/srm/tender-project/start-evaluation?id=${id}`);
+}
+
+// ========== 鐗╂枡瀛愯〃 ==========
+
+export function getMaterialList(tenderProjectId: number) {
+ return requestClient.get<SrmTenderApi.TenderMaterialVO[]>(`/srm/tender-project/material/list?tenderProjectId=${tenderProjectId}`);
+}
+
+export function createMaterial(data: SrmTenderApi.TenderMaterialVO) {
+ return requestClient.post('/srm/tender-project/material/create', data);
+}
+
+export function updateMaterial(data: SrmTenderApi.TenderMaterialVO) {
+ return requestClient.put('/srm/tender-project/material/update', data);
+}
+
+export function deleteMaterial(id: number) {
+ return requestClient.delete(`/srm/tender-project/material/delete?id=${id}`);
+}
+
+// ========== 鎶曟爣绠$悊 ==========
+
+export function getBidListByProject(tenderProjectId: number) {
+ return requestClient.get<SrmTenderApi.TenderBidVO[]>(`/srm/tender-bid/list-by-project?tenderProjectId=${tenderProjectId}`);
+}
+
+export function getBid(id: number) {
+ return requestClient.get<SrmTenderApi.TenderBidVO>(`/srm/tender-bid/get?id=${id}`);
+}
+
+export function createBid(data: SrmTenderApi.TenderBidVO) {
+ return requestClient.post('/srm/tender-bid/create', data);
+}
+
+export function withdrawBid(id: number) {
+ return requestClient.put(`/srm/tender-bid/withdraw?id=${id}`);
+}
+
+// ========== 鎶ヤ环绠$悊 ==========
+
+export function getQuoteList(bidId: number) {
+ return requestClient.get<SrmTenderApi.QuoteVO[]>(`/srm/tender-bid/quote/list-by-bid?bidId=${bidId}`);
+}
+
+export function createQuote(data: SrmTenderApi.QuoteVO) {
+ return requestClient.post('/srm/tender-bid/quote/create', data);
+}
+
+export function updateQuote(data: SrmTenderApi.QuoteVO) {
+ return requestClient.put('/srm/tender-bid/quote/update', data);
+}
+
+// ========== 璇勬爣绠$悊 ==========
+
+export function getEvaluationList(tenderProjectId: number) {
+ return requestClient.get<SrmTenderApi.BidEvaluationVO[]>(`/srm/bid-evaluation/list-by-project?tenderProjectId=${tenderProjectId}`);
+}
+
+export function createBidEvaluation(data: SrmTenderApi.BidEvaluationVO) {
+ return requestClient.post('/srm/bid-evaluation/create', data);
+}
+
+export function calculateRanking(tenderProjectId: number) {
+ return requestClient.put(`/srm/bid-evaluation/calculate-ranking?tenderProjectId=${tenderProjectId}`);
+}
+
+// ========== 寮�鏍� ==========
+
+export function createBidOpen(tenderProjectId: number) {
+ return requestClient.post(`/srm/bid-evaluation/bid-open/create?tenderProjectId=${tenderProjectId}`);
+}
+
+export function getBidOpen(id: number) {
+ return requestClient.get(`/srm/bid-evaluation/bid-open/get?id=${id}`);
+}
+
+// ========== 瀹氭爣绠$悊 ==========
+
+export function createAward(data: SrmTenderApi.BidAwardVO) {
+ return requestClient.post('/srm/bid-award/create', data);
+}
+
+export function getAward(id: number) {
+ return requestClient.get<SrmTenderApi.BidAwardVO>(`/srm/bid-award/get?id=${id}`);
+}
+
+export function approveAward(id: number) {
+ return requestClient.put(`/srm/bid-award/approve?id=${id}`);
+}
+
+export function generatePurchaseOrder(id: number) {
+ return requestClient.put(`/srm/bid-award/generate-purchase-order/${id}`);
+}
diff --git a/src/router/routes/modules/srm.ts b/src/router/routes/modules/srm.ts
new file mode 100644
index 0000000..113b30d
--- /dev/null
+++ b/src/router/routes/modules/srm.ts
@@ -0,0 +1,27 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+const routes: RouteRecordRaw[] = [
+ {
+ path: '/srm',
+ name: 'SrmCenter',
+ meta: {
+ title: 'SRM 渚涘簲鍟嗙鐞�',
+ icon: 'lucide:package-search',
+ keepAlive: true,
+ hideInMenu: true,
+ },
+ children: [
+ {
+ path: 'tender/detail',
+ name: 'SrmTenderDetail',
+ meta: {
+ title: '鎷涙爣璇︽儏',
+ activePath: '/srm/tender',
+ },
+ component: () => import('#/views/srm/tender/detail.vue'),
+ },
+ ],
+ },
+];
+
+export default routes;
diff --git a/src/views/bpm/oa/leave/data.ts b/src/views/bpm/oa/leave/data.ts
index 9570f42..bff2476 100644
--- a/src/views/bpm/oa/leave/data.ts
+++ b/src/views/bpm/oa/leave/data.ts
@@ -41,7 +41,7 @@
componentProps: {
placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
@@ -53,7 +53,7 @@
componentProps: {
placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
diff --git a/src/views/crm/business/data.ts b/src/views/crm/business/data.ts
index 2b07d09..d1c9a3e 100644
--- a/src/views/crm/business/data.ts
+++ b/src/views/crm/business/data.ts
@@ -98,7 +98,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨棰勮鎴愪氦鏃ユ湡',
},
},
diff --git a/src/views/crm/clue/data.ts b/src/views/crm/clue/data.ts
index 018c079..9973372 100644
--- a/src/views/crm/clue/data.ts
+++ b/src/views/crm/clue/data.ts
@@ -144,7 +144,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨涓嬫鑱旂郴鏃堕棿',
},
},
diff --git a/src/views/crm/contact/data.ts b/src/views/crm/contact/data.ts
index a95a655..331135d 100644
--- a/src/views/crm/contact/data.ts
+++ b/src/views/crm/contact/data.ts
@@ -165,7 +165,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨涓嬫鑱旂郴鏃堕棿',
},
},
diff --git a/src/views/crm/contract/data.ts b/src/views/crm/contract/data.ts
index 5ea024f..2903856 100644
--- a/src/views/crm/contract/data.ts
+++ b/src/views/crm/contract/data.ts
@@ -79,7 +79,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨涓嬪崟鏃ユ湡',
},
},
@@ -128,7 +128,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍚堝悓寮�濮嬫椂闂�',
},
},
@@ -139,7 +139,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍚堝悓缁撴潫鏃堕棿',
},
},
diff --git a/src/views/crm/customer/data.ts b/src/views/crm/customer/data.ts
index 199eac0..d3372ee 100644
--- a/src/views/crm/customer/data.ts
+++ b/src/views/crm/customer/data.ts
@@ -154,7 +154,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨涓嬫鑱旂郴鏃堕棿',
allowClear: true,
},
diff --git a/src/views/crm/followup/data.ts b/src/views/crm/followup/data.ts
index 9ff3914..7132d4b 100644
--- a/src/views/crm/followup/data.ts
+++ b/src/views/crm/followup/data.ts
@@ -49,7 +49,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨涓嬫鑱旂郴鏃堕棿',
},
rules: 'required',
diff --git a/src/views/crm/receivable/data.ts b/src/views/crm/receivable/data.ts
index e1e0255..300d02b 100644
--- a/src/views/crm/receivable/data.ts
+++ b/src/views/crm/receivable/data.ts
@@ -183,7 +183,7 @@
componentProps: {
placeholder: '璇烽�夋嫨鍥炴鏃ユ湡',
showTime: false,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
format: 'YYYY-MM-DD',
},
},
diff --git a/src/views/crm/receivable/plan/data.ts b/src/views/crm/receivable/plan/data.ts
index 6527ce2..cfd71c4 100644
--- a/src/views/crm/receivable/plan/data.ts
+++ b/src/views/crm/receivable/plan/data.ts
@@ -109,7 +109,7 @@
componentProps: {
placeholder: '璇烽�夋嫨璁″垝鍥炴鏃ユ湡',
showTime: false,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
format: 'YYYY-MM-DD',
},
defaultValue: new Date(),
diff --git a/src/views/crm/saleQuotation/data.ts b/src/views/crm/saleQuotation/data.ts
index ce713e1..ab372f0 100644
--- a/src/views/crm/saleQuotation/data.ts
+++ b/src/views/crm/saleQuotation/data.ts
@@ -128,7 +128,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鎶ヤ环鏃ユ湡',
},
},
@@ -139,7 +139,7 @@
componentProps: {
showTime: false,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鏈夋晥鏈�',
},
},
diff --git a/src/views/erp/finance/payment/data.ts b/src/views/erp/finance/payment/data.ts
index 3d04084..22aa220 100644
--- a/src/views/erp/finance/payment/data.ts
+++ b/src/views/erp/finance/payment/data.ts
@@ -6,7 +6,7 @@
import { erpPriceInputFormatter } from '@vben/utils';
import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -42,7 +42,7 @@
placeholder: '閫夋嫨浠樻鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/finance/receipt/data.ts b/src/views/erp/finance/receipt/data.ts
index 87e7ea4..a81b7c0 100644
--- a/src/views/erp/finance/receipt/data.ts
+++ b/src/views/erp/finance/receipt/data.ts
@@ -42,7 +42,7 @@
placeholder: '閫夋嫨鏀舵鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/purchase/in/data.ts b/src/views/erp/purchase/in/data.ts
index b379d63..ef9d96b 100644
--- a/src/views/erp/purchase/in/data.ts
+++ b/src/views/erp/purchase/in/data.ts
@@ -8,7 +8,7 @@
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
import { getProductSimpleList } from '#/api/erp/product/product';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -45,7 +45,7 @@
placeholder: '閫夋嫨鍏ュ簱鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/purchase/order/data.ts b/src/views/erp/purchase/order/data.ts
index 0aa5868..19ccc26 100644
--- a/src/views/erp/purchase/order/data.ts
+++ b/src/views/erp/purchase/order/data.ts
@@ -9,7 +9,7 @@
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getSimpleUserList } from '#/api/system/user';
import { MdmItemSelect } from '#/views/basicData/mdm/components';
import { getRangePickerDefaultProps } from '#/utils';
@@ -45,7 +45,7 @@
placeholder: '閫夋嫨璁㈠崟鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/purchase/request/data.ts b/src/views/erp/purchase/request/data.ts
index 58f9035..59cc3c5 100644
--- a/src/views/erp/purchase/request/data.ts
+++ b/src/views/erp/purchase/request/data.ts
@@ -4,7 +4,7 @@
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -48,7 +48,7 @@
placeholder: '閫夋嫨鐢宠鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
disabled: formType === 'detail',
},
rules: 'required',
diff --git a/src/views/erp/purchase/request/modules/order-form.vue b/src/views/erp/purchase/request/modules/order-form.vue
index 3878b6e..4fd325c 100644
--- a/src/views/erp/purchase/request/modules/order-form.vue
+++ b/src/views/erp/purchase/request/modules/order-form.vue
@@ -8,7 +8,7 @@
import { message, Select } from 'ant-design-vue';
import { generatePurchaseOrder } from '#/api/erp/purchase/request';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
const emit = defineEmits(['success']);
diff --git a/src/views/erp/purchase/return/data.ts b/src/views/erp/purchase/return/data.ts
index fdbcf2e..5bb8b24 100644
--- a/src/views/erp/purchase/return/data.ts
+++ b/src/views/erp/purchase/return/data.ts
@@ -8,7 +8,7 @@
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
import { getProductSimpleList } from '#/api/erp/product/product';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -45,7 +45,7 @@
placeholder: '閫夋嫨閫�璐ф椂闂�',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/purchase/statistics/supplier.vue b/src/views/erp/purchase/statistics/supplier.vue
index e6ffc4d..bb8e75d 100644
--- a/src/views/erp/purchase/statistics/supplier.vue
+++ b/src/views/erp/purchase/statistics/supplier.vue
@@ -6,7 +6,7 @@
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSupplierStatistics } from '#/api/erp/purchase/statistics';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getRangePickerDefaultProps } from '#/utils';
import type { VbenFormSchema } from '#/adapter/form';
diff --git a/src/views/erp/purchase/statistics/uncompleted.vue b/src/views/erp/purchase/statistics/uncompleted.vue
index 8634cf4..07e6d1c 100644
--- a/src/views/erp/purchase/statistics/uncompleted.vue
+++ b/src/views/erp/purchase/statistics/uncompleted.vue
@@ -8,7 +8,7 @@
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getUncompletedOrders } from '#/api/erp/purchase/statistics';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getRangePickerDefaultProps } from '#/utils';
import type { VbenFormSchema } from '#/adapter/form';
diff --git a/src/views/erp/purchase/supplier/data.ts b/src/views/erp/purchase/supplier/data.ts
index 490415c..ea96133 100644
--- a/src/views/erp/purchase/supplier/data.ts
+++ b/src/views/erp/purchase/supplier/data.ts
@@ -89,7 +89,7 @@
componentProps: {
placeholder: '璇烽�夋嫨鍚堜綔寮�濮嬫椂闂�',
class: '!w-full',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 254f22a..7886d14 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -72,7 +72,7 @@
placeholder: '閫夋嫨璁㈠崟鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/sale/out/data.ts b/src/views/erp/sale/out/data.ts
index 15f309e..298eed3 100644
--- a/src/views/erp/sale/out/data.ts
+++ b/src/views/erp/sale/out/data.ts
@@ -45,7 +45,7 @@
placeholder: '閫夋嫨鍑哄簱鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/sale/return/data.ts b/src/views/erp/sale/return/data.ts
index bb92f50..68e9e22 100644
--- a/src/views/erp/sale/return/data.ts
+++ b/src/views/erp/sale/return/data.ts
@@ -45,7 +45,7 @@
placeholder: '閫夋嫨閫�璐ф椂闂�',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/stock/check/data.ts b/src/views/erp/stock/check/data.ts
index d58c9f5..e65fce8 100644
--- a/src/views/erp/stock/check/data.ts
+++ b/src/views/erp/stock/check/data.ts
@@ -40,7 +40,7 @@
placeholder: '閫夋嫨鐩樼偣鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/stock/in/data.ts b/src/views/erp/stock/in/data.ts
index 85e8e81..353ec6c 100644
--- a/src/views/erp/stock/in/data.ts
+++ b/src/views/erp/stock/in/data.ts
@@ -5,7 +5,7 @@
import { getDictOptions } from '@vben/hooks';
import { getProductSimpleList } from '#/api/erp/product/product';
-import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -41,7 +41,7 @@
placeholder: '閫夋嫨鍏ュ簱鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/stock/move/data.ts b/src/views/erp/stock/move/data.ts
index 0baf242..4a0bac9 100644
--- a/src/views/erp/stock/move/data.ts
+++ b/src/views/erp/stock/move/data.ts
@@ -40,7 +40,7 @@
placeholder: '閫夋嫨璋冨害鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/erp/stock/out/data.ts b/src/views/erp/stock/out/data.ts
index c764bff..524e3a6 100644
--- a/src/views/erp/stock/out/data.ts
+++ b/src/views/erp/stock/out/data.ts
@@ -41,7 +41,7 @@
placeholder: '閫夋嫨鍑哄簱鏃堕棿',
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/infra/demo/demo01/data.ts b/src/views/infra/demo/demo01/data.ts
index 3656a96..5c22e14 100644
--- a/src/views/infra/demo/demo01/data.ts
+++ b/src/views/infra/demo/demo01/data.ts
@@ -46,7 +46,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鍑虹敓骞�',
},
},
diff --git a/src/views/infra/demo/demo03/erp/data.ts b/src/views/infra/demo/demo03/erp/data.ts
index f33d20c..eb17117 100644
--- a/src/views/infra/demo/demo03/erp/data.ts
+++ b/src/views/infra/demo/demo03/erp/data.ts
@@ -46,7 +46,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鍑虹敓鏃ユ湡',
},
},
diff --git a/src/views/infra/demo/demo03/inner/data.ts b/src/views/infra/demo/demo03/inner/data.ts
index 0166ba1..1c183ab 100644
--- a/src/views/infra/demo/demo03/inner/data.ts
+++ b/src/views/infra/demo/demo03/inner/data.ts
@@ -46,7 +46,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/infra/demo/demo03/normal/data.ts b/src/views/infra/demo/demo03/normal/data.ts
index aaf2e53..7995289 100644
--- a/src/views/infra/demo/demo03/normal/data.ts
+++ b/src/views/infra/demo/demo03/normal/data.ts
@@ -46,7 +46,7 @@
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鍑虹敓鏃ユ湡',
},
},
diff --git a/src/views/mes/cal/plan/data.ts b/src/views/mes/cal/plan/data.ts
index 7127744..1d98d19 100644
--- a/src/views/mes/cal/plan/data.ts
+++ b/src/views/mes/cal/plan/data.ts
@@ -97,7 +97,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨寮�濮嬫棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -108,7 +108,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨缁撴潫鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/dv/checkplan/data.ts b/src/views/mes/dv/checkplan/data.ts
index 5eee160..40b6629 100644
--- a/src/views/mes/dv/checkplan/data.ts
+++ b/src/views/mes/dv/checkplan/data.ts
@@ -97,7 +97,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨寮�濮嬫棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -108,7 +108,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨缁撴潫鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/dv/checkrecord/data.ts b/src/views/mes/dv/checkrecord/data.ts
index 7152a5e..dc71a89 100644
--- a/src/views/mes/dv/checkrecord/data.ts
+++ b/src/views/mes/dv/checkrecord/data.ts
@@ -79,7 +79,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鐐规鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/mes/dv/machinery/data.ts b/src/views/mes/dv/machinery/data.ts
index 4dbb6a0..ef002c5 100644
--- a/src/views/mes/dv/machinery/data.ts
+++ b/src/views/mes/dv/machinery/data.ts
@@ -129,7 +129,7 @@
disabled: true,
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['id'],
@@ -145,7 +145,7 @@
disabled: true,
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['id'],
diff --git a/src/views/mes/dv/maintenrecord/data.ts b/src/views/mes/dv/maintenrecord/data.ts
index 8e16b83..d7359d6 100644
--- a/src/views/mes/dv/maintenrecord/data.ts
+++ b/src/views/mes/dv/maintenrecord/data.ts
@@ -79,7 +79,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨淇濆吇鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/mes/dv/repair/data.ts b/src/views/mes/dv/repair/data.ts
index 9506cd2..6d9e47b 100644
--- a/src/views/mes/dv/repair/data.ts
+++ b/src/views/mes/dv/repair/data.ts
@@ -109,7 +109,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鎶ヤ慨鏃ユ湡',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
@@ -141,7 +141,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨瀹屾垚鏃ユ湡',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
// 缁翠慨涓�(鈮ONFIRMED)鎬佸睍绀猴紱浠�"瀹屾垚缁翠慨"寮圭獥鍙紪杈戝苟蹇呭~锛屽叾浣欐�佸彧璇诲洖鏄�
dependencies: {
@@ -182,7 +182,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨楠屾敹鏃ユ湡',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
// 楠屾敹淇℃伅涓哄凡纭(鈮INISHED)鎬佽嚜鍔ㄤ骇鐢熺殑鍙鍥炴樉瀛楁
dependencies: {
diff --git a/src/views/mes/pd/project/data.ts b/src/views/mes/pd/project/data.ts
index 9c1d3e1..3ac91aa 100644
--- a/src/views/mes/pd/project/data.ts
+++ b/src/views/mes/pd/project/data.ts
@@ -36,7 +36,7 @@
document.body,
inputReadOnly: true,
placeholder: options?.placeholder ?? "璇烽�夋嫨鏃ユ湡",
- valueFormat: "x",
+ valueFormat: 'YYYY-MM-DD',
};
}
diff --git a/src/views/mes/pd/project/modules/task/data.ts b/src/views/mes/pd/project/modules/task/data.ts
index f7d551a..b319a71 100644
--- a/src/views/mes/pd/project/modules/task/data.ts
+++ b/src/views/mes/pd/project/modules/task/data.ts
@@ -106,7 +106,7 @@
fieldName: 'planFinishDate',
label: '璁″垝瀹屾垚',
component: 'DatePicker',
- componentProps: { disabled: readonly, class: 'w-full', valueFormat: 'x' },
+ componentProps: { disabled: readonly, class: 'w-full', valueFormat: 'YYYY-MM-DD' },
},
{
fieldName: 'remark',
@@ -135,7 +135,7 @@
fieldName: 'planFinishDate',
label: '璁″垝瀹屾垚',
component: 'DatePicker',
- componentProps: { class: 'w-full', valueFormat: 'x' },
+ componentProps: { class: 'w-full', valueFormat: 'YYYY-MM-DD' },
},
{
fieldName: 'remark',
diff --git a/src/views/mes/pro/andon/record/data.ts b/src/views/mes/pro/andon/record/data.ts
index 6c8496b..e8c8a4a 100644
--- a/src/views/mes/pro/andon/record/data.ts
+++ b/src/views/mes/pro/andon/record/data.ts
@@ -275,7 +275,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨澶勭疆鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
isUpdate
diff --git a/src/views/mes/pro/card/data.ts b/src/views/mes/pro/card/data.ts
index c7785cd..4e3b4a8 100644
--- a/src/views/mes/pro/card/data.ts
+++ b/src/views/mes/pro/card/data.ts
@@ -347,7 +347,7 @@
class: '!w-full',
placeholder: '璇烽�夋嫨杩涘叆宸ュ簭鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
@@ -358,7 +358,7 @@
class: '!w-full',
placeholder: '璇烽�夋嫨鍑哄伐搴忔椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/mes/pro/feedback/data.ts b/src/views/mes/pro/feedback/data.ts
index e2728f1..affe7dd 100644
--- a/src/views/mes/pro/feedback/data.ts
+++ b/src/views/mes/pro/feedback/data.ts
@@ -554,7 +554,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨鎶ュ伐鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/mes/pro/mps/data.ts b/src/views/mes/pro/mps/data.ts
index 7e0a1a9..8afc6da 100644
--- a/src/views/mes/pro/mps/data.ts
+++ b/src/views/mes/pro/mps/data.ts
@@ -108,7 +108,7 @@
class: '!w-full',
placeholder: '璇烽�夋嫨闇�姹傛棩鏈�',
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
disabled: !isCreate && !isEdit,
},
rules: 'required',
diff --git a/src/views/mes/pro/task/data.ts b/src/views/mes/pro/task/data.ts
index 30470f0..bba413d 100644
--- a/src/views/mes/pro/task/data.ts
+++ b/src/views/mes/pro/task/data.ts
@@ -281,7 +281,7 @@
class: '!w-full',
disabled: true,
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
@@ -415,7 +415,7 @@
class: '!w-full',
placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
// 寮�濮嬫椂闂村彉鏇达細閲嶆柊璁$畻缁撴潫鏃堕棿
onChange: () => recalcEndTime(formApi),
},
@@ -443,7 +443,7 @@
class: '!w-full',
disabled: true,
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/mes/pro/workorder/data.ts b/src/views/mes/pro/workorder/data.ts
index add9af7..d01a104 100644
--- a/src/views/mes/pro/workorder/data.ts
+++ b/src/views/mes/pro/workorder/data.ts
@@ -232,7 +232,7 @@
disabled: headerReadonly,
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨闇�姹傛棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/qc/ipqc/data.ts b/src/views/mes/qc/ipqc/data.ts
index d035a14..eeeff54 100644
--- a/src/views/mes/qc/ipqc/data.ts
+++ b/src/views/mes/qc/ipqc/data.ts
@@ -328,7 +328,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨妫�娴嬫棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/qc/iqc/data.ts b/src/views/mes/qc/iqc/data.ts
index 0a77b97..da23bdb 100644
--- a/src/views/mes/qc/iqc/data.ts
+++ b/src/views/mes/qc/iqc/data.ts
@@ -208,7 +208,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鏉ユ枡鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -232,7 +232,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨妫�娴嬫棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/qc/oqc/data.ts b/src/views/mes/qc/oqc/data.ts
index 6706cca..62ef238 100644
--- a/src/views/mes/qc/oqc/data.ts
+++ b/src/views/mes/qc/oqc/data.ts
@@ -254,7 +254,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍑鸿揣鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -265,7 +265,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨妫�娴嬫棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/mes/qc/rqc/data.ts b/src/views/mes/qc/rqc/data.ts
index 4d08cbf..4ea4739 100644
--- a/src/views/mes/qc/rqc/data.ts
+++ b/src/views/mes/qc/rqc/data.ts
@@ -219,7 +219,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨妫�娴嬫棩鏈�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/mes/tm/tool/data.ts b/src/views/mes/tm/tool/data.ts
index b5252fd..af2c051 100644
--- a/src/views/mes/tm/tool/data.ts
+++ b/src/views/mes/tm/tool/data.ts
@@ -183,7 +183,7 @@
componentProps: {
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['maintenType'],
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
index 97235e4..1e0a056 100644
--- a/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
+++ b/src/views/mes/workbench/components/tabs/ProductIssueFormModal.vue
@@ -98,7 +98,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
index 9731a1b..ae52420 100644
--- a/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
+++ b/src/views/mes/workbench/components/tabs/ProductIssueTab.vue
@@ -119,7 +119,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue b/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
index 89453de..3a0e29e 100644
--- a/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueFormModal.vue
@@ -117,7 +117,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
index 236020c..5ab1037 100644
--- a/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
+++ b/src/views/mes/workbench/components/tabs/ReturnIssueTab.vue
@@ -131,7 +131,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/srm/enums.ts b/src/views/srm/enums.ts
new file mode 100644
index 0000000..71712b5
--- /dev/null
+++ b/src/views/srm/enums.ts
@@ -0,0 +1,263 @@
+// ============================================================
+// SRM 缁熶竴鏋氫妇甯搁噺
+// 鎵�鏈夊�间弗鏍煎榻愬悗绔灇涓惧畾涔� (yudao-module-srm/enums/)
+// ============================================================
+
+/** 閫夐」绫诲瀷 (Select/RadioGroup閫氱敤) */
+export interface EnumOption {
+ label: string;
+ value: number | string;
+}
+
+/** CellTag 鏁版嵁绫诲瀷 */
+export interface CellTagItem {
+ value: number | string;
+ color: string;
+}
+
+// ========== 渚涘簲鍟� (瀛楁鍊煎榻� CommonStatusEnum + SupplierTypeEnum) ==========
+
+/** 渚涘簲鍟嗙姸鎬� 鈥� 瀵归綈 CommonStatusEnum (0=寮�鍚� 1=鍏抽棴) */
+export const SUPPLIER_STATUS = {
+ ENABLE: 0,
+ DISABLE: 1,
+} as const;
+
+export const SUPPLIER_STATUS_OPTIONS: EnumOption[] = [
+ { label: '寮�鍚�', value: 0 },
+ { label: '鍏抽棴', value: 1 },
+];
+
+export const SUPPLIER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 0, color: 'green' },
+ { value: 1, color: 'red' },
+ ],
+ labels: { 0: '寮�鍚�', 1: '鍏抽棴' },
+};
+
+/** 渚涘簲鍟嗙被鍨� 鈥� 瀵归綈 SupplierTypeEnum (1=鐢熶骇鍨� 2=璐告槗鍨� 3=鏈嶅姟鍨� 4=鍏朵粬) */
+export const SUPPLIER_TYPE = {
+ PRODUCTION: 1,
+ TRADING: 2,
+ SERVICE: 3,
+ OTHER: 4,
+} as const;
+
+export const SUPPLIER_TYPE_OPTIONS: EnumOption[] = [
+ { label: '鐢熶骇鍨�', value: 1 },
+ { label: '璐告槗鍨�', value: 2 },
+ { label: '鏈嶅姟鍨�', value: 3 },
+ { label: '鍏朵粬', value: 4 },
+];
+
+export const SUPPLIER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 1, color: 'blue' },
+ { value: 2, color: 'green' },
+ { value: 3, color: 'orange' },
+ { value: 4, color: 'default' },
+ ],
+ labels: { 1: '鐢熶骇鍨�', 2: '璐告槗鍨�', 3: '鏈嶅姟鍨�', 4: '鍏朵粬' },
+};
+
+// ========== 鍑嗗叆鐢宠 鈥� 瀵归綈 SupplierApplyStatusEnum (0/10/20/30/40/50) ==========
+
+export const APPLY_STATUS = {
+ DRAFT: 0,
+ PENDING_PURCHASE: 10,
+ PENDING_QUALITY: 20,
+ PENDING_FINANCE: 30,
+ APPROVED: 40,
+ REJECTED: 50,
+} as const;
+
+export const APPLY_STATUS_OPTIONS: EnumOption[] = [
+ { label: '寰呮彁浜�', value: 0 },
+ { label: '寰呴噰璐鏍�', value: 10 },
+ { label: '寰呰川閲忓鏍�', value: 20 },
+ { label: '寰呰储鍔″鏍�', value: 30 },
+ { label: '宸查�氳繃', value: 40 },
+ { label: '宸叉嫆缁�', value: 50 },
+];
+
+export const APPLY_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 0, color: 'gray' },
+ { value: 10, color: 'blue' },
+ { value: 20, color: 'orange' },
+ { value: 30, color: 'purple' },
+ { value: 40, color: 'green' },
+ { value: 50, color: 'red' },
+ ],
+ labels: { 0: '寰呮彁浜�', 10: '寰呴噰璐鏍�', 20: '寰呰川閲忓鏍�', 30: '寰呰储鍔″鏍�', 40: '宸查�氳繃', 50: '宸叉嫆缁�' },
+};
+
+// ========== 璧勮川璇佷功 鈥� 瀵归綈 CertificateTypeEnum (1=钀ヤ笟鎵х収 2=璁よ瘉璇佷功 3=妫�娴嬫姤鍛� 4=鍏朵粬) ==========
+
+export const CERTIFICATE_TYPE = {
+ BUSINESS_LICENSE: 1,
+ CERTIFICATION: 2,
+ INSPECTION_REPORT: 3,
+ OTHER: 4,
+} as const;
+
+export const CERTIFICATE_TYPE_OPTIONS: EnumOption[] = [
+ { label: '钀ヤ笟鎵х収', value: 1 },
+ { label: '璁よ瘉璇佷功', value: 2 },
+ { label: '妫�娴嬫姤鍛�', value: 3 },
+ { label: '鍏朵粬', value: 4 },
+];
+
+export const CERTIFICATE_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 1, color: 'blue' },
+ { value: 2, color: 'green' },
+ { value: 3, color: 'orange' },
+ { value: 4, color: 'default' },
+ ],
+ labels: { 1: '钀ヤ笟鎵х収', 2: '璁よ瘉璇佷功', 3: '妫�娴嬫姤鍛�', 4: '鍏朵粬' },
+};
+
+// ========== 缁╂晥璇勪环 鈥� 鍚庣 evaluationLevel 涓� String 绫诲瀷 (A/B/C/D) ==========
+
+export const EVALUATION_GRADE = {
+ A: 'A',
+ B: 'B',
+ C: 'C',
+ D: 'D',
+} as const;
+
+export const EVALUATION_GRADE_CELLTAG: { data: CellTagItem[] } = {
+ data: [
+ { value: 'A', color: 'green' },
+ { value: 'B', color: 'blue' },
+ { value: 'C', color: 'orange' },
+ { value: 'D', color: 'red' },
+ ],
+};
+
+/** 缁煎悎璇勫垎鍖洪棿 鈥� 鍚庣璁$畻: >=90鈫扐, >=80鈫払, >=70鈫扖, <70鈫扗 */
+export const COMPOSITE_SCORE_CELLTAG: { data: CellTagItem[] } = {
+ data: [
+ { value: 90, color: 'green' },
+ { value: 80, color: 'blue' },
+ { value: 70, color: 'orange' },
+ { value: 0, color: 'red' },
+ ],
+};
+
+// ========== 鎷涙爣椤圭洰 鈥� 瀵归綈 TenderStatusEnum (0/1/2/3/4/5) + TenderTypeEnum (1/2/3) ==========
+
+/** 鎷涙爣绫诲瀷 鈥� 瀵归綈 TenderTypeEnum (1=鍏紑鎷涙爣 2=閭�璇锋嫑鏍� 3=绔炰簤鎬ц皥鍒�) */
+export const TENDER_TYPE = {
+ PUBLIC: 1,
+ INVITATION: 2,
+ COMPETITIVE_NEGOTIATION: 3,
+} as const;
+
+export const TENDER_TYPE_OPTIONS: EnumOption[] = [
+ { label: '鍏紑鎷涙爣', value: 1 },
+ { label: '閭�璇锋嫑鏍�', value: 2 },
+ { label: '绔炰簤鎬ц皥鍒�', value: 3 },
+];
+
+export const TENDER_TYPE_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 1, color: 'blue' },
+ { value: 2, color: 'blue' },
+ { value: 3, color: 'green' },
+ ],
+ labels: { 1: '鍏紑鎷涙爣', 2: '閭�璇锋嫑鏍�', 3: '绔炰簤鎬ц皥鍒�' },
+};
+
+/** 鎷涙爣椤圭洰鐘舵�� 鈥� 瀵归綈 TenderStatusEnum (0=鑽夌 1=鍙戝竷涓� 2=鎶曟爣涓� 3=璇勬爣涓� 4=宸插畾鏍� 5=宸插叧闂�) */
+export const TENDER_STATUS = {
+ DRAFT: 0,
+ PUBLISHED: 1,
+ BIDDING: 2,
+ EVALUATING: 3,
+ AWARDED: 4,
+ CLOSED: 5,
+} as const;
+
+export const TENDER_STATUS_OPTIONS: EnumOption[] = [
+ { label: '鑽夌', value: 0 },
+ { label: '鍙戝竷涓�', value: 1 },
+ { label: '鎶曟爣涓�', value: 2 },
+ { label: '璇勬爣涓�', value: 3 },
+ { label: '宸插畾鏍�', value: 4 },
+ { label: '宸插叧闂�', value: 5 },
+];
+
+export const TENDER_STATUS_CELLTAG: { data: CellTagItem[]; labels: Record<number, string> } = {
+ data: [
+ { value: 0, color: 'gray' },
+ { value: 1, color: 'blue' },
+ { value: 2, color: 'cyan' },
+ { value: 3, color: 'orange' },
+ { value: 4, color: 'green' },
+ { value: 5, color: 'red' },
+ ],
+ labels: { 0: '鑽夌', 1: '鍙戝竷涓�', 2: '鎶曟爣涓�', 3: '璇勬爣涓�', 4: '宸插畾鏍�', 5: '宸插叧闂�' },
+};
+
+/** 鎷涙爣鐘舵�� Ant Design Tag 棰滆壊鏄犲皠 */
+export const TENDER_STATUS_COLORS: Record<number, string> = {
+ [TENDER_STATUS.DRAFT]: 'default',
+ [TENDER_STATUS.PUBLISHED]: 'blue',
+ [TENDER_STATUS.BIDDING]: 'cyan',
+ [TENDER_STATUS.EVALUATING]: 'orange',
+ [TENDER_STATUS.AWARDED]: 'green',
+ [TENDER_STATUS.CLOSED]: 'red',
+};
+
+// ========== 鎶曟爣 鈥� 瀵归綈 BidStatusEnum (0=鎶ュ悕 10=宸叉姇鏍� 20=鎾ゆ爣 30=涓爣 40=鏈腑鏍�) ==========
+
+export const BID_STATUS = {
+ REGISTERED: 0,
+ BID: 10,
+ WITHDRAWN: 20,
+ WIN: 30,
+ LOSE: 40,
+} as const;
+
+export const BID_STATUS_MAP: Record<number, string> = {
+ 0: '鎶ュ悕',
+ 10: '宸叉姇鏍�',
+ 20: '鎾ゆ爣',
+ 30: '涓爣',
+ 40: '鏈腑鏍�',
+};
+
+// ========== 瀹氭爣 鈥� 瀵归綈 AwardStatusEnum (0=寰呭鎵� 10=宸插畾鏍� 20=宸茬敓鎴愯鍗�) ==========
+
+export const AWARD_STATUS = {
+ PENDING_APPROVAL: 0,
+ AWARDED: 10,
+ ORDER_GENERATED: 20,
+} as const;
+
+export const AWARD_STATUS_MAP: Record<number, string> = {
+ 0: '寰呭鎵�',
+ 10: '宸插畾鏍�',
+ 20: '宸茬敓鎴愯鍗�',
+};
+
+// ========== 宸ュ叿鍑芥暟 ==========
+
+/** 鏍规嵁鍊艰幏鍙栨爣绛� */
+export function getLabel(map: Record<number, string>, value?: number): string {
+ return value !== undefined ? map[value] || String(value) : '';
+}
+
+/** 灏� options 鏁扮粍杞负 labels 鏄犲皠 */
+export function optionsToLabels(options: EnumOption[]): Record<number, string> {
+ const labels: Record<number, string> = {};
+ for (const opt of options) {
+ if (typeof opt.value === 'number') {
+ labels[opt.value] = opt.label;
+ }
+ }
+ return labels;
+}
diff --git a/src/views/srm/statistics/index.vue b/src/views/srm/statistics/index.vue
new file mode 100644
index 0000000..439ae78
--- /dev/null
+++ b/src/views/srm/statistics/index.vue
@@ -0,0 +1,128 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+import type { SrmStatisticsApi } from '#/api/srm/statistics';
+
+import { onMounted, ref, watch } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+import { getTenderStatistics } from '#/api/srm/statistics';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
+
+defineOptions({ name: 'SrmStatistics' });
+
+const year = ref(new Date().getFullYear().toString());
+const stats = ref<SrmStatisticsApi.TenderStatisticsVO>({});
+const supplierCount = ref(0);
+
+async function loadStats() {
+ stats.value = await getTenderStatistics(year.value);
+}
+async function loadSupplierCount() {
+ const list = await getSupplierSimpleList();
+ supplierCount.value = list?.length ?? 0;
+}
+
+// 鍥捐〃
+const pieChartRef = ref<EchartsUIType>();
+const barChartRef = ref<EchartsUIType>();
+const { renderEcharts: renderPie } = useEcharts(pieChartRef);
+const { renderEcharts: renderBar } = useEcharts(barChartRef);
+
+async function renderCharts() {
+ // 楗煎浘锛氫緵搴斿晢鐘舵�佸垎甯�
+ await renderPie({
+ title: { text: '渚涘簲鍟嗙姸鎬佸垎甯�', left: 'center' },
+ tooltip: { trigger: 'item' },
+ legend: { bottom: 0 },
+ series: [{
+ type: 'pie',
+ radius: ['40%', '70%'],
+ data: [
+ { value: supplierCount.value, name: '宸插惎鐢�' },
+ { value: 0, name: '宸茬鐢�' },
+ ],
+ }],
+ });
+
+ // 鏌辩姸鍥撅細鎷涙爣缁熻姒傝
+ await renderBar({
+ title: { text: `${year.value} 骞存嫑鏍囩粺璁℃瑙坄, left: 'center' },
+ tooltip: { trigger: 'axis' },
+ xAxis: { type: 'category', data: ['鎷涙爣鎬绘暟', '鍙備笌渚涘簲鍟�', '瀹氭爣閲戦(涓�)', '鑺傜害閲戦(涓�)'] },
+ yAxis: { type: 'value' },
+ series: [{
+ type: 'bar',
+ data: [
+ stats.value.totalTenderCount ?? 0,
+ stats.value.supplierCount ?? 0,
+ +(((stats.value.totalAwardAmount ?? 0) / 10000).toFixed(2)),
+ +(((stats.value.savedAmount ?? 0) / 10000).toFixed(2)),
+ ],
+ itemStyle: {
+ color: (params: any) => ['#3b82f6', '#ec4899', '#f97316', '#8b5cf6'][params.dataIndex],
+ },
+ }],
+ });
+}
+
+watch([stats, supplierCount], () => renderCharts());
+onMounted(() => { loadStats(); loadSupplierCount(); });
+</script>
+
+<template>
+ <Page auto-content-height>
+ <div class="p-4">
+ <div class="mb-6 flex items-center gap-4">
+ <span class="text-lg font-medium">SRM 缁熻鐪嬫澘</span>
+ <a-input v-model:value="year" placeholder="骞翠唤" style="width: 120px" @change="loadStats" />
+ </div>
+
+ <!-- 6 涓粺璁″崱鐗� -->
+ <div class="mb-6 grid grid-cols-3 gap-4">
+ <div class="rounded-lg bg-blue-50 p-6 text-center shadow-sm">
+ <div class="text-3xl font-bold text-blue-600">{{ stats.totalTenderCount ?? 0 }}</div>
+ <div class="mt-2 text-gray-500">鎷涙爣鎬绘暟</div>
+ </div>
+ <div class="rounded-lg bg-green-50 p-6 text-center shadow-sm">
+ <div class="text-3xl font-bold text-green-600">{{ stats.totalTenderAmount?.toLocaleString() ?? 0 }}</div>
+ <div class="mt-2 text-gray-500">鎷涙爣鎬婚噾棰�(鍏�)</div>
+ </div>
+ <div class="rounded-lg bg-orange-50 p-6 text-center shadow-sm">
+ <div class="text-3xl font-bold text-orange-600">{{ stats.totalAwardAmount?.toLocaleString() ?? 0 }}</div>
+ <div class="mt-2 text-gray-500">瀹氭爣鎬婚噾棰�(鍏�)</div>
+ </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>
+ <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>
+ <div class="mt-2 text-gray-500">涓爣鐜�</div>
+ </div>
+ <div class="rounded-lg bg-rose-50 p-6 text-center shadow-sm">
+ <div class="text-3xl font-bold text-rose-600">{{ stats.supplierCount ?? 0 }}</div>
+ <div class="mt-2 text-gray-500">鍙備笌鎷涙爣渚涘簲鍟�</div>
+ </div>
+ </div>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="mb-6 grid grid-cols-2 gap-4">
+ <div class="rounded-lg bg-white p-4 shadow-sm">
+ <EchartsUI ref="pieChartRef" class="h-[350px] w-full" />
+ </div>
+ <div class="rounded-lg bg-white p-4 shadow-sm">
+ <EchartsUI ref="barChartRef" class="h-[350px] w-full" />
+ </div>
+ </div>
+
+ <!-- 搴曢儴淇℃伅 -->
+ <div class="rounded-lg bg-white p-4 shadow-sm">
+ <h3 class="mb-3 text-base font-medium">渚涘簲鍟嗘�绘暟: {{ supplierCount }} 瀹�</h3>
+ <a-empty v-if="supplierCount === 0" description="鏆傛棤渚涘簲鍟嗘暟鎹�" />
+ </div>
+ </div>
+ </Page>
+</template>
diff --git a/src/views/srm/supplier/components/index.ts b/src/views/srm/supplier/components/index.ts
new file mode 100644
index 0000000..0033d6f
--- /dev/null
+++ b/src/views/srm/supplier/components/index.ts
@@ -0,0 +1,2 @@
+export { default as SrmSupplierSelect } from './select.vue';
+export { default as SrmSupplierSelectDialog } from './select-dialog.vue';
diff --git a/src/views/srm/supplier/components/select-dialog.vue b/src/views/srm/supplier/components/select-dialog.vue
new file mode 100644
index 0000000..1f3c614
--- /dev/null
+++ b/src/views/srm/supplier/components/select-dialog.vue
@@ -0,0 +1,259 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierApi } from '#/api/srm/supplier';
+
+import { nextTick, ref } from 'vue';
+
+import { CommonStatusEnum } from '@vben/constants';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSupplierPage } from '#/api/srm/supplier';
+
+defineOptions({ name: 'SrmSupplierSelectDialog' });
+
+const emit = defineEmits<{
+ selected: [rows: SrmSupplierApi.SupplierVO[]];
+}>();
+
+const open = ref(false);
+const multiple = ref(true);
+const approvalFilter = ref(true);
+const selectedRows = ref<SrmSupplierApi.SupplierVO[]>([]);
+const preSelectedIds = ref<number[]>([]);
+
+function getTableRows() {
+ return gridApi.grid.getTableData().fullData as SrmSupplierApi.SupplierVO[];
+}
+
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, SrmSupplierApi.SupplierVO>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as SrmSupplierApi.SupplierVO[];
+ records.forEach((row) => {
+ const rowId = row.id;
+ if (rowId) {
+ selectedMap.set(rowId, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+function handleRadioChange(row: SrmSupplierApi.SupplierVO) {
+ selectedRows.value = [row];
+}
+
+async function toggleMultipleRow(row: SrmSupplierApi.SupplierVO) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+async function handleCellDblclick({ row }: { row: SrmSupplierApi.SupplierVO }) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = getTableRows();
+ for (const row of rows) {
+ if (
+ row.id === undefined ||
+ row.id === null ||
+ !preSelectedIds.value.includes(row.id)
+ ) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+function useSelectGridFormSchema() {
+ return [
+ {
+ fieldName: 'name',
+ label: '渚涘簲鍟嗗悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '渚涘簲鍟嗙紪鐮�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜',
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+function useSelectGridColumns(multiple = false): VxeTableGridOptions<SrmSupplierApi.SupplierVO>['columns'] {
+ return [
+ {
+ type: multiple ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '缂栫爜',
+ minWidth: 100,
+ },
+ {
+ field: 'name',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'contactName',
+ title: '鑱旂郴浜�',
+ minWidth: 100,
+ },
+ {
+ field: 'contactMobile',
+ title: '鑱旂郴鐢佃瘽',
+ minWidth: 120,
+ },
+ {
+ field: 'contactEmail',
+ title: '閭',
+ minWidth: 150,
+ },
+ ];
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useSelectGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useSelectGridColumns(true),
+ height: 520,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ const params: Record<string, unknown> = {
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ status: CommonStatusEnum.ENABLE,
+ };
+ if (approvalFilter.value) {
+ params.approved = true;
+ }
+ return await getSupplierPage(params);
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: SrmSupplierApi.SupplierVO }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean; approvalFilter?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? true;
+ approvalFilter.value = options?.approvalFilter ?? true;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useSelectGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="SRM 渚涘簲鍟嗛�夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @ok="handleConfirm"
+ @cancel="closeModal"
+ >
+ <Grid table-title="渚涘簲鍟嗗垪琛�" />
+ </Modal>
+</template>
diff --git a/src/views/srm/supplier/components/select.vue b/src/views/srm/supplier/components/select.vue
new file mode 100644
index 0000000..0da35fd
--- /dev/null
+++ b/src/views/srm/supplier/components/select.vue
@@ -0,0 +1,117 @@
+<script lang="ts" setup>
+import type { SrmSupplierApi } from '#/api/srm/supplier';
+
+import { computed, ref, useAttrs } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import SrmSupplierSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'SrmSupplierSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ approvalFilter?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ supplierName?: string;
+ }>(),
+ {
+ allowClear: true,
+ approvalFilter: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ supplierName: undefined,
+ },
+);
+const emit = defineEmits<{
+ change: [item: SrmSupplierApi.SupplierVO | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof SrmSupplierSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<SrmSupplierApi.SupplierVO>();
+
+const displayLabel = computed(() => props.supplierName || selectedItem.value?.name || '');
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false, approvalFilter: props.approvalFilter });
+}
+
+function handleSelected(rows: SrmSupplierApi.SupplierVO[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>缂栫爜锛歿{ selectedItem.code || '-' }}</div>
+ <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+ <div>鑱旂郴浜猴細{{ selectedItem.contactName || '-' }}</div>
+ <div>鐢佃瘽锛歿{ selectedItem.contactMobile || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <SrmSupplierSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/srm/supplier/data.ts b/src/views/srm/supplier/data.ts
new file mode 100644
index 0000000..58dc484
--- /dev/null
+++ b/src/views/srm/supplier/data.ts
@@ -0,0 +1,178 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { z } from '#/adapter/form';
+
+import {
+ SUPPLIER_STATUS_OPTIONS,
+ SUPPLIER_STATUS_CELLTAG,
+ SUPPLIER_TYPE_OPTIONS,
+ SUPPLIER_TYPE_CELLTAG,
+} from '../enums';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '渚涘簲鍟嗙紪鐮�',
+ component: 'Input',
+ rules: 'required',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜' },
+ },
+ {
+ fieldName: 'name',
+ label: '渚涘簲鍟嗗悕绉�',
+ component: 'Input',
+ rules: 'required',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О' },
+ },
+ {
+ fieldName: 'type',
+ label: '渚涘簲鍟嗙被鍨�',
+ component: 'Select',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟嗙被鍨�',
+ options: SUPPLIER_TYPE_OPTIONS,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: SUPPLIER_STATUS_OPTIONS,
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ rules: z.number().default(1),
+ },
+ {
+ fieldName: 'contactName',
+ label: '鑱旂郴浜�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ仈绯讳汉' },
+ },
+ {
+ fieldName: 'contactMobile',
+ label: '鑱旂郴鐢佃瘽',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ仈绯荤數璇�' },
+ },
+ {
+ fieldName: 'contactEmail',
+ label: '閭',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ偖绠�' },
+ },
+ {
+ fieldName: 'address',
+ label: '鍦板潃',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ュ湴鍧�' },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'bankName',
+ label: '寮�鎴烽摱琛�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ュ紑鎴烽摱琛�' },
+ },
+ {
+ fieldName: 'bankAccount',
+ label: '閾惰璐﹀彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ摱琛岃处鍙�' },
+ },
+ {
+ fieldName: 'taxNo',
+ label: '绋庡彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ◣鍙�' },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '缂栫爜',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ紪鐮�', allowClear: true },
+ },
+ {
+ fieldName: 'name',
+ label: '渚涘簲鍟嗗悕绉�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О', allowClear: true },
+ },
+ {
+ fieldName: 'type',
+ label: '绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨绫诲瀷',
+ allowClear: true,
+ options: SUPPLIER_TYPE_OPTIONS,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ options: SUPPLIER_STATUS_OPTIONS,
+ },
+ },
+ ];
+}
+
+/** 琛ㄦ牸鍒� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { field: 'code', title: '缂栫爜', minWidth: 120 },
+ { field: 'name', title: '渚涘簲鍟嗗悕绉�', minWidth: 150 },
+ {
+ field: 'type',
+ title: '绫诲瀷',
+ minWidth: 100,
+ cellRender: { name: 'CellTag', props: SUPPLIER_TYPE_CELLTAG },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ minWidth: 80,
+ cellRender: { name: 'CellTag', props: SUPPLIER_STATUS_CELLTAG },
+ },
+ { field: 'contactName', title: '鑱旂郴浜�', minWidth: 100 },
+ { field: 'contactMobile', title: '鑱旂郴鐢佃瘽', minWidth: 120 },
+ { field: 'contactEmail', title: '閭', minWidth: 150 },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', minWidth: 160 },
+ {
+ title: '鎿嶄綔',
+ width: 130,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/srm/supplier/index.vue b/src/views/srm/supplier/index.vue
new file mode 100644
index 0000000..ff73e1a
--- /dev/null
+++ b/src/views/srm/supplier/index.vue
@@ -0,0 +1,120 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierApi } from '#/api/srm/supplier';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteSupplier,
+ getSupplierPage,
+} from '#/api/srm/supplier';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import SupplierForm from './modules/form.vue';
+
+defineOptions({ name: 'SrmSupplier' });
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SrmSupplierApi.SupplierVO) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SrmSupplierApi.SupplierVO) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteSupplier(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: SupplierForm,
+ destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSupplierPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="渚涘簲鍟嗗垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['渚涘簲鍟�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['srm:supplier:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['srm:supplier:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['srm:supplier:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/srm/supplier/modules/form.vue b/src/views/srm/supplier/modules/form.vue
new file mode 100644
index 0000000..4542a25
--- /dev/null
+++ b/src/views/srm/supplier/modules/form.vue
@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { SrmSupplierApi } from '#/api/srm/supplier';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createSupplier,
+ getSupplier,
+ updateSupplier,
+} from '#/api/srm/supplier';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<SrmSupplierApi.SupplierVO>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['渚涘簲鍟�'])
+ : $t('ui.actionTitle.create', ['渚涘簲鍟�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as SrmSupplierApi.SupplierVO;
+ try {
+ await (formData.value?.id ? updateSupplier(data) : createSupplier(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<SrmSupplierApi.SupplierVO>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getSupplier(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({ modalApi });
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/srm/supplierApply/data.ts b/src/views/srm/supplierApply/data.ts
new file mode 100644
index 0000000..0e172a0
--- /dev/null
+++ b/src/views/srm/supplierApply/data.ts
@@ -0,0 +1,160 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierApi } from '#/api/srm/supplier';
+
+import { markRaw } from 'vue';
+
+import { z } from '#/adapter/form';
+
+import { SrmSupplierSelect } from '#/views/srm/supplier/components';
+
+import { APPLY_STATUS_CELLTAG, APPLY_STATUS_OPTIONS } from '../enums';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '閫夋嫨渚涘簲鍟�',
+ component: markRaw(SrmSupplierSelect),
+ componentProps: {
+ approvalFilter: false,
+ placeholder: '璇烽�夋嫨SRM渚涘簲鍟嗭紙鎴栫洿鎺ュ~鍐欎緵搴斿晢鍚嶇О锛�',
+ onChange: async (supplier?: SrmSupplierApi.SupplierVO) => {
+ if (!supplier || !formApi) return;
+ await formApi.setValues({
+ supplierName: supplier.name || '',
+ contactName: supplier.contactName || '',
+ contactMobile: supplier.contactMobile || '',
+ contactEmail: supplier.contactEmail || '',
+ taxNo: supplier.taxNo || '',
+ address: supplier.address || '',
+ });
+ },
+ },
+ dependencies: {
+ triggerFields: ['supplierName'],
+ componentProps: (values) => ({
+ approvalFilter: false,
+ placeholder: '璇烽�夋嫨SRM渚涘簲鍟嗭紙鎴栫洿鎺ュ~鍐欎緵搴斿晢鍚嶇О锛�',
+ supplierName: values.supplierName as string,
+ }),
+ },
+ },
+ {
+ fieldName: 'supplierName',
+ label: '渚涘簲鍟嗗悕绉�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О' },
+ },
+ {
+ fieldName: 'contactName',
+ label: '鑱旂郴浜�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ仈绯讳汉' },
+ },
+ {
+ fieldName: 'contactMobile',
+ label: '鑱旂郴鐢佃瘽',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ仈绯荤數璇�' },
+ },
+ {
+ fieldName: 'contactEmail',
+ label: '閭',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ偖绠�' },
+ },
+ {
+ fieldName: 'address',
+ label: '鍦板潃',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ュ湴鍧�' },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'bankName',
+ label: '寮�鎴烽摱琛�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ュ紑鎴烽摱琛�' },
+ },
+ {
+ fieldName: 'bankAccount',
+ label: '閾惰璐﹀彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ摱琛岃处鍙�' },
+ },
+ {
+ fieldName: 'taxNo',
+ label: '绋庡彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ◣鍙�' },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'applyNo',
+ label: '鐢宠缂栧彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ敵璇风紪鍙�', allowClear: true },
+ },
+ {
+ fieldName: 'supplierName',
+ label: '渚涘簲鍟嗗悕绉�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О', allowClear: true },
+ },
+ {
+ fieldName: 'applyStatus',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ options: APPLY_STATUS_OPTIONS,
+ },
+ },
+ ];
+}
+
+/** 琛ㄦ牸鍒� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { field: 'applyNo', title: '鐢宠缂栧彿', minWidth: 140 },
+ { field: 'supplierName', title: '渚涘簲鍟嗗悕绉�', minWidth: 150 },
+ {
+ field: 'applyStatus',
+ title: '鐘舵��',
+ minWidth: 100,
+ cellRender: { name: 'CellTag', props: APPLY_STATUS_CELLTAG },
+ },
+ { field: 'contactName', title: '鑱旂郴浜�', minWidth: 100 },
+ { field: 'contactMobile', title: '鑱旂郴鐢佃瘽', minWidth: 120 },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', minWidth: 160 },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/srm/supplierApply/index.vue b/src/views/srm/supplierApply/index.vue
new file mode 100644
index 0000000..47b8a8f
--- /dev/null
+++ b/src/views/srm/supplierApply/index.vue
@@ -0,0 +1,264 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierApplyApi } from '#/api/srm/supplierApply';
+
+import { computed, ref } from 'vue';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteApply,
+ getApplyPage,
+ submitApply,
+ reviewPurchase,
+ reviewQuality,
+ reviewFinance,
+} from '#/api/srm/supplierApply';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import ApplyForm from './modules/form.vue';
+import { APPLY_STATUS } from '../enums';
+
+defineOptions({ name: 'SrmSupplierApply' });
+
+// ---------- reviewModal ----------
+const reviewFormData = ref({
+ applyId: 0,
+ reviewType: '' as 'purchase' | 'quality' | 'finance',
+ passed: true,
+ comment: '',
+});
+const reviewTitle = computed(() => {
+ const labels: Record<string, string> = {
+ purchase: '閲囪喘瀹℃牳',
+ quality: '璐ㄩ噺瀹℃牳',
+ finance: '璐㈠姟瀹℃牳',
+ };
+ return labels[reviewFormData.value.reviewType] || '瀹℃牳';
+});
+
+const [ReviewForm, reviewFormApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 100,
+ },
+ wrapperClass: 'grid-cols-1',
+ layout: 'horizontal',
+ schema: [
+ {
+ fieldName: 'passed',
+ label: '瀹℃牳缁撴灉',
+ component: 'RadioGroup',
+ rules: 'required',
+ componentProps: {
+ options: [
+ { label: '閫氳繃', value: true },
+ { label: '鎷掔粷', value: false },
+ ],
+ buttonStyle: 'solid',
+ optionType: 'button',
+ },
+ },
+ {
+ fieldName: 'comment',
+ label: '瀹℃牳鎰忚',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ュ鏍告剰瑙�', rows: 3 },
+ },
+ ],
+ showDefaultActions: false,
+});
+
+const [ReviewModal, reviewModalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await reviewFormApi.validate();
+ if (!valid) {
+ return;
+ }
+ reviewModalApi.lock();
+ const data = (await reviewFormApi.getValues()) as { passed: boolean; comment?: string };
+ try {
+ const payload: SrmSupplierApplyApi.ReviewVO = {
+ id: reviewFormData.value.applyId,
+ passed: data.passed,
+ comment: data.comment,
+ };
+ if (reviewFormData.value.reviewType === 'purchase') {
+ await reviewPurchase(payload);
+ } else if (reviewFormData.value.reviewType === 'quality') {
+ await reviewQuality(payload);
+ } else if (reviewFormData.value.reviewType === 'finance') {
+ await reviewFinance(payload);
+ }
+ await reviewModalApi.close();
+ handleRefresh();
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ reviewModalApi.unlock();
+ }
+ },
+});
+
+function openReviewModal(row: SrmSupplierApplyApi.ApplyVO, reviewType: 'purchase' | 'quality' | 'finance') {
+ reviewFormData.value = {
+ applyId: row.id!,
+ reviewType,
+ passed: true,
+ comment: '',
+ };
+ reviewFormApi.resetForm();
+ reviewModalApi.open();
+}
+
+// ---------- grid ----------
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SrmSupplierApplyApi.ApplyVO) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SrmSupplierApplyApi.ApplyVO) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.supplierName]),
+ duration: 0,
+ });
+ try {
+ await deleteApply(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.supplierName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+async function handleSubmit(row: SrmSupplierApplyApi.ApplyVO) {
+ const hideLoading = message.loading({ content: '鎻愪氦涓�...', duration: 0 });
+ try {
+ await submitApply(row.id!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: ApplyForm,
+ destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getApplyPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SrmSupplierApplyApi.ApplyVO>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <ReviewModal :title="reviewTitle" class="w-1/3">
+ <ReviewForm class="mx-4" />
+ </ReviewModal>
+ <Grid table-title="鍑嗗叆鐢宠鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍑嗗叆鐢宠']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['srm:supplier-apply:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['srm:supplier-apply:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '鎻愪氦',
+ type: 'link',
+ icon: ACTION_ICON.CHECK,
+ auth: ['srm:supplier-apply:update'],
+ ifShow: row.applyStatus === APPLY_STATUS.DRAFT,
+ onClick: handleSubmit.bind(null, row),
+ },
+ {
+ label: '閲囪喘瀹℃牳',
+ type: 'link',
+ icon: ACTION_ICON.CHECK,
+ auth: ['srm:supplier-apply:update'],
+ ifShow: row.applyStatus === APPLY_STATUS.PENDING_PURCHASE,
+ onClick: openReviewModal.bind(null, row, 'purchase'),
+ },
+ {
+ label: '璐ㄩ噺瀹℃牳',
+ type: 'link',
+ icon: ACTION_ICON.CHECK,
+ auth: ['srm:supplier-apply:update'],
+ ifShow: row.applyStatus === APPLY_STATUS.PENDING_QUALITY,
+ onClick: openReviewModal.bind(null, row, 'quality'),
+ },
+ {
+ label: '璐㈠姟瀹℃牳',
+ type: 'link',
+ icon: ACTION_ICON.CHECK,
+ auth: ['srm:supplier-apply:update'],
+ ifShow: row.applyStatus === APPLY_STATUS.PENDING_FINANCE,
+ onClick: openReviewModal.bind(null, row, 'finance'),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['srm:supplier-apply:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.supplierName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/srm/supplierApply/modules/form.vue b/src/views/srm/supplierApply/modules/form.vue
new file mode 100644
index 0000000..22305a4
--- /dev/null
+++ b/src/views/srm/supplierApply/modules/form.vue
@@ -0,0 +1,84 @@
+<script lang="ts" setup>
+import type { SrmSupplierApplyApi } from '#/api/srm/supplierApply';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createApply,
+ getApply,
+ updateApply,
+} from '#/api/srm/supplierApply';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<SrmSupplierApplyApi.ApplyVO>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鍑嗗叆鐢宠'])
+ : $t('ui.actionTitle.create', ['鍑嗗叆鐢宠']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+// 娉ㄥ叆 formApi 浠ユ敮鎸侀�夋嫨 SRM 渚涘簲鍟嗗悗鑷姩濉厖琛ㄥ崟瀛楁
+formApi.setState({ schema: useFormSchema(formApi) });
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as SrmSupplierApplyApi.ApplyVO;
+ try {
+ await (formData.value?.id ? updateApply(data) : createApply(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<SrmSupplierApplyApi.ApplyVO>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getApply(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({ modalApi });
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/srm/supplierCertificate/data.ts b/src/views/srm/supplierCertificate/data.ts
new file mode 100644
index 0000000..34a7cde
--- /dev/null
+++ b/src/views/srm/supplierCertificate/data.ts
@@ -0,0 +1,153 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { getSupplierSimpleList } from '#/api/srm/supplier';
+
+import { CERTIFICATE_TYPE_CELLTAG, CERTIFICATE_TYPE_OPTIONS } from '../enums';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'certificateName',
+ label: '璧勮川璇佷功鍚嶇О',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ祫璐ㄨ瘉涔﹀悕绉�' },
+ rules: 'required',
+ },
+ {
+ fieldName: 'certificateNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�' },
+ },
+ {
+ fieldName: 'certificateType',
+ label: '璇佷功绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨璇佷功绫诲瀷',
+ options: CERTIFICATE_TYPE_OPTIONS,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'issuingAuthority',
+ label: '棰佸彂鏈烘瀯',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ鍙戞満鏋�' },
+ },
+ {
+ fieldName: 'issueDate',
+ label: '棰佸彂鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨棰佸彂鏃ユ湡',
+ allowClear: true,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ },
+ {
+ fieldName: 'expireDate',
+ label: '鍒版湡鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鍒版湡鏃ユ湡',
+ allowClear: true,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ュ娉�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'certificateName',
+ label: '璧勮川璇佷功鍚嶇О',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ祫璐ㄨ瘉涔﹀悕绉�', allowClear: true },
+ },
+ {
+ fieldName: 'certificateType',
+ label: '璇佷功绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨璇佷功绫诲瀷',
+ allowClear: true,
+ options: CERTIFICATE_TYPE_OPTIONS,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ ];
+}
+
+/** 琛ㄦ牸鍒� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { field: 'supplierName', title: '渚涘簲鍟�', minWidth: 120 },
+ { field: 'certificateName', title: '璧勮川璇佷功鍚嶇О', minWidth: 150 },
+ { field: 'certificateNo', title: '璇佷功缂栧彿', minWidth: 120 },
+ {
+ field: 'certificateType',
+ title: '璇佷功绫诲瀷',
+ minWidth: 120,
+ cellRender: { name: 'CellTag', props: CERTIFICATE_TYPE_CELLTAG },
+ },
+ { field: 'issuingAuthority', title: '棰佸彂鏈烘瀯', minWidth: 120 },
+ { field: 'issueDate', title: '棰佸彂鏃ユ湡', minWidth: 120 },
+ { field: 'expireDate', title: '鍒版湡鏃ユ湡', minWidth: 120 },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', minWidth: 160 },
+ {
+ title: '鎿嶄綔',
+ width: 130,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/srm/supplierCertificate/index.vue b/src/views/srm/supplierCertificate/index.vue
new file mode 100644
index 0000000..29ea03f
--- /dev/null
+++ b/src/views/srm/supplierCertificate/index.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierCertificateApi } from '#/api/srm/supplierCertificate';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteCertificate,
+ getCertificatePage,
+ getExpiringCertificates,
+} from '#/api/srm/supplierCertificate';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import CertificateForm from './modules/form.vue';
+
+defineOptions({ name: 'SrmSupplierCertificate' });
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SrmSupplierCertificateApi.CertificateVO) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SrmSupplierCertificateApi.CertificateVO) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.certificateName]),
+ duration: 0,
+ });
+ try {
+ await deleteCertificate(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.certificateName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+async function handleExpiringCertificates() {
+ try {
+ const data = await getExpiringCertificates();
+ if (data && data.length > 0) {
+ message.warning(`鏈� ${data.length} 涓祫璐ㄨ瘉涔﹀嵆灏嗗埌鏈焋);
+ } else {
+ message.success('娌℃湁鍗冲皢鍒版湡鐨勮祫璐ㄨ瘉涔�');
+ }
+ } catch {
+ message.error('鑾峰彇鍗冲皢鍒版湡鐨勮祫璐ㄨ瘉涔﹀け璐�');
+ }
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: CertificateForm,
+ destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getCertificatePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SrmSupplierCertificateApi.CertificateVO>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="璧勮川璇佷功鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['璧勮川璇佷功']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['srm:supplier-certificate:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: '鍒版湡鎻愰啋',
+ type: 'warning',
+ icon: 'ant-design:bell-outlined',
+ auth: ['srm:supplier-certificate:query'],
+ onClick: handleExpiringCertificates,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['srm:supplier-certificate:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['srm:supplier-certificate:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.certificateName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/srm/supplierCertificate/modules/form.vue b/src/views/srm/supplierCertificate/modules/form.vue
new file mode 100644
index 0000000..d0bc05b
--- /dev/null
+++ b/src/views/srm/supplierCertificate/modules/form.vue
@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { SrmSupplierCertificateApi } from '#/api/srm/supplierCertificate';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createCertificate,
+ getCertificate,
+ updateCertificate,
+} from '#/api/srm/supplierCertificate';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<SrmSupplierCertificateApi.CertificateVO>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['璧勮川璇佷功'])
+ : $t('ui.actionTitle.create', ['璧勮川璇佷功']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as SrmSupplierCertificateApi.CertificateVO;
+ try {
+ await (formData.value?.id ? updateCertificate(data) : createCertificate(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<SrmSupplierCertificateApi.CertificateVO>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getCertificate(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({ modalApi });
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/srm/supplierEvaluation/data.ts b/src/views/srm/supplierEvaluation/data.ts
new file mode 100644
index 0000000..4a20888
--- /dev/null
+++ b/src/views/srm/supplierEvaluation/data.ts
@@ -0,0 +1,132 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { getSupplierSimpleList } from '#/api/srm/supplier';
+
+import { COMPOSITE_SCORE_CELLTAG, EVALUATION_GRADE_CELLTAG } from '../enums';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'evaluationPeriod',
+ label: '璇勪环鍛ㄦ湡',
+ component: 'Input',
+ rules: 'required',
+ componentProps: { placeholder: '濡� 2024-Q1' },
+ },
+ {
+ fieldName: 'qualityScore',
+ label: '璐ㄩ噺璇勫垎',
+ component: 'InputNumber',
+ rules: 'required',
+ componentProps: { min: 0, max: 100, placeholder: '璇疯緭鍏ヨ川閲忚瘎鍒�' },
+ },
+ {
+ fieldName: 'deliveryScore',
+ label: '浜や粯璇勫垎',
+ component: 'InputNumber',
+ rules: 'required',
+ componentProps: { min: 0, max: 100, placeholder: '璇疯緭鍏ヤ氦浠樿瘎鍒�' },
+ },
+ {
+ fieldName: 'priceScore',
+ label: '浠锋牸璇勫垎',
+ component: 'InputNumber',
+ rules: 'required',
+ componentProps: { min: 0, max: 100, placeholder: '璇疯緭鍏ヤ环鏍艰瘎鍒�' },
+ },
+ {
+ fieldName: 'serviceScore',
+ label: '鏈嶅姟璇勫垎',
+ component: 'InputNumber',
+ rules: 'required',
+ componentProps: { min: 0, max: 100, placeholder: '璇疯緭鍏ユ湇鍔¤瘎鍒�' },
+ },
+ {
+ fieldName: 'remark',
+ label: '璇勪环璇存槑',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ヨ瘎浠疯鏄�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'supplierId',
+ label: '渚涘簲鍟�',
+ component: 'ApiSelect',
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ allowClear: true,
+ showSearch: true,
+ api: getSupplierSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'evaluationPeriod',
+ label: '璇勪环鍛ㄦ湡',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヨ瘎浠峰懆鏈�', allowClear: true },
+ },
+ ];
+}
+
+/** 琛ㄦ牸鍒� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { field: 'supplierName', title: '渚涘簲鍟�', minWidth: 150 },
+ { field: 'evaluationPeriod', title: '璇勪环鍛ㄦ湡', minWidth: 120 },
+ { field: 'qualityScore', title: '璐ㄩ噺璇勫垎', minWidth: 100 },
+ { field: 'deliveryScore', title: '浜や粯璇勫垎', minWidth: 100 },
+ { field: 'priceScore', title: '浠锋牸璇勫垎', minWidth: 100 },
+ { field: 'serviceScore', title: '鏈嶅姟璇勫垎', minWidth: 100 },
+ {
+ field: 'compositeScore',
+ title: '缁煎悎璇勫垎',
+ minWidth: 120,
+ cellRender: { name: 'CellTag', props: COMPOSITE_SCORE_CELLTAG },
+ },
+ {
+ field: 'grade',
+ title: '绛夌骇',
+ minWidth: 80,
+ cellRender: { name: 'CellTag', props: EVALUATION_GRADE_CELLTAG },
+ },
+ { field: 'createTime', title: '鍒涘缓鏃堕棿', minWidth: 160 },
+ {
+ title: '鎿嶄綔',
+ width: 130,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/srm/supplierEvaluation/index.vue b/src/views/srm/supplierEvaluation/index.vue
new file mode 100644
index 0000000..0bc2675
--- /dev/null
+++ b/src/views/srm/supplierEvaluation/index.vue
@@ -0,0 +1,120 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmSupplierEvaluationApi } from '#/api/srm/supplierEvaluation';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteEvaluation,
+ getEvaluationPage,
+} from '#/api/srm/supplierEvaluation';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import EvaluationForm from './modules/form.vue';
+
+defineOptions({ name: 'SrmSupplierEvaluation' });
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SrmSupplierEvaluationApi.EvaluationVO) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SrmSupplierEvaluationApi.EvaluationVO) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.evaluationPeriod]),
+ duration: 0,
+ });
+ try {
+ await deleteEvaluation(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.evaluationPeriod]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: EvaluationForm,
+ destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getEvaluationPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SrmSupplierEvaluationApi.EvaluationVO>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="缁╂晥璇勪环鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['缁╂晥璇勪环']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['srm:supplier-evaluation:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['srm:supplier-evaluation:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['srm:supplier-evaluation:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.evaluationPeriod]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/srm/supplierEvaluation/modules/form.vue b/src/views/srm/supplierEvaluation/modules/form.vue
new file mode 100644
index 0000000..d29aa1d
--- /dev/null
+++ b/src/views/srm/supplierEvaluation/modules/form.vue
@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { SrmSupplierEvaluationApi } from '#/api/srm/supplierEvaluation';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createEvaluation,
+ getEvaluation,
+ updateEvaluation,
+} from '#/api/srm/supplierEvaluation';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<SrmSupplierEvaluationApi.EvaluationVO>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['缁╂晥璇勪环'])
+ : $t('ui.actionTitle.create', ['缁╂晥璇勪环']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as SrmSupplierEvaluationApi.EvaluationVO;
+ try {
+ await (formData.value?.id ? updateEvaluation(data) : createEvaluation(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<SrmSupplierEvaluationApi.EvaluationVO>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getEvaluation(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({ modalApi });
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/srm/tender/data.ts b/src/views/srm/tender/data.ts
new file mode 100644
index 0000000..d0bb157
--- /dev/null
+++ b/src/views/srm/tender/data.ts
@@ -0,0 +1,188 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import {
+ TENDER_STATUS_CELLTAG,
+ TENDER_STATUS_OPTIONS,
+ TENDER_TYPE_CELLTAG,
+ TENDER_TYPE_OPTIONS,
+} from '../enums';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'tenderNo',
+ label: '鎷涙爣缂栧彿',
+ component: 'Input',
+ rules: 'required',
+ componentProps: { placeholder: '璇疯緭鍏ユ嫑鏍囩紪鍙�' },
+ },
+ {
+ fieldName: 'tenderName',
+ label: '鎷涙爣椤圭洰鍚嶇О',
+ component: 'Input',
+ rules: 'required',
+ componentProps: { placeholder: '璇疯緭鍏ユ嫑鏍囬」鐩悕绉�' },
+ },
+ {
+ fieldName: 'tenderType',
+ label: '鎷涙爣绫诲瀷',
+ component: 'Select',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鎷涙爣绫诲瀷',
+ options: TENDER_TYPE_OPTIONS,
+ },
+ },
+ {
+ fieldName: 'purchaseOrg',
+ label: '閲囪喘缁勭粐',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ噰璐粍缁�' },
+ },
+ {
+ fieldName: 'projectLeaderName',
+ label: '椤圭洰璐熻矗浜�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ」鐩礋璐d汉' },
+ },
+ {
+ fieldName: 'budgetAmount',
+ label: '棰勭畻閲戦',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ绠楅噾棰�',
+ },
+ },
+ {
+ fieldName: 'startTime',
+ label: '寮�濮嬫椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ },
+ },
+ {
+ fieldName: 'endTime',
+ label: '鎴鏃堕棿',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇烽�夋嫨鎴鏃堕棿',
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ },
+ },
+ {
+ fieldName: 'bidOpenTime',
+ label: '寮�鏍囨椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇烽�夋嫨寮�鏍囨椂闂�',
+ showTime: true,
+ format: 'YYYY-MM-DD HH:mm:ss',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
+ },
+ },
+ {
+ fieldName: 'tenderDesc',
+ label: '鎷涙爣璇存槑',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ユ嫑鏍囪鏄�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'qualificationRequirements',
+ label: '璧勮川瑕佹眰',
+ component: 'Textarea',
+ componentProps: { placeholder: '璇疯緭鍏ヨ祫璐ㄨ姹�', rows: 3 },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'tenderNo',
+ label: '鎷涙爣缂栧彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ユ嫑鏍囩紪鍙�', allowClear: true },
+ },
+ {
+ fieldName: 'tenderName',
+ label: '鎷涙爣椤圭洰鍚嶇О',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ユ嫑鏍囬」鐩悕绉�', allowClear: true },
+ },
+ {
+ fieldName: 'tenderType',
+ label: '鎷涙爣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鎷涙爣绫诲瀷',
+ allowClear: true,
+ options: TENDER_TYPE_OPTIONS,
+ },
+ },
+ {
+ fieldName: 'tenderStatus',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ options: TENDER_STATUS_OPTIONS,
+ },
+ },
+ ];
+}
+
+/** 琛ㄦ牸鍒� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { field: 'tenderNo', title: '鎷涙爣缂栧彿', minWidth: 140 },
+ { field: 'tenderName', title: '鎷涙爣椤圭洰鍚嶇О', minWidth: 180 },
+ {
+ field: 'tenderType',
+ title: '鎷涙爣绫诲瀷',
+ minWidth: 120,
+ cellRender: { name: 'CellTag', props: TENDER_TYPE_CELLTAG },
+ },
+ {
+ field: 'tenderStatus',
+ title: '鐘舵��',
+ minWidth: 100,
+ cellRender: { name: 'CellTag', props: TENDER_STATUS_CELLTAG },
+ },
+ { field: 'budgetAmount', title: '棰勭畻閲戦', minWidth: 120 },
+ { field: 'purchaseOrg', title: '閲囪喘缁勭粐', minWidth: 100 },
+ { field: 'startTime', title: '寮�濮嬫椂闂�', minWidth: 160 },
+ { field: 'endTime', title: '鎴鏃堕棿', minWidth: 160 },
+ { field: 'purchaseOrderNo', title: '閲囪喘璁㈠崟鍙�', minWidth: 140 },
+ {
+ title: '鎿嶄綔',
+ width: 250,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
new file mode 100644
index 0000000..4b34190
--- /dev/null
+++ b/src/views/srm/tender/detail.vue
@@ -0,0 +1,811 @@
+<script lang="ts" setup>
+import type { SrmTenderApi } from '#/api/srm/tender';
+
+import { computed, onMounted, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+import { Page } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+import { IconifyIcon } from '@vben/icons';
+
+import {
+ getTenderProject,
+ getMaterialList, createMaterial, updateMaterial, deleteMaterial,
+ getBidListByProject, createBid, withdrawBid,
+ getQuoteList, createQuote, updateQuote,
+ createBidOpen, getBidOpen,
+ getEvaluationList, createBidEvaluation, calculateRanking,
+ createAward, getAward, approveAward, generatePurchaseOrder,
+} from '#/api/srm/tender';
+import { getSupplierSimpleList } from '#/api/srm/supplier';
+import { getItemSimpleList } from '#/api/mdm/item';
+import type { MdmItemApi } from '#/api/mdm/item';
+import {
+ TENDER_STATUS,
+ TENDER_STATUS_CELLTAG,
+ TENDER_STATUS_COLORS,
+ TENDER_TYPE_CELLTAG,
+ BID_STATUS,
+ BID_STATUS_MAP,
+ AWARD_STATUS,
+ AWARD_STATUS_MAP,
+ getLabel,
+} from '../enums';
+
+defineOptions({ name: 'SrmTenderDetail' });
+
+const route = useRoute();
+const router = useRouter();
+const projectId = Number(route.query.id);
+
+const project = ref<SrmTenderApi.TenderProjectVO>();
+const activeTab = ref('info');
+
+// ========== 涓嬫媺閫夐」鏁版嵁 ==========
+const mdmItemList = ref<MdmItemApi.Item[]>([]);
+const supplierList = ref<{ id: number; name: string }[]>([]);
+
+onMounted(async () => {
+ const [items, suppliers] = await Promise.all([
+ getItemSimpleList(),
+ getSupplierSimpleList(),
+ ]);
+ mdmItemList.value = items;
+ supplierList.value = suppliers;
+});
+
+const filterOption = (input: string, option: any) =>
+ option.label?.toLowerCase().indexOf(input.toLowerCase()) >= 0;
+
+// ========== 鍩烘湰淇℃伅 ==========
+const materialList = ref<SrmTenderApi.TenderMaterialVO[]>([]);
+const materialLoading = ref(false);
+const materialFormVisible = ref(false);
+const materialForm = ref<SrmTenderApi.TenderMaterialVO>({ tenderProjectId: projectId });
+
+async function loadProject() {
+ project.value = await getTenderProject(projectId);
+}
+async function loadMaterials() {
+ materialLoading.value = true;
+ try { materialList.value = await getMaterialList(projectId); } finally { materialLoading.value = false; }
+}
+async function handleSaveMaterial() {
+ if (materialForm.value.id) {
+ await updateMaterial(materialForm.value);
+ } else {
+ await createMaterial(materialForm.value);
+ }
+ materialFormVisible.value = false;
+ loadMaterials();
+}
+function onMaterialSelect(itemId: number) {
+ const item = mdmItemList.value.find((i) => i.id === itemId);
+ if (item) {
+ materialForm.value.productCode = item.code;
+ materialForm.value.productName = item.name;
+ materialForm.value.productSpec = item.specification;
+ materialForm.value.unit = item.unitMeasureName;
+ }
+}
+
+function handleAddMaterial() {
+ materialForm.value = { tenderProjectId: projectId };
+ materialFormVisible.value = true;
+}
+function handleEditMaterial(row: SrmTenderApi.TenderMaterialVO) {
+ materialForm.value = { ...row };
+ materialFormVisible.value = true;
+}
+async function handleDeleteMaterial(id: number) {
+ await deleteMaterial(id);
+ loadMaterials();
+}
+
+// ========== 鎶曟爣绠$悊 ==========
+const bidList = ref<SrmTenderApi.TenderBidVO[]>([]);
+const bidLoading = ref(false);
+const bidFormVisible = ref(false);
+const bidForm = ref<SrmTenderApi.TenderBidVO>({ tenderProjectId: projectId, supplierId: 0, bidNo: '' });
+
+async function loadBids() {
+ bidLoading.value = true;
+ try { bidList.value = await getBidListByProject(projectId); } finally { bidLoading.value = false; }
+}
+async function handleCreateBid() {
+ await createBid(bidForm.value);
+ bidFormVisible.value = false;
+ loadBids();
+}
+async function handleWithdrawBid(id: number) {
+ await withdrawBid(id);
+ loadBids();
+}
+
+// ========== 鎶ヤ环绠$悊 ==========
+const quoteList = ref<SrmTenderApi.QuoteVO[]>([]);
+const quoteLoading = ref(false);
+const quoteFormVisible = ref(false);
+const quoteForm = ref<SrmTenderApi.QuoteVO>({ bidId: 0, tenderMaterialId: 0 });
+const selectedBidId = ref<number>();
+
+const availableMaterials = computed(() => {
+ const quotedIds = new Set(
+ quoteList.value
+ .filter((q) => q.tenderMaterialId !== undefined)
+ .map((q) => q.tenderMaterialId),
+ );
+ // 缂栬緫鏃跺厑璁镐繚鐣欏綋鍓嶇墿鏂�
+ if (quoteForm.value.id && quoteForm.value.tenderMaterialId) {
+ quotedIds.delete(quoteForm.value.tenderMaterialId);
+ }
+ return materialList.value.filter((m) => !quotedIds.has(m.id));
+});
+
+async function loadQuotes(bidId?: number) {
+ if (!bidId) return;
+ selectedBidId.value = bidId;
+ quoteLoading.value = true;
+ try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; }
+}
+async function handleSaveQuote() {
+ if (quoteForm.value.id) {
+ await updateQuote(quoteForm.value);
+ } else {
+ await createQuote(quoteForm.value);
+ }
+ quoteFormVisible.value = false;
+ loadQuotes(selectedBidId.value);
+}
+function handleAddQuote() {
+ quoteForm.value = { bidId: selectedBidId.value!, tenderMaterialId: 0 };
+ quoteFormVisible.value = true;
+}
+function handleEditQuote(row: SrmTenderApi.QuoteVO) {
+ quoteForm.value = { ...row };
+ quoteFormVisible.value = true;
+}
+
+// ========== 寮�鏍� ==========
+const bidOpen = ref<any>(null);
+
+async function loadBidOpen() {
+ try {
+ bidOpen.value = null;
+ } catch { /* 鏆傛棤寮�鏍囪褰� */ }
+}
+async function handleCreateBidOpen() {
+ await createBidOpen(projectId);
+ message.success('寮�鏍囨垚鍔�');
+ loadBidOpen();
+ loadProject();
+}
+
+// ========== 璇勬爣绠$悊 ==========
+const evaluationList = ref<SrmTenderApi.BidEvaluationVO[]>([]);
+const evalLoading = ref(false);
+const evalFormVisible = ref(false);
+const evalForm = ref<SrmTenderApi.BidEvaluationVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0 });
+
+async function loadEvaluations() {
+ evalLoading.value = true;
+ try { evaluationList.value = await getEvaluationList(projectId); } finally { evalLoading.value = false; }
+}
+async function handleCreateEvaluation() {
+ await createBidEvaluation(evalForm.value);
+ evalFormVisible.value = false;
+ loadEvaluations();
+ loadProject();
+}
+async function handleCalculateRanking() {
+ await calculateRanking(projectId);
+ message.success('鎺掑悕璁$畻瀹屾垚');
+ loadEvaluations();
+}
+
+// ========== 瀹氭爣 ==========
+const award = ref<SrmTenderApi.BidAwardVO>();
+const awardFormVisible = ref(false);
+const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' });
+
+async function loadAward() {
+ try { award.value = await getAward(projectId); } catch { award.value = undefined; }
+}
+async function handleCreateAward() {
+ await createAward(awardForm.value);
+ awardFormVisible.value = false;
+ loadAward();
+ loadProject();
+}
+async function handleApproveAward() {
+ if (!award.value?.id) return;
+ await approveAward(award.value.id);
+ message.success('瀹氭爣瀹℃壒閫氳繃');
+ loadAward();
+ loadProject();
+}
+async function handleGeneratePurchaseOrder() {
+ if (!award.value?.id) return;
+ await generatePurchaseOrder(award.value.id);
+ message.success('閲囪喘璁㈠崟宸茬敓鎴�');
+ loadAward();
+ loadProject();
+}
+
+// ========== 鍒濆鍖� ==========
+loadProject();
+loadMaterials();
+loadBids();
+loadEvaluations();
+loadAward();
+
+// ========== 鐘舵�佸垽鏂� ==========
+const statusLabel = computed(() => getLabel(TENDER_STATUS_CELLTAG.labels, project.value?.tenderStatus));
+const statusColor = computed(() => TENDER_STATUS_COLORS[project.value?.tenderStatus!] || 'default');
+const typeLabel = computed(() => getLabel(TENDER_TYPE_CELLTAG.labels, project.value?.tenderType));
+
+const stepItems = [
+ { title: '鑽夌', status: TENDER_STATUS.DRAFT },
+ { title: '鍙戝竷', status: TENDER_STATUS.PUBLISHED },
+ { title: '鎶曟爣涓�', status: TENDER_STATUS.BIDDING },
+ { title: '璇勬爣涓�', status: TENDER_STATUS.EVALUATING },
+ { title: '宸插畾鏍�', status: TENDER_STATUS.AWARDED },
+];
+const currentStep = computed(() => {
+ const s = project.value?.tenderStatus ?? 0;
+ if (s === TENDER_STATUS.CLOSED) return 4;
+ const idx = stepItems.findIndex((item) => item.status >= s);
+ return idx === -1 ? 0 : idx;
+});
+const stepStatus = computed(() => {
+ const s = project.value?.tenderStatus;
+ if (s === TENDER_STATUS.CLOSED) return 'error';
+ if (s === TENDER_STATUS.AWARDED) return 'finish';
+ return 'process';
+});
+
+const materialTotalAmount = computed(() =>
+ materialList.value.reduce((sum, m) => sum + (m.estimatedPrice ?? 0) * (m.quantity ?? 0), 0),
+);
+const bidCount = computed(() => bidList.value.length);
+const bidTotalAmount = computed(() =>
+ bidList.value.reduce((sum, b) => sum + (b.bidTotalAmount ?? 0), 0),
+);
+const evalCount = computed(() => evaluationList.value.length);
+const rankedCount = computed(() => evaluationList.value.filter((e) => e.rank != null).length);
+
+const selectedBid = computed(() =>
+ bidList.value.find((b) => b.id === selectedBidId.value),
+);
+
+const materialColumns = [
+ { title: '鐗╂枡缂栫爜', dataIndex: 'productCode', width: 120 },
+ { title: '鐗╂枡鍚嶇О', dataIndex: 'productName', width: 150 },
+ { title: '瑙勬牸鍨嬪彿', dataIndex: 'productSpec', width: 120 },
+ { title: '鍗曚綅', dataIndex: 'unit', width: 80 },
+ { title: '鏁伴噺', dataIndex: 'quantity', width: 80 },
+ { title: '鎶�鏈姹�', dataIndex: 'techRequirement', width: 150 },
+ { title: '棰勪及鍗曚环', dataIndex: 'estimatedPrice', width: 100 },
+ { title: '鎿嶄綔', key: 'actions', width: 150 },
+];
+
+const bidColumns = [
+ { title: '鎶曟爣缂栧彿', dataIndex: 'bidNo', width: 140 },
+ { title: '渚涘簲鍟�', dataIndex: 'supplierName', width: 200 },
+ { title: '鎶曟爣閲戦', dataIndex: 'bidTotalAmount', width: 120 },
+ { title: '鎶曟爣鏃堕棿', dataIndex: 'bidTime', width: 160 },
+ { title: '鐘舵��', dataIndex: 'bidStatus', width: 100, customRender: ({ text }: any) => BID_STATUS_MAP[text] || text },
+ { title: '鎿嶄綔', key: 'actions', width: 180 },
+];
+
+const quoteColumns = [
+ { title: '鐗╂枡', dataIndex: 'materialName', width: 120 },
+ { title: '鎶ヤ环浠锋牸', dataIndex: 'quotePrice', width: 100 },
+ { title: '绋庣巼(%)', dataIndex: 'taxRate', width: 80 },
+ { title: '浜ゆ湡(澶�)', dataIndex: 'deliveryCycle', width: 80 },
+ { title: '浠樻鏉′欢', dataIndex: 'paymentTerms', width: 120 },
+ { title: '璐ㄤ繚鏈�(鏈�)', dataIndex: 'warrantyPeriod', width: 80 },
+ { title: '澶囨敞', dataIndex: 'remark', width: 150 },
+ { title: '鎿嶄綔', key: 'actions', width: 120 },
+];
+
+const evalColumns = [
+ { title: '鎶曟爣ID', dataIndex: 'bidId', width: 80 },
+ { title: '渚涘簲鍟�', dataIndex: 'supplierName', width: 150 },
+ { title: '浠锋牸璇勫垎', dataIndex: 'priceScore', width: 100 },
+ { title: '鎶�鏈瘎鍒�', dataIndex: 'techScore', width: 100 },
+ { title: '浜や粯璇勫垎', dataIndex: 'deliveryScore', width: 100 },
+ { title: '鏈嶅姟璇勫垎', dataIndex: 'serviceScore', width: 100 },
+ { title: '缁煎悎璇勫垎', dataIndex: 'compositeScore', width: 100 },
+ { title: '鎺掑悕', dataIndex: 'rank', width: 80 },
+ { title: '璇勫', dataIndex: 'evaluatorName', width: 100 },
+ { title: '璇勮', dataIndex: 'evaluationOpinion', width: 150 },
+ { title: '鎿嶄綔', key: 'actions', width: 120 },
+];
+</script>
+
+<template>
+ <Page auto-content-height>
+ <!-- 鍔犺浇涓� -->
+ <a-skeleton v-if="!project" active class="p-4" />
+ <template v-else>
+ <!-- 椤堕儴鏍囬鏍� -->
+ <div class="flex items-center gap-2 px-4 pt-3 pb-2">
+ <a-button type="text" size="small" @click="router.back()">
+ <template #icon><IconifyIcon icon="ant-design:arrow-left-outlined" /></template>
+ </a-button>
+ <span class="text-lg font-bold truncate max-w-md">{{ project.tenderName }}</span>
+ <span class="text-gray-400 text-xs">{{ project.tenderNo }}</span>
+ <a-tag :color="statusColor">{{ statusLabel }}</a-tag>
+ <a-tag color="blue">{{ typeLabel }}</a-tag>
+ <div class="flex-1" />
+ <a-button size="small" @click="loadProject(); loadMaterials(); loadBids(); loadEvaluations(); loadAward();">
+ <template #icon><IconifyIcon icon="ant-design:reload-outlined" /></template>
+ 鍒锋柊
+ </a-button>
+ </div>
+
+ <!-- 宸ヤ綔娴佽繘搴� + 鍏抽敭淇℃伅 -->
+ <div class="px-4 mb-3">
+ <a-card size="small">
+ <!-- 娴佺▼姝ラ -->
+ <a-steps :current="currentStep" :status="stepStatus" size="small" class="mb-3">
+ <a-step v-for="item in stepItems" :key="item.status" :title="item.title" />
+ </a-steps>
+
+ <!-- 鍏抽敭瀛楁 -->
+ <div class="grid grid-cols-6 gap-3 text-xs">
+ <div>
+ <div class="text-gray-400 mb-0.5">棰勭畻閲戦</div>
+ <div class="font-semibold text-blue-500">{{ project.budgetAmount?.toLocaleString() ?? '-' }} 鍏�</div>
+ </div>
+ <div>
+ <div class="text-gray-400 mb-0.5">璐熻矗浜�</div>
+ <div class="font-semibold">{{ project.projectLeaderName || '-' }}</div>
+ </div>
+ <div>
+ <div class="text-gray-400 mb-0.5">閲囪喘缁勭粐</div>
+ <div class="font-semibold">{{ project.purchaseOrg || '-' }}</div>
+ </div>
+ <div>
+ <div class="text-gray-400 mb-0.5">寮�鏍囨椂闂�</div>
+ <div class="font-semibold">{{ project.bidOpenTime || '寰呭畾' }}</div>
+ </div>
+ <div>
+ <div class="text-gray-400 mb-0.5">寮�濮嬫椂闂�</div>
+ <div class="font-semibold">{{ project.startTime || '-' }}</div>
+ </div>
+ <div>
+ <div class="text-gray-400 mb-0.5">鎴鏃堕棿</div>
+ <div class="font-semibold">{{ project.endTime || '-' }}</div>
+ </div>
+ </div>
+
+ <!-- 鎷涙爣璇存槑 / 璧勮川瑕佹眰 -->
+ <template v-if="project.tenderDesc || project.qualificationRequirements">
+ <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>
+ </a-collapse>
+ </template>
+ </a-card>
+ </div>
+
+ <!-- Tab 椤电 -->
+ <div class="px-4 pb-4">
+ <a-card size="small">
+ <a-tabs v-model:activeKey="activeTab" size="small">
+ <!-- Tab1: 鐗╂枡娓呭崟 -->
+ <a-tab-pane key="info">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:unordered-list-outlined" />鐗╂枡娓呭崟</a-space>
+ </template>
+ <div class="flex items-center justify-between mb-3">
+ <a-space>
+ <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ materialList.length }}</b> 椤癸紝棰勪及鎬婚噾棰� <b class="text-blue-500">{{ materialTotalAmount.toLocaleString() }}</b> 鍏�</span>
+ </a-space>
+ <a-button type="primary" size="small" @click="handleAddMaterial">
+ <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+ 娣诲姞鐗╂枡
+ </a-button>
+ </div>
+ <a-table :columns="materialColumns" :data-source="materialList" :loading="materialLoading" :pagination="false" row-key="id" size="small">
+ <template #bodyCell="{ column, record }">
+ <template v-if="column.dataIndex === 'estimatedPrice'">
+ {{ record.estimatedPrice ? record.estimatedPrice.toLocaleString() : '-' }}
+ </template>
+ <template v-if="column.key === 'actions'">
+ <a-space>
+ <a-button size="small" type="link" @click="handleEditMaterial(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+ <a-popconfirm title="纭鍒犻櫎璇ョ墿鏂欙紵" @confirm="handleDeleteMaterial(record.id)">
+ <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:delete-outlined" />鍒犻櫎</a-button>
+ </a-popconfirm>
+ </a-space>
+ </template>
+ </template>
+ </a-table>
+ <a-empty v-if="!materialLoading && materialList.length === 0" description="鏆傛棤鐗╂枡" class="mt-8" />
+ </a-tab-pane>
+
+ <!-- Tab2: 鎶曟爣绠$悊 -->
+ <a-tab-pane key="bid">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:team-outlined" />鎶曟爣绠$悊</a-space>
+ </template>
+ <div class="flex items-center justify-between mb-3">
+ <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ bidCount }}</b> 涓姇鏍�</span>
+ <a-button type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true">
+ <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+ 鏂板鎶曟爣
+ </a-button>
+ </div>
+ <a-table :columns="bidColumns" :data-source="bidList" :loading="bidLoading" :pagination="false" row-key="id" size="small">
+ <template #bodyCell="{ column, record }">
+ <template v-if="column.dataIndex === 'bidTotalAmount'">
+ <span class="font-medium">{{ record.bidTotalAmount?.toLocaleString() }}</span>
+ </template>
+ <template v-if="column.dataIndex === 'bidStatus'">
+ <a-tag :color="record.bidStatus === BID_STATUS.WIN ? 'green' : record.bidStatus === BID_STATUS.LOSE ? 'red' : 'default'">
+ {{ BID_STATUS_MAP[record.bidStatus] }}
+ </a-tag>
+ </template>
+ <template v-if="column.key === 'actions'">
+ <a-space>
+ <a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />鎶ヤ环</a-button>
+ <a-popconfirm v-if="record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
+ <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />鎾ゆ爣</a-button>
+ </a-popconfirm>
+ </a-space>
+ </template>
+ </template>
+ </a-table>
+ <a-empty v-if="!bidLoading && bidList.length === 0" description="鏆傛棤鎶曟爣璁板綍" class="mt-8" />
+ </a-tab-pane>
+
+ <!-- Tab3: 鎶ヤ环绠$悊 -->
+ <a-tab-pane key="quote">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:dollar-outlined" />鎶ヤ环绠$悊</a-space>
+ </template>
+ <template v-if="selectedBidId && selectedBid">
+ <div class="flex items-center gap-3 mb-3 px-3 py-2 bg-gray-50 rounded text-xs">
+ <span class="text-gray-400">鎶曟爣锛�<b class="text-gray-700">{{ selectedBid.bidNo }}</b></span>
+ <a-divider type="vertical" />
+ <span class="text-gray-400">渚涘簲鍟嗭細<b class="text-gray-700">{{ selectedBid.supplierName }}</b></span>
+ <a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">鍒囨崲鎶曟爣</a-button>
+ </div>
+ <div class="text-right mb-3">
+ <a-button v-if="selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
+ <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+ 鏂板鎶ヤ环
+ </a-button>
+ </div>
+ <a-table :columns="quoteColumns" :data-source="quoteList" :loading="quoteLoading" :pagination="false" row-key="id" size="small">
+ <template #bodyCell="{ column, record }">
+ <template v-if="column.dataIndex === 'quotePrice'">
+ <span class="font-medium text-blue-600">{{ record.quotePrice?.toLocaleString() }}</span>
+ </template>
+ <template v-if="column.dataIndex === 'taxRate'">
+ {{ record.taxRate != null ? `${record.taxRate}%` : '-' }}
+ </template>
+ <template v-if="column.dataIndex === 'deliveryCycle'">
+ {{ record.deliveryCycle != null ? `${record.deliveryCycle} 澶ー : '-' }}
+ </template>
+ <template v-if="column.dataIndex === 'warrantyPeriod'">
+ {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 涓湀` : '-' }}
+ </template>
+ <template v-if="column.key === 'actions'">
+ <a-button v-if="selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+ </template>
+ </template>
+ </a-table>
+ <a-empty v-if="!quoteLoading && quoteList.length === 0" description="鏆傛棤鎶ヤ环鏄庣粏" class="mt-8" />
+ </template>
+ <a-empty v-else description="璇峰厛鍦ㄣ�屾姇鏍囩鐞嗐�嶄腑閫夋嫨涓�涓姇鏍囨煡鐪嬫姤浠�">
+ <a-button type="primary" @click="activeTab = 'bid'">鍓嶅線鎶曟爣绠$悊</a-button>
+ </a-empty>
+ </a-tab-pane>
+
+ <!-- Tab4: 寮�鏍� -->
+ <a-tab-pane key="bidOpen">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:unlock-outlined" />寮�鏍�</a-space>
+ </template>
+ <template v-if="bidOpen">
+ <a-result status="success" title="宸插紑鏍�">
+ <template #sub-title>
+ <a-descriptions :column="2" size="small" class="mt-4">
+ <a-descriptions-item label="寮�鏍囩紪鍙�">{{ bidOpen.id }}</a-descriptions-item>
+ <a-descriptions-item label="寮�鏍囨椂闂�">{{ bidOpen.openTime }}</a-descriptions-item>
+ </a-descriptions>
+ </template>
+ </a-result>
+ </template>
+ <template v-else>
+ <div class="text-center py-8">
+ <a-result status="info" title="灏氭湭寮�鏍�" sub-title="鎵ц寮�鏍囧悗椤圭洰杩涘叆鎶曟爣闃舵锛屼緵搴斿晢鍙寮忔姇鏍�">
+ <template #extra>
+ <a-button type="primary" @click="handleCreateBidOpen">
+ <template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template>
+ 鎵ц寮�鏍�
+ </a-button>
+ </template>
+ </a-result>
+ </div>
+ </template>
+ </a-tab-pane>
+
+ <!-- Tab5: 璇勬爣 -->
+ <a-tab-pane key="evaluation">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:trophy-outlined" />璇勬爣</a-space>
+ </template>
+ <div class="flex items-center justify-between mb-3">
+ <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ evalCount }}</b> 鏉★紝宸叉帓鍚� <b class="text-green-500">{{ rankedCount }}</b> 鏉�</span>
+ <a-space>
+ <a-button size="small" @click="handleCalculateRanking">
+ <template #icon><IconifyIcon icon="ant-design:bar-chart-outlined" /></template>
+ 璁$畻鎺掑悕
+ </a-button>
+ <a-button type="primary" size="small" @click="evalForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0 }; evalFormVisible = true">
+ <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+ 鏂板璇勬爣
+ </a-button>
+ </a-space>
+ </div>
+ <a-table :columns="evalColumns" :data-source="evaluationList" :loading="evalLoading" :pagination="false" row-key="id" size="small">
+ <template #bodyCell="{ column, record }">
+ <template v-if="['priceScore', 'techScore', 'deliveryScore', 'serviceScore'].includes(column.dataIndex)">
+ <div class="flex items-center gap-2">
+ <a-progress :percent="record[column.dataIndex] || 0" :show-info="false" size="small"
+ :stroke-color="record[column.dataIndex] >= 80 ? '#52c41a' : record[column.dataIndex] >= 60 ? '#faad14' : '#ff4d4f'"
+ class="flex-1" style="min-width: 50px;" />
+ <span class="text-xs w-8 text-right">{{ record[column.dataIndex] ?? '-' }}</span>
+ </div>
+ </template>
+ <template v-if="column.dataIndex === 'compositeScore'">
+ <span class="font-bold" :class="record.compositeScore >= 80 ? 'text-green-500' : record.compositeScore >= 60 ? 'text-orange-500' : 'text-red-500'">
+ {{ record.compositeScore ?? '-' }}
+ </span>
+ </template>
+ <template v-if="column.dataIndex === 'rank'">
+ <a-tag v-if="record.rank === 1" color="gold"><IconifyIcon icon="ant-design:trophy-outlined" /> 绗�1鍚�</a-tag>
+ <a-tag v-else-if="record.rank" color="default">绗瑊{ record.rank }}鍚�</a-tag>
+ <span v-else class="text-gray-300">-</span>
+ </template>
+ <template v-if="column.key === 'actions'">
+ <a-button size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+ </template>
+ </template>
+ </a-table>
+ <a-empty v-if="!evalLoading && evaluationList.length === 0" description="鏆傛棤璇勬爣璁板綍" class="mt-8" />
+ </a-tab-pane>
+
+ <!-- Tab6: 瀹氭爣 -->
+ <a-tab-pane key="award">
+ <template #tab>
+ <a-space :size="4"><IconifyIcon icon="ant-design:check-circle-outlined" />瀹氭爣</a-space>
+ </template>
+ <template v-if="award">
+ <a-row :gutter="16">
+ <a-col :span="16">
+ <a-descriptions :column="2" bordered size="small">
+ <a-descriptions-item label="瀹氭爣缂栧彿">
+ <span class="font-mono">{{ award.awardNo }}</span>
+ </a-descriptions-item>
+ <a-descriptions-item label="涓爣渚涘簲鍟�">
+ <a-tag color="blue">{{ award.supplierName || award.supplierId }}</a-tag>
+ </a-descriptions-item>
+ <a-descriptions-item label="瀹氭爣閲戦">
+ <span class="font-bold text-blue-600">{{ award.awardAmount?.toLocaleString() }}</span> 鍏�
+ </a-descriptions-item>
+ <a-descriptions-item label="瀹氭爣鏃堕棿">{{ award.awardTime || '-' }}</a-descriptions-item>
+ <a-descriptions-item label="鐘舵��">
+ <a-tag :color="award.awardStatus === AWARD_STATUS.AWARDED ? 'green' : 'orange'">{{ AWARD_STATUS_MAP[award.awardStatus] }}</a-tag>
+ </a-descriptions-item>
+ <a-descriptions-item label="閲囪喘璁㈠崟">{{ award.purchaseOrderId || '寰呯敓鎴�' }}</a-descriptions-item>
+ <a-descriptions-item label="澶囨敞" :span="2">{{ award.remark || '-' }}</a-descriptions-item>
+ </a-descriptions>
+ </a-col>
+ <a-col :span="8" class="flex items-center justify-center">
+ <template v-if="award.awardStatus === AWARD_STATUS.ORDER_GENERATED">
+ <a-result status="success" title="璁㈠崟宸茬敓鎴�" class="p-0">
+ <template #sub-title>
+ <span class="text-sm">閲囪喘璁㈠崟 ID: {{ award.purchaseOrderId }}</span>
+ </template>
+ </a-result>
+ </template>
+ <template v-else-if="award.awardStatus === AWARD_STATUS.AWARDED">
+ <div class="text-center">
+ <a-result status="success" title="瀹氭爣瀹屾垚" class="p-0 mb-3" />
+ <a-button type="primary" block @click="handleGeneratePurchaseOrder">
+ <template #icon><IconifyIcon icon="ant-design:shopping-cart-outlined" /></template>
+ 鐢熸垚閲囪喘璁㈠崟
+ </a-button>
+ </div>
+ </template>
+ <template v-else>
+ <div class="text-center">
+ <div class="text-gray-500 text-sm mb-3">瀹氭爣淇℃伅宸插垱寤猴紝璇峰鎵�</div>
+ <a-button type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;">
+ <template #icon><IconifyIcon icon="ant-design:check-outlined" /></template>
+ 瀹℃壒閫氳繃
+ </a-button>
+ </div>
+ </template>
+ </a-col>
+ </a-row>
+ </template>
+ <template v-else>
+ <div class="text-center py-8">
+ <a-result status="info" title="灏氭湭瀹氭爣" sub-title="鍩轰簬璇勬爣鎺掑悕閫夋嫨涓爣渚涘簲鍟嗗苟鍒涘缓瀹氭爣">
+ <template #extra>
+ <a-button type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true">
+ <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+ 鍒涘缓瀹氭爣
+ </a-button>
+ </template>
+ </a-result>
+ </div>
+ </template>
+ </a-tab-pane>
+ </a-tabs>
+ </a-card>
+ </div>
+
+ <!-- ========== 寮圭獥 ========== -->
+
+ <!-- 鐗╂枡琛ㄥ崟 -->
+ <a-modal v-model:open="materialFormVisible" :title="materialForm.id ? '缂栬緫鐗╂枡' : '娣诲姞鐗╂枡'" :width="560" @ok="handleSaveMaterial">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="MDM鐗╂枡" required>
+ <a-select v-model:value="materialForm.productId" show-search placeholder="鎼滅储骞堕�夋嫨MDM鐗╂枡" :filter-option="filterOption" option-label-prop="label" @change="onMaterialSelect">
+ <a-select-option v-for="item in mdmItemList" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`">
+ <div class="flex flex-col">
+ <span class="font-medium">{{ item.code }}</span>
+ <span class="text-xs text-gray-400">{{ item.name }}{{ item.specification ? ` 路 ${item.specification}` : '' }}</span>
+ </div>
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-row :gutter="12">
+ <a-col :span="12">
+ <a-form-item label="鐗╂枡鍚嶇О" required><a-input v-model:value="materialForm.productName" placeholder="鑷姩甯﹀叆" /></a-form-item>
+ </a-col>
+ <a-col :span="12">
+ <a-form-item label="瑙勬牸鍨嬪彿"><a-input v-model:value="materialForm.productSpec" placeholder="鑷姩甯﹀叆" /></a-form-item>
+ </a-col>
+ </a-row>
+ <a-row :gutter="12">
+ <a-col :span="8">
+ <a-form-item label="鍗曚綅"><a-input v-model:value="materialForm.unit" placeholder="鑷姩甯﹀叆" /></a-form-item>
+ </a-col>
+ <a-col :span="8">
+ <a-form-item label="鏁伴噺" required><a-input-number v-model:value="materialForm.quantity" :min="0" class="!w-full" placeholder="鏁伴噺" /></a-form-item>
+ </a-col>
+ <a-col :span="8">
+ <a-form-item label="棰勪及鍗曚环"><a-input-number v-model:value="materialForm.estimatedPrice" :min="0" class="!w-full" placeholder="鍗曚环" /></a-form-item>
+ </a-col>
+ </a-row>
+ <a-form-item label="鎶�鏈姹�">
+ <a-textarea v-model:value="materialForm.techRequirement" placeholder="濡傦細绗﹀悎GB/T鏍囧噯" :rows="2" />
+ </a-form-item>
+ </a-form>
+ </a-modal>
+
+ <!-- 鎶曟爣琛ㄥ崟 -->
+ <a-modal v-model:open="bidFormVisible" title="鏂板鎶曟爣" :width="460" @ok="handleCreateBid">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="鎶曟爣缂栧彿" required><a-input v-model:value="bidForm.bidNo" placeholder="濡� BID2024010001" /></a-form-item>
+ <a-form-item label="渚涘簲鍟�" required>
+ <a-select v-model:value="bidForm.supplierId" show-search placeholder="鎼滅储骞堕�夋嫨渚涘簲鍟�" :filter-option="filterOption">
+ <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-form-item label="鎶曟爣鎬婚噾棰�">
+ <a-input-number v-model:value="bidForm.bidTotalAmount" :min="0" class="!w-full" placeholder="鎶曟爣鎬婚噾棰�"><template #addonAfter>鍏�</template></a-input-number>
+ </a-form-item>
+ </a-form>
+ </a-modal>
+
+ <!-- 鎶ヤ环琛ㄥ崟 -->
+ <a-modal v-model:open="quoteFormVisible" :title="quoteForm.id ? '缂栬緫鎶ヤ环' : '鏂板鎶ヤ环'" :width="560" @ok="handleSaveQuote">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="鎷涙爣鐗╂枡" required>
+ <a-select v-model:value="quoteForm.tenderMaterialId" show-search placeholder="鎼滅储骞堕�夋嫨鎷涙爣鐗╂枡" :filter-option="filterOption">
+ <a-select-option v-for="m in availableMaterials" :key="m.id" :value="m.id" :label="m.productName">{{ m.productCode }} - {{ m.productName }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-row :gutter="12">
+ <a-col :span="8">
+ <a-form-item label="鎶ヤ环浠锋牸" required><a-input-number v-model:value="quoteForm.quotePrice" :min="0" class="!w-full" placeholder="鎶ヤ环"><template #addonAfter>鍏�</template></a-input-number></a-form-item>
+ </a-col>
+ <a-col :span="8">
+ <a-form-item label="绋庣巼(%)"><a-input-number v-model:value="quoteForm.taxRate" :min="0" :max="100" class="!w-full" placeholder="濡� 13" /></a-form-item>
+ </a-col>
+ <a-col :span="8">
+ <a-form-item label="浜ゆ湡(澶�)"><a-input-number v-model:value="quoteForm.deliveryCycle" :min="0" class="!w-full" placeholder="澶╂暟" /></a-form-item>
+ </a-col>
+ </a-row>
+ <a-row :gutter="12">
+ <a-col :span="12">
+ <a-form-item label="浠樻鏉′欢"><a-input v-model:value="quoteForm.paymentTerms" placeholder="濡傦細璐у埌30澶╀粯娆�" /></a-form-item>
+ </a-col>
+ <a-col :span="12">
+ <a-form-item label="璐ㄤ繚鏈�(鏈�)"><a-input-number v-model:value="quoteForm.warrantyPeriod" :min="0" class="!w-full" placeholder="鏈堟暟" /></a-form-item>
+ </a-col>
+ </a-row>
+ <a-form-item label="澶囨敞"><a-textarea v-model:value="quoteForm.remark" placeholder="濡傚惈杩愯垂" :rows="2" /></a-form-item>
+ </a-form>
+ </a-modal>
+
+ <!-- 璇勬爣琛ㄥ崟 -->
+ <a-modal v-model:open="evalFormVisible" :title="evalForm.id ? '缂栬緫璇勬爣' : '鏂板璇勬爣'" :width="600" @ok="handleCreateEvaluation">
+ <a-form layout="vertical" class="mt-4">
+ <a-row :gutter="12">
+ <a-col :span="12">
+ <a-form-item label="鎶曟爣" required>
+ <a-select v-model:value="evalForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption"
+ @change="(val: number) => { const b = bidList.find(i => i.id === val); if (b) { evalForm.supplierId = b.supplierId; evalForm.supplierName = b.supplierName; } }">
+ <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ </a-col>
+ <a-col :span="12">
+ <a-form-item label="渚涘簲鍟�">
+ <a-select v-model:value="evalForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption">
+ <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ </a-col>
+ </a-row>
+ <a-divider class="!my-2">璇勫垎椤癸紙鐧惧垎鍒讹級</a-divider>
+ <a-row :gutter="12">
+ <a-col :span="12"><a-form-item label="浠锋牸璇勫垎"><a-input-number v-model:value="evalForm.priceScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col>
+ <a-col :span="12"><a-form-item label="鎶�鏈瘎鍒�"><a-input-number v-model:value="evalForm.techScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col>
+ <a-col :span="12"><a-form-item label="浜や粯璇勫垎"><a-input-number v-model:value="evalForm.deliveryScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col>
+ <a-col :span="12"><a-form-item label="鏈嶅姟璇勫垎"><a-input-number v-model:value="evalForm.serviceScore" :min="0" :max="100" class="!w-full" placeholder="0-100" /></a-form-item></a-col>
+ </a-row>
+ <a-divider class="!my-2" />
+ <a-row :gutter="12">
+ <a-col :span="12"><a-form-item label="璇勫"><a-input v-model:value="evalForm.evaluatorName" placeholder="璇勫濮撳悕" /></a-form-item></a-col>
+ </a-row>
+ <a-form-item label="璇勬爣鎰忚"><a-textarea v-model:value="evalForm.evaluationOpinion" placeholder="渚涘簲鍟嗙殑鎶�鏈柟妗堛�佷氦浠樿兘鍔涚瓑缁煎悎璇勪环" :rows="3" /></a-form-item>
+ </a-form>
+ </a-modal>
+
+ <!-- 瀹氭爣琛ㄥ崟 -->
+ <a-modal v-model:open="awardFormVisible" title="鍒涘缓瀹氭爣" :width="500" @ok="handleCreateAward">
+ <a-form layout="vertical" class="mt-4">
+ <a-form-item label="瀹氭爣缂栧彿" required><a-input v-model:value="awardForm.awardNo" placeholder="濡� AWARD2024010001" /></a-form-item>
+ <a-row :gutter="12">
+ <a-col :span="12">
+ <a-form-item label="渚涘簲鍟�" required>
+ <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption">
+ <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ </a-col>
+ <a-col :span="12">
+ <a-form-item label="鎶曟爣" required>
+ <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption">
+ <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
+ </a-select>
+ </a-form-item>
+ </a-col>
+ </a-row>
+ <a-form-item label="瀹氭爣閲戦" required>
+ <a-input-number v-model:value="awardForm.awardAmount" :min="0" class="!w-full" placeholder="瀹氭爣閲戦"><template #addonAfter>鍏�</template></a-input-number>
+ </a-form-item>
+ <a-form-item label="澶囨敞"><a-textarea v-model:value="awardForm.remark" placeholder="濡傦細缁煎悎璇勬爣绗竴鍚�" :rows="2" /></a-form-item>
+ </a-form>
+ </a-modal>
+ </template>
+ </Page>
+</template>
diff --git a/src/views/srm/tender/index.vue b/src/views/srm/tender/index.vue
new file mode 100644
index 0000000..f966d8f
--- /dev/null
+++ b/src/views/srm/tender/index.vue
@@ -0,0 +1,223 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { SrmTenderApi } from '#/api/srm/tender';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+import { useRouter } from 'vue-router';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ closeTender,
+ deleteTenderProject,
+ generatePurchaseOrder,
+ getTenderProjectPage,
+ publishTender,
+ startBidding,
+ startEvaluation,
+} from '#/api/srm/tender';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import TenderForm from './modules/form.vue';
+import { TENDER_STATUS } from '../enums';
+
+defineOptions({ name: 'SrmTender' });
+
+const router = useRouter();
+
+function handleDetail(row: SrmTenderApi.TenderProjectVO) {
+ router.push({ path: '/srm/tender/detail', query: { id: row.id } });
+}
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+function handleEdit(row: SrmTenderApi.TenderProjectVO) {
+ formModalApi.setData(row).open();
+}
+
+async function handleDelete(row: SrmTenderApi.TenderProjectVO) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.tenderName]),
+ duration: 0,
+ });
+ try {
+ await deleteTenderProject(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.tenderName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+async function handlePublish(row: SrmTenderApi.TenderProjectVO) {
+ await publishTender(row.id!);
+ message.success('鍙戝竷鎴愬姛');
+ handleRefresh();
+}
+
+async function handleStartBidding(row: SrmTenderApi.TenderProjectVO) {
+ await startBidding(row.id!);
+ message.success('宸插紑濮嬫姇鏍�');
+ handleRefresh();
+}
+
+async function handleStartEvaluation(row: SrmTenderApi.TenderProjectVO) {
+ await startEvaluation(row.id!);
+ message.success('宸插紑濮嬭瘎鏍�');
+ handleRefresh();
+}
+
+async function handleClose(row: SrmTenderApi.TenderProjectVO) {
+ await closeTender(row.id!);
+ message.success('宸插叧闂�');
+ handleRefresh();
+}
+
+async function handleGeneratePurchaseOrder(row: SrmTenderApi.TenderProjectVO) {
+ if (!row.awardId) return;
+ await generatePurchaseOrder(row.awardId);
+ message.success('閲囪喘璁㈠崟宸茬敓鎴�');
+ handleRefresh();
+}
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: TenderForm,
+ destroyOnClose: true,
+});
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getTenderProjectPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<SrmTenderApi.TenderProjectVO>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="鎷涙爣椤圭洰鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鎷涙爣椤圭洰']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['srm:tender-project:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '璇︽儏',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ onClick: handleDetail.bind(null, row),
+ },
+ {
+ label: '鍙戝竷',
+ type: 'link',
+ auth: ['srm:tender-project:update'],
+ ifShow: row.tenderStatus === TENDER_STATUS.DRAFT,
+ popConfirm: {
+ title: '纭瑕佸彂甯冭椤圭洰鍚楋紵鍙戝竷鍚庢棤娉曚慨鏀广��',
+ confirm: handlePublish.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['srm:tender-project:update'],
+ ifShow: row.tenderStatus === TENDER_STATUS.DRAFT,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['srm:tender-project:delete'],
+ ifShow: row.tenderStatus === TENDER_STATUS.DRAFT,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.tenderName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '寮�濮嬫姇鏍�',
+ type: 'link',
+ auth: ['srm:tender-project:update'],
+ ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED,
+ popConfirm: {
+ title: '纭瑕佸紑濮嬫姇鏍囧悧锛�',
+ confirm: handleStartBidding.bind(null, row),
+ },
+ },
+ {
+ label: '鍏抽棴',
+ type: 'link',
+ danger: true,
+ auth: ['srm:tender-project:update'],
+ ifShow: row.tenderStatus === TENDER_STATUS.PUBLISHED || row.tenderStatus === TENDER_STATUS.BIDDING,
+ popConfirm: {
+ title: '纭瑕佸叧闂椤圭洰鍚楋紵',
+ confirm: handleClose.bind(null, row),
+ },
+ },
+ {
+ label: '寮�濮嬭瘎鏍�',
+ type: 'link',
+ auth: ['srm:tender-project:update'],
+ ifShow: row.tenderStatus === TENDER_STATUS.BIDDING,
+ popConfirm: {
+ title: '纭瑕佸紑濮嬭瘎鏍囧悧锛�',
+ confirm: handleStartEvaluation.bind(null, row),
+ },
+ },
+ {
+ label: '鐢熸垚閲囪喘璁㈠崟',
+ type: 'link',
+ auth: ['srm:bid-award:update'],
+ ifShow: !!row.awardId && !row.purchaseOrderNo,
+ popConfirm: {
+ title: '纭瑕佺敓鎴愰噰璐鍗曞悧锛�',
+ confirm: handleGeneratePurchaseOrder.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/srm/tender/modules/form.vue b/src/views/srm/tender/modules/form.vue
new file mode 100644
index 0000000..49520dd
--- /dev/null
+++ b/src/views/srm/tender/modules/form.vue
@@ -0,0 +1,84 @@
+<script lang="ts" setup>
+import type { SrmTenderApi } from '#/api/srm/tender';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createTenderProject,
+ getTenderProject,
+ updateTenderProject,
+} from '#/api/srm/tender';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<SrmTenderApi.TenderProjectVO>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鎷涙爣椤圭洰'])
+ : $t('ui.actionTitle.create', ['鎷涙爣椤圭洰']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as SrmTenderApi.TenderProjectVO;
+ try {
+ await (formData.value?.id
+ ? updateTenderProject(data)
+ : createTenderProject(data));
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<SrmTenderApi.TenderProjectVO>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getTenderProject(data.id);
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({ modalApi });
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/system/tenant/data.ts b/src/views/system/tenant/data.ts
index b05e638..7cb9049 100644
--- a/src/views/system/tenant/data.ts
+++ b/src/views/system/tenant/data.ts
@@ -102,7 +102,7 @@
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨杩囨湡鏃堕棿',
},
rules: 'required',
diff --git a/src/views/wls/arrivalnotice/data.ts b/src/views/wls/arrivalnotice/data.ts
index 812b6dd..4fd3a0b 100644
--- a/src/views/wls/arrivalnotice/data.ts
+++ b/src/views/wls/arrivalnotice/data.ts
@@ -125,7 +125,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍒拌揣鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/itemreceipt/data.ts b/src/views/wls/itemreceipt/data.ts
index 4f72501..291714f 100644
--- a/src/views/wls/itemreceipt/data.ts
+++ b/src/views/wls/itemreceipt/data.ts
@@ -101,7 +101,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -385,7 +385,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
@@ -396,7 +396,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鏈夋晥鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
diff --git a/src/views/wls/miscissue/data.ts b/src/views/wls/miscissue/data.ts
index a61e01b..5018e10 100644
--- a/src/views/wls/miscissue/data.ts
+++ b/src/views/wls/miscissue/data.ts
@@ -101,7 +101,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/miscreceipt/data.ts b/src/views/wls/miscreceipt/data.ts
index 25bc48e..6614440 100644
--- a/src/views/wls/miscreceipt/data.ts
+++ b/src/views/wls/miscreceipt/data.ts
@@ -116,7 +116,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/outsourceissue/data.ts b/src/views/wls/outsourceissue/data.ts
index ddd8661..dd70166 100644
--- a/src/views/wls/outsourceissue/data.ts
+++ b/src/views/wls/outsourceissue/data.ts
@@ -100,7 +100,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍙戞枡鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
diff --git a/src/views/wls/outsourcereceipt/data.ts b/src/views/wls/outsourcereceipt/data.ts
index bc03596..4d336b4 100644
--- a/src/views/wls/outsourcereceipt/data.ts
+++ b/src/views/wls/outsourcereceipt/data.ts
@@ -122,7 +122,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -363,7 +363,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
@@ -374,7 +374,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鏈夋晥鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
diff --git a/src/views/wls/packages/data.ts b/src/views/wls/packages/data.ts
index 3c0b502..14358b2 100644
--- a/src/views/wls/packages/data.ts
+++ b/src/views/wls/packages/data.ts
@@ -78,7 +78,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨瑁呯鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
@@ -455,7 +455,7 @@
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鏈夋晥鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
},
{
diff --git a/src/views/wls/productissue/data.ts b/src/views/wls/productissue/data.ts
index 28f37ed..2ee6dcc 100644
--- a/src/views/wls/productissue/data.ts
+++ b/src/views/wls/productissue/data.ts
@@ -101,7 +101,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/wls/productreceipt/data.ts b/src/views/wls/productreceipt/data.ts
index 431138e..07cc1bf 100644
--- a/src/views/wls/productreceipt/data.ts
+++ b/src/views/wls/productreceipt/data.ts
@@ -98,7 +98,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/productsales/data.ts b/src/views/wls/productsales/data.ts
index 31a2dec..9522d75 100644
--- a/src/views/wls/productsales/data.ts
+++ b/src/views/wls/productsales/data.ts
@@ -182,7 +182,7 @@
disabled: isHeaderReadonly(formType),
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/returnissue/data.ts b/src/views/wls/returnissue/data.ts
index d95b2c4..4ca6336 100644
--- a/src/views/wls/returnissue/data.ts
+++ b/src/views/wls/returnissue/data.ts
@@ -128,7 +128,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
diff --git a/src/views/wls/returnsales/data.ts b/src/views/wls/returnsales/data.ts
index 4c983da..fadbd4a 100644
--- a/src/views/wls/returnsales/data.ts
+++ b/src/views/wls/returnsales/data.ts
@@ -129,7 +129,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/returnvendor/data.ts b/src/views/wls/returnvendor/data.ts
index 5d2b324..1903c81 100644
--- a/src/views/wls/returnvendor/data.ts
+++ b/src/views/wls/returnvendor/data.ts
@@ -137,7 +137,7 @@
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
diff --git a/src/views/wls/salesnotice/data.ts b/src/views/wls/salesnotice/data.ts
index e295707..6818446 100644
--- a/src/views/wls/salesnotice/data.ts
+++ b/src/views/wls/salesnotice/data.ts
@@ -114,7 +114,7 @@
class: '!w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍙戣揣鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
rules: 'required',
},
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
index cd3059b..a8b2d91 100644
--- a/src/views/wls/stocktaking/plan/data.ts
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -91,7 +91,7 @@
componentProps: {
placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['type'],
@@ -105,7 +105,7 @@
componentProps: {
placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['type'],
diff --git a/src/views/wls/stocktaking/task/data.ts b/src/views/wls/stocktaking/task/data.ts
index 762de99..ed62e0c 100644
--- a/src/views/wls/stocktaking/task/data.ts
+++ b/src/views/wls/stocktaking/task/data.ts
@@ -134,7 +134,7 @@
componentProps: {
placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['type'],
@@ -148,7 +148,7 @@
componentProps: {
placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
showTime: true,
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
dependencies: {
triggerFields: ['type'],
diff --git a/src/views/wms/order/check/data.ts b/src/views/wms/order/check/data.ts
index 0efaec3..3e3ba5b 100644
--- a/src/views/wms/order/check/data.ts
+++ b/src/views/wms/order/check/data.ts
@@ -396,7 +396,7 @@
class: 'w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍗曟嵁鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
fieldName: 'orderTime',
label: '鍗曟嵁鏃ユ湡',
diff --git a/src/views/wms/order/movement/data.ts b/src/views/wms/order/movement/data.ts
index 2ae4ac6..4fcb2c2 100644
--- a/src/views/wms/order/movement/data.ts
+++ b/src/views/wms/order/movement/data.ts
@@ -319,7 +319,7 @@
class: 'w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍗曟嵁鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
fieldName: 'orderTime',
label: '鍗曟嵁鏃ユ湡',
diff --git a/src/views/wms/order/receipt/data.ts b/src/views/wms/order/receipt/data.ts
index eebdadb..e31e002 100644
--- a/src/views/wms/order/receipt/data.ts
+++ b/src/views/wms/order/receipt/data.ts
@@ -359,7 +359,7 @@
class: 'w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍗曟嵁鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
fieldName: 'orderTime',
label: '鍗曟嵁鏃ユ湡',
diff --git a/src/views/wms/order/shipment/data.ts b/src/views/wms/order/shipment/data.ts
index afcc3c9..7d41840 100644
--- a/src/views/wms/order/shipment/data.ts
+++ b/src/views/wms/order/shipment/data.ts
@@ -369,7 +369,7 @@
class: 'w-full',
format: 'YYYY-MM-DD',
placeholder: '璇烽�夋嫨鍗曟嵁鏃ユ湡',
- valueFormat: 'x',
+ valueFormat: 'YYYY-MM-DD',
},
fieldName: 'orderTime',
label: '鍗曟嵁鏃ユ湡',
--
Gitblit v1.9.3