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