From f10cf167372f2d8c4c0e14f42f361d7ab96d8347 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期五, 03 七月 2026 13:59:00 +0800
Subject: [PATCH] Merge branch 'dev_pro2.0' of http://114.132.189.42:9002/r/mom-pro2-before into dev_pro2.0
---
src/api/crm/saleTrace/index.ts | 57 +
src/packages/icons/src/offline-icons.ts | 15
src/views/basicData/mdm/unit/data.ts | 67 -
pnpm-lock.yaml | 3
src/api/crm/report/index.ts | 106 ++
src/api/crm/saleQuotation/index.ts | 109 ++
src/views/erp/purchase/supplier/data.ts | 11
src/views/crm/contract/index.vue | 136 ++
src/views/erp/purchase/request/modules/order-form.vue | 71 +
src/views/crm/contract/detail/index.vue | 11
src/preferences.ts | 4
src/views/crm/product/components/edit-table.vue | 58
src/views/crm/contract/detail/modules/info.vue | 27
src/views/crm/contract/modules/form.vue | 2
src/views/erp/purchase/request/modules/item-form.vue | 62
src/views/system/menu/data.ts | 2
src/views/crm/saleQuotation/modules/item-form.vue | 258 +++++
src/views/crm/saleQuotation/data.ts | 413 ++++++++
src/views/erp/sale/order/index.vue | 115 ++
src/components/table-action/table-action.vue | 8
src/components/table-action/typing.ts | 2
src/views/basicData/mdm/data.ts | 103 ++
src/views/crm/report/index.vue | 174 +++
src/views/crm/saleQuotation/index.vue | 210 ++++
src/views/crm/contract/data.ts | 84 +
src/views/crm/product/components/data.ts | 63
src/views/crm/saleQuotation/modules/form.vue | 124 ++
vite.config.ts | 3
src/components/crm-customer-select.vue | 75 +
src/views/erp/purchase/request/index.vue | 100 -
src/views/erp/purchase/request/data.ts | 102 +
src/views/erp/purchase/request/modules/form.vue | 13
src/views/erp/sale/return/modules/item-form.vue | 8
src/views/erp/sale/return/data.ts | 18
src/views/basicData/mdm/index.vue | 24
src/api/erp/purchase/request/index.ts | 51
src/api/mdm/item/index.ts | 9
src/views/erp/sale/out/modules/item-form.vue | 8
src/views/erp/sale/order/modules/item-form.vue | 20
src/api/crm/contract/index.ts | 41
src/views/crm/contract/detail/data.ts | 15
src/views/erp/purchase/request/modules/process-select-modal.vue | 78 +
src/views/basicData/mdm/modules/form.vue | 4
src/views/erp/sale/order/data.ts | 51
src/packages/icons/package.json | 1
src/views/mes/dv/subject/modules/form.vue | 2
src/api/erp/sale/order/index.ts | 17
src/views/erp/purchase/supplier/modules/form.vue | 2
src/views/erp/sale/out/data.ts | 24
49 files changed, 2,598 insertions(+), 363 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 54fadff..97d3ddd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1620,6 +1620,9 @@
src/packages/icons:
dependencies:
+ '@iconify/json':
+ specifier: ^2.2.483
+ version: 2.2.489
'@vben-core/icons':
specifier: workspace:*
version: link:../@core/base/icons
diff --git a/src/api/crm/contract/index.ts b/src/api/crm/contract/index.ts
index 92e9695..f245bb9 100644
--- a/src/api/crm/contract/index.ts
+++ b/src/api/crm/contract/index.ts
@@ -26,6 +26,7 @@
totalProductPrice: number;
discountPercent: number;
totalPrice: number;
+ depositPrice?: number; // 瀹氶噾閲戦锛屽崟浣嶏細鍏�
totalReceivablePrice: number;
signContactId: number;
signContactName?: string;
@@ -38,19 +39,31 @@
updateTime?: Date;
products?: ContractProduct[];
contactName?: string;
+ orderId?: number; // 鍏宠仈 ERP 閿�鍞鍗曠紪鍙�
+ orderNo?: string; // ERP 閿�鍞鍗曞崟鍙�
}
/** 鍚堝悓浜у搧淇℃伅 */
export interface ContractProduct {
id: number;
- productId: number;
- productName: string;
- productNo: string;
- productUnit: number;
- productPrice: number;
+ itemId: number;
+ itemName: string;
+ itemCode: string;
+ itemBarCode: string;
+ itemSpecification?: string;
+ itemUnitName: string;
+ itemUnitName2?: string;
+ itemUnitName3?: string;
+ itemPrice: number;
contractPrice: number;
count: number;
totalPrice: number;
+ }
+
+ /** 瀹℃壒娴佺▼ */
+ export interface ApproveProcess {
+ key: string;
+ name: string;
}
}
@@ -113,8 +126,17 @@
}
/** 鎻愪氦瀹℃牳 */
-export function submitContract(id: number) {
- return requestClient.put(`/crm/contract/submit?id=${id}`);
+export function submitContract(id: number, processDefinitionKey: string) {
+ return requestClient.put('/crm/contract/submit', null, {
+ params: { id, processDefinitionKey },
+ });
+}
+
+/** 鑾峰彇鍚堝悓瀹℃壒娴佺▼鍒楄〃 */
+export function getContractApproveProcessList() {
+ return requestClient.get<CrmContractApi.ApproveProcess[]>(
+ '/crm/contract/approve-process-list',
+ );
}
/** 鍚堝悓杞Щ */
@@ -131,3 +153,8 @@
export function getRemindContractCount() {
return requestClient.get<number>('/crm/contract/remind-count');
}
+
+/** 鏍规嵁鍚堝悓鐢熸垚閿�鍞鍗� */
+export function generateSaleOrder(id: number) {
+ return requestClient.post<number>('/crm/contract/generate-sale-order', null, { params: { id } });
+}
diff --git a/src/api/crm/report/index.ts b/src/api/crm/report/index.ts
new file mode 100644
index 0000000..0d44c42
--- /dev/null
+++ b/src/api/crm/report/index.ts
@@ -0,0 +1,106 @@
+import { requestClient } from '#/api/request';
+
+export namespace CrmReportApi {
+ /** 瀹㈡埛鍒嗘瀽缁熻 */
+ export interface CustomerAnalyze {
+ totalCount: number;
+ todayCount: number;
+ dealCount: number;
+ dealRate: number;
+ lockCount: number;
+ poolCount: number;
+ }
+
+ /** 鍟嗘満鍒嗘瀽缁熻 */
+ export interface BusinessAnalyze {
+ totalCount: number;
+ todayCount: number;
+ totalPrice: number;
+ successCount: number;
+ successPrice: number;
+ successRate: number;
+ processingCount: number;
+ failCount: number;
+ }
+
+ /** 鍚堝悓鍒嗘瀽缁熻 */
+ export interface ContractAnalyze {
+ totalCount: number;
+ todayCount: number;
+ totalPrice: number;
+ approveCount: number;
+ approvePrice: number;
+ processCount: number;
+ receivablePrice: number;
+ receivableRate: number;
+ }
+
+ /** 閿�鍞紡鏂楅樁娈� */
+ export interface SalesFunnelStage {
+ name: string;
+ count: number | null;
+ price: number;
+ conversionRate: number;
+ }
+
+ /** 閿�鍞紡鏂楃粺璁� */
+ export interface SalesFunnel {
+ stages: SalesFunnelStage[];
+ }
+
+ /** 閿�鍞笟缁╅」 */
+ export interface SalesPerformanceItem {
+ userId: number;
+ userName: string;
+ deptName: string;
+ contractCount: number;
+ contractPrice: number;
+ receivablePrice: number;
+ businessCount: number;
+ businessPrice: number;
+ }
+
+ /** 閿�鍞笟缁╃粺璁� */
+ export interface SalesPerformance {
+ items: SalesPerformanceItem[];
+ }
+}
+
+/** 鑾峰彇瀹㈡埛鍒嗘瀽缁熻 */
+export function getCustomerAnalyze(startTime?: string, endTime?: string) {
+ return requestClient.get<CrmReportApi.CustomerAnalyze>(
+ '/crm/report/get-customer-analyze',
+ { params: { startTime, endTime } },
+ );
+}
+
+/** 鑾峰彇鍟嗘満鍒嗘瀽缁熻 */
+export function getBusinessAnalyze(startTime?: string, endTime?: string) {
+ return requestClient.get<CrmReportApi.BusinessAnalyze>(
+ '/crm/report/get-business-analyze',
+ { params: { startTime, endTime } },
+ );
+}
+
+/** 鑾峰彇鍚堝悓鍒嗘瀽缁熻 */
+export function getContractAnalyze(startTime?: string, endTime?: string) {
+ return requestClient.get<CrmReportApi.ContractAnalyze>(
+ '/crm/report/get-contract-analyze',
+ { params: { startTime, endTime } },
+ );
+}
+
+/** 鑾峰彇閿�鍞紡鏂楃粺璁� */
+export function getSalesFunnel() {
+ return requestClient.get<CrmReportApi.SalesFunnel>(
+ '/crm/report/get-sales-funnel',
+ );
+}
+
+/** 鑾峰彇閿�鍞笟缁╃粺璁� */
+export function getSalesPerformance(startTime?: string, endTime?: string) {
+ return requestClient.get<CrmReportApi.SalesPerformance>(
+ '/crm/report/get-sales-performance',
+ { params: { startTime, endTime } },
+ );
+}
\ No newline at end of file
diff --git a/src/api/crm/saleQuotation/index.ts b/src/api/crm/saleQuotation/index.ts
new file mode 100644
index 0000000..01267ff
--- /dev/null
+++ b/src/api/crm/saleQuotation/index.ts
@@ -0,0 +1,109 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace CrmSaleQuotationApi {
+ /** 鎶ヤ环鍗曚俊鎭� */
+ export interface SaleQuotation {
+ id: number;
+ name: string;
+ no: string;
+ customerId: number;
+ customerName?: string;
+ businessId?: number;
+ businessName?: string;
+ contactId?: number;
+ contactName?: string;
+ quotationTime: Date;
+ validUntil?: Date;
+ totalProductPrice: number;
+ discountPercent: number;
+ totalPrice: number;
+ taxRate?: number;
+ fileUrl?: string;
+ remark?: string;
+ status: number;
+ contractId?: number;
+ contractNo?: string;
+ ownerUserId: number;
+ ownerUserName?: string;
+ creator: string;
+ creatorName: string;
+ createTime?: Date;
+ updateTime?: Date;
+ items?: SaleQuotationItem[];
+ }
+
+ /** 鎶ヤ环鍗曠墿鏂欐槑缁� */
+ export interface SaleQuotationItem {
+ id: number;
+ itemId: number;
+ itemName: string;
+ itemCode: string;
+ itemBarCode?: string;
+ itemSpecification?: string;
+ itemUnitName: string;
+ itemUnitName2?: string;
+ itemUnitName3?: string;
+ itemPrice: number;
+ quotationPrice: number;
+ count: number;
+ totalPrice: number;
+ taxPercent?: number;
+ taxPrice?: number;
+ remark?: string;
+ }
+}
+
+/** 鏌ヨ鎶ヤ环鍗曞垎椤靛垪琛� */
+export function getSaleQuotationPage(params: PageParam) {
+ return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>(
+ '/crm/sale-quotation/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鎶ヤ环鍗曞垎椤靛垪琛紙鍩轰簬瀹㈡埛锛� */
+export function getSaleQuotationPageByCustomer(params: PageParam) {
+ return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>(
+ '/crm/sale-quotation/page-by-customer',
+ { params },
+ );
+}
+
+/** 鏌ヨ鎶ヤ环鍗曞垎椤靛垪琛紙鍩轰簬鍟嗘満锛� */
+export function getSaleQuotationPageByBusiness(params: PageParam) {
+ return requestClient.get<PageResult<CrmSaleQuotationApi.SaleQuotation>>(
+ '/crm/sale-quotation/page-by-business',
+ { params },
+ );
+}
+
+/** 鏌ヨ鎶ヤ环鍗曡鎯� */
+export function getSaleQuotation(id: number) {
+ return requestClient.get<CrmSaleQuotationApi.SaleQuotation>(
+ `/crm/sale-quotation/get?id=${id}`,
+ );
+}
+
+/** 鏂板鎶ヤ环鍗� */
+export function createSaleQuotation(data: CrmSaleQuotationApi.SaleQuotation) {
+ return requestClient.post('/crm/sale-quotation/create', data);
+}
+
+/** 淇敼鎶ヤ环鍗� */
+export function updateSaleQuotation(data: CrmSaleQuotationApi.SaleQuotation) {
+ return requestClient.put('/crm/sale-quotation/update', data);
+}
+
+/** 鍒犻櫎鎶ヤ环鍗� */
+export function deleteSaleQuotation(id: number) {
+ return requestClient.delete(`/crm/sale-quotation/delete?id=${id}`);
+}
+
+/** 杞负鍚堝悓 */
+export function convertSaleQuotationToContract(id: number) {
+ return requestClient.post<number>('/crm/sale-quotation/convert-contract', null, {
+ params: { id },
+ });
+}
\ No newline at end of file
diff --git a/src/api/crm/saleTrace/index.ts b/src/api/crm/saleTrace/index.ts
new file mode 100644
index 0000000..baf5d62
--- /dev/null
+++ b/src/api/crm/saleTrace/index.ts
@@ -0,0 +1,57 @@
+import { requestClient } from '#/api/request';
+
+export namespace CrmSaleTraceApi {
+ /** 閿�鍞拷婧俊鎭� */
+ export interface SaleTrace {
+ businessId?: number;
+ businessName?: string;
+ businessPrice?: number;
+ businessCreateTime?: Date;
+ customerId?: number;
+ customerName?: string;
+ contractId?: number;
+ contractName?: string;
+ contractNo?: string;
+ contractPrice?: number;
+ contractCreateTime?: Date;
+ contractStatus?: number;
+ orderId?: number;
+ orderNo?: string;
+ orderPrice?: number;
+ orderCreateTime?: Date;
+ receivablePrice?: number;
+ receivableItems?: ReceivableItem[];
+ }
+
+ /** 鍥炴鏄庣粏 */
+ export interface ReceivableItem {
+ receivableId: number;
+ receivableNo: string;
+ price: number;
+ createTime?: Date;
+ }
+}
+
+/** 鏍规嵁鍟嗘満缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭� */
+export function getTraceByBusinessId(businessId: number) {
+ return requestClient.get<CrmSaleTraceApi.SaleTrace>(
+ '/crm/sale-trace/get-by-business-id',
+ { params: { businessId } },
+ );
+}
+
+/** 鏍规嵁鍚堝悓缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧俊鎭� */
+export function getTraceByContractId(contractId: number) {
+ return requestClient.get<CrmSaleTraceApi.SaleTrace>(
+ '/crm/sale-trace/get-by-contract-id',
+ { params: { contractId } },
+ );
+}
+
+/** 鏍规嵁瀹㈡埛缂栧彿鑾峰彇閿�鍞祦绋嬭拷婧垪琛� */
+export function getTraceListByCustomerId(customerId: number) {
+ return requestClient.get<CrmSaleTraceApi.SaleTrace[]>(
+ '/crm/sale-trace/get-list-by-customer-id',
+ { params: { customerId } },
+ );
+}
\ No newline at end of file
diff --git a/src/api/erp/purchase/request/index.ts b/src/api/erp/purchase/request/index.ts
index c35f89c..a7702d1 100644
--- a/src/api/erp/purchase/request/index.ts
+++ b/src/api/erp/purchase/request/index.ts
@@ -7,15 +7,23 @@
export interface PurchaseRequest {
id?: number; // 缂栧彿
no?: string; // 鐢宠缂栧彿
+ status?: number; // 鐘舵�侊細0鏈彁浜� 10瀹℃壒涓� 20瀹℃牳閫氳繃 30瀹℃牳涓嶉�氳繃 40宸插彇娑�
+ requestUserId?: number; // 鐢宠浜虹紪鍙�
+ requestUserName?: string; // 鐢宠浜哄悕绉�
+ requestDeptId?: number; // 鐢宠閮ㄩ棬缂栧彿
+ requestDeptName?: string; // 鐢宠閮ㄩ棬鍚嶇О
+ requestTime: string; // 鐢宠鏃堕棿
+ requestReason?: string; // 鐢宠鐞嗙敱
supplierId?: number; // 渚涘簲鍟嗙紪鍙�
supplierName?: string; // 渚涘簲鍟嗗悕绉�
- requestTime: string; // 鐢宠鏃堕棿
- requesterId?: number; // 鐢宠浜虹紪鍙�
- requesterName?: string; // 鐢宠浜哄悕绉�
- reason?: string; // 鐢宠鐞嗙敱
- estimatedAmount?: number; // 棰勪及閲戦
- status?: number; // 鐘舵��
+ discountPercent?: number; // 浼樻儬鐜�
+ fileUrl?: string; // 闄勪欢鍦板潃
remark?: string; // 澶囨敞
+ totalCount?: number; // 鎬绘暟閲�
+ totalPrice?: number; // 鎬婚噾棰�
+ productNames?: string; // 浜у搧鍚嶇О鍒楄〃
+ orderId?: number; // 鐢熸垚鐨勯噰璐鍗曠紪鍙�
+ orderNo?: string; // 鐢熸垚鐨勯噰璐鍗曞彿
items?: PurchaseRequestItem[]; // 鐢宠鏄庣粏
createTime?: string; // 鍒涘缓鏃堕棿
}
@@ -25,11 +33,13 @@
id?: number; // 缂栧彿
productId: number; // 浜у搧缂栧彿
productName?: string; // 浜у搧鍚嶇О
- productUnitName?: string; // 浜у搧鍗曚綅
+ productUnitId?: number; // 浜у搧鍗曚綅缂栧彿
+ productUnitName?: string; // 浜у搧鍗曚綅鍚嶇О
productBarCode?: string; // 浜у搧鏉$爜
- requestCount: number; // 鐢宠鏁伴噺
- estimatedPrice?: number; // 棰勪及鍗曚环
- requiredDate?: string; // 闇�姹傛棩鏈�
+ productPrice?: number; // 浜у搧鍗曚环锛堝弬鑰冧环锛�
+ count: number; // 鏁伴噺
+ taxPercent?: number; // 绋庣巼
+ demandTime?: string; // 闇�姹傛棩鏈�
remark?: string; // 澶囨敞
}
}
@@ -44,18 +54,14 @@
return requestClient.put('/erp/purchase-request/update', data);
}
-/** 瀹℃壒閲囪喘鐢宠 */
-export function approvePurchaseRequest(params: {
- id: number;
- status: number;
- remark?: string;
-}) {
- return requestClient.put('/erp/purchase-request/approve', null, { params });
+/** 鎻愪氦閲囪喘鐢宠瀹℃壒 */
+export function submitPurchaseRequest(id: number, processDefinitionKey: string) {
+ return requestClient.put('/erp/purchase-request/submit', null, { params: { id, processDefinitionKey } });
}
-/** 杞负閲囪喘璁㈠崟 */
-export function convertToPurchaseOrder(id: number) {
- return requestClient.put('/erp/purchase-request/convert', null, { params: { id } });
+/** 鐢熸垚閲囪喘璁㈠崟 */
+export function generatePurchaseOrder(id: number, supplierId: number) {
+ return requestClient.post<number>('/erp/purchase-request/generate-order', null, { params: { id, supplierId } });
}
/** 鍒犻櫎閲囪喘鐢宠 */
@@ -82,3 +88,8 @@
export function exportPurchaseRequest(params: any) {
return requestClient.download('/erp/purchase-request/export-excel', { params });
}
+
+/** 鑾峰彇瀹℃壒娴佺▼鍒楄〃 */
+export function getApproveProcessList(id: number) {
+ return requestClient.get(`/erp/purchase-request/approve-process-list`, { params: { id } });
+}
diff --git a/src/api/erp/sale/order/index.ts b/src/api/erp/sale/order/index.ts
index 17ae5d6..acc7a2c 100644
--- a/src/api/erp/sale/order/index.ts
+++ b/src/api/erp/sale/order/index.ts
@@ -8,6 +8,7 @@
id?: number; // 璁㈠崟宸ュ崟缂栧彿
no: string; // 閿�鍞鍗曞彿
customerId: number; // 瀹㈡埛缂栧彿
+ customerName?: string; // 瀹㈡埛鍚嶇О
accountId?: number; // 鏀舵璐︽埛缂栧彿
orderTime: Date; // 璁㈠崟鏃堕棿
totalCount: number; // 鍚堣鏁伴噺
@@ -22,6 +23,8 @@
discountPercent?: number; // 浼樻儬鐜囷紝鐧惧垎姣�
discountPrice?: number; // 浼樻儬閲戦锛屽崟浣嶏細鍏�
depositPrice?: number; // 瀹氶噾閲戦锛屽崟浣嶏細鍏�
+ contractId?: number; // 鍏宠仈 CRM 鍚堝悓缂栧彿
+ contractNo?: string; // CRM 鍚堝悓鍗曞彿
items?: SaleOrderItem[]; // 閿�鍞鍗曚骇鍝佹槑缁嗗垪琛�
}
@@ -97,3 +100,17 @@
export function exportSaleOrder(params: any) {
return requestClient.download('/erp/sale-order/export-excel', { params });
}
+
+/** 浣滃簾閿�鍞鍗� */
+export function cancelSaleOrder(id: number) {
+ return requestClient.put('/erp/sale-order/cancel', null, {
+ params: { id },
+ });
+}
+
+/** 鍚敤閿�鍞鍗曪紙鎭㈠宸蹭綔搴熻鍗曪級 */
+export function enableSaleOrder(id: number) {
+ return requestClient.put('/erp/sale-order/enable', null, {
+ params: { id },
+ });
+}
diff --git a/src/api/mdm/item/index.ts b/src/api/mdm/item/index.ts
index 6ceb79a..275a7da 100644
--- a/src/api/mdm/item/index.ts
+++ b/src/api/mdm/item/index.ts
@@ -14,6 +14,12 @@
categoryName?: string;
unitMeasureId?: number;
unitMeasureName?: string;
+ unitMeasureId2?: number;
+ unitMeasureName2?: string;
+ unitMeasureRate1?: number;
+ unitMeasureId3?: number;
+ unitMeasureName3?: string;
+ unitMeasureRate2?: number;
brandId?: number;
brandName?: string;
itemType: number;
@@ -26,6 +32,9 @@
maxStock?: number;
isBatchManaged: boolean;
expiryDay?: number;
+ warehouseId?: number;
+ warehouseCode?: string;
+ warehouseName?: string;
remark?: string;
createTime?: string;
}
diff --git a/src/components/crm-customer-select.vue b/src/components/crm-customer-select.vue
new file mode 100644
index 0000000..8f982bc
--- /dev/null
+++ b/src/components/crm-customer-select.vue
@@ -0,0 +1,75 @@
+<script lang="ts" setup>
+import type { CrmCustomerApi } from '#/api/crm/customer';
+
+import { computed, ref, watch } from 'vue';
+
+import { Select } from 'ant-design-vue';
+
+import { getCustomerSimpleList } from '#/api/crm/customer';
+
+interface Props {
+ value?: number;
+ disabled?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ value: undefined,
+ disabled: false,
+});
+
+const emit = defineEmits(['update:value', 'change']);
+
+const customerList = ref<CrmCustomerApi.Customer[]>([]);
+const loading = ref(false);
+
+const options = computed(() => {
+ return customerList.value.map((item) => ({
+ label: item.name,
+ value: item.id,
+ }));
+});
+
+const selectedValue = ref(props.value);
+
+watch(
+ () => props.value,
+ (val) => {
+ selectedValue.value = val;
+ },
+);
+
+watch(selectedValue, (val) => {
+ emit('update:value', val);
+ const customer = customerList.value.find((item) => item.id === val);
+ emit('change', customer);
+});
+
+async function loadCustomers() {
+ if (customerList.value.length > 0) return;
+ loading.value = true;
+ try {
+ customerList.value = await getCustomerSimpleList();
+ } finally {
+ loading.value = false;
+ }
+}
+
+function filterOption(input: string, option: any) {
+ return option.label.toLowerCase().includes(input.toLowerCase());
+}
+
+loadCustomers();
+</script>
+
+<template>
+ <Select
+ v-model:value="selectedValue"
+ :options="options"
+ :loading="loading"
+ :disabled="disabled"
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ show-search
+ :filter-option="filterOption"
+ allow-clear
+ />
+</template>
\ No newline at end of file
diff --git a/src/components/table-action/table-action.vue b/src/components/table-action/table-action.vue
index a265463..cdf81a0 100644
--- a/src/components/table-action/table-action.vue
+++ b/src/components/table-action/table-action.vue
@@ -97,6 +97,8 @@
attrs.onCancel = popConfirm.cancel;
}
+ // 涓嶈缃� getPopupContainer锛岃瀹冮粯璁ゆ寕杞藉埌 body锛岄伩鍏嶈閬尅
+
return attrs;
}
@@ -269,6 +271,12 @@
}
.ant-popconfirm {
+ width: 210px;
+
+ //.ant-popconfirm-message {
+ // white-space: nowrap;
+ //}
+
.ant-popconfirm-buttons {
.ant-btn {
margin-inline-start: 4px !important;
diff --git a/src/components/table-action/typing.ts b/src/components/table-action/typing.ts
index 1e44fb2..91814c5 100644
--- a/src/components/table-action/typing.ts
+++ b/src/components/table-action/typing.ts
@@ -12,6 +12,8 @@
cancel?: () => void;
icon?: string;
disabled?: boolean;
+ placement?: string;
+ getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
}
export interface ActionItem extends ButtonProps {
diff --git a/src/packages/icons/package.json b/src/packages/icons/package.json
index 1a5fc72..0f8273c 100644
--- a/src/packages/icons/package.json
+++ b/src/packages/icons/package.json
@@ -17,6 +17,7 @@
}
},
"dependencies": {
+ "@iconify/json": "^2.2.483",
"@vben-core/icons": "workspace:*"
}
}
diff --git a/src/packages/icons/src/offline-icons.ts b/src/packages/icons/src/offline-icons.ts
new file mode 100644
index 0000000..d873267
--- /dev/null
+++ b/src/packages/icons/src/offline-icons.ts
@@ -0,0 +1,15 @@
+/**
+ * 绂荤嚎鍥炬爣棰勫姞杞介厤缃�
+ * 鏆傛椂绂佺敤锛屼娇鐢ㄥ湪绾垮姞杞芥柟寮�
+ */
+
+import { addCollection } from '@iconify/vue';
+
+/**
+ * 鍒濆鍖栫绾垮浘鏍�
+ * 鐩墠浣跨敤鍦ㄧ嚎鍔犺浇锛屾鍑芥暟涓虹┖
+ */
+export function initOfflineIcons() {
+ // 鏆傛椂涓嶉鍔犺浇锛屼娇鐢� Iconify 鍦ㄧ嚎 API
+ // 濡傛灉闇�瑕佺绾垮姞杞斤紝闇�瑕佸皢 JSON 鏂囦欢澶嶅埗鍒伴」鐩腑
+}
diff --git a/src/preferences.ts b/src/preferences.ts
index 131be05..626b2ff 100644
--- a/src/preferences.ts
+++ b/src/preferences.ts
@@ -32,6 +32,10 @@
companyName: import.meta.env.VITE_APP_TITLE,
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
},
+ tabbar: {
+ /** 鍒锋柊娴忚鍣ㄥ悗鍙繚鐣欏綋鍓嶉〉闈紝涓嶆寔涔呭寲鎵�鏈夋爣绛鹃〉 */
+ persist: false,
+ },
});
export const preferencesExtension =
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index 1808f14..479f150 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -6,6 +6,7 @@
import { z } from '#/adapter/form';
import { getUnitPage } from '#/api/mdm/unit';
+import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
/** 鐗╂枡绫诲瀷閫夐」 */
export const ITEM_TYPE_OPTIONS = [
@@ -72,10 +73,19 @@
rules: 'required',
},
{
+ component: 'Input',
+ fieldName: 'specification',
+ label: '瑙勬牸鍨嬪彿',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ鏍煎瀷鍙�',
+ },
+ },
+ {
component: 'ApiSelect',
fieldName: 'unitMeasureId',
label: '璁¢噺鍗曚綅',
rules: 'required',
+ formItemClass: 'col-span-1',
componentProps: {
placeholder: '璇烽�夋嫨璁¢噺鍗曚綅',
allowClear: true,
@@ -83,6 +93,74 @@
const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 });
return res.list || [];
},
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ component: 'ApiSelect',
+ fieldName: 'unitMeasureId2',
+ label: '杈呭崟浣�1',
+ formItemClass: 'col-span-1',
+ componentProps: {
+ placeholder: '璇烽�夋嫨杈呭崟浣�1',
+ allowClear: true,
+ api: async () => {
+ const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ component: 'InputNumber',
+ fieldName: 'unitMeasureRate1',
+ label: '杈呭崟浣�1鎹㈢畻姣旂巼',
+ formItemClass: 'col-span-1',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 6,
+ placeholder: '璇疯緭鍏ユ崲绠楁瘮鐜�',
+ },
+ },
+ {
+ component: 'ApiSelect',
+ fieldName: 'unitMeasureId3',
+ label: '杈呭崟浣�2',
+ formItemClass: 'col-span-1',
+ componentProps: {
+ placeholder: '璇烽�夋嫨杈呭崟浣�2',
+ allowClear: true,
+ api: async () => {
+ const res = await getUnitPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
+ labelField: 'name',
+ valueField: 'id',
+ },
+ },
+ {
+ component: 'InputNumber',
+ fieldName: 'unitMeasureRate2',
+ label: '杈呭崟浣�2鎹㈢畻姣旂巼',
+ formItemClass: 'col-span-1',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 6,
+ placeholder: '璇疯緭鍏ユ崲绠楁瘮鐜�',
+ },
+ },
+ {
+ component: 'ApiSelect',
+ fieldName: 'warehouseId',
+ label: '榛樿浠撳簱',
+ componentProps: {
+ placeholder: '璇烽�夋嫨榛樿浠撳簱',
+ allowClear: true,
+ api: getWarehouseSimpleList,
labelField: 'name',
valueField: 'id',
},
@@ -275,6 +353,31 @@
minWidth: 80,
},
{
+ field: 'unitMeasureName2',
+ title: '杈呭崟浣�1',
+ minWidth: 80,
+ },
+ {
+ field: 'unitMeasureRate1',
+ title: '杈呭崟浣�1鎹㈢畻姣旂巼',
+ minWidth: 130,
+ },
+ {
+ field: 'unitMeasureName3',
+ title: '杈呭崟浣�2',
+ minWidth: 80,
+ },
+ {
+ field: 'unitMeasureRate2',
+ title: '杈呭崟浣�2鎹㈢畻姣旂巼',
+ minWidth: 130,
+ },
+ {
+ field: 'warehouseName',
+ title: '榛樿浠撳簱',
+ minWidth: 100,
+ },
+ {
field: 'itemType',
title: '鐗╂枡绫诲瀷',
minWidth: 100,
diff --git a/src/views/basicData/mdm/index.vue b/src/views/basicData/mdm/index.vue
index c10a75f..16b34d4 100644
--- a/src/views/basicData/mdm/index.vue
+++ b/src/views/basicData/mdm/index.vue
@@ -13,7 +13,7 @@
import {
deleteItem,
deleteItemList,
- exportItem,
+ // exportItem,
getItemPage,
updateItemStatus,
} from '#/api/mdm/item';
@@ -35,10 +35,10 @@
}
/** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
- const data = await exportItem(await gridApi.formApi.getValues());
- downloadFileFromBlobPart({ fileName: '鐗╂枡.xls', source: data });
-}
+// async function handleExport() {
+// const data = await exportItem(await gridApi.formApi.getValues());
+// downloadFileFromBlobPart({ fileName: '鐗╂枡.xls', source: data });
+// }
/** 鍒涘缓鐗╂枡 */
function handleCreate() {
@@ -156,13 +156,13 @@
auth: ['mdm:item:create'],
onClick: handleCreate,
},
- {
- label: $t('ui.actionTitle.export'),
- type: 'primary',
- icon: ACTION_ICON.DOWNLOAD,
- auth: ['mdm:item:export'],
- onClick: handleExport,
- },
+ // {
+ // label: $t('ui.actionTitle.export'),
+ // type: 'primary',
+ // icon: ACTION_ICON.DOWNLOAD,
+ // auth: ['mdm:item:export'],
+ // onClick: handleExport,
+ // },
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
diff --git a/src/views/basicData/mdm/modules/form.vue b/src/views/basicData/mdm/modules/form.vue
index 6b339cb..27b8097 100644
--- a/src/views/basicData/mdm/modules/form.vue
+++ b/src/views/basicData/mdm/modules/form.vue
@@ -28,7 +28,7 @@
componentProps: {
class: 'w-full',
},
- labelWidth: 100,
+ labelWidth: 110,
},
layout: 'horizontal',
schema: useFormSchema(),
@@ -74,7 +74,7 @@
</script>
<template>
- <Modal class="w-[800px]" :title="getTitle">
+ <Modal class="w-[1000px]" :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>
\ No newline at end of file
diff --git a/src/views/basicData/mdm/unit/data.ts b/src/views/basicData/mdm/unit/data.ts
index e6081e4..b9d0be3 100644
--- a/src/views/basicData/mdm/unit/data.ts
+++ b/src/views/basicData/mdm/unit/data.ts
@@ -5,7 +5,6 @@
import { getDictOptions } from '#/packages/effects/hooks/src';
import { z } from '#/adapter/form';
-import { getUnitPage } from '#/api/mdm/unit';
/** 鏂板/淇敼鐨勮〃鍗� */
export function useFormSchema(): VbenFormSchema[] {
@@ -34,61 +33,6 @@
rules: 'required',
componentProps: {
placeholder: '璇疯緭鍏ュ崟浣嶅悕绉�',
- },
- },
- {
- component: 'Switch',
- fieldName: 'primaryFlag',
- label: '鏄惁涓诲崟浣�',
- componentProps: {
- class: 'w-auto',
- checkedChildren: '鏄�',
- unCheckedChildren: '鍚�',
- },
- rules: z.boolean().default(false),
- },
- {
- component: 'ApiSelect',
- fieldName: 'primaryId',
- label: '涓诲崟浣�',
- dependencies: {
- triggerFields: ['primaryFlag'],
- show: (values) => {
- return values.primaryFlag === false;
- },
- required: (values) => {
- return values.primaryFlag === false;
- },
- },
- componentProps: {
- placeholder: '璇烽�夋嫨涓诲崟浣�',
- allowClear: true,
- api: async () => {
- const res = await getUnitPage({ pageNo: 1, pageSize: 100, primaryFlag: true, status: 0 });
- return res.list || [];
- },
- labelField: 'name',
- valueField: 'id',
- },
- },
- {
- component: 'InputNumber',
- fieldName: 'changeRate',
- label: '鎹㈢畻姣斾緥',
- dependencies: {
- triggerFields: ['primaryFlag'],
- show: (values) => {
- return values.primaryFlag === false;
- },
- required: (values) => {
- return values.primaryFlag === false;
- },
- },
- componentProps: {
- class: '!w-full',
- min: 0,
- precision: 4,
- placeholder: '璇疯緭鍏ユ崲绠楁瘮渚�',
},
},
{
@@ -161,17 +105,6 @@
field: 'name',
title: '鍗曚綅鍚嶇О',
minWidth: 150,
- },
- {
- field: 'primaryFlag',
- title: '涓诲崟浣�',
- minWidth: 80,
- slots: { default: 'primaryFlag' },
- },
- {
- field: 'changeRate',
- title: '鎹㈢畻姣斾緥',
- minWidth: 100,
},
{
field: 'status',
diff --git a/src/views/crm/contract/data.ts b/src/views/crm/contract/data.ts
index 454afe8..1efd63c 100644
--- a/src/views/crm/contract/data.ts
+++ b/src/views/crm/contract/data.ts
@@ -1,7 +1,6 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import { DICT_TYPE } from '../../../packages/constants/src';
import { useUserStore } from '../../../packages/stores/src';
import { erpPriceInputFormatter, erpPriceMultiply } from '../../../packages/utils/src';
@@ -70,6 +69,18 @@
},
},
{
+ fieldName: 'orderDate',
+ label: '涓嬪崟鏃ユ湡',
+ component: 'DatePicker',
+ rules: 'required',
+ componentProps: {
+ showTime: false,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'x',
+ placeholder: '璇烽�夋嫨涓嬪崟鏃ユ湡',
+ },
+ },
+ {
fieldName: 'businessId',
label: '鍟嗘満鍚嶇О',
component: 'Select',
@@ -102,18 +113,6 @@
},
},
{
- fieldName: 'orderDate',
- label: '涓嬪崟鏃ユ湡',
- component: 'DatePicker',
- rules: 'required',
- componentProps: {
- showTime: false,
- format: 'YYYY-MM-DD',
- valueFormat: 'x',
- placeholder: '璇烽�夋嫨涓嬪崟鏃ユ湡',
- },
- },
- {
fieldName: 'startTime',
label: '鍚堝悓寮�濮嬫椂闂�',
component: 'DatePicker',
@@ -134,6 +133,18 @@
valueFormat: 'x',
placeholder: '璇烽�夋嫨鍚堝悓缁撴潫鏃堕棿',
},
+ },
+ {
+ fieldName: 'depositPrice',
+ label: '瀹氶噾閲戦锛堝厓锛�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ュ畾閲戦噾棰�',
+ },
+ rules: z.number().min(0).optional(),
},
{
fieldName: 'signUserId',
@@ -278,6 +289,32 @@
allowClear: true,
},
},
+ {
+ fieldName: 'auditStatus',
+ label: '鍚堝悓鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: [
+ { label: '鏈彁浜�', value: 0 },
+ { label: '瀹℃壒涓�', value: 10 },
+ { label: '瀹℃牳閫氳繃', value: 20 },
+ { label: '瀹℃牳涓嶉�氳繃', value: 30 },
+ { label: '宸插彇娑�', value: 40 },
+ { label: '宸蹭綔搴�', value: 50 },
+ ],
+ placeholder: '璇烽�夋嫨鍚堝悓鐘舵��',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'orderNo',
+ label: '鍏宠仈璁㈠崟',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ鍗曞彿',
+ allowClear: true,
+ },
+ },
];
}
@@ -288,6 +325,7 @@
field: 'no',
minWidth: 180,
fixed: 'left',
+ slots: { default: 'no' },
},
{
title: '鍚堝悓鍚嶇О',
@@ -311,6 +349,12 @@
{
title: '鍚堝悓閲戦锛堝厓锛�',
field: 'totalPrice',
+ minWidth: 140,
+ formatter: 'formatAmount2',
+ },
+ {
+ title: '瀹氶噾閲戦锛堝厓锛�',
+ field: 'depositPrice',
minWidth: 140,
formatter: 'formatAmount2',
},
@@ -402,16 +446,20 @@
field: 'auditStatus',
fixed: 'right',
minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.CRM_AUDIT_STATUS },
- },
+ slots: { default: 'auditStatus' },
+ },
+ {
+ title: '鍏宠仈璁㈠崟',
+ field: 'orderNo',
+ fixed: 'right',
+ minWidth: 150,
+ slots: { default: 'orderNo' },
},
{
title: '鎿嶄綔',
field: 'actions',
fixed: 'right',
- minWidth: 130,
+ minWidth: 220,
slots: { default: 'actions' },
},
];
diff --git a/src/views/crm/contract/detail/data.ts b/src/views/crm/contract/detail/data.ts
index 1602256..d81aae3 100644
--- a/src/views/crm/contract/detail/data.ts
+++ b/src/views/crm/contract/detail/data.ts
@@ -20,6 +20,11 @@
render: (val) => erpPriceInputFormatter(val) as string,
},
{
+ field: 'depositPrice',
+ label: '瀹氶噾閲戦锛堝厓锛�',
+ render: (val) => erpPriceInputFormatter(val) as string,
+ },
+ {
field: 'orderDate',
label: '涓嬪崟鏃堕棿',
render: (val) => formatDateTime(val) as string,
@@ -61,6 +66,11 @@
render: (val) => erpPriceInputFormatter(val) as string,
},
{
+ field: 'depositPrice',
+ label: '瀹氶噾閲戦锛堝厓锛�',
+ render: (val) => erpPriceInputFormatter(val) as string,
+ },
+ {
field: 'orderDate',
label: '涓嬪崟鏃堕棿',
render: (val) => formatDateTime(val) as string,
@@ -84,6 +94,11 @@
label: '鍏徃绛剧害浜�',
},
{
+ field: 'orderNo',
+ label: '閿�鍞鍗�',
+ slot: 'orderNo', // 浣跨敤 slot 鑷畾涔夋覆鏌�
+ },
+ {
field: 'remark',
label: '澶囨敞',
},
diff --git a/src/views/crm/contract/detail/index.vue b/src/views/crm/contract/detail/index.vue
index ca15dfe..dbef431 100644
--- a/src/views/crm/contract/detail/index.vue
+++ b/src/views/crm/contract/detail/index.vue
@@ -38,6 +38,7 @@
const contract = ref<CrmContractApi.Contract>({} as CrmContractApi.Contract); // 鍚堝悓璇︽儏
const logList = ref<SystemOperateLogApi.OperateLog[]>([]); // 鎿嶄綔鏃ュ織
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 鍥㈤槦鎴愬憳鍒楄〃 Ref
+const activeTab = ref('1'); // 褰撳墠婵�娲荤殑 Tab
const [Descriptions] = useDescription({
bordered: false,
@@ -128,13 +129,13 @@
<Descriptions :data="contract" />
</Card>
<Card class="mt-4 min-h-[60%]">
- <Tabs>
- <Tabs.TabPane tab="璺熻繘璁板綍" key="1" :force-render="true">
- <FollowUp :biz-id="contractId" :biz-type="BizTypeEnum.CRM_CONTRACT" />
- </Tabs.TabPane>
- <Tabs.TabPane tab="鍩烘湰淇℃伅" key="2" :force-render="true">
+ <Tabs v-model:activeKey="activeTab">
+ <Tabs.TabPane tab="鍩烘湰淇℃伅" key="1" :force-render="true">
<ContractDetailsInfo :contract="contract" />
</Tabs.TabPane>
+ <Tabs.TabPane tab="璺熻繘璁板綍" key="2" :force-render="true">
+ <FollowUp :biz-id="contractId" :biz-type="BizTypeEnum.CRM_CONTRACT" />
+ </Tabs.TabPane>
<Tabs.TabPane tab="浜у搧" key="3" :force-render="true">
<ProductDetailsList
:biz-id="contractId"
diff --git a/src/views/crm/contract/detail/modules/info.vue b/src/views/crm/contract/detail/modules/info.vue
index da23e23..df757d3 100644
--- a/src/views/crm/contract/detail/modules/info.vue
+++ b/src/views/crm/contract/detail/modules/info.vue
@@ -1,16 +1,20 @@
<script lang="ts" setup>
import type { CrmContractApi } from '#/api/crm/contract';
-import { Divider } from 'ant-design-vue';
+import { useRouter } from 'vue-router';
+
+import { Button, Divider } from 'ant-design-vue';
import { useDescription } from '#/components/description';
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
import { useDetailBaseSchema } from '../data';
-defineProps<{
+const props = defineProps<{
contract: CrmContractApi.Contract; // 鍚堝悓淇℃伅
}>();
+
+const router = useRouter();
const [BaseDescriptions] = useDescription({
title: '鍩烘湰淇℃伅',
@@ -27,11 +31,28 @@
class: 'mx-4',
schema: useFollowUpDetailSchema(),
});
+
+/** 鏌ョ湅閿�鍞鍗� */
+function handleViewOrder() {
+ if (props.contract.orderId) {
+ router.push({
+ path: '/erp/sale/order',
+ query: { orderId: props.contract.orderId },
+ });
+ }
+}
</script>
<template>
<div>
- <BaseDescriptions :data="contract" />
+ <BaseDescriptions :data="contract">
+ <template #orderNo="{ data }">
+ <Button v-if="data.orderId" type="link" @click="handleViewOrder">
+ {{ data.orderNo }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ </BaseDescriptions>
<Divider />
<SystemDescriptions :data="contract" />
</div>
diff --git a/src/views/crm/contract/index.vue b/src/views/crm/contract/index.vue
index 374c07c..99e19fa 100644
--- a/src/views/crm/contract/index.vue
+++ b/src/views/crm/contract/index.vue
@@ -8,12 +8,14 @@
import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '../../../packages/utils/src';
-import { Button, message, Tabs } from 'ant-design-vue';
+import { Button, message, Modal, Select, Tabs, Tag, Tooltip } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteContract,
exportContract,
+ generateSaleOrder,
+ getContractApproveProcessList,
getContractPage,
submitContract,
} from '#/api/crm/contract';
@@ -76,18 +78,34 @@
}
}
-/** 鎻愪氦瀹℃牳 */
+/** 鎻愪氦瀹℃壒寮圭獥 */
+const submitModalVisible = ref(false);
+const currentRow = ref<CrmContractApi.Contract>();
+const processList = ref<CrmContractApi.ApproveProcess[]>([]);
+const selectedProcessKey = ref<string>();
+const submitLoading = ref(false);
+
async function handleSubmit(row: CrmContractApi.Contract) {
- const hideLoading = message.loading({
- content: '鎻愪氦瀹℃牳涓�...',
- duration: 0,
- });
+ const res = await getContractApproveProcessList();
+ processList.value = res;
+ currentRow.value = row;
+ selectedProcessKey.value = undefined;
+ submitModalVisible.value = true;
+}
+
+async function confirmSubmit() {
+ if (!selectedProcessKey.value) {
+ message.warning('璇烽�夋嫨瀹℃壒娴佺▼');
+ return;
+ }
+ submitLoading.value = true;
try {
- await submitContract(row.id!);
- message.success('鎻愪氦瀹℃牳鎴愬姛');
+ await submitContract(currentRow.value!.id!, selectedProcessKey.value);
+ message.success('鎻愪氦瀹℃壒鎴愬姛');
+ submitModalVisible.value = false;
handleRefresh();
} finally {
- hideLoading();
+ submitLoading.value = false;
}
}
@@ -117,6 +135,39 @@
name: 'BpmProcessInstanceDetail',
query: { id: row.processInstanceId },
});
+}
+
+/** 鐢熸垚閿�鍞鍗� */
+async function handleGenerateOrder(row: CrmContractApi.Contract) {
+ if (row.auditStatus !== 20) {
+ message.warning('鍙湁宸插鎵圭殑鍚堝悓鎵嶈兘鐢熸垚閿�鍞鍗�');
+ return;
+ }
+ if (row.orderId) {
+ message.warning('璇ュ悎鍚屽凡鐢熸垚閿�鍞鍗�');
+ return;
+ }
+ const hideLoading = message.loading({
+ content: '姝e湪鐢熸垚閿�鍞鍗�...',
+ duration: 0,
+ });
+ try {
+ const orderId = await generateSaleOrder(row.id!);
+ message.success('鐢熸垚閿�鍞鍗曟垚鍔�');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅閿�鍞鍗� */
+function handleViewOrder(row: CrmContractApi.Contract) {
+ if (row.orderId) {
+ push({
+ name: 'ErpSaleOrder',
+ query: { id: row.orderId },
+ });
+ }
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -152,7 +203,8 @@
</script>
<template>
- <Page auto-content-height><FormModal @success="handleRefresh" />
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-actions>
<Tabs class="w-full" @change="handleChangeSceneType">
@@ -186,6 +238,16 @@
{{ row.name }}
</Button>
</template>
+ <template #no="{ row }">
+ <Tooltip v-if="row.auditStatus === 50" title="璇ュ悎鍚屽凡浣滃簾锛岃鍗曞凡澶辨晥">
+ <span class="text-red-500 cursor-pointer" @click="handleDetail(row)">
+ {{ row.no }}
+ </span>
+ </Tooltip>
+ <Button v-else type="link" @click="handleDetail(row)">
+ {{ row.no }}
+ </Button>
+ </template>
<template #customerName="{ row }">
<Button type="link" @click="handleCustomerDetail(row)">
{{ row.customerName }}
@@ -201,6 +263,20 @@
{{ row.signContactName }}
</Button>
</template>
+ <template #auditStatus="{ row }">
+ <Tag v-if="row.auditStatus === 0" color="default">鏈彁浜�</Tag>
+ <Tag v-if="row.auditStatus === 10" color="warning">瀹℃壒涓�</Tag>
+ <Tag v-if="row.auditStatus === 20" color="success">瀹℃牳閫氳繃</Tag>
+ <Tag v-if="row.auditStatus === 30" color="error">瀹℃牳涓嶉�氳繃</Tag>
+ <Tag v-if="row.auditStatus === 40" color="default">宸插彇娑�</Tag>
+ <Tag v-if="row.auditStatus === 50" color="error">宸蹭綔搴�</Tag>
+ </template>
+ <template #orderNo="{ row }">
+ <Button v-if="row.orderId" type="link" @click="handleViewOrder(row)">
+ {{ row.orderNo }}
+ </Button>
+ <span v-else>-</span>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
@@ -213,7 +289,7 @@
ifShow: row.auditStatus === 0,
},
{
- label: '鎻愪氦瀹℃牳',
+ label: '鎻愪氦瀹℃壒',
type: 'link',
auth: ['crm:contract:update'],
onClick: handleSubmit.bind(null, row),
@@ -227,6 +303,20 @@
ifShow: row.auditStatus !== 0,
},
{
+ label: '鐢熸垚閿�鍞鍗�',
+ type: 'link',
+ auth: ['crm:contract:update'],
+ onClick: handleGenerateOrder.bind(null, row),
+ ifShow: row.auditStatus === 20 && !row.orderId,
+ },
+ {
+ label: '鏌ョ湅璁㈠崟',
+ type: 'link',
+ auth: ['erp:sale-order:query'],
+ onClick: handleViewOrder.bind(null, row),
+ ifShow: !!row.orderId,
+ },
+ {
label: $t('common.delete'),
type: 'link',
danger: true,
@@ -235,10 +325,34 @@
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
+ ifShow: row.auditStatus === 0,
},
]"
/>
</template>
</Grid>
+
+ <!-- 鎻愪氦瀹℃壒寮圭獥 -->
+ <Modal
+ v-model:open="submitModalVisible"
+ title="閫夋嫨瀹℃壒娴佺▼"
+ width="400px"
+ @ok="confirmSubmit"
+ :confirmLoading="submitLoading"
+ >
+ <Select
+ v-model:value="selectedProcessKey"
+ placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+ class="w-full"
+ >
+ <Select.Option
+ v-for="item in processList"
+ :key="item.key"
+ :value="item.key"
+ >
+ {{ item.name }}
+ </Select.Option>
+ </Select>
+ </Modal>
</Page>
</template>
diff --git a/src/views/crm/contract/modules/form.vue b/src/views/crm/contract/modules/form.vue
index 0e02275..2e9e009 100644
--- a/src/views/crm/contract/modules/form.vue
+++ b/src/views/crm/contract/modules/form.vue
@@ -105,7 +105,7 @@
</script>
<template>
- <Modal :title="getTitle" class="w-1/2">
+ <Modal :title="getTitle" class="w-[80%]">
<Form class="mx-4">
<template #product="slotProps">
<ProductEditTable
diff --git a/src/views/crm/product/components/data.ts b/src/views/crm/product/components/data.ts
index ece3e43..2892603 100644
--- a/src/views/crm/product/components/data.ts
+++ b/src/views/crm/product/components/data.ts
@@ -1,31 +1,29 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-
/** 浜у搧璇︽儏鍒楄〃鐨勫垪瀹氫箟 */
export function useDetailListColumns(
showBusinessPrice: boolean,
): VxeTableGridOptions['columns'] {
return [
{
- field: 'productName',
+ field: 'itemName',
title: '浜у搧鍚嶇О',
},
{
- field: 'productNo',
- title: '浜у搧鏉$爜',
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
},
{
- field: 'productUnit',
- title: '浜у搧鍗曚綅',
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.CRM_PRODUCT_UNIT },
- },
+ field: 'itemBarCode',
+ title: '鐗╂枡鏉$爜',
},
{
- field: 'productPrice',
- title: '浜у搧浠锋牸锛堝厓锛�',
+ field: 'itemUnitName',
+ title: '鍗曚綅',
+ },
+ {
+ field: 'itemPrice',
+ title: '鍘熶环锛堝厓锛�',
formatter: 'formatAmount2',
},
{
@@ -58,36 +56,47 @@
return [
{ type: 'seq', title: '搴忓彿', minWidth: 50 },
{
- field: 'productId',
+ field: 'itemId',
title: '浜у搧鍚嶇О',
minWidth: 100,
- slots: { default: 'productId' },
+ slots: { default: 'itemId' },
},
{
- field: 'productNo',
+ field: 'itemSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'itemBarCode',
title: '鏉$爜',
minWidth: 150,
},
{
- field: 'productUnit',
+ field: 'itemUnitName',
title: '鍗曚綅',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.CRM_PRODUCT_UNIT },
- },
+ minWidth: 80,
},
{
- field: 'productPrice',
- title: '浠锋牸锛堝厓锛�',
+ field: 'itemUnitName2',
+ title: '杈呭崟浣�1',
+ minWidth: 80,
+ },
+ {
+ field: 'itemUnitName3',
+ title: '杈呭崟浣�2',
+ minWidth: 80,
+ },
+ {
+ field: 'itemPrice',
+ title: '鍘熶环锛堝厓锛�',
minWidth: 100,
formatter: 'formatAmount2',
},
{
- field: 'sellingPrice',
- title: '鍞环锛堝厓锛�',
+ field: 'contractPrice',
+ title: '鍚堝悓浠凤紙鍏冿級',
minWidth: 100,
- slots: { default: 'sellingPrice' },
+ slots: { default: 'contractPrice' },
},
{
field: 'count',
diff --git a/src/views/crm/product/components/edit-table.vue b/src/views/crm/product/components/edit-table.vue
index 3866712..55d608d 100644
--- a/src/views/crm/product/components/edit-table.vue
+++ b/src/views/crm/product/components/edit-table.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { CrmBusinessApi } from '#/api/crm/business';
import type { CrmContractApi } from '#/api/crm/contract';
-import type { CrmProductApi } from '#/api/crm/product';
+import type { MdmItemApi } from '#/api/mdm/item';
import { nextTick, onMounted, ref, watch } from 'vue';
@@ -11,7 +11,7 @@
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { BizTypeEnum } from '#/api/crm/permission';
-import { getProductSimpleList } from '#/api/crm/product';
+import { getItemPage } from '#/api/mdm/item';
import { $t } from '#/locales';
import { useProductEditTableColumns } from './data';
@@ -39,34 +39,36 @@
}
/** 鍒囨崲浜у搧鏃跺悓姝ュ熀纭�淇℃伅 */
-function handleProductChange(productId: any, row: any) {
- const product = productOptions.value.find((p) => p.id === productId);
+function handleProductChange(itemId: any, row: any) {
+ const product = productOptions.value.find((p) => p.id === itemId);
if (!product) {
return;
}
- row.productUnit = product.unit;
- row.productNo = product.no;
- row.productPrice = product.price;
- row.sellingPrice = product.price;
- row.count = 0;
- row.totalPrice = 0;
+ row.itemId = itemId;
+ row.itemName = product.name;
+ row.itemCode = product.code;
+ row.itemBarCode = product.barCode;
+ row.itemUnitName = product.unitMeasureName;
+ row.itemUnitName2 = product.unitMeasureName2;
+ row.itemUnitName3 = product.unitMeasureName3;
+ row.itemSpecification = product.specification;
+ row.itemPrice = product.salesPrice;
+ row.contractPrice = product.salesPrice;
+ row.count = row.count || 1;
+ row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
handleUpdateValue(row);
}
/** 閲戦鍙樺姩鏃堕噸鏂拌绠楀悎璁� */
function handlePriceChange(row: any) {
- row.totalPrice = erpPriceMultiply(row.sellingPrice, row.count) ?? 0;
+ row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
handleUpdateValue(row);
}
/** 灏嗘渶鏂版暟鎹啓鍥炲苟閫氱煡鐖剁粍浠� */
function handleUpdateValue(row: any) {
const index = tableData.value.findIndex((item) => item.id === row.id);
- if (props.bizType === BizTypeEnum.CRM_BUSINESS) {
- row.businessPrice = row.sellingPrice;
- } else if (props.bizType === BizTypeEnum.CRM_CONTRACT) {
- row.contractPrice = row.sellingPrice;
- }
+ row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
if (index === -1) {
row.id = tableData.value.length + 1;
tableData.value.push(row);
@@ -110,15 +112,6 @@
}
await nextTick();
tableData.value = products;
- if (props.bizType === BizTypeEnum.CRM_BUSINESS) {
- tableData.value.forEach((item) => {
- item.sellingPrice = item.businessPrice;
- });
- } else if (props.bizType === BizTypeEnum.CRM_CONTRACT) {
- tableData.value.forEach((item) => {
- item.sellingPrice = item.contractPrice;
- });
- }
await gridApi.grid.reloadData(tableData.value);
},
{
@@ -127,26 +120,27 @@
);
/** 鍒濆鍖� */
-const productOptions = ref<CrmProductApi.Product[]>([]); // 浜у搧涓嬫媺閫夐」
+const productOptions = ref<MdmItemApi.Item[]>([]); // 鐗╂枡涓嬫媺閫夐」
onMounted(async () => {
- productOptions.value = await getProductSimpleList();
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
});
</script>
<template>
<Grid class="w-full">
- <template #productId="{ row }">
+ <template #itemId="{ row }">
<Select
- v-model:value="row.productId"
+ v-model:value="row.itemId"
:options="productOptions"
:field-names="{ label: 'name', value: 'id' }"
class="w-full"
@change="handleProductChange($event, row)"
/>
</template>
- <template #sellingPrice="{ row }">
+ <template #contractPrice="{ row }">
<InputNumber
- v-model:value="row.sellingPrice"
+ v-model:value="row.contractPrice"
:min="0.001"
:precision="2"
@change="handlePriceChange(row)"
@@ -180,7 +174,7 @@
type: 'link',
danger: true,
popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
confirm: handleDelete.bind(null, row),
},
},
diff --git a/src/views/crm/report/index.vue b/src/views/crm/report/index.vue
new file mode 100644
index 0000000..b66cd17
--- /dev/null
+++ b/src/views/crm/report/index.vue
@@ -0,0 +1,174 @@
+<script lang="ts" setup>
+import type { CrmReportApi } from '#/api/crm/report';
+
+import { computed, onMounted, ref } from 'vue';
+
+import { Page } from '../../../packages/effects/common-ui/src';
+
+import { Card, Col, DatePicker, Row, Statistic, Table } from 'ant-design-vue';
+import dayjs from 'dayjs';
+
+import {
+ getBusinessAnalyze,
+ getContractAnalyze,
+ getCustomerAnalyze,
+ getSalesFunnel,
+ getSalesPerformance,
+} from '#/api/crm/report';
+
+defineOptions({ name: 'CrmReportDashboard' });
+
+const loading = ref(false);
+const dateRange = ref<[string, string] | null>(null);
+
+const customerAnalyze = ref<CrmReportApi.CustomerAnalyze>();
+const businessAnalyze = ref<CrmReportApi.BusinessAnalyze>();
+const contractAnalyze = ref<CrmReportApi.ContractAnalyze>();
+const salesFunnel = ref<CrmReportApi.SalesFunnel>();
+const salesPerformance = ref<CrmReportApi.SalesPerformance>();
+
+const startTime = computed(() =>
+ dateRange.value?.[0] ? dayjs(dateRange.value[0]).format('YYYY-MM-DD') : undefined,
+);
+const endTime = computed(() =>
+ dateRange.value?.[1] ? dayjs(dateRange.value[1]).format('YYYY-MM-DD') : undefined,
+);
+
+const performanceColumns = [
+ { title: '濮撳悕', dataIndex: 'userName', width: 120 },
+ { title: '閮ㄩ棬', dataIndex: 'deptName', width: 150 },
+ { title: '鍚堝悓鏁伴噺', dataIndex: 'contractCount', width: 100 },
+ { title: '鍚堝悓閲戦', dataIndex: 'contractPrice', width: 120, formatter: 'formatAmount2' },
+ { title: '鍥炴閲戦', dataIndex: 'receivablePrice', width: 120, formatter: 'formatAmount2' },
+ { title: '鍟嗘満鏁伴噺', dataIndex: 'businessCount', width: 100 },
+ { title: '鍟嗘満閲戦', dataIndex: 'businessPrice', width: 120, formatter: 'formatAmount2' },
+];
+
+async function loadData() {
+ loading.value = true;
+ try {
+ const [customer, business, contract, funnel, performance] = await Promise.all([
+ getCustomerAnalyze(startTime.value, endTime.value),
+ getBusinessAnalyze(startTime.value, endTime.value),
+ getContractAnalyze(startTime.value, endTime.value),
+ getSalesFunnel(),
+ getSalesPerformance(startTime.value, endTime.value),
+ ]);
+ customerAnalyze.value = customer;
+ businessAnalyze.value = business;
+ contractAnalyze.value = contract;
+ salesFunnel.value = funnel;
+ salesPerformance.value = performance;
+ } finally {
+ loading.value = false;
+ }
+}
+
+function handleDateChange() {
+ loadData();
+}
+
+onMounted(() => {
+ loadData();
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <div class="p-4">
+ <Card title="CRM 鏁版嵁姒傝" class="mb-4">
+ <div class="mb-4">
+ <DatePicker.RangePicker
+ v-model:value="dateRange"
+ :placeholder="['寮�濮嬫棩鏈�', '缁撴潫鏃ユ湡']"
+ format="YYYY-MM-DD"
+ @change="handleDateChange"
+ />
+ </div>
+ </Card>
+
+ <Row :gutter="16" class="mb-4">
+ <Col :span="6">
+ <Card title="瀹㈡埛鍒嗘瀽" :loading="loading">
+ <Statistic title="鎬诲鎴锋暟" :value="customerAnalyze?.totalCount || 0" />
+ <Statistic title="浠婃棩鏂板" :value="customerAnalyze?.todayCount || 0" class="mt-2" />
+ <Statistic title="鎴愪氦瀹㈡埛" :value="customerAnalyze?.dealCount || 0" class="mt-2" />
+ <Statistic
+ title="鎴愪氦鐜�"
+ :value="customerAnalyze?.dealRate || 0"
+ :precision="2"
+ suffix="%"
+ class="mt-2"
+ />
+ </Card>
+ </Col>
+ <Col :span="6">
+ <Card title="鍟嗘満鍒嗘瀽" :loading="loading">
+ <Statistic title="鎬诲晢鏈烘暟" :value="businessAnalyze?.totalCount || 0" />
+ <Statistic title="鍟嗘満閲戦" :value="businessAnalyze?.totalPrice || 0" :precision="2" class="mt-2" />
+ <Statistic title="鎴愬姛鍟嗘満" :value="businessAnalyze?.successCount || 0" class="mt-2" />
+ <Statistic
+ title="鎴愬姛鐜�"
+ :value="businessAnalyze?.successRate || 0"
+ :precision="2"
+ suffix="%"
+ class="mt-2"
+ />
+ </Card>
+ </Col>
+ <Col :span="6">
+ <Card title="鍚堝悓鍒嗘瀽" :loading="loading">
+ <Statistic title="鎬诲悎鍚屾暟" :value="contractAnalyze?.totalCount || 0" />
+ <Statistic title="鍚堝悓閲戦" :value="contractAnalyze?.totalPrice || 0" :precision="2" class="mt-2" />
+ <Statistic title="瀹℃壒涓�" :value="contractAnalyze?.processCount || 0" class="mt-2" />
+ <Statistic
+ title="鍥炴鐜�"
+ :value="contractAnalyze?.receivableRate || 0"
+ :precision="2"
+ suffix="%"
+ class="mt-2"
+ />
+ </Card>
+ </Col>
+ <Col :span="6">
+ <Card title="寰呭鐞�" :loading="loading">
+ <Statistic title="瀹㈡埛鍏捣" :value="customerAnalyze?.poolCount || 0" />
+ <Statistic title="閿佸畾瀹㈡埛" :value="customerAnalyze?.lockCount || 0" class="mt-2" />
+ <Statistic title="杩涜涓晢鏈�" :value="businessAnalyze?.processingCount || 0" class="mt-2" />
+ <Statistic title="澶辫触鍟嗘満" :value="businessAnalyze?.failCount || 0" class="mt-2" />
+ </Card>
+ </Col>
+ </Row>
+
+ <Row :gutter="16">
+ <Col :span="12">
+ <Card title="閿�鍞紡鏂�" :loading="loading">
+ <Table
+ :dataSource="salesFunnel?.stages || []"
+ :columns="[
+ { title: '闃舵', dataIndex: 'name', width: 150 },
+ { title: '鏁伴噺', dataIndex: 'count', width: 100 },
+ { title: '閲戦', dataIndex: 'price', width: 120 },
+ { title: '杞寲鐜�', dataIndex: 'conversionRate', width: 100, customRender: ({ text }) => `${text}%` },
+ ]"
+ :pagination="false"
+ size="small"
+ rowKey="name"
+ />
+ </Card>
+ </Col>
+ <Col :span="12">
+ <Card title="閿�鍞笟缁�" :loading="loading">
+ <Table
+ :dataSource="salesPerformance?.items || []"
+ :columns="performanceColumns"
+ :pagination="false"
+ size="small"
+ rowKey="userId"
+ />
+ </Card>
+ </Col>
+ </Row>
+ </div>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/crm/saleQuotation/data.ts b/src/views/crm/saleQuotation/data.ts
new file mode 100644
index 0000000..ca86211
--- /dev/null
+++ b/src/views/crm/saleQuotation/data.ts
@@ -0,0 +1,413 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { erpPriceMultiply } from '#/packages/utils/src';
+
+import { z } from '#/adapter/form';
+import { getSimpleBusinessList } from '#/api/crm/business';
+import { getSimpleContactList } from '#/api/crm/contact';
+import { getCustomerSimpleList } from '#/api/crm/customer';
+import { getItemPage } from '#/api/mdm/item';
+import { getSimpleUserList } from '#/api/system/user';
+import { useUserStore } from '#/packages/stores/src';
+
+/** 鎶ヤ环鍗曠姸鎬� */
+export const QUOTATION_STATUS = {
+ DRAFT: 0, // 鑽夌
+ CONVERTED: 10, // 宸茶浆鍚堝悓
+};
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ const userStore = useUserStore();
+ return [
+ {
+ component: 'Input',
+ fieldName: 'id',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'no',
+ label: '鎶ヤ环鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '淇濆瓨鏃惰嚜鍔ㄧ敓鎴�',
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鎶ヤ环鍗曞悕绉�',
+ component: 'Input',
+ rules: 'required',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ姤浠峰崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'ownerUserId',
+ label: '璐熻矗浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ valueField: 'id',
+ },
+ defaultValue: userStore.userInfo?.id,
+ rules: 'required',
+ },
+ {
+ fieldName: 'customerId',
+ label: '瀹㈡埛鍚嶇О',
+ component: 'ApiSelect',
+ rules: 'required',
+ componentProps: {
+ api: getCustomerSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'businessId',
+ label: '鍟嗘満鍚嶇О',
+ component: 'Select',
+ componentProps: {
+ options: [],
+ placeholder: '璇烽�夋嫨鍟嗘満',
+ allowClear: true,
+ },
+ dependencies: {
+ triggerFields: ['customerId'],
+ disabled: (values) => !values.customerId,
+ async componentProps(values) {
+ if (!values.customerId) {
+ return { options: [], placeholder: '璇烽�夋嫨瀹㈡埛' };
+ }
+ const res = await getSimpleBusinessList();
+ const list = res.filter((item) => item.customerId === values.customerId);
+ return {
+ options: list.map((item) => ({ label: item.name, value: item.id })),
+ placeholder: '璇烽�夋嫨鍟嗘満',
+ };
+ },
+ },
+ },
+ {
+ fieldName: 'contactId',
+ label: '鑱旂郴浜�',
+ component: 'Select',
+ componentProps: {
+ options: [],
+ placeholder: '璇烽�夋嫨鑱旂郴浜�',
+ allowClear: true,
+ },
+ dependencies: {
+ triggerFields: ['customerId'],
+ disabled: (values) => !values.customerId,
+ async componentProps(values) {
+ if (!values.customerId) {
+ return { options: [], placeholder: '璇烽�夋嫨瀹㈡埛' };
+ }
+ const res = await getSimpleContactList();
+ const list = res.filter((item) => item.customerId === values.customerId);
+ return {
+ options: list.map((item) => ({ label: item.name, value: item.id })),
+ placeholder: '璇烽�夋嫨鑱旂郴浜�',
+ };
+ },
+ },
+ },
+ {
+ fieldName: 'quotationTime',
+ label: '鎶ヤ环鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ showTime: false,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'x',
+ placeholder: '璇烽�夋嫨鎶ヤ环鏃ユ湡',
+ },
+ },
+ {
+ fieldName: 'validUntil',
+ label: '鏈夋晥鏈熻嚦',
+ component: 'DatePicker',
+ componentProps: {
+ showTime: false,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'x',
+ placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+ },
+ },
+ {
+ fieldName: 'taxRate',
+ label: '绋庣巼(%)',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ max: 100,
+ precision: 2,
+ placeholder: '璇疯緭鍏ョ◣鐜�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ {
+ fieldName: 'items',
+ label: '鐗╂枡娓呭崟',
+ component: 'Input',
+ formItemClass: 'col-span-3',
+ },
+ {
+ fieldName: 'totalProductPrice',
+ label: '鐗╂枡鎬婚噾棰�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ disabled: true,
+ },
+ rules: z.number().min(0).optional().default(0),
+ },
+ {
+ fieldName: 'discountPercent',
+ label: '鏁村崟鎶樻墸锛�%锛�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ユ姌鎵�',
+ },
+ rules: z.number().min(0).max(100).optional().default(0),
+ },
+ {
+ fieldName: 'totalPrice',
+ label: '鎶樻墸鍚庨噾棰�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['totalProductPrice', 'discountPercent'],
+ trigger(values, form) {
+ const discountPrice =
+ erpPriceMultiply(values.totalProductPrice, values.discountPercent / 100) ?? 0;
+ form.setFieldValue('totalPrice', values.totalProductPrice - discountPrice);
+ },
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'no',
+ label: '鎶ヤ环鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ姤浠峰崟鍙�',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鎶ヤ环鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ姤浠峰崟鍚嶇О',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'customerId',
+ label: '瀹㈡埛',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getCustomerSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐘舵��',
+ allowClear: true,
+ options: [
+ { label: '鑽夌', value: 0 },
+ { label: '宸茶浆鍚堝悓', value: 10 },
+ ],
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ fixed: 'left',
+ },
+ {
+ field: 'no',
+ title: '鎶ヤ环鍗曞彿',
+ minWidth: 180,
+ fixed: 'left',
+ },
+ {
+ field: 'name',
+ title: '鎶ヤ环鍗曞悕绉�',
+ minWidth: 160,
+ fixed: 'left',
+ },
+ {
+ field: 'customerName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'businessName',
+ title: '鍟嗘満鍚嶇О',
+ minWidth: 130,
+ },
+ {
+ field: 'totalPrice',
+ title: '鎶ヤ环閲戦锛堝厓锛�',
+ minWidth: 140,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'quotationTime',
+ title: '鎶ヤ环鏃ユ湡',
+ minWidth: 120,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'validUntil',
+ title: '鏈夋晥鏈熻嚦',
+ minWidth: 120,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ minWidth: 100,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'contractNo',
+ title: '鍏宠仈鍚堝悓',
+ minWidth: 150,
+ },
+ {
+ field: 'ownerUserName',
+ title: '璐熻矗浜�',
+ minWidth: 120,
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ minWidth: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 260,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鐗╂枡鏄庣粏琛ㄦ牸鍒� */
+export function useItemColumns(): VxeTableGridOptions['columns'] {
+ return [
+ { type: 'seq', title: '搴忓彿', minWidth: 50, fixed: 'left' },
+ {
+ field: 'itemId',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 200,
+ slots: { default: 'itemId' },
+ },
+ {
+ field: 'itemSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'itemBarCode',
+ title: '鏉$爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemUnitName',
+ title: '鍗曚綅',
+ minWidth: 80,
+ },
+ {
+ field: 'itemUnitName2',
+ title: '杈呭崟浣�1',
+ minWidth: 80,
+ },
+ {
+ field: 'itemUnitName3',
+ title: '杈呭崟浣�2',
+ minWidth: 80,
+ },
+ {
+ field: 'itemPrice',
+ title: '鍘熶环锛堝厓锛�',
+ minWidth: 100,
+ formatter: 'formatAmount2',
+ },
+ {
+ field: 'quotationPrice',
+ title: '鎶ヤ环锛堝厓锛�',
+ minWidth: 100,
+ slots: { default: 'quotationPrice' },
+ },
+ {
+ field: 'count',
+ title: '鏁伴噺',
+ minWidth: 100,
+ slots: { default: 'count' },
+ },
+ {
+ field: 'totalPrice',
+ title: '鍚堣',
+ minWidth: 100,
+ formatter: 'formatAmount2',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 50,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/crm/saleQuotation/index.vue b/src/views/crm/saleQuotation/index.vue
new file mode 100644
index 0000000..abd5c66
--- /dev/null
+++ b/src/views/crm/saleQuotation/index.vue
@@ -0,0 +1,210 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation';
+
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { Button, message, Modal, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ convertSaleQuotationToContract,
+ deleteSaleQuotation,
+ getSaleQuotationPage,
+} from '#/api/crm/saleQuotation';
+import { $t } from '#/locales';
+
+import { QUOTATION_STATUS, useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const { push } = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鎶ヤ环鍗� */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鎶ヤ环鍗� */
+function handleEdit(row: CrmSaleQuotationApi.SaleQuotation) {
+ formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鎶ヤ环鍗� */
+async function handleDelete(row: CrmSaleQuotationApi.SaleQuotation) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteSaleQuotation(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅鎶ヤ环鍗曡鎯� */
+function handleDetail(row: CrmSaleQuotationApi.SaleQuotation) {
+ push({ name: 'CrmSaleQuotationDetail', params: { id: row.id } });
+}
+
+/** 鏌ョ湅瀹㈡埛璇︽儏 */
+function handleCustomerDetail(row: CrmSaleQuotationApi.SaleQuotation) {
+ push({ name: 'CrmCustomerDetail', params: { id: row.customerId } });
+}
+
+/** 鏌ョ湅鍟嗘満璇︽儏 */
+function handleBusinessDetail(row: CrmSaleQuotationApi.SaleQuotation) {
+ if (row.businessId) {
+ push({ name: 'CrmBusinessDetail', params: { id: row.businessId } });
+ }
+}
+
+/** 鏌ョ湅鍚堝悓璇︽儏 */
+function handleContractDetail(row: CrmSaleQuotationApi.SaleQuotation) {
+ if (row.contractId) {
+ push({ name: 'CrmContractDetail', params: { id: row.contractId } });
+ }
+}
+
+/** 杞负鍚堝悓 */
+async function handleConvertContract(row: CrmSaleQuotationApi.SaleQuotation) {
+ Modal.confirm({
+ title: '纭杞悎鍚�',
+ content: '纭灏嗚鎶ヤ环鍗曡浆涓哄悎鍚岋紵',
+ async onOk() {
+ const contractId = await convertSaleQuotationToContract(row.id!);
+ message.success('杞悎鍚屾垚鍔�');
+ push({ name: 'CrmContractDetail', params: { id: contractId } });
+ },
+ });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSaleQuotationPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<CrmSaleQuotationApi.SaleQuotation>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid>
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鎶ヤ环鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['crm:sale-quotation:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #name="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.name }}
+ </Button>
+ </template>
+ <template #customerName="{ row }">
+ <Button type="link" @click="handleCustomerDetail(row)">
+ {{ row.customerName }}
+ </Button>
+ </template>
+ <template #businessName="{ row }">
+ <Button v-if="row.businessId" type="link" @click="handleBusinessDetail(row)">
+ {{ row.businessName }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ <template #contractNo="{ row }">
+ <Button v-if="row.contractId" type="link" @click="handleContractDetail(row)">
+ {{ row.contractNo }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ <template #status="{ row }">
+ <Tag v-if="row.status === QUOTATION_STATUS.DRAFT" color="default">鑽夌</Tag>
+ <Tag v-if="row.status === QUOTATION_STATUS.CONVERTED" color="success">宸茶浆鍚堝悓</Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['crm:sale-quotation:update'],
+ onClick: handleEdit.bind(null, row),
+ ifShow: row.status === QUOTATION_STATUS.DRAFT,
+ },
+ {
+ label: '杞悎鍚�',
+ type: 'link',
+ auth: ['crm:sale-quotation:update'],
+ onClick: handleConvertContract.bind(null, row),
+ ifShow: row.status === QUOTATION_STATUS.DRAFT && !row.contractId,
+ },
+ {
+ label: '鏌ョ湅鍚堝悓',
+ type: 'link',
+ onClick: handleContractDetail.bind(null, row),
+ ifShow: !!row.contractId,
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ auth: ['crm:sale-quotation:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ ifShow: row.status === QUOTATION_STATUS.DRAFT,
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/crm/saleQuotation/modules/form.vue b/src/views/crm/saleQuotation/modules/form.vue
new file mode 100644
index 0000000..8fa938f
--- /dev/null
+++ b/src/views/crm/saleQuotation/modules/form.vue
@@ -0,0 +1,124 @@
+<script lang="ts" setup>
+import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation';
+
+import { computed, ref, watch } from 'vue';
+
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+import { erpPriceMultiply } from '#/packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createSaleQuotation,
+ getSaleQuotation,
+ updateSaleQuotation,
+} from '#/api/crm/saleQuotation';
+import { $t } from '#/locales';
+
+import ItemForm from './item-form.vue';
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<CrmSaleQuotationApi.SaleQuotation>();
+const itemFormRef = ref();
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鎶ヤ环鍗�'])
+ : $t('ui.actionTitle.create', ['鎶ヤ环鍗�']);
+});
+
+const isEdit = computed(() => !!formData.value?.id);
+const isDisabled = computed(() => isEdit.value && formData.value?.status !== 0);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'vertical',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+/** 璁$畻鐗╂枡鎬婚噾棰� */
+async function calculateTotalPrice() {
+ const items = itemFormRef.value?.getData() || [];
+ const totalProductPrice = items.reduce(
+ (prev: number, curr: any) => prev + (curr.totalPrice || 0),
+ 0,
+ );
+ await formApi.setFieldValue('totalProductPrice', totalProductPrice);
+ const discountPercent = (await formApi.getValues())?.discountPercent || 0;
+ const discountPrice = erpPriceMultiply(totalProductPrice, discountPercent / 100) ?? 0;
+ await formApi.setFieldValue('totalPrice', totalProductPrice - discountPrice);
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ try {
+ await itemFormRef.value?.validate();
+ } catch (e: any) {
+ message.error(e.message);
+ return;
+ }
+ modalApi.lock();
+ const data = (await formApi.getValues()) as CrmSaleQuotationApi.SaleQuotation;
+ data.items = itemFormRef.value?.getData();
+ try {
+ await (formData.value?.id ? updateSaleQuotation(data) : createSaleQuotation(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<CrmSaleQuotationApi.SaleQuotation>();
+ if (!data || !data.id) {
+ formData.value = {} as CrmSaleQuotationApi.SaleQuotation;
+ await formApi.resetForm();
+ // 鏂板鏃讹紝閲嶇疆骞舵坊鍔犱竴琛岀┖鏁版嵁
+ itemFormRef.value?.resetData();
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getSaleQuotation(data.id);
+ await formApi.setValues(formData.value);
+ // 缂栬緫鏃惰缃墿鏂欐暟鎹�
+ itemFormRef.value?.setData(formData.value.items || []);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-[80%]">
+ <Form class="mx-4">
+ <template #items>
+ <ItemForm
+ ref="itemFormRef"
+ class="w-full"
+ :disabled="isDisabled"
+ @change="calculateTotalPrice"
+ />
+ </template>
+ </Form>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/crm/saleQuotation/modules/item-form.vue b/src/views/crm/saleQuotation/modules/item-form.vue
new file mode 100644
index 0000000..e3aacab
--- /dev/null
+++ b/src/views/crm/saleQuotation/modules/item-form.vue
@@ -0,0 +1,258 @@
+<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
+import type { CrmSaleQuotationApi } from '#/api/crm/saleQuotation';
+
+import { computed, onMounted, ref } from 'vue';
+
+import { erpPriceMultiply } from '#/packages/utils/src';
+
+import { InputNumber, Select } from 'ant-design-vue';
+
+import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getItemPage } from '#/api/mdm/item';
+
+import { useItemColumns } from '../data';
+
+interface Props {
+ disabled?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+ disabled: false,
+});
+
+const emit = defineEmits(['change']);
+
+const tableData = ref<CrmSaleQuotationApi.SaleQuotationItem[]>([]);
+const itemOptions = ref<MdmItemApi.Item[]>([]);
+
+/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
+const summaries = computed(() => {
+ return tableData.value.reduce(
+ (acc, item) => {
+ acc.count += item.count || 0;
+ acc.totalPrice += item.totalPrice || 0;
+ return acc;
+ },
+ { count: 0, totalPrice: 0 },
+ );
+});
+
+/** 琛ㄦ牸閰嶇疆 */
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useItemColumns(),
+ data: [],
+ minHeight: 250,
+ autoResize: true,
+ border: true,
+ rowConfig: {
+ keyField: 'seq',
+ isHover: true,
+ },
+ pagerConfig: {
+ enabled: false,
+ },
+ toolbarConfig: {
+ enabled: false,
+ },
+ },
+});
+
+/** 瑙﹀彂鍙樻洿浜嬩欢 */
+function triggerChange() {
+ emit('change');
+}
+
+/** 澶勭悊鏂板 */
+function handleAdd() {
+ tableData.value.push({
+ itemId: undefined,
+ itemName: undefined,
+ itemCode: undefined,
+ itemBarCode: undefined,
+ itemSpecification: undefined,
+ itemUnitName: undefined,
+ itemUnitName2: undefined,
+ itemUnitName3: undefined,
+ itemPrice: undefined,
+ quotationPrice: undefined,
+ count: 1,
+ totalPrice: 0,
+ });
+ gridApi.grid.loadData(tableData.value);
+}
+
+/** 澶勭悊鍒犻櫎 */
+function handleDelete(row: CrmSaleQuotationApi.SaleQuotationItem) {
+ const index = tableData.value.findIndex((item) => item.seq === row.seq);
+ if (index !== -1) {
+ tableData.value.splice(index, 1);
+ gridApi.grid.loadData(tableData.value);
+ triggerChange();
+ }
+}
+
+/** 澶勭悊鐗╂枡鍙樻洿 */
+function handleItemChange(itemId: number, row: any) {
+ const item = itemOptions.value.find((p) => p.id === itemId);
+ if (!item) {
+ return;
+ }
+ row.itemId = itemId;
+ row.itemName = item.name;
+ row.itemCode = item.code;
+ row.itemBarCode = item.barCode;
+ row.itemSpecification = item.specification;
+ row.itemUnitName = item.unitMeasureName;
+ row.itemUnitName2 = item.unitMeasureName2;
+ row.itemUnitName3 = item.unitMeasureName3;
+ row.itemPrice = item.salesPrice;
+ row.quotationPrice = item.salesPrice;
+ row.totalPrice = erpPriceMultiply(row.quotationPrice, row.count) ?? 0;
+ triggerChange();
+}
+
+/** 澶勭悊鎶ヤ环鍙樻洿 */
+function handleQuotationPriceChange(value: number, row: any) {
+ row.quotationPrice = value;
+ row.totalPrice = erpPriceMultiply(value, row.count) ?? 0;
+ triggerChange();
+}
+
+/** 澶勭悊鏁伴噺鍙樻洿 */
+function handleCountChange(value: number, row: any) {
+ row.count = value;
+ row.totalPrice = erpPriceMultiply(row.quotationPrice, value) ?? 0;
+ triggerChange();
+}
+
+/** 琛ㄥ崟鏍¢獙 */
+function validate() {
+ for (let i = 0; i < tableData.value.length; i++) {
+ const item = tableData.value[i];
+ if (item) {
+ if (!item.itemId) {
+ throw new Error(`绗� ${i + 1} 琛岋細鐗╂枡涓嶈兘涓虹┖`);
+ }
+ if (!item.count || item.count <= 0) {
+ throw new Error(`绗� ${i + 1} 琛岋細鏁伴噺涓嶈兘涓虹┖`);
+ }
+ if (!item.quotationPrice || item.quotationPrice <= 0) {
+ throw new Error(`绗� ${i + 1} 琛岋細鎶ヤ环涓嶈兘涓虹┖`);
+ }
+ }
+ }
+}
+
+/** 璁剧疆鏁版嵁锛堜緵鐖剁粍浠惰皟鐢級 */
+function setData(items: CrmSaleQuotationApi.SaleQuotationItem[]) {
+ items.forEach((item) => {
+ if (item.quotationPrice && item.count) {
+ item.totalPrice = erpPriceMultiply(item.quotationPrice, item.count) ?? 0;
+ }
+ });
+ tableData.value = [...items];
+ gridApi.grid.loadData(tableData.value);
+}
+
+/** 鑾峰彇鏁版嵁 */
+function getData() {
+ return [...tableData.value];
+}
+
+/** 閲嶇疆鏁版嵁 */
+function resetData() {
+ tableData.value = [];
+ handleAdd();
+}
+
+defineExpose({
+ validate,
+ setData,
+ getData,
+ resetData,
+});
+
+/** 鍒濆鍖� */
+onMounted(async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ itemOptions.value = res.list || [];
+});
+</script>
+
+<template>
+ <Grid class="w-full">
+ <template #itemId="{ row }">
+ <Select
+ v-if="!disabled"
+ v-model:value="row.itemId"
+ :options="itemOptions"
+ :field-names="{ label: 'name', value: 'id' }"
+ class="w-full"
+ placeholder="璇烽�夋嫨鐗╂枡"
+ @change="(val) => handleItemChange(val, row)"
+ />
+ <span v-else>{{ row.itemName || '-' }}</span>
+ </template>
+ <template #quotationPrice="{ row }">
+ <InputNumber
+ v-if="!disabled"
+ v-model:value="row.quotationPrice"
+ :min="0"
+ :precision="2"
+ @change="(val) => handleQuotationPriceChange(val, row)"
+ />
+ <span v-else>{{ row.quotationPrice || '-' }}</span>
+ </template>
+ <template #count="{ row }">
+ <InputNumber
+ v-if="!disabled"
+ v-model:value="row.count"
+ :min="0.001"
+ :precision="3"
+ @change="(val) => handleCountChange(val, row)"
+ />
+ <span v-else>{{ row.count || '-' }}</span>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ v-if="!disabled"
+ :actions="[
+ {
+ label: '鍒犻櫎',
+ type: 'link',
+ danger: true,
+ popConfirm: {
+ title: '纭鍒犻櫎璇ョ墿鏂欏悧锛�',
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+
+ <template #bottom>
+ <div class="mt-2 rounded border border-border bg-muted p-2">
+ <div class="flex justify-between text-sm text-muted-foreground">
+ <span class="font-medium text-foreground">鍚堣锛�</span>
+ <div class="flex space-x-4">
+ <span>鏁伴噺锛歿{ summaries.count }}</span>
+ <span>閲戦锛歿{ summaries.totalPrice.toFixed(2) }}鍏�</span>
+ </div>
+ </div>
+ </div>
+ <TableAction
+ v-if="!disabled"
+ class="mt-2 flex justify-center"
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'default',
+ onClick: handleAdd,
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+</template>
diff --git a/src/views/erp/purchase/request/data.ts b/src/views/erp/purchase/request/data.ts
index 34682d6..fb3c4ae 100644
--- a/src/views/erp/purchase/request/data.ts
+++ b/src/views/erp/purchase/request/data.ts
@@ -14,10 +14,11 @@
/** 鐘舵�佸父閲� */
export const PURCHASE_REQUEST_STATUS = {
- PENDING: 0, // 寰呭鎵�
- APPROVED: 1, // 宸插鎵�
- REJECTED: 2, // 宸叉嫆缁�
- CONVERTED: 3, // 宸茶浆璁㈠崟
+ DRAFT: 0, // 鏈彁浜わ紙鑽夌锛�
+ PROCESSING: 10, // 瀹℃壒涓�
+ APPROVED: 20, // 瀹℃牳閫氳繃
+ REJECTED: 30, // 瀹℃牳涓嶉�氳繃
+ CANCELLED: 40, // 宸插彇娑�
};
/** 琛ㄥ崟鐨勯厤缃」 */
@@ -54,7 +55,7 @@
rules: 'required',
},
{
- label: '渚涘簲鍟�',
+ label: '渚涘簲鍟嗭紙鍙�夛級',
fieldName: 'supplierId',
component: 'ApiSelect',
componentProps: {
@@ -68,12 +69,37 @@
},
},
{
- fieldName: 'reason',
+ fieldName: 'discountPercent',
+ label: '浼樻儬鐜�(%)',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ紭鎯犵巼',
+ min: 0,
+ max: 100,
+ precision: 2,
+ disabled: formType === 'detail',
+ },
+ },
+ {
+ fieldName: 'requestReason',
label: '鐢宠鐞嗙敱',
component: 'Textarea',
componentProps: {
placeholder: '璇疯緭鍏ョ敵璇风悊鐢�',
autoSize: { minRows: 1, maxRows: 1 },
+ disabled: formType === 'detail',
+ },
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'fileUrl',
+ label: '闄勪欢',
+ component: 'FileUpload',
+ componentProps: {
+ placeholder: '璇蜂笂浼犻檮浠�',
+ maxNumber: 5,
+ maxSize: 20,
+ accept: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png'],
disabled: formType === 'detail',
},
formItemClass: 'col-span-2',
@@ -121,18 +147,18 @@
minWidth: 80,
},
{
- field: 'requestCount',
- title: '鐢宠鏁伴噺',
+ field: 'count',
+ title: '鏁伴噺',
minWidth: 120,
fixed: 'right',
- slots: { default: 'requestCount' },
+ slots: { default: 'count' },
},
{
- field: 'estimatedPrice',
- title: '棰勪及鍗曚环',
+ field: 'productPrice',
+ title: '鍗曚环锛堝弬鑰冧环锛�',
minWidth: 120,
fixed: 'right',
- slots: { default: 'estimatedPrice' },
+ slots: { default: 'productPrice' },
},
{
field: 'totalPrice',
@@ -142,11 +168,18 @@
formatter: 'formatAmount2',
},
{
- field: 'requiredDate',
+ field: 'taxPercent',
+ title: '绋庣巼(%)',
+ minWidth: 100,
+ fixed: 'right',
+ slots: { default: 'taxPercent' },
+ },
+ {
+ field: 'demandTime',
title: '闇�姹傛棩鏈�',
minWidth: 140,
fixed: 'right',
- slots: { default: 'requiredDate' },
+ slots: { default: 'demandTime' },
},
{
field: 'remark',
@@ -199,7 +232,7 @@
},
},
{
- fieldName: 'requesterId',
+ fieldName: 'requestUserId',
label: '鐢宠浜�',
component: 'ApiSelect',
componentProps: {
@@ -217,10 +250,11 @@
component: 'Select',
componentProps: {
options: [
- { label: '寰呭鎵�', value: 0 },
- { label: '宸插鎵�', value: 1 },
- { label: '宸叉嫆缁�', value: 2 },
- { label: '宸茶浆璁㈠崟', value: 3 },
+ { label: '鏈彁浜�', value: 0 },
+ { label: '瀹℃壒涓�', value: 10 },
+ { label: '瀹℃牳閫氳繃', value: 20 },
+ { label: '瀹℃牳涓嶉�氳繃', value: 30 },
+ { label: '宸插彇娑�', value: 40 },
],
placeholder: '璇烽�夋嫨鐘舵��',
allowClear: true,
@@ -255,15 +289,37 @@
formatter: 'formatDate',
},
{
- field: 'requesterName',
+ field: 'requestUserName',
title: '鐢宠浜�',
minWidth: 120,
},
{
- field: 'estimatedAmount',
+ field: 'requestDeptName',
+ title: '鐢宠閮ㄩ棬',
+ minWidth: 120,
+ },
+ {
+ field: 'totalCount',
+ title: '鐢宠鏁伴噺',
+ minWidth: 120,
+ formatter: 'formatAmount3',
+ },
+ {
+ field: 'totalPrice',
title: '棰勪及閲戦',
formatter: 'formatAmount2',
minWidth: 120,
+ },
+ {
+ field: 'productNames',
+ title: '鐢宠浜у搧',
+ minWidth: 150,
+ showOverflow: 'tooltip',
+ },
+ {
+ field: 'orderNo',
+ title: '閲囪喘璁㈠崟',
+ minWidth: 180,
},
{
field: 'status',
@@ -272,7 +328,7 @@
slots: { default: 'status' },
},
{
- field: 'reason',
+ field: 'requestReason',
title: '鐢宠鐞嗙敱',
minWidth: 150,
showOverflow: 'tooltip',
@@ -285,7 +341,7 @@
},
{
title: '鎿嶄綔',
- width: 200,
+ width: 240,
fixed: 'right',
slots: { default: 'actions' },
},
diff --git a/src/views/erp/purchase/request/index.vue b/src/views/erp/purchase/request/index.vue
index a3cd39c..1eebbc5 100644
--- a/src/views/erp/purchase/request/index.vue
+++ b/src/views/erp/purchase/request/index.vue
@@ -4,29 +4,41 @@
import { ref } from 'vue';
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
import { message, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
- approvePurchaseRequest,
- convertToPurchaseOrder,
deletePurchaseRequest,
exportPurchaseRequest,
+ getApproveProcessList,
getPurchaseRequestPage,
+ submitPurchaseRequest,
} from '#/api/erp/purchase/request';
import { $t } from '#/locales';
-import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data';
+import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+import OrderForm from './modules/order-form.vue';
+import ProcessSelectModal from './modules/process-select-modal.vue';
/** ERP 閲囪喘鐢宠鍒楄〃 */
defineOptions({ name: 'ErpPurchaseRequest' });
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [OrderFormModal, orderFormModalApi] = useVbenModal({
+ connectedComponent: OrderForm,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
destroyOnClose: true,
});
@@ -66,34 +78,29 @@
}
}
-/** 瀹℃壒閲囪喘鐢宠 */
-async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) {
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) {
const hideLoading = message.loading({
- content: `纭畾${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}璇ョ敵璇峰悧锛焋,
+ content: '鑾峰彇瀹℃壒娴佺▼...',
duration: 0,
});
try {
- await approvePurchaseRequest({ id: row.id!, status, remark: '' });
- message.success(`${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}鎴愬姛`);
- handleRefresh();
- } finally {
+ const processList = await getApproveProcessList(row.id!);
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ // 鎵撳紑娴佺▼閫夋嫨寮圭獥
+ processModalApi.setData({ id: row.id, processList }).open();
+ } catch {
hideLoading();
}
}
-/** 杞负閲囪喘璁㈠崟 */
-async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) {
- const hideLoading = message.loading({
- content: '姝e湪杞负閲囪喘璁㈠崟...',
- duration: 0,
- });
- try {
- await convertToPurchaseOrder(row.id!);
- message.success('宸叉垚鍔熻浆涓洪噰璐鍗�');
- handleRefresh();
- } finally {
- hideLoading();
- }
+/** 鐢熸垚閲囪喘璁㈠崟 */
+function handleGenerateOrder(row: ErpPurchaseRequestApi.PurchaseRequest) {
+ orderFormModalApi.setData({ id: row.id, supplierId: row.supplierId }).open();
}
const checkedIds = ref<number[]>([]);
@@ -148,6 +155,8 @@
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
+ <OrderFormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
<Grid table-title="閲囪喘鐢宠鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -182,10 +191,11 @@
/>
</template>
<template #status="{ row }">
- <Tag v-if="row.status === 0" color="warning">寰呭鎵�</Tag>
- <Tag v-else-if="row.status === 1" color="success">宸插鎵�</Tag>
- <Tag v-else-if="row.status === 2" color="error">宸叉嫆缁�</Tag>
- <Tag v-else-if="row.status === 3" color="default">宸茶浆璁㈠崟</Tag>
+ <Tag v-if="row.status === 0" color="default">鏈彁浜�</Tag>
+ <Tag v-else-if="row.status === 10" color="processing">瀹℃壒涓�</Tag>
+ <Tag v-else-if="row.status === 20" color="success">瀹℃牳閫氳繃</Tag>
+ <Tag v-else-if="row.status === 30" color="error">瀹℃牳涓嶉�氳繃</Tag>
+ <Tag v-else-if="row.status === 40" color="warning">宸插彇娑�</Tag>
</template>
<template #actions="{ row }">
<TableAction
@@ -206,35 +216,17 @@
onClick: handleEdit.bind(null, row),
},
{
- label: '瀹℃壒閫氳繃',
+ label: '鎻愪氦',
type: 'link',
- auth: ['erp:purchase-request:approve'],
- ifShow: () => row.status === 0,
- popConfirm: {
- title: '纭瀹℃壒閫氳繃璇ョ敵璇峰悧锛�',
- confirm: handleApprove.bind(null, row, 1),
- },
+ ifShow: () => row.status === 0 || row.status === 30,
+ onClick: handleSubmit.bind(null, row),
},
{
- label: '瀹℃壒鎷掔粷',
+ label: '鐢熸垚璁㈠崟',
type: 'link',
- danger: true,
- auth: ['erp:purchase-request:approve'],
- ifShow: () => row.status === 0,
- popConfirm: {
- title: '纭瀹℃壒鎷掔粷璇ョ敵璇峰悧锛�',
- confirm: handleApprove.bind(null, row, 2),
- },
- },
- {
- label: '杞鍗�',
- type: 'link',
- auth: ['erp:purchase-request:convert'],
- ifShow: () => row.status === 1,
- popConfirm: {
- title: '纭杞负閲囪喘璁㈠崟鍚楋紵',
- confirm: handleConvert.bind(null, row),
- },
+ auth: ['erp:purchase-request:generate-order'],
+ ifShow: () => row.status === 20 && !row.orderId,
+ onClick: handleGenerateOrder.bind(null, row),
},
{
label: $t('common.delete'),
@@ -253,4 +245,4 @@
</template>
</Grid>
</Page>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/erp/purchase/request/modules/form.vue b/src/views/erp/purchase/request/modules/form.vue
index 4bf6954..ab656c1 100644
--- a/src/views/erp/purchase/request/modules/form.vue
+++ b/src/views/erp/purchase/request/modules/form.vue
@@ -7,7 +7,7 @@
import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
-import { message, Tag } from 'ant-design-vue';
+import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
@@ -17,7 +17,7 @@
} from '#/api/erp/purchase/request';
import { $t } from '#/locales';
-import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data';
+import { useFormSchema } from '../data';
import PurchaseRequestItemForm from './item-form.vue';
const emit = defineEmits(['success']);
@@ -78,8 +78,13 @@
const data =
(await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest;
data.items = formData.value?.items?.map((item) => ({
- ...item,
- id: undefined,
+ productId: item.productId,
+ productUnitId: item.productUnitId,
+ productPrice: item.productPrice,
+ count: item.count,
+ taxPercent: item.taxPercent,
+ demandTime: item.demandTime,
+ remark: item.remark,
}));
try {
await (formType.value === 'create'
diff --git a/src/views/erp/purchase/request/modules/item-form.vue b/src/views/erp/purchase/request/modules/item-form.vue
index 14b2713..8cd2a2f 100644
--- a/src/views/erp/purchase/request/modules/item-form.vue
+++ b/src/views/erp/purchase/request/modules/item-form.vue
@@ -35,10 +35,10 @@
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
const summaries = computed(() => {
return {
- count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0),
+ count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
totalPrice: tableData.value.reduce(
(sum, item) => {
- const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0;
+ const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0;
return sum + price;
},
0,
@@ -89,11 +89,13 @@
const newRow = {
id: undefined,
productId: undefined,
+ productUnitId: undefined,
productUnitName: undefined,
productBarCode: undefined,
- requestCount: 1,
- estimatedPrice: undefined,
- requiredDate: undefined,
+ count: 1,
+ productPrice: undefined,
+ taxPercent: undefined,
+ demandTime: undefined,
remark: undefined,
totalPrice: 0,
};
@@ -118,10 +120,11 @@
}
row.productId = productId;
row.productBarCode = product.barCode;
+ row.productUnitId = product.unitMeasureId;
row.productUnitName = product.unitMeasureName;
row.productName = product.name;
- row.estimatedPrice = product.purchasePrice || 0;
- row.requestCount = row.requestCount || 1;
+ row.productPrice = product.purchasePrice || 0;
+ row.count = row.count || 1;
handleRowChange(row);
}
@@ -134,16 +137,16 @@
tableData.value[index] = row;
}
// 璁$畻棰勪及閲戦
- if (row.estimatedPrice && row.requestCount) {
- row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+ if (row.productPrice && row.count) {
+ row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
}
emit('update:items', [...tableData.value]);
}
/** 鍒濆鍖栬鏁版嵁 */
function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
- if (row.estimatedPrice && row.requestCount) {
- row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+ if (row.productPrice && row.count) {
+ row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
}
}
@@ -155,8 +158,8 @@
if (!item.productId) {
throw new Error(`绗� ${i + 1} 琛岋細浜у搧涓嶈兘涓虹┖`);
}
- if (!item.requestCount || item.requestCount <= 0) {
- throw new Error(`绗� ${i + 1} 琛岋細鐢宠鏁伴噺涓嶈兘涓虹┖`);
+ if (!item.count || item.count <= 0) {
+ throw new Error(`绗� ${i + 1} 琛岋細鏁伴噺涓嶈兘涓虹┖`);
}
}
}
@@ -191,35 +194,46 @@
/>
<span v-else>{{ row.productName || '-' }}</span>
</template>
- <template #requestCount="{ row }">
+ <template #count="{ row }">
<InputNumber
v-if="!disabled"
- v-model:value="row.requestCount"
+ v-model:value="row.count"
:min="0"
:precision="3"
@change="handleRowChange(row)"
/>
- <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span>
+ <span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span>
</template>
- <template #estimatedPrice="{ row }">
+ <template #productPrice="{ row }">
<InputNumber
v-if="!disabled"
- v-model:value="row.estimatedPrice"
+ v-model:value="row.productPrice"
:min="0"
:precision="2"
@change="handleRowChange(row)"
/>
- <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span>
+ <span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span>
</template>
- <template #requiredDate="{ row }">
+ <template #taxPercent="{ row }">
+ <InputNumber
+ v-if="!disabled"
+ v-model:value="row.taxPercent"
+ :min="0"
+ :max="100"
+ :precision="2"
+ @change="handleRowChange(row)"
+ />
+ <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span>
+ </template>
+ <template #demandTime="{ row }">
<DatePicker
v-if="!disabled"
- v-model:value="row.requiredDate"
+ v-model:value="row.demandTime"
valueFormat="YYYY-MM-DD"
class="w-full"
@change="handleRowChange(row)"
/>
- <span v-else>{{ row.requiredDate || '-' }}</span>
+ <span v-else>{{ row.demandTime || '-' }}</span>
</template>
<template #remark="{ row }">
<Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" />
@@ -246,7 +260,7 @@
<div class="flex justify-between text-sm text-muted-foreground">
<span class="font-medium text-foreground">鍚堣锛�</span>
<div class="flex space-x-4">
- <span>鐢宠鏁伴噺锛歿{ erpCountInputFormatter(summaries.count) }}</span>
+ <span>鏁伴噺锛歿{ erpCountInputFormatter(summaries.count) }}</span>
<span>棰勪及閲戦锛歿{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
</div>
</div>
@@ -264,4 +278,4 @@
/>
</template>
</Grid>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/erp/purchase/request/modules/order-form.vue b/src/views/erp/purchase/request/modules/order-form.vue
new file mode 100644
index 0000000..8ea784d
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/order-form.vue
@@ -0,0 +1,71 @@
+<script lang="ts" setup>
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { ref, computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message, Select } from 'ant-design-vue';
+
+import { generatePurchaseOrder } from '#/api/erp/purchase/request';
+import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+
+const emit = defineEmits(['success']);
+
+const requestId = ref<number>();
+const supplierId = ref<number>();
+const supplierOptions = ref<{ id: number; name: string }[]>([]);
+
+const supplierSelectOptions = computed(() => {
+ return supplierOptions.value.map((s) => ({
+ label: s.name,
+ value: s.id,
+ }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!supplierId.value) {
+ message.warning('璇烽�夋嫨渚涘簲鍟�');
+ return;
+ }
+ modalApi.lock();
+ try {
+ const orderId = await generatePurchaseOrder(requestId.value!, supplierId.value!);
+ await modalApi.close();
+ emit('success');
+ message.success(`鐢熸垚閲囪喘璁㈠崟鎴愬姛锛岃鍗曠紪鍙凤細${orderId}`);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ requestId.value = undefined;
+ supplierId.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id: number; supplierId?: number }>();
+ requestId.value = data.id;
+ supplierId.value = data.supplierId;
+ // 鑾峰彇渚涘簲鍟嗗垪琛�
+ supplierOptions.value = await getSupplierSimpleList();
+ },
+});
+</script>
+
+<template>
+ <Modal title="鐢熸垚閲囪喘璁㈠崟" class="w-1/3">
+ <div class="mb-4">
+ <span class="mr-2">閫夋嫨渚涘簲鍟嗭細</span>
+ <Select
+ v-model:value="supplierId"
+ :options="supplierSelectOptions"
+ style="width: 200px"
+ placeholder="璇烽�夋嫨渚涘簲鍟�"
+ show-search
+ :filter-option="(input: string, option: any) => option.label.toLowerCase().includes(input.toLowerCase())"
+ />
+ </div>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/request/modules/process-select-modal.vue b/src/views/erp/purchase/request/modules/process-select-modal.vue
new file mode 100644
index 0000000..f4b3d83
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/process-select-modal.vue
@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { ref, computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message, Select } from 'ant-design-vue';
+
+import { submitPurchaseRequest } from '#/api/erp/purchase/request';
+
+interface ProcessDefinition {
+ id: string;
+ name: string;
+ version: number;
+ key: string;
+}
+
+const emit = defineEmits(['success']);
+
+const requestId = ref<number>();
+const processList = ref<ProcessDefinition[]>([]);
+const selectedProcessKey = ref<string>('');
+
+const processOptions = computed(() => {
+ return processList.value.map((p) => ({
+ label: `${p.name} (V${p.version})`,
+ value: p.key,
+ }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!selectedProcessKey.value) {
+ message.warning('璇烽�夋嫨瀹℃壒娴佺▼');
+ return;
+ }
+ modalApi.lock();
+ try {
+ await submitPurchaseRequest(requestId.value!, selectedProcessKey.value);
+ await modalApi.close();
+ emit('success');
+ message.success('鎻愪氦鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ requestId.value = undefined;
+ selectedProcessKey.value = '';
+ processList.value = [];
+ return;
+ }
+ const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>();
+ requestId.value = data.id;
+ processList.value = data.processList || [];
+ // 榛樿閫夋嫨绗竴涓祦绋�
+ if (processList.value.length > 0) {
+ selectedProcessKey.value = processList.value[0].key;
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="閫夋嫨瀹℃壒娴佺▼" class="w-1/3">
+ <div class="mb-4">
+ <span class="mr-2">瀹℃壒娴佺▼锛�</span>
+ <Select
+ v-model:value="selectedProcessKey"
+ :options="processOptions"
+ style="width: 200px"
+ placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+ />
+ </div>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/supplier/data.ts b/src/views/erp/purchase/supplier/data.ts
index 2dc2f3f..1277a48 100644
--- a/src/views/erp/purchase/supplier/data.ts
+++ b/src/views/erp/purchase/supplier/data.ts
@@ -116,6 +116,17 @@
rules: z.number().default(0),
},
{
+ fieldName: 'sort',
+ label: '鎺掑簭',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ帓搴�',
+ min: 0,
+ class: '!w-full',
+ },
+ rules: z.number().default(0),
+ },
+ {
fieldName: 'qualification',
label: '璧勮川淇℃伅',
component: 'Textarea',
diff --git a/src/views/erp/purchase/supplier/modules/form.vue b/src/views/erp/purchase/supplier/modules/form.vue
index 423b63f..4b0b761 100644
--- a/src/views/erp/purchase/supplier/modules/form.vue
+++ b/src/views/erp/purchase/supplier/modules/form.vue
@@ -30,7 +30,7 @@
componentProps: {
class: 'w-full',
},
- labelWidth: 100,
+ labelWidth: 120,
},
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 8f0cdcc..24cd0b3 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -1,14 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import { DICT_TYPE } from '../../../../packages/constants/src';
-import { getDictOptions } from '../../../../packages/effects/hooks/src';
import { erpPriceInputFormatter } from '../../../../packages/utils/src';
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getProductSimpleList } from '#/api/erp/product/product';
-import { getCustomerSimpleList } from '#/api/erp/sale/customer';
+import { getItemPage } from '#/api/mdm/item';
+import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -55,7 +53,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
labelField: 'name',
valueField: 'id',
},
@@ -192,6 +190,11 @@
slots: { default: 'productId' },
},
{
+ field: 'productSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
field: 'stockCount',
title: '搴撳瓨',
minWidth: 80,
@@ -205,6 +208,16 @@
{
field: 'productUnitName',
title: '鍗曚綅',
+ minWidth: 80,
+ },
+ {
+ field: 'productUnitName2',
+ title: '杈呭崟浣�1',
+ minWidth: 80,
+ },
+ {
+ field: 'productUnitName3',
+ title: '杈呭崟浣�2',
minWidth: 80,
},
{
@@ -285,7 +298,10 @@
placeholder: '璇烽�夋嫨浜у搧',
allowClear: true,
showSearch: true,
- api: getProductSimpleList,
+ api: async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
labelField: 'name',
valueField: 'id',
},
@@ -307,7 +323,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
labelField: 'name',
valueField: 'id',
},
@@ -330,7 +346,11 @@
label: '鐘舵��',
component: 'Select',
componentProps: {
- options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'),
+ options: [
+ { label: '鏈鏍�', value: 10 },
+ { label: '宸插鏍�', value: 20 },
+ { label: '宸蹭綔搴�', value: 30 },
+ ],
placeholder: '璇烽�夋嫨鐘舵��',
allowClear: true,
},
@@ -388,6 +408,7 @@
title: '璁㈠崟鍗曞彿',
width: 200,
fixed: 'left',
+ slots: { default: 'no' },
},
{
field: 'productNames',
@@ -399,6 +420,12 @@
field: 'customerName',
title: '瀹㈡埛',
minWidth: 120,
+ },
+ {
+ field: 'contractNo',
+ title: '鍏宠仈鍚堝悓',
+ minWidth: 150,
+ slots: { default: 'contractNo' },
},
{
field: 'orderTime',
@@ -450,11 +477,9 @@
{
field: 'status',
title: '鐘舵��',
- minWidth: 120,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.ERP_AUDIT_STATUS },
- },
+ minWidth: 100,
+ fixed: 'right',
+ slots: { default: 'status' },
},
{
title: '鎿嶄綔',
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index 8c663a6..abccc6d 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/src/views/erp/sale/order/index.vue
@@ -3,15 +3,18 @@
import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
import { ref } from 'vue';
+import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
-import { message } from 'ant-design-vue';
+import { message, Tag, Tooltip } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
+ cancelSaleOrder,
deleteSaleOrder,
+ enableSaleOrder,
exportSaleOrder,
getSaleOrderPage,
updateSaleOrderStatus,
@@ -23,6 +26,8 @@
/** ERP 閿�鍞鍗曞垪琛� */
defineOptions({ name: 'ErpSaleOrder' });
+
+const router = useRouter();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -83,6 +88,36 @@
}
}
+/** 浣滃簾閿�鍞鍗� */
+async function handleCancel(row: ErpSaleOrderApi.SaleOrder) {
+ const hideLoading = message.loading({
+ content: '姝e湪浣滃簾璇ヨ鍗�...',
+ duration: 0,
+ });
+ try {
+ await cancelSaleOrder(row.id!);
+ message.success('浣滃簾鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍚敤閿�鍞鍗曪紙鎭㈠宸蹭綔搴熻鍗曪級 */
+async function handleEnable(row: ErpSaleOrderApi.SaleOrder) {
+ const hideLoading = message.loading({
+ content: '姝e湪鍚敤璇ヨ鍗�...',
+ duration: 0,
+ });
+ try {
+ await enableSaleOrder(row.id!);
+ message.success('鍚敤鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
@@ -95,6 +130,16 @@
/** 鏌ョ湅璇︽儏 */
function handleDetail(row: ErpSaleOrderApi.SaleOrder) {
formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 鏌ョ湅鍏宠仈鍚堝悓 */
+function handleViewContract(row: ErpSaleOrderApi.SaleOrder) {
+ if (row.contractId) {
+ router.push({
+ path: '/crm/contract/detail',
+ query: { id: row.contractId },
+ });
+ }
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -167,6 +212,27 @@
]"
/>
</template>
+ <template #no="{ row }">
+ <Tooltip v-if="row.status === 30" title="璇ヨ鍗曞凡浣滃簾锛岃鍗曞凡澶辨晥">
+ <span class="text-red-500 cursor-pointer" @click="handleDetail(row)">
+ {{ row.no }}
+ </span>
+ </Tooltip>
+ <a v-else class="text-primary cursor-pointer" @click="handleDetail(row)">
+ {{ row.no }}
+ </a>
+ </template>
+ <template #contractNo="{ row }">
+ <a v-if="row.contractId" class="text-primary cursor-pointer" @click="handleViewContract(row)">
+ {{ row.contractNo }}
+ </a>
+ <span v-else>-</span>
+ </template>
+ <template #status="{ row }">
+ <Tag v-if="row.status === 10" color="warning">鏈鏍�</Tag>
+ <Tag v-if="row.status === 20" color="success">宸插鏍�</Tag>
+ <Tag v-if="row.status === 30" color="error">宸蹭綔搴�</Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
@@ -182,21 +248,51 @@
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['erp:sale-order:update'],
- ifShow: () => row.status !== 20,
+ ifShow: () => row.status === 10,
onClick: handleEdit.bind(null, row),
},
{
- label: row.status === 10 ? '瀹℃壒' : '鍙嶅鎵�',
+ label: '瀹℃壒',
type: 'link',
icon: ACTION_ICON.AUDIT,
auth: ['erp:sale-order:update-status'],
+ ifShow: () => row.status === 10,
popConfirm: {
- title: `纭${row.status === 10 ? '瀹℃壒' : '鍙嶅鎵�'}${row.no}鍚楋紵`,
- confirm: handleUpdateStatus.bind(
- null,
- row,
- row.status === 10 ? 20 : 10,
- ),
+ title: `纭瀹℃壒${row.no}鍚楋紵`,
+ confirm: handleUpdateStatus.bind(null, row, 20),
+ },
+ },
+ {
+ label: '鍙嶅鎵�',
+ type: 'link',
+ icon: ACTION_ICON.AUDIT,
+ auth: ['erp:sale-order:update-status'],
+ ifShow: () => row.status === 20,
+ popConfirm: {
+ title: `纭鍙嶅鎵�${row.no}鍚楋紵`,
+ confirm: handleUpdateStatus.bind(null, row, 10),
+ },
+ },
+ {
+ label: '浣滃簾',
+ type: 'link',
+ icon: 'ant-design:stop-outlined',
+ auth: ['erp:sale-order:cancel'],
+ ifShow: () => row.status === 20,
+ popConfirm: {
+ title: `纭浣滃簾${row.no}鍚楋紵浣滃簾鍚庝笉鍙仮澶嶏紒`,
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ {
+ label: '鍚敤',
+ type: 'link',
+ icon: 'ant-design:check-circle-outlined',
+ auth: ['erp:sale-order:enable'],
+ ifShow: () => row.status === 30,
+ popConfirm: {
+ title: `纭鍚敤${row.no}鍚楋紵`,
+ confirm: handleEnable.bind(null, row),
},
},
{
@@ -205,6 +301,7 @@
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['erp:sale-order:delete'],
+ ifShow: () => row.status === 10,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.no]),
confirm: handleDelete.bind(null, [row.id!]),
diff --git a/src/views/erp/sale/order/modules/item-form.vue b/src/views/erp/sale/order/modules/item-form.vue
index 3c42fec..8dc45b3 100644
--- a/src/views/erp/sale/order/modules/item-form.vue
+++ b/src/views/erp/sale/order/modules/item-form.vue
@@ -1,5 +1,5 @@
<script lang="ts" setup>
-import type { ErpProductApi } from '#/api/erp/product/product';
+import type { MdmItemApi } from '#/api/mdm/item';
import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
@@ -13,7 +13,7 @@
import { Input, InputNumber, Select } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getProductSimpleList } from '#/api/erp/product/product';
+import { getItemPage } from '#/api/mdm/item';
import { getStockCount } from '#/api/erp/stock/stock';
import { useFormItemColumns } from '../data';
@@ -37,7 +37,7 @@
]);
const tableData = ref<ErpSaleOrderApi.SaleOrderItem[]>([]); // 琛ㄦ牸鏁版嵁
-const productOptions = ref<ErpProductApi.Product[]>([]); // 浜у搧涓嬫媺閫夐」
+const productOptions = ref<MdmItemApi.Item[]>([]); // 鐗╂枡涓嬫媺閫夐」
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
const summaries = computed(() => {
@@ -158,12 +158,15 @@
return;
}
row.productId = productId;
- row.productUnitId = product.unitId;
+ row.productUnitId = product.unitMeasureId;
row.productBarCode = product.barCode;
- row.productUnitName = product.unitName;
+ row.productUnitName = product.unitMeasureName;
+ row.productUnitName2 = product.unitMeasureName2;
+ row.productUnitName3 = product.unitMeasureName3;
+ row.productSpecification = product.specification;
row.productName = product.name;
row.stockCount = (await getStockCount(productId)) || 0;
- row.productPrice = product.salePrice || 0;
+ row.productPrice = product.salesPrice || 0;
row.count = row.count || 1;
handleRowChange(row);
}
@@ -213,7 +216,8 @@
/** 鍒濆鍖� */
onMounted(async () => {
- productOptions.value = await getProductSimpleList();
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
// 鐩殑锛氭柊澧炴椂锛岄粯璁ゆ坊鍔犱竴琛�
if (tableData.value.length === 0) {
handleAdd();
@@ -225,6 +229,7 @@
<Grid class="w-full">
<template #productId="{ row }">
<Select
+ v-if="!disabled"
v-model:value="row.productId"
:options="productOptions"
:field-names="{ label: 'name', value: 'id' }"
@@ -233,6 +238,7 @@
show-search
@change="handleProductChange($event, row)"
/>
+ <span v-else>{{ row.productName || '-' }}</span>
</template>
<template #count="{ row }">
<InputNumber
diff --git a/src/views/erp/sale/out/data.ts b/src/views/erp/sale/out/data.ts
index ffef251..82dcfe5 100644
--- a/src/views/erp/sale/out/data.ts
+++ b/src/views/erp/sale/out/data.ts
@@ -7,8 +7,8 @@
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getProductSimpleList } from '#/api/erp/product/product';
-import { getCustomerSimpleList } from '#/api/erp/sale/customer';
+import { getItemPage } from '#/api/mdm/item';
+import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -69,7 +69,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
fieldNames: {
label: 'name',
value: 'id',
@@ -350,11 +350,12 @@
placeholder: '璇烽�夋嫨浜у搧',
allowClear: true,
showSearch: true,
- api: getProductSimpleList,
- fieldNames: {
- label: 'name',
- value: 'id',
+ api: async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
},
+ labelField: 'name',
+ valueField: 'id',
},
},
{
@@ -374,7 +375,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
fieldNames: {
label: 'name',
value: 'id',
@@ -554,7 +555,10 @@
placeholder: '璇烽�夋嫨浜у搧',
allowClear: true,
showSearch: true,
- api: getProductSimpleList,
+ api: async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
labelField: 'name',
valueField: 'id',
},
@@ -572,6 +576,8 @@
}
/** 鍒楄〃鐨勫瓧娈� */
+
+/** 鍒楄〃鐨勫瓧娈� */
export function useOrderGridColumns(): VxeTableGridOptions['columns'] {
return [
{
diff --git a/src/views/erp/sale/out/modules/item-form.vue b/src/views/erp/sale/out/modules/item-form.vue
index 6134f18..f7fb76c 100644
--- a/src/views/erp/sale/out/modules/item-form.vue
+++ b/src/views/erp/sale/out/modules/item-form.vue
@@ -1,4 +1,5 @@
<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
import type { ErpSaleOutApi } from '#/api/erp/sale/out';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
@@ -12,7 +13,7 @@
import { Input, InputNumber, Select } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getProductSimpleList } from '#/api/erp/product/product';
+import { getItemPage } from '#/api/mdm/item';
import { getWarehouseStockCount } from '#/api/erp/stock/stock';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
@@ -40,7 +41,7 @@
]);
const tableData = ref<ErpSaleOutApi.SaleOutItem[]>([]); // 琛ㄦ牸鏁版嵁
-const productOptions = ref<any[]>([]); // 浜у搧涓嬫媺閫夐」
+const productOptions = ref<MdmItemApi.Item[]>([]); // 鐗╂枡涓嬫媺閫夐」
const warehouseOptions = ref<any[]>([]); // 浠撳簱涓嬫媺閫夐」
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
@@ -192,7 +193,8 @@
/** 鍒濆鍖� */
onMounted(async () => {
- productOptions.value = await getProductSimpleList();
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
warehouseOptions.value = await getWarehouseSimpleList();
});
</script>
diff --git a/src/views/erp/sale/return/data.ts b/src/views/erp/sale/return/data.ts
index 102f692..1614bd5 100644
--- a/src/views/erp/sale/return/data.ts
+++ b/src/views/erp/sale/return/data.ts
@@ -7,8 +7,8 @@
import { z } from '#/adapter/form';
import { getAccountSimpleList } from '#/api/erp/finance/account';
-import { getProductSimpleList } from '#/api/erp/product/product';
-import { getCustomerSimpleList } from '#/api/erp/sale/customer';
+import { getItemPage } from '#/api/mdm/item';
+import { getCustomerSimpleList as getCrmCustomerSimpleList } from '#/api/crm/customer';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
import { getSimpleUserList } from '#/api/system/user';
import { getRangePickerDefaultProps } from '#/utils';
@@ -69,7 +69,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
labelField: 'name',
valueField: 'id',
},
@@ -343,7 +343,10 @@
placeholder: '璇烽�夋嫨浜у搧',
allowClear: true,
showSearch: true,
- api: getProductSimpleList,
+ api: async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
labelField: 'name',
valueField: 'id',
},
@@ -365,7 +368,7 @@
placeholder: '璇烽�夋嫨瀹㈡埛',
allowClear: true,
showSearch: true,
- api: getCustomerSimpleList,
+ api: getCrmCustomerSimpleList,
labelField: 'name',
valueField: 'id',
},
@@ -541,7 +544,10 @@
placeholder: '璇烽�夋嫨浜у搧',
allowClear: true,
showSearch: true,
- api: getProductSimpleList,
+ api: async () => {
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ return res.list || [];
+ },
labelField: 'name',
valueField: 'id',
},
diff --git a/src/views/erp/sale/return/modules/item-form.vue b/src/views/erp/sale/return/modules/item-form.vue
index e927e43..2344596 100644
--- a/src/views/erp/sale/return/modules/item-form.vue
+++ b/src/views/erp/sale/return/modules/item-form.vue
@@ -1,4 +1,5 @@
<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
import type { ErpSaleReturnApi } from '#/api/erp/sale/return';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
@@ -12,7 +13,7 @@
import { Input, InputNumber, Select } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { getProductSimpleList } from '#/api/erp/product/product';
+import { getItemPage } from '#/api/mdm/item';
import { getWarehouseStockCount } from '#/api/erp/stock/stock';
import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
@@ -40,7 +41,7 @@
]);
const tableData = ref<ErpSaleReturnApi.SaleReturnItem[]>([]); // 琛ㄦ牸鏁版嵁
-const productOptions = ref<any[]>([]); // 浜у搧涓嬫媺閫夐」
+const productOptions = ref<MdmItemApi.Item[]>([]); // 鐗╂枡涓嬫媺閫夐」
const warehouseOptions = ref<any[]>([]); // 浠撳簱涓嬫媺閫夐」
/** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
@@ -192,7 +193,8 @@
/** 鍒濆鍖� */
onMounted(async () => {
- productOptions.value = await getProductSimpleList();
+ const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 });
+ productOptions.value = res.list || [];
warehouseOptions.value = await getWarehouseSimpleList();
});
</script>
diff --git a/src/views/mes/dv/subject/modules/form.vue b/src/views/mes/dv/subject/modules/form.vue
index 56cba2d..7865d6a 100644
--- a/src/views/mes/dv/subject/modules/form.vue
+++ b/src/views/mes/dv/subject/modules/form.vue
@@ -86,7 +86,7 @@
</script>
<template>
- <Modal :title="getTitle" class="w-2/5">
+ <Modal :title="getTitle" class="w-[900px]">
<Form class="mx-4" />
</Modal>
</template>
diff --git a/src/views/system/menu/data.ts b/src/views/system/menu/data.ts
index f20984c..9490a83 100644
--- a/src/views/system/menu/data.ts
+++ b/src/views/system/menu/data.ts
@@ -101,7 +101,7 @@
component: 'IconPicker',
componentProps: {
placeholder: '璇烽�夋嫨鑿滃崟鍥炬爣',
- prefix: 'carbon',
+ prefix: 'ep',
},
rules: 'required',
dependencies: {
diff --git a/vite.config.ts b/vite.config.ts
index 4f673c6..97be055 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -9,6 +9,9 @@
},
server: {
allowedHosts: true,
+ watch: {
+ ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
+ },
proxy: {
"/admin-api": {
changeOrigin: true,
--
Gitblit v1.9.3