Merge pull request !189 from xingyu/devpull/190/MERGE
						commit
						3f1c3a283f
					
				|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/web-antd", |   "name": "@vben/web-antd", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://vben.pro", |   "homepage": "https://vben.pro", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import { IconifyIcon } from '@vben/icons'; | ||||||
| import { $te } from '@vben/locales'; | import { $te } from '@vben/locales'; | ||||||
| import { | import { | ||||||
|   AsyncComponents, |   AsyncComponents, | ||||||
|  |   createRequiredValidation, | ||||||
|   setupVbenVxeTable, |   setupVbenVxeTable, | ||||||
|   useVbenVxeGrid, |   useVbenVxeGrid, | ||||||
| } from '@vben/plugins/vxe-table'; | } from '@vben/plugins/vxe-table'; | ||||||
|  | @ -354,7 +355,7 @@ setupVbenVxeTable({ | ||||||
|   useVbenForm, |   useVbenForm, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export { useVbenVxeGrid }; | export { createRequiredValidation, useVbenVxeGrid }; | ||||||
| 
 | 
 | ||||||
| const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents; | const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents; | ||||||
| export { VxeColumn, VxeTable, VxeToolbar }; | export { VxeColumn, VxeTable, VxeToolbar }; | ||||||
|  |  | ||||||
|  | @ -40,6 +40,7 @@ export namespace BpmProcessInstanceApi { | ||||||
|     nodeType: BpmNodeTypeEnum; |     nodeType: BpmNodeTypeEnum; | ||||||
|     startTime?: Date; |     startTime?: Date; | ||||||
|     status: number; |     status: number; | ||||||
|  |     processInstanceId?: string; | ||||||
|     tasks: ApprovalTaskInfo[]; |     tasks: ApprovalTaskInfo[]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -130,3 +130,8 @@ export const getChildrenTaskList = async (id: string) => { | ||||||
|     `/bpm/task/list-by-parent-task-id?parentTaskId=${id}`, |     `/bpm/task/list-by-parent-task-id?parentTaskId=${id}`, | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | // 撤回任务
 | ||||||
|  | export const withdrawTask = async (taskId: string) => { | ||||||
|  |   return await requestClient.put('/bpm/task/withdraw', null, { params: { taskId } }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpAccountApi { | ||||||
|  |   /** ERP 结算账户信息 */ | ||||||
|  |   export interface Account { | ||||||
|  |     id?: number; // 结算账户编号
 | ||||||
|  |     no: string; // 账户编码
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     status: number; // 开启状态
 | ||||||
|  |     sort: number; // 排序
 | ||||||
|  |     defaultStatus: boolean; // 是否默认
 | ||||||
|  |     name: string; // 账户名称
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 结算账户分页查询参数 */ | ||||||
|  |   export interface AccountPageParam extends PageParam { | ||||||
|  |     name?: string; | ||||||
|  |     no?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询结算账户分页 */ | ||||||
|  | export function getAccountPage(params: ErpAccountApi.AccountPageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpAccountApi.Account>>( | ||||||
|  |     '/erp/account/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询结算账户精简列表 */ | ||||||
|  | export function getAccountSimpleList() { | ||||||
|  |   return requestClient.get<ErpAccountApi.Account[]>('/erp/account/simple-list'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询结算账户详情 */ | ||||||
|  | export function getAccount(id: number) { | ||||||
|  |   return requestClient.get<ErpAccountApi.Account>(`/erp/account/get?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增结算账户 */ | ||||||
|  | export function createAccount(data: ErpAccountApi.Account) { | ||||||
|  |   return requestClient.post('/erp/account/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改结算账户 */ | ||||||
|  | export function updateAccount(data: ErpAccountApi.Account) { | ||||||
|  |   return requestClient.put('/erp/account/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改结算账户默认状态 */ | ||||||
|  | export function updateAccountDefaultStatus(id: number, defaultStatus: boolean) { | ||||||
|  |   return requestClient.put('/erp/account/update-default-status', null, { | ||||||
|  |     params: { id, defaultStatus }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除结算账户 */ | ||||||
|  | export function deleteAccount(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/account/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出结算账户 Excel */ | ||||||
|  | export function exportAccount(params: any) { | ||||||
|  |   return requestClient.download('/erp/account/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,103 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpFinancePaymentApi { | ||||||
|  |   /** 付款单信息 */ | ||||||
|  |   export interface FinancePayment { | ||||||
|  |     id?: number; // 付款单编号
 | ||||||
|  |     no: string; // 付款单号
 | ||||||
|  |     supplierId: number; // 供应商编号
 | ||||||
|  |     paymentTime: Date; // 付款时间
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 付款单分页查询参数 */ | ||||||
|  |   export interface FinancePaymentPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     supplierId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 付款单状态更新参数 */ | ||||||
|  |   export interface FinancePaymentStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询付款单分页 | ||||||
|  |  */ | ||||||
|  | export function getFinancePaymentPage( | ||||||
|  |   params: ErpFinancePaymentApi.FinancePaymentPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpFinancePaymentApi.FinancePayment>>( | ||||||
|  |     '/erp/finance-payment/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询付款单详情 | ||||||
|  |  */ | ||||||
|  | export function getFinancePayment(id: number) { | ||||||
|  |   return requestClient.get<ErpFinancePaymentApi.FinancePayment>( | ||||||
|  |     `/erp/finance-payment/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增付款单 | ||||||
|  |  */ | ||||||
|  | export function createFinancePayment( | ||||||
|  |   data: ErpFinancePaymentApi.FinancePayment, | ||||||
|  | ) { | ||||||
|  |   return requestClient.post('/erp/finance-payment/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改付款单 | ||||||
|  |  */ | ||||||
|  | export function updateFinancePayment( | ||||||
|  |   data: ErpFinancePaymentApi.FinancePayment, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/finance-payment/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新付款单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateFinancePaymentStatus( | ||||||
|  |   params: ErpFinancePaymentApi.FinancePaymentStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/finance-payment/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除付款单 | ||||||
|  |  */ | ||||||
|  | export function deleteFinancePayment(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/finance-payment/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出付款单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportFinancePayment( | ||||||
|  |   params: ErpFinancePaymentApi.FinancePaymentPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/finance-payment/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,103 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpFinanceReceiptApi { | ||||||
|  |   /** 收款单信息 */ | ||||||
|  |   export interface FinanceReceipt { | ||||||
|  |     id?: number; // 收款单编号
 | ||||||
|  |     no: string; // 收款单号
 | ||||||
|  |     customerId: number; // 客户编号
 | ||||||
|  |     receiptTime: Date; // 收款时间
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 收款单分页查询参数 */ | ||||||
|  |   export interface FinanceReceiptPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     customerId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 收款单状态更新参数 */ | ||||||
|  |   export interface FinanceReceiptStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询收款单分页 | ||||||
|  |  */ | ||||||
|  | export function getFinanceReceiptPage( | ||||||
|  |   params: ErpFinanceReceiptApi.FinanceReceiptPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpFinanceReceiptApi.FinanceReceipt>>( | ||||||
|  |     '/erp/finance-receipt/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询收款单详情 | ||||||
|  |  */ | ||||||
|  | export function getFinanceReceipt(id: number) { | ||||||
|  |   return requestClient.get<ErpFinanceReceiptApi.FinanceReceipt>( | ||||||
|  |     `/erp/finance-receipt/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增收款单 | ||||||
|  |  */ | ||||||
|  | export function createFinanceReceipt( | ||||||
|  |   data: ErpFinanceReceiptApi.FinanceReceipt, | ||||||
|  | ) { | ||||||
|  |   return requestClient.post('/erp/finance-receipt/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改收款单 | ||||||
|  |  */ | ||||||
|  | export function updateFinanceReceipt( | ||||||
|  |   data: ErpFinanceReceiptApi.FinanceReceipt, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/finance-receipt/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新收款单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateFinanceReceiptStatus( | ||||||
|  |   params: ErpFinanceReceiptApi.FinanceReceiptStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/finance-receipt/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除收款单 | ||||||
|  |  */ | ||||||
|  | export function deleteFinanceReceipt(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/finance-receipt/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出收款单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportFinanceReceipt( | ||||||
|  |   params: ErpFinanceReceiptApi.FinanceReceiptPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/finance-receipt/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpProductCategoryApi { | ||||||
|  |   /** ERP 产品分类信息 */ | ||||||
|  |   export interface ProductCategory { | ||||||
|  |     id?: number; // 分类编号
 | ||||||
|  |     parentId: number; // 父分类编号
 | ||||||
|  |     name: string; // 分类名称
 | ||||||
|  |     code: string; // 分类编码
 | ||||||
|  |     sort: number; // 分类排序
 | ||||||
|  |     status: number; // 开启状态
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品分类列表 */ | ||||||
|  | export function getProductCategoryList() { | ||||||
|  |   return requestClient.get<ErpProductCategoryApi.ProductCategory[]>( | ||||||
|  |     '/erp/product-category/list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品分类精简列表 */ | ||||||
|  | export function getProductCategorySimpleList() { | ||||||
|  |   return requestClient.get<ErpProductCategoryApi.ProductCategory[]>( | ||||||
|  |     '/erp/product-category/simple-list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品分类详情 */ | ||||||
|  | export function getProductCategory(id: number) { | ||||||
|  |   return requestClient.get<ErpProductCategoryApi.ProductCategory>( | ||||||
|  |     `/erp/product-category/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增产品分类 */ | ||||||
|  | export function createProductCategory( | ||||||
|  |   data: ErpProductCategoryApi.ProductCategory, | ||||||
|  | ) { | ||||||
|  |   return requestClient.post('/erp/product-category/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改产品分类 */ | ||||||
|  | export function updateProductCategory( | ||||||
|  |   data: ErpProductCategoryApi.ProductCategory, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/product-category/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除产品分类 */ | ||||||
|  | export function deleteProductCategory(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/product-category/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出产品分类 Excel */ | ||||||
|  | export function exportProductCategory(params: any) { | ||||||
|  |   return requestClient.download('/erp/product-category/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpProductApi { | ||||||
|  |   /** ERP 产品信息 */ | ||||||
|  |   export interface Product { | ||||||
|  |     id?: number; // 产品编号
 | ||||||
|  |     name: string; // 产品名称
 | ||||||
|  |     barCode: string; // 产品条码
 | ||||||
|  |     categoryId: number; // 产品类型编号
 | ||||||
|  |     unitId: number; // 单位编号
 | ||||||
|  |     unitName?: string; // 单位名字
 | ||||||
|  |     status: number; // 产品状态
 | ||||||
|  |     standard: string; // 产品规格
 | ||||||
|  |     remark: string; // 产品备注
 | ||||||
|  |     expiryDay: number; // 保质期天数
 | ||||||
|  |     weight: number; // 重量(kg)
 | ||||||
|  |     purchasePrice: number; // 采购价格,单位:元
 | ||||||
|  |     salePrice: number; // 销售价格,单位:元
 | ||||||
|  |     minPrice: number; // 最低价格,单位:元
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品分页 */ | ||||||
|  | export function getProductPage(params: PageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpProductApi.Product>>( | ||||||
|  |     '/erp/product/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品精简列表 */ | ||||||
|  | export function getProductSimpleList() { | ||||||
|  |   return requestClient.get<ErpProductApi.Product[]>('/erp/product/simple-list'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品详情 */ | ||||||
|  | export function getProduct(id: number) { | ||||||
|  |   return requestClient.get<ErpProductApi.Product>(`/erp/product/get?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增产品 */ | ||||||
|  | export function createProduct(data: ErpProductApi.Product) { | ||||||
|  |   return requestClient.post('/erp/product/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改产品 */ | ||||||
|  | export function updateProduct(data: ErpProductApi.Product) { | ||||||
|  |   return requestClient.put('/erp/product/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除产品 */ | ||||||
|  | export function deleteProduct(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/product/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出产品 Excel */ | ||||||
|  | export function exportProduct(params: any) { | ||||||
|  |   return requestClient.download('/erp/product/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpProductUnitApi { | ||||||
|  |   /** ERP 产品单位信息 */ | ||||||
|  |   export interface ProductUnit { | ||||||
|  |     id?: number; // 单位编号
 | ||||||
|  |     name: string; // 单位名字
 | ||||||
|  |     status: number; // 单位状态
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 产品单位分页查询参数 */ | ||||||
|  |   export interface ProductUnitPageParam extends PageParam { | ||||||
|  |     name?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品单位分页 */ | ||||||
|  | export function getProductUnitPage( | ||||||
|  |   params: ErpProductUnitApi.ProductUnitPageParam, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpProductUnitApi.ProductUnit>>( | ||||||
|  |     '/erp/product-unit/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品单位精简列表 */ | ||||||
|  | export function getProductUnitSimpleList() { | ||||||
|  |   return requestClient.get<ErpProductUnitApi.ProductUnit[]>( | ||||||
|  |     '/erp/product-unit/simple-list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品单位详情 */ | ||||||
|  | export function getProductUnit(id: number) { | ||||||
|  |   return requestClient.get<ErpProductUnitApi.ProductUnit>( | ||||||
|  |     `/erp/product-unit/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增产品单位 */ | ||||||
|  | export function createProductUnit(data: ErpProductUnitApi.ProductUnit) { | ||||||
|  |   return requestClient.post('/erp/product-unit/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改产品单位 */ | ||||||
|  | export function updateProductUnit(data: ErpProductUnitApi.ProductUnit) { | ||||||
|  |   return requestClient.put('/erp/product-unit/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除产品单位 */ | ||||||
|  | export function deleteProductUnit(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/product-unit/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出产品单位 Excel */ | ||||||
|  | export function exportProductUnit(params: any) { | ||||||
|  |   return requestClient.download('/erp/product-unit/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpPurchaseInApi { | ||||||
|  |   /** 采购入库信息 */ | ||||||
|  |   export interface PurchaseIn { | ||||||
|  |     id?: number; // 入库工单编号
 | ||||||
|  |     no: string; // 采购入库号
 | ||||||
|  |     supplierId: number; // 供应商编号
 | ||||||
|  |     inTime: Date; // 入库时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     outCount: number; // 采购出库数量
 | ||||||
|  |     returnCount: number; // 采购退货数量
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 采购入库分页查询参数 */ | ||||||
|  |   export interface PurchaseInPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     supplierId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 采购入库状态更新参数 */ | ||||||
|  |   export interface PurchaseInStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询采购入库分页 | ||||||
|  |  */ | ||||||
|  | export function getPurchaseInPage( | ||||||
|  |   params: ErpPurchaseInApi.PurchaseInPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpPurchaseInApi.PurchaseIn>>( | ||||||
|  |     '/erp/purchase-in/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询采购入库详情 | ||||||
|  |  */ | ||||||
|  | export function getPurchaseIn(id: number) { | ||||||
|  |   return requestClient.get<ErpPurchaseInApi.PurchaseIn>( | ||||||
|  |     `/erp/purchase-in/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增采购入库 | ||||||
|  |  */ | ||||||
|  | export function createPurchaseIn(data: ErpPurchaseInApi.PurchaseIn) { | ||||||
|  |   return requestClient.post('/erp/purchase-in/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改采购入库 | ||||||
|  |  */ | ||||||
|  | export function updatePurchaseIn(data: ErpPurchaseInApi.PurchaseIn) { | ||||||
|  |   return requestClient.put('/erp/purchase-in/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新采购入库的状态 | ||||||
|  |  */ | ||||||
|  | export function updatePurchaseInStatus( | ||||||
|  |   params: ErpPurchaseInApi.PurchaseInStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/purchase-in/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除采购入库 | ||||||
|  |  */ | ||||||
|  | export function deletePurchaseIn(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/purchase-in/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出采购入库 Excel | ||||||
|  |  */ | ||||||
|  | export function exportPurchaseIn( | ||||||
|  |   params: ErpPurchaseInApi.PurchaseInPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/purchase-in/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,116 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpPurchaseOrderApi { | ||||||
|  |   /** ERP 采购订单项信息 */ | ||||||
|  |   export interface PurchaseOrderItem { | ||||||
|  |     id?: number; // 订单项编号
 | ||||||
|  |     orderId?: number; // 采购订单编号
 | ||||||
|  |     productId?: number; // 产品编号
 | ||||||
|  |     productName?: string; // 产品名称
 | ||||||
|  |     productBarCode?: string; // 产品条码
 | ||||||
|  |     productUnitId?: number; // 产品单位编号
 | ||||||
|  |     productUnitName?: string; // 产品单位名称
 | ||||||
|  |     productPrice?: number; // 产品单价,单位:元
 | ||||||
|  |     totalProductPrice?: number; // 产品总价,单位:元
 | ||||||
|  |     count?: number; // 数量
 | ||||||
|  |     totalPrice?: number; // 总价,单位:元
 | ||||||
|  |     taxPercent?: number; // 税率,百分比
 | ||||||
|  |     taxPrice?: number; // 税额,单位:元
 | ||||||
|  |     totalTaxPrice?: number; // 含税总价,单位:元
 | ||||||
|  |     remark?: string; // 备注
 | ||||||
|  |     stockCount?: number; // 库存数量(显示字段)
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** ERP 采购订单信息 */ | ||||||
|  |   export interface PurchaseOrder { | ||||||
|  |     id?: number; // 订单工单编号
 | ||||||
|  |     no?: string; // 采购订单号
 | ||||||
|  |     supplierId?: number; // 供应商编号
 | ||||||
|  |     supplierName?: string; // 供应商名称
 | ||||||
|  |     orderTime?: Date | string; // 订单时间
 | ||||||
|  |     totalCount?: number; // 合计数量
 | ||||||
|  |     totalPrice?: number; // 合计金额,单位:元
 | ||||||
|  |     totalProductPrice?: number; // 产品金额,单位:元
 | ||||||
|  |     discountPercent?: number; // 优惠率,百分比
 | ||||||
|  |     discountPrice?: number; // 优惠金额,单位:元
 | ||||||
|  |     depositPrice?: number; // 定金金额,单位:元
 | ||||||
|  |     accountId?: number; // 结算账户编号
 | ||||||
|  |     status?: number; // 状态
 | ||||||
|  |     remark?: string; // 备注
 | ||||||
|  |     fileUrl?: string; // 附件地址
 | ||||||
|  |     inCount?: number; // 采购入库数量
 | ||||||
|  |     returnCount?: number; // 采购退货数量
 | ||||||
|  |     inStatus?: number; // 入库状态
 | ||||||
|  |     returnStatus?: number; // 退货状态
 | ||||||
|  |     productNames?: string; // 产品名称列表
 | ||||||
|  |     creatorName?: string; // 创建人名称
 | ||||||
|  |     createTime?: Date; // 创建时间
 | ||||||
|  |     items?: PurchaseOrderItem[]; // 订单项列表
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 采购订单分页查询参数 */ | ||||||
|  |   export interface PurchaseOrderPageParam extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     supplierId?: number; | ||||||
|  |     productId?: number; | ||||||
|  |     orderTime?: string[]; | ||||||
|  |     status?: number; | ||||||
|  |     remark?: string; | ||||||
|  |     creator?: string; | ||||||
|  |     inStatus?: number; | ||||||
|  |     returnStatus?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询采购订单分页 */ | ||||||
|  | export function getPurchaseOrderPage( | ||||||
|  |   params: ErpPurchaseOrderApi.PurchaseOrderPageParam, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpPurchaseOrderApi.PurchaseOrder>>( | ||||||
|  |     '/erp/purchase-order/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询采购订单详情 */ | ||||||
|  | export function getPurchaseOrder(id: number) { | ||||||
|  |   return requestClient.get<ErpPurchaseOrderApi.PurchaseOrder>( | ||||||
|  |     `/erp/purchase-order/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增采购订单 */ | ||||||
|  | export function createPurchaseOrder(data: ErpPurchaseOrderApi.PurchaseOrder) { | ||||||
|  |   return requestClient.post('/erp/purchase-order/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改采购订单 */ | ||||||
|  | export function updatePurchaseOrder(data: ErpPurchaseOrderApi.PurchaseOrder) { | ||||||
|  |   return requestClient.put('/erp/purchase-order/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 更新采购订单的状态 */ | ||||||
|  | export function updatePurchaseOrderStatus(id: number, status: number) { | ||||||
|  |   return requestClient.put('/erp/purchase-order/update-status', null, { | ||||||
|  |     params: { id, status }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除采购订单 */ | ||||||
|  | export function deletePurchaseOrder(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/purchase-order/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 批量删除采购订单 */ | ||||||
|  | export function deletePurchaseOrderList(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/purchase-order/delete', { | ||||||
|  |     params: { ids: ids.join(',') }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出采购订单 Excel */ | ||||||
|  | export function exportPurchaseOrder(params: any) { | ||||||
|  |   return requestClient.download('/erp/purchase-order/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,104 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpPurchaseReturnApi { | ||||||
|  |   /** 采购退货信息 */ | ||||||
|  |   export interface PurchaseReturn { | ||||||
|  |     id?: number; // 采购退货编号
 | ||||||
|  |     no: string; // 采购退货号
 | ||||||
|  |     supplierId: number; // 供应商编号
 | ||||||
|  |     returnTime: Date; // 退货时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 采购退货分页查询参数 */ | ||||||
|  |   export interface PurchaseReturnPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     supplierId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 采购退货状态更新参数 */ | ||||||
|  |   export interface PurchaseReturnStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询采购退货分页 | ||||||
|  |  */ | ||||||
|  | export function getPurchaseReturnPage( | ||||||
|  |   params: ErpPurchaseReturnApi.PurchaseReturnPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpPurchaseReturnApi.PurchaseReturn>>( | ||||||
|  |     '/erp/purchase-return/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询采购退货详情 | ||||||
|  |  */ | ||||||
|  | export function getPurchaseReturn(id: number) { | ||||||
|  |   return requestClient.get<ErpPurchaseReturnApi.PurchaseReturn>( | ||||||
|  |     `/erp/purchase-return/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增采购退货 | ||||||
|  |  */ | ||||||
|  | export function createPurchaseReturn( | ||||||
|  |   data: ErpPurchaseReturnApi.PurchaseReturn, | ||||||
|  | ) { | ||||||
|  |   return requestClient.post('/erp/purchase-return/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改采购退货 | ||||||
|  |  */ | ||||||
|  | export function updatePurchaseReturn( | ||||||
|  |   data: ErpPurchaseReturnApi.PurchaseReturn, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/purchase-return/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新采购退货的状态 | ||||||
|  |  */ | ||||||
|  | export function updatePurchaseReturnStatus( | ||||||
|  |   params: ErpPurchaseReturnApi.PurchaseReturnStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/purchase-return/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除采购退货 | ||||||
|  |  */ | ||||||
|  | export function deletePurchaseReturn(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/purchase-return/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出采购退货 Excel | ||||||
|  |  */ | ||||||
|  | export function exportPurchaseReturn( | ||||||
|  |   params: ErpPurchaseReturnApi.PurchaseReturnPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/purchase-return/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpSupplierApi { | ||||||
|  |   /** ERP 供应商信息 */ | ||||||
|  |   export interface Supplier { | ||||||
|  |     id?: number; // 供应商编号
 | ||||||
|  |     name: string; // 供应商名称
 | ||||||
|  |     contact: string; // 联系人
 | ||||||
|  |     mobile: string; // 手机号码
 | ||||||
|  |     telephone: string; // 联系电话
 | ||||||
|  |     email: string; // 电子邮箱
 | ||||||
|  |     fax: string; // 传真
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     status: number; // 开启状态
 | ||||||
|  |     sort: number; // 排序
 | ||||||
|  |     taxNo: string; // 纳税人识别号
 | ||||||
|  |     taxPercent: number; // 税率
 | ||||||
|  |     bankName: string; // 开户行
 | ||||||
|  |     bankAccount: string; // 开户账号
 | ||||||
|  |     bankAddress: string; // 开户地址
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 供应商分页查询参数 */ | ||||||
|  |   export interface SupplierPageParam extends PageParam { | ||||||
|  |     name?: string; | ||||||
|  |     mobile?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询供应商分页 */ | ||||||
|  | export function getSupplierPage(params: ErpSupplierApi.SupplierPageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpSupplierApi.Supplier>>( | ||||||
|  |     '/erp/supplier/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获得供应商精简列表 */ | ||||||
|  | export function getSupplierSimpleList() { | ||||||
|  |   return requestClient.get<ErpSupplierApi.Supplier[]>( | ||||||
|  |     '/erp/supplier/simple-list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询供应商详情 */ | ||||||
|  | export function getSupplier(id: number) { | ||||||
|  |   return requestClient.get<ErpSupplierApi.Supplier>( | ||||||
|  |     `/erp/supplier/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增供应商 */ | ||||||
|  | export function createSupplier(data: ErpSupplierApi.Supplier) { | ||||||
|  |   return requestClient.post('/erp/supplier/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改供应商 */ | ||||||
|  | export function updateSupplier(data: ErpSupplierApi.Supplier) { | ||||||
|  |   return requestClient.put('/erp/supplier/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除供应商 */ | ||||||
|  | export function deleteSupplier(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/supplier/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出供应商 Excel */ | ||||||
|  | export function exportSupplier(params: any) { | ||||||
|  |   return requestClient.download('/erp/supplier/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,73 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpCustomerApi { | ||||||
|  |   /** ERP 客户信息 */ | ||||||
|  |   export interface Customer { | ||||||
|  |     id?: number; // 客户编号
 | ||||||
|  |     name: string; // 客户名称
 | ||||||
|  |     contact: string; // 联系人
 | ||||||
|  |     mobile: string; // 手机号码
 | ||||||
|  |     telephone: string; // 联系电话
 | ||||||
|  |     email: string; // 电子邮箱
 | ||||||
|  |     fax: string; // 传真
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     status: number; // 开启状态
 | ||||||
|  |     sort: number; // 排序
 | ||||||
|  |     taxNo: string; // 纳税人识别号
 | ||||||
|  |     taxPercent: number; // 税率
 | ||||||
|  |     bankName: string; // 开户行
 | ||||||
|  |     bankAccount: string; // 开户账号
 | ||||||
|  |     bankAddress: string; // 开户地址
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 客户分页查询参数 */ | ||||||
|  |   export interface CustomerPageParam extends PageParam { | ||||||
|  |     name?: string; | ||||||
|  |     mobile?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询客户分页 */ | ||||||
|  | export function getCustomerPage(params: ErpCustomerApi.CustomerPageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpCustomerApi.Customer>>( | ||||||
|  |     '/erp/customer/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询客户精简列表 */ | ||||||
|  | export function getCustomerSimpleList() { | ||||||
|  |   return requestClient.get<ErpCustomerApi.Customer[]>( | ||||||
|  |     '/erp/customer/simple-list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询客户详情 */ | ||||||
|  | export function getCustomer(id: number) { | ||||||
|  |   return requestClient.get<ErpCustomerApi.Customer>( | ||||||
|  |     `/erp/customer/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增客户 */ | ||||||
|  | export function createCustomer(data: ErpCustomerApi.Customer) { | ||||||
|  |   return requestClient.post('/erp/customer/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改客户 */ | ||||||
|  | export function updateCustomer(data: ErpCustomerApi.Customer) { | ||||||
|  |   return requestClient.put('/erp/customer/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除客户 */ | ||||||
|  | export function deleteCustomer(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/customer/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出客户 Excel */ | ||||||
|  | export function exportCustomer(params: any) { | ||||||
|  |   return requestClient.download('/erp/customer/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpSaleOrderApi { | ||||||
|  |   /** ERP 销售订单信息 */ | ||||||
|  |   export interface SaleOrder { | ||||||
|  |     id?: number; // 订单工单编号
 | ||||||
|  |     no: string; // 销售订单号
 | ||||||
|  |     customerId: number; // 客户编号
 | ||||||
|  |     orderTime: Date; // 订单时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     outCount: number; // 销售出库数量
 | ||||||
|  |     returnCount: number; // 销售退货数量
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 销售订单分页查询参数 */ | ||||||
|  |   export interface SaleOrderPageParam extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     customerId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询销售订单分页 */ | ||||||
|  | export function getSaleOrderPage(params: ErpSaleOrderApi.SaleOrderPageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpSaleOrderApi.SaleOrder>>( | ||||||
|  |     '/erp/sale-order/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询销售订单详情 */ | ||||||
|  | export function getSaleOrder(id: number) { | ||||||
|  |   return requestClient.get<ErpSaleOrderApi.SaleOrder>( | ||||||
|  |     `/erp/sale-order/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增销售订单 */ | ||||||
|  | export function createSaleOrder(data: ErpSaleOrderApi.SaleOrder) { | ||||||
|  |   return requestClient.post('/erp/sale-order/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改销售订单 */ | ||||||
|  | export function updateSaleOrder(data: ErpSaleOrderApi.SaleOrder) { | ||||||
|  |   return requestClient.put('/erp/sale-order/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 更新销售订单的状态 */ | ||||||
|  | export function updateSaleOrderStatus(id: number, status: number) { | ||||||
|  |   return requestClient.put('/erp/sale-order/update-status', null, { | ||||||
|  |     params: { id, status }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除销售订单 */ | ||||||
|  | export function deleteSaleOrder(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/sale-order/delete', { | ||||||
|  |     params: { ids: ids.join(',') }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出销售订单 Excel */ | ||||||
|  | export function exportSaleOrder(params: any) { | ||||||
|  |   return requestClient.download('/erp/sale-order/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpSaleOutApi { | ||||||
|  |   /** 销售出库信息 */ | ||||||
|  |   export interface SaleOut { | ||||||
|  |     id?: number; // 销售出库编号
 | ||||||
|  |     no: string; // 销售出库号
 | ||||||
|  |     customerId: number; // 客户编号
 | ||||||
|  |     outTime: Date; // 出库时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 销售出库分页查询参数 */ | ||||||
|  |   export interface SaleOutPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     customerId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 销售出库状态更新参数 */ | ||||||
|  |   export interface SaleOutStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询销售出库分页 | ||||||
|  |  */ | ||||||
|  | export function getSaleOutPage(params: ErpSaleOutApi.SaleOutPageParams) { | ||||||
|  |   return requestClient.get<PageResult<ErpSaleOutApi.SaleOut>>( | ||||||
|  |     '/erp/sale-out/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询销售出库详情 | ||||||
|  |  */ | ||||||
|  | export function getSaleOut(id: number) { | ||||||
|  |   return requestClient.get<ErpSaleOutApi.SaleOut>(`/erp/sale-out/get?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增销售出库 | ||||||
|  |  */ | ||||||
|  | export function createSaleOut(data: ErpSaleOutApi.SaleOut) { | ||||||
|  |   return requestClient.post('/erp/sale-out/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改销售出库 | ||||||
|  |  */ | ||||||
|  | export function updateSaleOut(data: ErpSaleOutApi.SaleOut) { | ||||||
|  |   return requestClient.put('/erp/sale-out/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新销售出库的状态 | ||||||
|  |  */ | ||||||
|  | export function updateSaleOutStatus(params: ErpSaleOutApi.SaleOutStatusParams) { | ||||||
|  |   return requestClient.put('/erp/sale-out/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除销售出库 | ||||||
|  |  */ | ||||||
|  | export function deleteSaleOut(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/sale-out/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出销售出库 Excel | ||||||
|  |  */ | ||||||
|  | export function exportSaleOut(params: ErpSaleOutApi.SaleOutPageParams) { | ||||||
|  |   return requestClient.download('/erp/sale-out/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,100 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpSaleReturnApi { | ||||||
|  |   /** 销售退货信息 */ | ||||||
|  |   export interface SaleReturn { | ||||||
|  |     id?: number; // 销售退货编号
 | ||||||
|  |     no: string; // 销售退货号
 | ||||||
|  |     customerId: number; // 客户编号
 | ||||||
|  |     returnTime: Date; // 退货时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 销售退货分页查询参数 */ | ||||||
|  |   export interface SaleReturnPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     customerId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 销售退货状态更新参数 */ | ||||||
|  |   export interface SaleReturnStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询销售退货分页 | ||||||
|  |  */ | ||||||
|  | export function getSaleReturnPage( | ||||||
|  |   params: ErpSaleReturnApi.SaleReturnPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpSaleReturnApi.SaleReturn>>( | ||||||
|  |     '/erp/sale-return/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询销售退货详情 | ||||||
|  |  */ | ||||||
|  | export function getSaleReturn(id: number) { | ||||||
|  |   return requestClient.get<ErpSaleReturnApi.SaleReturn>( | ||||||
|  |     `/erp/sale-return/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增销售退货 | ||||||
|  |  */ | ||||||
|  | export function createSaleReturn(data: ErpSaleReturnApi.SaleReturn) { | ||||||
|  |   return requestClient.post('/erp/sale-return/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改销售退货 | ||||||
|  |  */ | ||||||
|  | export function updateSaleReturn(data: ErpSaleReturnApi.SaleReturn) { | ||||||
|  |   return requestClient.put('/erp/sale-return/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新销售退货的状态 | ||||||
|  |  */ | ||||||
|  | export function updateSaleReturnStatus( | ||||||
|  |   params: ErpSaleReturnApi.SaleReturnStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/sale-return/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除销售退货 | ||||||
|  |  */ | ||||||
|  | export function deleteSaleReturn(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/sale-return/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出销售退货 Excel | ||||||
|  |  */ | ||||||
|  | export function exportSaleReturn( | ||||||
|  |   params: ErpSaleReturnApi.SaleReturnPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/sale-return/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpPurchaseStatisticsApi { | ||||||
|  |   /** ERP 采购全局统计 */ | ||||||
|  |   export interface PurchaseSummary { | ||||||
|  |     todayPrice: number; // 今日采购金额
 | ||||||
|  |     yesterdayPrice: number; // 昨日采购金额
 | ||||||
|  |     monthPrice: number; // 本月采购金额
 | ||||||
|  |     yearPrice: number; // 今年采购金额
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** ERP 采购时间段统计 */ | ||||||
|  |   export interface PurchaseTimeSummary { | ||||||
|  |     time: string; // 时间
 | ||||||
|  |     price: number; // 采购金额
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获得采购统计 */ | ||||||
|  | export function getPurchaseSummary() { | ||||||
|  |   return requestClient.get<ErpPurchaseStatisticsApi.PurchaseSummary>( | ||||||
|  |     '/erp/purchase-statistics/summary', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获得采购时间段统计 */ | ||||||
|  | export function getPurchaseTimeSummary() { | ||||||
|  |   return requestClient.get<ErpPurchaseStatisticsApi.PurchaseTimeSummary[]>( | ||||||
|  |     '/erp/purchase-statistics/time-summary', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpSaleStatisticsApi { | ||||||
|  |   /** ERP 销售全局统计 */ | ||||||
|  |   export interface SaleSummary { | ||||||
|  |     todayPrice: number; // 今日销售金额
 | ||||||
|  |     yesterdayPrice: number; // 昨日销售金额
 | ||||||
|  |     monthPrice: number; // 本月销售金额
 | ||||||
|  |     yearPrice: number; // 今年销售金额
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** ERP 销售时间段统计 */ | ||||||
|  |   export interface SaleTimeSummary { | ||||||
|  |     time: string; // 时间
 | ||||||
|  |     price: number; // 销售金额
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获得销售统计 */ | ||||||
|  | export function getSaleSummary() { | ||||||
|  |   return requestClient.get<ErpSaleStatisticsApi.SaleSummary>( | ||||||
|  |     '/erp/sale-statistics/summary', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获得销售时间段统计 */ | ||||||
|  | export function getSaleTimeSummary() { | ||||||
|  |   return requestClient.get<ErpSaleStatisticsApi.SaleTimeSummary[]>( | ||||||
|  |     '/erp/sale-statistics/time-summary', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,98 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpStockCheckApi { | ||||||
|  |   /** 库存盘点单信息 */ | ||||||
|  |   export interface StockCheck { | ||||||
|  |     id?: number; // 盘点编号
 | ||||||
|  |     no: string; // 盘点单号
 | ||||||
|  |     checkTime: Date; // 盘点时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 库存盘点单分页查询参数 */ | ||||||
|  |   export interface StockCheckPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 库存盘点单状态更新参数 */ | ||||||
|  |   export interface StockCheckStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询库存盘点单分页 | ||||||
|  |  */ | ||||||
|  | export function getStockCheckPage( | ||||||
|  |   params: ErpStockCheckApi.StockCheckPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockCheckApi.StockCheck>>( | ||||||
|  |     '/erp/stock-check/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询库存盘点单详情 | ||||||
|  |  */ | ||||||
|  | export function getStockCheck(id: number) { | ||||||
|  |   return requestClient.get<ErpStockCheckApi.StockCheck>( | ||||||
|  |     `/erp/stock-check/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增库存盘点单 | ||||||
|  |  */ | ||||||
|  | export function createStockCheck(data: ErpStockCheckApi.StockCheck) { | ||||||
|  |   return requestClient.post('/erp/stock-check/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改库存盘点单 | ||||||
|  |  */ | ||||||
|  | export function updateStockCheck(data: ErpStockCheckApi.StockCheck) { | ||||||
|  |   return requestClient.put('/erp/stock-check/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新库存盘点单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateStockCheckStatus( | ||||||
|  |   params: ErpStockCheckApi.StockCheckStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/stock-check/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除库存盘点单 | ||||||
|  |  */ | ||||||
|  | export function deleteStockCheck(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/stock-check/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出库存盘点单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportStockCheck( | ||||||
|  |   params: ErpStockCheckApi.StockCheckPageParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.download('/erp/stock-check/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpStockInApi { | ||||||
|  |   /** 其它入库单信息 */ | ||||||
|  |   export interface StockIn { | ||||||
|  |     id?: number; // 入库编号
 | ||||||
|  |     no: string; // 入库单号
 | ||||||
|  |     supplierId: number; // 供应商编号
 | ||||||
|  |     supplierName?: string; // 供应商名称
 | ||||||
|  |     inTime: Date; // 入库时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     fileUrl?: string; // 附件
 | ||||||
|  |     productNames?: string; // 产品信息
 | ||||||
|  |     creatorName?: string; // 创建人
 | ||||||
|  |     items?: StockInItem[]; // 入库产品清单
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 其它入库单产品信息 */ | ||||||
|  |   export interface StockInItem { | ||||||
|  |     id?: number; // 编号
 | ||||||
|  |     warehouseId: number; // 仓库编号
 | ||||||
|  |     productId: number; // 产品编号
 | ||||||
|  |     productName?: string; // 产品名称
 | ||||||
|  |     productUnitId?: number; // 产品单位编号
 | ||||||
|  |     productUnitName?: string; // 产品单位名称
 | ||||||
|  |     productBarCode?: string; // 产品条码
 | ||||||
|  |     count: number; // 数量
 | ||||||
|  |     productPrice: number; // 产品单价
 | ||||||
|  |     totalPrice: number; // 总价
 | ||||||
|  |     stockCount?: number; // 库存数量
 | ||||||
|  |     remark?: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 其它入库单分页查询参数 */ | ||||||
|  |   export interface StockInPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     supplierId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 其它入库单状态更新参数 */ | ||||||
|  |   export interface StockInStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询其它入库单分页 | ||||||
|  |  */ | ||||||
|  | export function getStockInPage(params: ErpStockInApi.StockInPageParams) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockInApi.StockIn>>( | ||||||
|  |     '/erp/stock-in/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询其它入库单详情 | ||||||
|  |  */ | ||||||
|  | export function getStockIn(id: number) { | ||||||
|  |   return requestClient.get<ErpStockInApi.StockIn>(`/erp/stock-in/get?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增其它入库单 | ||||||
|  |  */ | ||||||
|  | export function createStockIn(data: ErpStockInApi.StockIn) { | ||||||
|  |   return requestClient.post('/erp/stock-in/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改其它入库单 | ||||||
|  |  */ | ||||||
|  | export function updateStockIn(data: ErpStockInApi.StockIn) { | ||||||
|  |   return requestClient.put('/erp/stock-in/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新其它入库单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateStockInStatus(params: ErpStockInApi.StockInStatusParams) { | ||||||
|  |   return requestClient.put('/erp/stock-in/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除其它入库单 | ||||||
|  |  */ | ||||||
|  | export function deleteStockIn(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/stock-in/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出其它入库单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportStockIn(params: ErpStockInApi.StockInPageParams) { | ||||||
|  |   return requestClient.download('/erp/stock-in/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,94 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpStockMoveApi { | ||||||
|  |   /** 库存调拨单信息 */ | ||||||
|  |   export interface StockMove { | ||||||
|  |     id?: number; // 调拨编号
 | ||||||
|  |     no: string; // 调拨单号
 | ||||||
|  |     outTime: Date; // 调拨时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 库存调拨单分页查询参数 */ | ||||||
|  |   export interface StockMovePageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 库存调拨单状态更新参数 */ | ||||||
|  |   export interface StockMoveStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询库存调拨单分页 | ||||||
|  |  */ | ||||||
|  | export function getStockMovePage(params: ErpStockMoveApi.StockMovePageParams) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockMoveApi.StockMove>>( | ||||||
|  |     '/erp/stock-move/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询库存调拨单详情 | ||||||
|  |  */ | ||||||
|  | export function getStockMove(id: number) { | ||||||
|  |   return requestClient.get<ErpStockMoveApi.StockMove>( | ||||||
|  |     `/erp/stock-move/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增库存调拨单 | ||||||
|  |  */ | ||||||
|  | export function createStockMove(data: ErpStockMoveApi.StockMove) { | ||||||
|  |   return requestClient.post('/erp/stock-move/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改库存调拨单 | ||||||
|  |  */ | ||||||
|  | export function updateStockMove(data: ErpStockMoveApi.StockMove) { | ||||||
|  |   return requestClient.put('/erp/stock-move/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新库存调拨单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateStockMoveStatus( | ||||||
|  |   params: ErpStockMoveApi.StockMoveStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/stock-move/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除库存调拨单 | ||||||
|  |  */ | ||||||
|  | export function deleteStockMove(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/stock-move/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出库存调拨单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportStockMove(params: ErpStockMoveApi.StockMovePageParams) { | ||||||
|  |   return requestClient.download('/erp/stock-move/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,96 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | namespace ErpStockOutApi { | ||||||
|  |   /** 其它出库单信息 */ | ||||||
|  |   export interface StockOut { | ||||||
|  |     id?: number; // 出库编号
 | ||||||
|  |     no: string; // 出库单号
 | ||||||
|  |     customerId: number; // 客户编号
 | ||||||
|  |     outTime: Date; // 出库时间
 | ||||||
|  |     totalCount: number; // 合计数量
 | ||||||
|  |     totalPrice: number; // 合计金额,单位:元
 | ||||||
|  |     status: number; // 状态
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 其它出库单分页查询参数 */ | ||||||
|  |   export interface StockOutPageParams extends PageParam { | ||||||
|  |     no?: string; | ||||||
|  |     customerId?: number; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 其它出库单状态更新参数 */ | ||||||
|  |   export interface StockOutStatusParams { | ||||||
|  |     id: number; | ||||||
|  |     status: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询其它出库单分页 | ||||||
|  |  */ | ||||||
|  | export function getStockOutPage(params: ErpStockOutApi.StockOutPageParams) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockOutApi.StockOut>>( | ||||||
|  |     '/erp/stock-out/page', | ||||||
|  |     { | ||||||
|  |       params, | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询其它出库单详情 | ||||||
|  |  */ | ||||||
|  | export function getStockOut(id: number) { | ||||||
|  |   return requestClient.get<ErpStockOutApi.StockOut>( | ||||||
|  |     `/erp/stock-out/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 新增其它出库单 | ||||||
|  |  */ | ||||||
|  | export function createStockOut(data: ErpStockOutApi.StockOut) { | ||||||
|  |   return requestClient.post('/erp/stock-out/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 修改其它出库单 | ||||||
|  |  */ | ||||||
|  | export function updateStockOut(data: ErpStockOutApi.StockOut) { | ||||||
|  |   return requestClient.put('/erp/stock-out/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 更新其它出库单的状态 | ||||||
|  |  */ | ||||||
|  | export function updateStockOutStatus( | ||||||
|  |   params: ErpStockOutApi.StockOutStatusParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/stock-out/update-status', null, { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 删除其它出库单 | ||||||
|  |  */ | ||||||
|  | export function deleteStockOut(ids: number[]) { | ||||||
|  |   return requestClient.delete('/erp/stock-out/delete', { | ||||||
|  |     params: { | ||||||
|  |       ids: ids.join(','), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出其它出库单 Excel | ||||||
|  |  */ | ||||||
|  | export function exportStockOut(params: ErpStockOutApi.StockOutPageParams) { | ||||||
|  |   return requestClient.download('/erp/stock-out/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpStockRecordApi { | ||||||
|  |   /** ERP 产品库存明细 */ | ||||||
|  |   export interface StockRecord { | ||||||
|  |     id?: number; // 编号
 | ||||||
|  |     productId: number; // 产品编号
 | ||||||
|  |     warehouseId: number; // 仓库编号
 | ||||||
|  |     count: number; // 出入库数量
 | ||||||
|  |     totalCount: number; // 总库存量
 | ||||||
|  |     bizType: number; // 业务类型
 | ||||||
|  |     bizId: number; // 业务编号
 | ||||||
|  |     bizItemId: number; // 业务项编号
 | ||||||
|  |     bizNo: string; // 业务单号
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 库存记录分页查询参数 */ | ||||||
|  |   export interface StockRecordPageParam extends PageParam { | ||||||
|  |     productId?: number; | ||||||
|  |     warehouseId?: number; | ||||||
|  |     bizType?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品库存明细分页 */ | ||||||
|  | export function getStockRecordPage( | ||||||
|  |   params: ErpStockRecordApi.StockRecordPageParam, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockRecordApi.StockRecord>>( | ||||||
|  |     '/erp/stock-record/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询产品库存明细详情 */ | ||||||
|  | export function getStockRecord(id: number) { | ||||||
|  |   return requestClient.get<ErpStockRecordApi.StockRecord>( | ||||||
|  |     `/erp/stock-record/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出产品库存明细 Excel */ | ||||||
|  | export function exportStockRecord(params: any) { | ||||||
|  |   return requestClient.download('/erp/stock-record/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpStockApi { | ||||||
|  |   /** 产品库存信息 */ | ||||||
|  |   export interface Stock { | ||||||
|  |     id?: number; // 编号
 | ||||||
|  |     productId: number; // 产品编号
 | ||||||
|  |     warehouseId: number; // 仓库编号
 | ||||||
|  |     count: number; // 库存数量
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 产品库存分页查询参数 */ | ||||||
|  |   export interface StockPageParams extends PageParam { | ||||||
|  |     productId?: number; | ||||||
|  |     warehouseId?: number; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 产品库存查询参数 */ | ||||||
|  |   export interface StockQueryParams { | ||||||
|  |     productId: number; | ||||||
|  |     warehouseId: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询产品库存分页 | ||||||
|  |  */ | ||||||
|  | export function getStockPage(params: ErpStockApi.StockPageParams) { | ||||||
|  |   return requestClient.get<PageResult<ErpStockApi.Stock>>('/erp/stock/page', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 查询产品库存详情 | ||||||
|  |  */ | ||||||
|  | export function getStock(id: number) { | ||||||
|  |   return requestClient.get<ErpStockApi.Stock>(`/erp/stock/get?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 根据产品和仓库查询库存详情 | ||||||
|  |  */ | ||||||
|  | export function getStockByProductAndWarehouse( | ||||||
|  |   params: ErpStockApi.StockQueryParams, | ||||||
|  | ) { | ||||||
|  |   return requestClient.get<ErpStockApi.Stock>('/erp/stock/get', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 获得产品库存数量 | ||||||
|  |  */ | ||||||
|  | export function getStockCount(productId: number, warehouseId?: number) { | ||||||
|  |   const params: any = { productId }; | ||||||
|  |   if (warehouseId !== undefined) { | ||||||
|  |     params.warehouseId = warehouseId; | ||||||
|  |   } | ||||||
|  |   return requestClient.get<number>('/erp/stock/get-count', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 导出产品库存 Excel | ||||||
|  |  */ | ||||||
|  | export function exportStock(params: ErpStockApi.StockPageParams) { | ||||||
|  |   return requestClient.download('/erp/stock/export-excel', { | ||||||
|  |     params, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | import type { PageParam, PageResult } from '@vben/request'; | ||||||
|  | 
 | ||||||
|  | import { requestClient } from '#/api/request'; | ||||||
|  | 
 | ||||||
|  | export namespace ErpWarehouseApi { | ||||||
|  |   /** ERP 仓库信息 */ | ||||||
|  |   export interface Warehouse { | ||||||
|  |     id?: number; // 仓库编号
 | ||||||
|  |     name: string; // 仓库名称
 | ||||||
|  |     address: string; // 仓库地址
 | ||||||
|  |     sort: number; // 排序
 | ||||||
|  |     remark: string; // 备注
 | ||||||
|  |     principal: string; // 负责人
 | ||||||
|  |     warehousePrice: number; // 仓储费,单位:元
 | ||||||
|  |     truckagePrice: number; // 搬运费,单位:元
 | ||||||
|  |     status: number; // 开启状态
 | ||||||
|  |     defaultStatus: boolean; // 是否默认
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** 仓库分页查询参数 */ | ||||||
|  |   export interface WarehousePageParam extends PageParam { | ||||||
|  |     name?: string; | ||||||
|  |     status?: number; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询仓库分页 */ | ||||||
|  | export function getWarehousePage(params: ErpWarehouseApi.WarehousePageParam) { | ||||||
|  |   return requestClient.get<PageResult<ErpWarehouseApi.Warehouse>>( | ||||||
|  |     '/erp/warehouse/page', | ||||||
|  |     { params }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询仓库精简列表 */ | ||||||
|  | export function getWarehouseSimpleList() { | ||||||
|  |   return requestClient.get<ErpWarehouseApi.Warehouse[]>( | ||||||
|  |     '/erp/warehouse/simple-list', | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查询仓库详情 */ | ||||||
|  | export function getWarehouse(id: number) { | ||||||
|  |   return requestClient.get<ErpWarehouseApi.Warehouse>( | ||||||
|  |     `/erp/warehouse/get?id=${id}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增仓库 */ | ||||||
|  | export function createWarehouse(data: ErpWarehouseApi.Warehouse) { | ||||||
|  |   return requestClient.post('/erp/warehouse/create', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改仓库 */ | ||||||
|  | export function updateWarehouse(data: ErpWarehouseApi.Warehouse) { | ||||||
|  |   return requestClient.put('/erp/warehouse/update', data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改仓库默认状态 */ | ||||||
|  | export function updateWarehouseDefaultStatus( | ||||||
|  |   id: number, | ||||||
|  |   defaultStatus: boolean, | ||||||
|  | ) { | ||||||
|  |   return requestClient.put('/erp/warehouse/update-default-status', null, { | ||||||
|  |     params: { id, defaultStatus }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除仓库 */ | ||||||
|  | export function deleteWarehouse(id: number) { | ||||||
|  |   return requestClient.delete(`/erp/warehouse/delete?id=${id}`); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出仓库 Excel */ | ||||||
|  | export function exportWarehouse(params: any) { | ||||||
|  |   return requestClient.download('/erp/warehouse/export-excel', { params }); | ||||||
|  | } | ||||||
|  | @ -137,7 +137,7 @@ function handleButtonClick(action: ActionItem) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 监听props变化,强制重新计算 | /** 监听props变化,强制重新计算 */ | ||||||
| watch( | watch( | ||||||
|   () => [props.actions, props.dropDownActions], |   () => [props.actions, props.dropDownActions], | ||||||
|   () => { |   () => { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|  | // TODO @gjd:https://t.zsxq.com/pmNb1 AI 对话、绘图底部没对齐 | ||||||
| import type { AiChatConversationApi } from '#/api/ai/chat/conversation'; | import type { AiChatConversationApi } from '#/api/ai/chat/conversation'; | ||||||
| import type { AiChatMessageApi } from '#/api/ai/chat/message'; | import type { AiChatMessageApi } from '#/api/ai/chat/message'; | ||||||
| 
 | 
 | ||||||
|  | @ -23,6 +24,7 @@ import MessageList from './components/message/MessageList.vue'; | ||||||
| import MessageListEmpty from './components/message/MessageListEmpty.vue'; | import MessageListEmpty from './components/message/MessageListEmpty.vue'; | ||||||
| import MessageLoading from './components/message/MessageLoading.vue'; | import MessageLoading from './components/message/MessageLoading.vue'; | ||||||
| import MessageNewConversation from './components/message/MessageNewConversation.vue'; | import MessageNewConversation from './components/message/MessageNewConversation.vue'; | ||||||
|  | 
 | ||||||
| /** AI 聊天对话 列表 */ | /** AI 聊天对话 列表 */ | ||||||
| defineOptions({ name: 'AiChat' }); | defineOptions({ name: 'AiChat' }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -123,6 +123,7 @@ const formData: any = ref({ | ||||||
|     enable: false, |     enable: false, | ||||||
|     summary: [], |     summary: [], | ||||||
|   }, |   }, | ||||||
|  |   allowWithdrawTask: false, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // 流程数据 | // 流程数据 | ||||||
|  | @ -178,6 +179,16 @@ async function initData() { | ||||||
|     // 特殊:复制场景 |     // 特殊:复制场景 | ||||||
|     if (route.params.type === 'copy') { |     if (route.params.type === 'copy') { | ||||||
|       delete formData.value.id; |       delete formData.value.id; | ||||||
|  |       if (formData.value.bpmnXml) { | ||||||
|  |         formData.value.bpmnXml = formData.value.bpmnXml.replaceAll( | ||||||
|  |           formData.value.name, | ||||||
|  |           `${formData.value.name}副本`, | ||||||
|  |         ); | ||||||
|  |         formData.value.bpmnXml = formData.value.bpmnXml.replaceAll( | ||||||
|  |           formData.value.key, | ||||||
|  |           `${formData.value.key}_copy`, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|       formData.value.name += '副本'; |       formData.value.name += '副本'; | ||||||
|       formData.value.key += '_copy'; |       formData.value.key += '_copy'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -69,7 +69,27 @@ const selectedUsers = ref<number[]>(); | ||||||
| 
 | 
 | ||||||
| const rules: Record<string, Rule[]> = { | const rules: Record<string, Rule[]> = { | ||||||
|   name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }], |   name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }], | ||||||
|   key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }], |   key: [ | ||||||
|  |     { required: true, message: '流程标识不能为空', trigger: 'blur' }, | ||||||
|  |     { | ||||||
|  |       validator: (_rule: any, value: string, callback: any) => { | ||||||
|  |         if (!value) { | ||||||
|  |           callback(); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         if (!/^[a-z_][\-\w.$]*$/i.test(value)) { | ||||||
|  |           callback( | ||||||
|  |             new Error( | ||||||
|  |               '只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头', | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         callback(); | ||||||
|  |       }, | ||||||
|  |       trigger: 'blur', | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|   category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }], |   category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }], | ||||||
|   type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }], |   type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }], | ||||||
|   visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }], |   visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }], | ||||||
|  |  | ||||||
|  | @ -217,6 +217,9 @@ function initData() { | ||||||
|   if (modelData.value.taskAfterTriggerSetting) { |   if (modelData.value.taskAfterTriggerSetting) { | ||||||
|     taskAfterTriggerEnable.value = true; |     taskAfterTriggerEnable.value = true; | ||||||
|   } |   } | ||||||
|  |   if (modelData.value.allowWithdrawTask === undefined) { | ||||||
|  |     modelData.value.allowWithdrawTask = false; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 监听表单 ID 变化,加载表单数据 */ | /** 监听表单 ID 变化,加载表单数据 */ | ||||||
|  | @ -267,6 +270,18 @@ defineExpose({ initData, validate }); | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </FormItem> |     </FormItem> | ||||||
|  |     <FormItem class="mb-5" label="审批人权限"> | ||||||
|  |       <div class="mt-1 flex flex-col"> | ||||||
|  |         <Checkbox v-model:checked="modelData.allowWithdrawTask"> | ||||||
|  |           允许审批人撤回任务 | ||||||
|  |         </Checkbox> | ||||||
|  |         <div class="ml-6"> | ||||||
|  |           <TypographyText type="secondary"> | ||||||
|  |             审批人可撤回正在审批节点的前一节点 | ||||||
|  |           </TypographyText> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </FormItem> | ||||||
|     <FormItem v-if="modelData.processIdRule" class="mb-5" label="流程编码"> |     <FormItem v-if="modelData.processIdRule" class="mb-5" label="流程编码"> | ||||||
|       <Row :gutter="8" align="middle"> |       <Row :gutter="8" align="middle"> | ||||||
|         <Col :span="1"> |         <Col :span="1"> | ||||||
|  |  | ||||||
|  | @ -415,6 +415,7 @@ const handleRenameSuccess = () => { | ||||||
|     > |     > | ||||||
|       <div class="flex h-12 items-center"> |       <div class="flex h-12 items-center"> | ||||||
|         <!-- 头部:分类名 --> |         <!-- 头部:分类名 --> | ||||||
|  |         <!-- TODO @jason:1)无法拖动排序;2)拖动后,直接请求排序,不用有个【保存】;排序模型分类,和排序分类里的模型,交互有点不同哈。 --> | ||||||
|         <div class="flex items-center"> |         <div class="flex items-center"> | ||||||
|           <Tooltip v-if="isCategorySorting" title="拖动排序"> |           <Tooltip v-if="isCategorySorting" title="拖动排序"> | ||||||
|             <span |             <span | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ | ||||||
| import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | ||||||
| import type { SystemUserApi } from '#/api/system/user'; | import type { SystemUserApi } from '#/api/system/user'; | ||||||
| 
 | 
 | ||||||
|  | // TODO @jason:业务表单审批时,读取不到界面,参见 https://t.zsxq.com/eif2e | ||||||
|  | 
 | ||||||
| import { nextTick, onMounted, ref, shallowRef, watch } from 'vue'; | import { nextTick, onMounted, ref, shallowRef, watch } from 'vue'; | ||||||
| 
 | 
 | ||||||
| import { Page } from '@vben/common-ui'; | import { Page } from '@vben/common-ui'; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import type { Rule } from 'ant-design-vue/es/form'; | ||||||
| 
 | 
 | ||||||
| import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | ||||||
| 
 | 
 | ||||||
| import { computed, reactive, ref, watch } from 'vue'; | import { computed, nextTick, reactive, ref, watch } from 'vue'; | ||||||
| import { useRouter } from 'vue-router'; | import { useRouter } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
| import { useVbenModal } from '@vben/common-ui'; | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | @ -102,6 +102,7 @@ const approveSignFormRef = ref(); | ||||||
| const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>( | const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>( | ||||||
|   [], |   [], | ||||||
| ); // 下一个审批节点信息 | ); // 下一个审批节点信息 | ||||||
|  | const nextAssigneesTimelineRef = ref(); // 下一个节点审批人时间线组件的引用 | ||||||
| const approveReasonForm: any = reactive({ | const approveReasonForm: any = reactive({ | ||||||
|   reason: '', |   reason: '', | ||||||
|   signPicUrl: '', |   signPicUrl: '', | ||||||
|  | @ -278,6 +279,10 @@ function closePopover(type: string, formRef: any | FormInstance) { | ||||||
|   } |   } | ||||||
|   if (popOverVisible.value[type]) popOverVisible.value[type] = false; |   if (popOverVisible.value[type]) popOverVisible.value[type] = false; | ||||||
|   nextAssigneesActivityNode.value = []; |   nextAssigneesActivityNode.value = []; | ||||||
|  |   // 清理 Timeline 组件中的自定义审批人数据 | ||||||
|  |   if (nextAssigneesTimelineRef.value) { | ||||||
|  |     nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({}); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */ | /** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */ | ||||||
|  | @ -290,6 +295,7 @@ async function initNextAssigneesFormField() { | ||||||
|     processVariablesStr: JSON.stringify(variables), |     processVariablesStr: JSON.stringify(variables), | ||||||
|   }); |   }); | ||||||
|   if (data && data.length > 0) { |   if (data && data.length > 0) { | ||||||
|  |     const customApproveUsersData: Record<string, any[]> = {}; // 用于收集需要设置到 Timeline 组件的自定义审批人数据 | ||||||
|     data.forEach((node: BpmProcessInstanceApi.ApprovalNodeInfo) => { |     data.forEach((node: BpmProcessInstanceApi.ApprovalNodeInfo) => { | ||||||
|       if ( |       if ( | ||||||
|         // 情况一:当前节点没有审批人,并且是发起人自选 |         // 情况一:当前节点没有审批人,并且是发起人自选 | ||||||
|  | @ -302,7 +308,23 @@ async function initNextAssigneesFormField() { | ||||||
|       ) { |       ) { | ||||||
|         nextAssigneesActivityNode.value.push(node); |         nextAssigneesActivityNode.value.push(node); | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|  |       // 如果节点有 candidateUsers,设置到 customApproveUsers 中 | ||||||
|  |       if (node.candidateUsers && node.candidateUsers.length > 0) { | ||||||
|  |         customApproveUsersData[node.id] = node.candidateUsers; | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     // 将 candidateUsers 设置到 Timeline 组件中 | ||||||
|  |     await nextTick(); // 等待下一个 tick,确保 Timeline 组件已经渲染 | ||||||
|  |     if ( | ||||||
|  |       nextAssigneesTimelineRef.value && | ||||||
|  |       Object.keys(customApproveUsersData).length > 0 | ||||||
|  |     ) { | ||||||
|  |       nextAssigneesTimelineRef.value.batchSetCustomApproveUsers( | ||||||
|  |         customApproveUsersData, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -364,6 +386,10 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { | ||||||
|       await TaskApi.approveTask(data); |       await TaskApi.approveTask(data); | ||||||
|       popOverVisible.value.approve = false; |       popOverVisible.value.approve = false; | ||||||
|       nextAssigneesActivityNode.value = []; |       nextAssigneesActivityNode.value = []; | ||||||
|  |       // 清理 Timeline 组件中的自定义审批人数据 | ||||||
|  |       if (nextAssigneesTimelineRef.value) { | ||||||
|  |         nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({}); | ||||||
|  |       } | ||||||
|       message.success('审批通过成功'); |       message.success('审批通过成功'); | ||||||
|     } else { |     } else { | ||||||
|       // 审批不通过数据 |       // 审批不通过数据 | ||||||
|  | @ -733,9 +759,10 @@ defineExpose({ loadTodoTask }); | ||||||
|               > |               > | ||||||
|                 <div class="-mb-8 -mt-3.5 ml-2.5"> |                 <div class="-mb-8 -mt-3.5 ml-2.5"> | ||||||
|                   <ProcessInstanceTimeline |                   <ProcessInstanceTimeline | ||||||
|  |                     ref="nextAssigneesTimelineRef" | ||||||
|                     :activity-nodes="nextAssigneesActivityNode" |                     :activity-nodes="nextAssigneesActivityNode" | ||||||
|                     :show-status-icon="false" |                     :show-status-icon="false" | ||||||
|                     :use-next-assignees="true" |                     :enable-approve-user-select="true" | ||||||
|                     @select-user-confirm="selectNextAssigneesConfirm" |                     @select-user-confirm="selectNextAssigneesConfirm" | ||||||
|                   /> |                   /> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|  | @ -23,12 +23,12 @@ defineOptions({ name: 'BpmProcessInstanceTimeline' }); | ||||||
| const props = withDefaults( | const props = withDefaults( | ||||||
|   defineProps<{ |   defineProps<{ | ||||||
|     activityNodes: BpmProcessInstanceApi.ApprovalNodeInfo[]; // 审批节点信息 |     activityNodes: BpmProcessInstanceApi.ApprovalNodeInfo[]; // 审批节点信息 | ||||||
|  |     enableApproveUserSelect?: boolean; // 是否开启审批人自选功能 | ||||||
|     showStatusIcon?: boolean; // 是否显示头像右下角状态图标 |     showStatusIcon?: boolean; // 是否显示头像右下角状态图标 | ||||||
|     useNextAssignees?: boolean; //  是否用于下一个节点审批人选择 |  | ||||||
|   }>(), |   }>(), | ||||||
|   { |   { | ||||||
|     showStatusIcon: true, // 默认值为 true |     showStatusIcon: true, // 默认值为 true | ||||||
|     useNextAssignees: false, // 默认值为 false |     enableApproveUserSelect: false, // 默认值为 false | ||||||
|   }, |   }, | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | @ -181,6 +181,9 @@ function handleUserSelectConfirm(userList: any[]) { | ||||||
| 
 | 
 | ||||||
| /** 跳转子流程 */ | /** 跳转子流程 */ | ||||||
| function handleChildProcess(activity: any) { | function handleChildProcess(activity: any) { | ||||||
|  |   if (!activity.processInstanceId) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   push({ |   push({ | ||||||
|     name: 'BpmProcessInstanceDetail', |     name: 'BpmProcessInstanceDetail', | ||||||
|     query: { |     query: { | ||||||
|  | @ -195,12 +198,12 @@ function shouldShowCustomUserSelect( | ||||||
| ) { | ) { | ||||||
|   return ( |   return ( | ||||||
|     isEmpty(activity.tasks) && |     isEmpty(activity.tasks) && | ||||||
|     isEmpty(activity.candidateUsers) && |     ((BpmCandidateStrategyEnum.START_USER_SELECT === | ||||||
|     (BpmCandidateStrategyEnum.START_USER_SELECT === |       activity.candidateStrategy && | ||||||
|       activity.candidateStrategy || |       isEmpty(activity.candidateUsers)) || | ||||||
|       (BpmCandidateStrategyEnum.APPROVE_USER_SELECT === |       (props.enableApproveUserSelect && | ||||||
|         activity.candidateStrategy && |         BpmCandidateStrategyEnum.APPROVE_USER_SELECT === | ||||||
|         props.useNextAssignees)) |           activity.candidateStrategy)) | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -223,6 +226,21 @@ function handleUserSelectClosed() { | ||||||
| function handleUserSelectCancel() { | function handleUserSelectCancel() { | ||||||
|   selectedUsers.value = []; |   selectedUsers.value = []; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** 设置自定义审批人 */ | ||||||
|  | const setCustomApproveUsers = (activityId: string, users: any[]) => { | ||||||
|  |   customApproveUsers.value[activityId] = users || []; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 批量设置多个节点的自定义审批人 */ | ||||||
|  | const batchSetCustomApproveUsers = (data: Record<string, any[]>) => { | ||||||
|  |   Object.keys(data).forEach((activityId) => { | ||||||
|  |     customApproveUsers.value[activityId] = data[activityId] || []; | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // 暴露方法给父组件 | ||||||
|  | defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -284,6 +302,7 @@ function handleUserSelectCancel() { | ||||||
|               ghost |               ghost | ||||||
|               size="small" |               size="small" | ||||||
|               @click="handleChildProcess(activity)" |               @click="handleChildProcess(activity)" | ||||||
|  |               :disabled="!activity.processInstanceId" | ||||||
|             > |             > | ||||||
|               查看子流程 |               查看子流程 | ||||||
|             </Button> |             </Button> | ||||||
|  |  | ||||||
|  | @ -4,8 +4,10 @@ import type { BpmTaskApi } from '#/api/bpm/task'; | ||||||
| 
 | 
 | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import { DocAlert, Page } from '@vben/common-ui'; | ||||||
| 
 | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
| import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
| import { getTaskDonePage } from '#/api/bpm/task'; | import { getTaskDonePage, withdrawTask } from '#/api/bpm/task'; | ||||||
| import { router } from '#/router'; | import { router } from '#/router'; | ||||||
| 
 | 
 | ||||||
| import { useGridColumns, useGridFormSchema } from './data'; | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | @ -23,7 +25,15 @@ function handleHistory(row: BpmTaskApi.TaskManager) { | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const [Grid] = useVbenVxeGrid({ | /** 撤回任务 */ | ||||||
|  | async function handleWithdraw(row: BpmTaskApi.TaskManager) { | ||||||
|  |   await withdrawTask(row.id); | ||||||
|  |   message.success('撤回成功'); | ||||||
|  |   // 刷新表格数据 | ||||||
|  |   await gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|   formOptions: { |   formOptions: { | ||||||
|     schema: useGridFormSchema(), |     schema: useGridFormSchema(), | ||||||
|   }, |   }, | ||||||
|  | @ -52,7 +62,7 @@ const [Grid] = useVbenVxeGrid({ | ||||||
|     cellConfig: { |     cellConfig: { | ||||||
|       height: 64, |       height: 64, | ||||||
|     }, |     }, | ||||||
|   } as VxeTableGridOptions<BpmTaskApi.Task>, |   } as VxeTableGridOptions<BpmTaskApi.TaskManager>, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -75,6 +85,13 @@ const [Grid] = useVbenVxeGrid({ | ||||||
|       <template #actions="{ row }"> |       <template #actions="{ row }"> | ||||||
|         <TableAction |         <TableAction | ||||||
|           :actions="[ |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '撤回', | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               color: 'warning', | ||||||
|  |               onClick: handleWithdraw.bind(null, row), | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|               label: '历史', |               label: '历史', | ||||||
|               type: 'link', |               type: 'link', | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ function handleUpdateValue(row: any) { | ||||||
|   } else { |   } else { | ||||||
|     tableData.value[index] = row; |     tableData.value[index] = row; | ||||||
|   } |   } | ||||||
|   emit('update:products', tableData.value); |   emit('update:products', [...tableData.value]); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** 表格配置 */ | /** 表格配置 */ | ||||||
|  |  | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { AnalysisOverviewItem } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { AnalysisOverview } from '@vben/common-ui'; | ||||||
|  | import { | ||||||
|  |   SvgBellIcon, | ||||||
|  |   SvgCakeIcon, | ||||||
|  |   SvgCardIcon, | ||||||
|  |   SvgDownloadIcon, | ||||||
|  | } from '@vben/icons'; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   saleSummary?: { | ||||||
|  |     monthPrice?: number; | ||||||
|  |     todayPrice?: number; | ||||||
|  |     yearPrice?: number; | ||||||
|  |     yesterdayPrice?: number; | ||||||
|  |   }; | ||||||
|  |   purchaseSummary?: { | ||||||
|  |     monthPrice?: number; | ||||||
|  |     todayPrice?: number; | ||||||
|  |     yearPrice?: number; | ||||||
|  |     yesterdayPrice?: number; | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<Props>(), { | ||||||
|  |   saleSummary: () => ({}), | ||||||
|  |   purchaseSummary: () => ({}), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 概览数据 */ | ||||||
|  | const overviewItems = computed<AnalysisOverviewItem[]>(() => [ | ||||||
|  |   { | ||||||
|  |     icon: SvgCardIcon, | ||||||
|  |     title: '今日销售', | ||||||
|  |     totalTitle: '今日采购', | ||||||
|  |     totalValue: props.purchaseSummary?.todayPrice || 0, | ||||||
|  |     value: props.saleSummary?.todayPrice || 0, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     icon: SvgCakeIcon, | ||||||
|  |     title: '昨日销售', | ||||||
|  |     totalTitle: '昨日采购', | ||||||
|  |     totalValue: props.purchaseSummary?.yesterdayPrice || 0, | ||||||
|  |     value: props.saleSummary?.yesterdayPrice || 0, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     icon: SvgDownloadIcon, | ||||||
|  |     title: '本月销售', | ||||||
|  |     totalTitle: '本月采购', | ||||||
|  |     totalValue: props.purchaseSummary?.monthPrice || 0, | ||||||
|  |     value: props.saleSummary?.monthPrice || 0, | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     icon: SvgBellIcon, | ||||||
|  |     title: '今年销售', | ||||||
|  |     totalTitle: '今年采购', | ||||||
|  |     totalValue: props.purchaseSummary?.yearPrice || 0, | ||||||
|  |     value: props.saleSummary?.yearPrice || 0, | ||||||
|  |   }, | ||||||
|  | ]); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <AnalysisOverview :items="overviewItems" /> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,174 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { EChartsOption } from 'echarts'; | ||||||
|  | 
 | ||||||
|  | import type { EchartsUIType } from '@vben/plugins/echarts'; | ||||||
|  | 
 | ||||||
|  | import type { ErpPurchaseStatisticsApi } from '#/api/erp/statistics/purchase'; | ||||||
|  | import type { ErpSaleStatisticsApi } from '#/api/erp/statistics/sale'; | ||||||
|  | 
 | ||||||
|  | import { onMounted, ref, watch } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||||||
|  | 
 | ||||||
|  | import { Card } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { | ||||||
|  |   getPurchaseSummary, | ||||||
|  |   getPurchaseTimeSummary, | ||||||
|  | } from '#/api/erp/statistics/purchase'; | ||||||
|  | import { getSaleSummary, getSaleTimeSummary } from '#/api/erp/statistics/sale'; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   title: string; | ||||||
|  |   type?: 'purchase' | 'sale'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<Props>(), { | ||||||
|  |   type: 'sale', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 销售统计数据 */ | ||||||
|  | const saleSummary = ref<ErpSaleStatisticsApi.SaleSummary>(); // 销售概况统计 | ||||||
|  | const saleTimeSummaryList = ref<ErpSaleStatisticsApi.SaleTimeSummary[]>(); // 销售时段统计 | ||||||
|  | const getSaleStatistics = async () => { | ||||||
|  |   saleSummary.value = await getSaleSummary(); | ||||||
|  |   saleTimeSummaryList.value = await getSaleTimeSummary(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 采购统计数据 */ | ||||||
|  | const purchaseSummary = ref<ErpPurchaseStatisticsApi.PurchaseSummary>(); // 采购概况统计 | ||||||
|  | const purchaseTimeSummaryList = | ||||||
|  |   ref<ErpPurchaseStatisticsApi.PurchaseTimeSummary[]>(); // 采购时段统计 | ||||||
|  | const getPurchaseStatistics = async () => { | ||||||
|  |   purchaseSummary.value = await getPurchaseSummary(); | ||||||
|  |   purchaseTimeSummaryList.value = await getPurchaseTimeSummary(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 获取当前类型的时段数据 */ | ||||||
|  | const currentTimeSummaryList = ref<Array<{ price: number; time: string }>>(); | ||||||
|  | 
 | ||||||
|  | const chartRef = ref<EchartsUIType>(); | ||||||
|  | const { renderEcharts } = useEcharts(chartRef); | ||||||
|  | 
 | ||||||
|  | /** 折线图配置 */ | ||||||
|  | const lineChartOptions: EChartsOption = { | ||||||
|  |   grid: { | ||||||
|  |     left: 20, | ||||||
|  |     right: 20, | ||||||
|  |     bottom: 20, | ||||||
|  |     top: 80, | ||||||
|  |     containLabel: true, | ||||||
|  |   }, | ||||||
|  |   legend: { | ||||||
|  |     top: 50, | ||||||
|  |   }, | ||||||
|  |   series: [ | ||||||
|  |     { | ||||||
|  |       name: '金额', | ||||||
|  |       type: 'line', | ||||||
|  |       smooth: true, | ||||||
|  |       areaStyle: {}, | ||||||
|  |       data: [], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |   toolbox: { | ||||||
|  |     feature: { | ||||||
|  |       dataZoom: { | ||||||
|  |         yAxisIndex: false, | ||||||
|  |       }, | ||||||
|  |       brush: { | ||||||
|  |         type: ['lineX', 'clear'], | ||||||
|  |       }, | ||||||
|  |       saveAsImage: { | ||||||
|  |         show: true, | ||||||
|  |         name: props.title, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   tooltip: { | ||||||
|  |     trigger: 'axis', | ||||||
|  |     axisPointer: { | ||||||
|  |       type: 'cross', | ||||||
|  |     }, | ||||||
|  |     padding: [5, 10], | ||||||
|  |   }, | ||||||
|  |   xAxis: { | ||||||
|  |     type: 'category', | ||||||
|  |     boundaryGap: false, | ||||||
|  |     axisTick: { | ||||||
|  |       show: false, | ||||||
|  |     }, | ||||||
|  |     data: [], | ||||||
|  |   }, | ||||||
|  |   yAxis: { | ||||||
|  |     axisTick: { | ||||||
|  |       show: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 初始化数据 */ | ||||||
|  | const initData = async () => { | ||||||
|  |   if (props.type === 'sale') { | ||||||
|  |     await getSaleStatistics(); | ||||||
|  |     currentTimeSummaryList.value = saleTimeSummaryList.value; | ||||||
|  |   } else { | ||||||
|  |     await getPurchaseStatistics(); | ||||||
|  |     currentTimeSummaryList.value = purchaseTimeSummaryList.value; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 监听数据变化并更新图表 */ | ||||||
|  | watch( | ||||||
|  |   () => currentTimeSummaryList.value, | ||||||
|  |   (val) => { | ||||||
|  |     if (!val || val.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 更新图表数据 | ||||||
|  |     const xAxisData = val.map((item) => item.time); | ||||||
|  |     const seriesData = val.map((item) => item.price); | ||||||
|  | 
 | ||||||
|  |     const options = { | ||||||
|  |       ...lineChartOptions, | ||||||
|  |       xAxis: { | ||||||
|  |         ...lineChartOptions.xAxis, | ||||||
|  |         data: xAxisData, | ||||||
|  |       }, | ||||||
|  |       series: [ | ||||||
|  |         { | ||||||
|  |           ...lineChartOptions.series![0], | ||||||
|  |           data: seriesData, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     renderEcharts(options); | ||||||
|  |   }, | ||||||
|  |   { immediate: true }, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | /** 组件挂载时初始化数据 */ | ||||||
|  | onMounted(() => { | ||||||
|  |   initData(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 暴露数据给父组件使用 */ | ||||||
|  | defineExpose({ | ||||||
|  |   saleSummary, | ||||||
|  |   purchaseSummary, | ||||||
|  |   saleTimeSummaryList, | ||||||
|  |   purchaseTimeSummaryList, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Card> | ||||||
|  |     <template #title> | ||||||
|  |       <span>{{ title }}</span> | ||||||
|  |     </template> | ||||||
|  |     <!-- 折线图 --> | ||||||
|  |     <EchartsUI ref="chartRef" /> | ||||||
|  |   </Card> | ||||||
|  | </template> | ||||||
|  | @ -1,7 +1,21 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import { DocAlert, Page } from '@vben/common-ui'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { Col, Row, Spin } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import SummaryCard from './components/SummaryCard.vue'; | ||||||
|  | import TimeSummaryChart from './components/TimeSummaryChart.vue'; | ||||||
|  | 
 | ||||||
|  | /** ERP首页 */ | ||||||
|  | defineOptions({ name: 'ErpHome' }); | ||||||
|  | 
 | ||||||
|  | const loading = ref(false); // 加载中 | ||||||
|  | 
 | ||||||
|  | /** 图表组件引用 */ | ||||||
|  | const saleChartRef = ref(); | ||||||
|  | const purchaseChartRef = ref(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -12,23 +26,28 @@ import { Button } from 'ant-design-vue'; | ||||||
|         url="https://doc.iocoder.cn/erp/build/" |         url="https://doc.iocoder.cn/erp/build/" | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Button | 
 | ||||||
|       danger |     <Spin :spinning="loading"> | ||||||
|       type="link" |       <div class="flex flex-col gap-4"> | ||||||
|       target="_blank" |         <!-- 销售/采购的全局统计 --> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |         <SummaryCard /> | ||||||
|     > | 
 | ||||||
|       该功能支持 Vue3 + element-plus 版本! |         <!-- 销售/采购的时段统计 --> | ||||||
|     </Button> |         <Row :gutter="16"> | ||||||
|     <br /> |           <!-- 销售统计 --> | ||||||
|     <Button |           <Col :md="12" :sm="12" :xs="24"> | ||||||
|       type="link" |             <TimeSummaryChart ref="saleChartRef" title="销售统计" type="sale" /> | ||||||
|       target="_blank" |           </Col> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/home/index.vue" |           <!-- 采购统计 --> | ||||||
|     > |           <Col :md="12" :sm="12" :xs="24"> | ||||||
|       可参考 |             <TimeSummaryChart | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/home/index.vue |               ref="purchaseChartRef" | ||||||
|       代码,pull request 贡献给我们! |               title="采购统计" | ||||||
|     </Button> |               type="purchase" | ||||||
|  |             /> | ||||||
|  |           </Col> | ||||||
|  |         </Row> | ||||||
|  |       </div> | ||||||
|  |     </Spin> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,427 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { erpPriceInputFormatter } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { z } from '#/adapter/form'; | ||||||
|  | import { getAccountSimpleList } from '#/api/erp/finance/account'; | ||||||
|  | import { getProductSimpleList } from '#/api/erp/product/product'; | ||||||
|  | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; | ||||||
|  | import { getSimpleUserList } from '#/api/system/user'; | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 表单的配置项 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         style: { display: 'none' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'id', | ||||||
|  |       label: 'ID', | ||||||
|  |       hideLabel: true, | ||||||
|  |       formItemClass: 'hidden', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '系统自动生成', | ||||||
|  |         disabled: true, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '订单单号', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择供应商', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSupplierSimpleList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'name', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'supplierId', | ||||||
|  |       label: '供应商', | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'DatePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '选择订单时间', | ||||||
|  |         showTime: true, | ||||||
|  |         format: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |         valueFormat: 'x', | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'orderTime', | ||||||
|  |       label: '订单时间', | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         autoSize: { minRows: 2, maxRows: 4 }, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'FileUpload', | ||||||
|  |       componentProps: { | ||||||
|  |         maxNumber: 1, | ||||||
|  |         maxSize: 10, | ||||||
|  |         accept: [ | ||||||
|  |           'pdf', | ||||||
|  |           'doc', | ||||||
|  |           'docx', | ||||||
|  |           'xls', | ||||||
|  |           'xlsx', | ||||||
|  |           'txt', | ||||||
|  |           'jpg', | ||||||
|  |           'jpeg', | ||||||
|  |           'png', | ||||||
|  |         ], | ||||||
|  |         showDescription: true, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'fileUrl', | ||||||
|  |       label: '附件', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'product', | ||||||
|  |       label: '产品清单', | ||||||
|  |       component: 'Input', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入优惠率', | ||||||
|  |         min: 0, | ||||||
|  |         max: 100, | ||||||
|  |         precision: 2, | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'discountPercent', | ||||||
|  |       label: '优惠率(%)', | ||||||
|  |       rules: z.number().min(0).optional(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '付款优惠', | ||||||
|  |         precision: 2, | ||||||
|  |         formatter: erpPriceInputFormatter, | ||||||
|  |         disabled: true, | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'discountPrice', | ||||||
|  |       label: '付款优惠', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '优惠后金额', | ||||||
|  |         precision: 2, | ||||||
|  |         formatter: erpPriceInputFormatter, | ||||||
|  |         disabled: true, | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'totalPrice', | ||||||
|  |       label: '优惠后金额', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择结算账户', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getAccountSimpleList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'name', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'accountId', | ||||||
|  |       label: '结算账户', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入支付订金', | ||||||
|  |         precision: 2, | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |         min: 0, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'depositPrice', | ||||||
|  |       label: '支付订金', | ||||||
|  |       rules: z.number().min(0).optional(), | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 采购订单项表格列定义 */ | ||||||
|  | export function usePurchaseOrderItemTableColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, | ||||||
|  |     { | ||||||
|  |       field: 'productId', | ||||||
|  |       title: '产品名称', | ||||||
|  |       minWidth: 200, | ||||||
|  |       slots: { default: 'productId' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'stockCount', | ||||||
|  |       title: '库存', | ||||||
|  |       minWidth: 80, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productBarCode', | ||||||
|  |       title: '条码', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productUnitName', | ||||||
|  |       title: '单位', | ||||||
|  |       minWidth: 80, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'count', | ||||||
|  |       title: '数量', | ||||||
|  |       minWidth: 120, | ||||||
|  |       slots: { default: 'count' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productPrice', | ||||||
|  |       title: '产品单价', | ||||||
|  |       minWidth: 120, | ||||||
|  |       slots: { default: 'productPrice' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalProductPrice', | ||||||
|  |       title: '金额', | ||||||
|  |       minWidth: 120, | ||||||
|  |       formatter: 'formatAmount2', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'taxPercent', | ||||||
|  |       title: '税率(%)', | ||||||
|  |       minWidth: 100, | ||||||
|  |       slots: { default: 'taxPercent' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'taxPrice', | ||||||
|  |       title: '税额', | ||||||
|  |       minWidth: 120, | ||||||
|  |       formatter: 'formatAmount2', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalPrice', | ||||||
|  |       title: '税额合计', | ||||||
|  |       minWidth: 120, | ||||||
|  |       formatter: 'formatAmount2', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'remark', | ||||||
|  |       title: '备注', | ||||||
|  |       minWidth: 150, | ||||||
|  |       slots: { default: 'remark' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       width: 50, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '订单单号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入订单单号', | ||||||
|  |         allowClear: true, | ||||||
|  |         disabled: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'productId', | ||||||
|  |       label: '产品', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择产品', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getProductSimpleList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'name', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'orderTime', | ||||||
|  |       label: '订单时间', | ||||||
|  |       component: 'RangePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: ['开始时间', '结束时间'], | ||||||
|  |         showTime: true, | ||||||
|  |         format: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |         valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'supplierId', | ||||||
|  |       label: '供应商', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择供应商', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSupplierSimpleList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'name', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'creator', | ||||||
|  |       label: '创建人', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择创建人', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSimpleUserList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'nickname', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), | ||||||
|  |         placeholder: '请选择状态', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'returnStatus', | ||||||
|  |       label: '退货状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         options: [ | ||||||
|  |           { label: '未退货', value: 0 }, | ||||||
|  |           { label: '部分退货', value: 1 }, | ||||||
|  |           { label: '全部退货', value: 2 }, | ||||||
|  |         ], | ||||||
|  |         placeholder: '请选择退货状态', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       type: 'checkbox', | ||||||
|  |       width: 50, | ||||||
|  |       fixed: 'left', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'no', | ||||||
|  |       title: '订单单号', | ||||||
|  |       width: 200, | ||||||
|  |       fixed: 'left', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productNames', | ||||||
|  |       title: '产品信息', | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'supplierName', | ||||||
|  |       title: '供应商', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'orderTime', | ||||||
|  |       title: '订单时间', | ||||||
|  |       width: 160, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'creatorName', | ||||||
|  |       title: '创建人', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalCount', | ||||||
|  |       title: '总数量', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'inCount', | ||||||
|  |       title: '入库数量', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'returnCount', | ||||||
|  |       title: '退货数量', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalProductPrice', | ||||||
|  |       title: '金额合计', | ||||||
|  |       formatter: 'formatNumber', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalPrice', | ||||||
|  |       title: '含税金额', | ||||||
|  |       formatter: 'formatNumber', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'depositPrice', | ||||||
|  |       title: '支付订金', | ||||||
|  |       formatter: 'formatNumber', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '状态', | ||||||
|  |       minWidth: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       width: 220, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,34 +1,265 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deletePurchaseOrder, | ||||||
|  |   deletePurchaseOrderList, | ||||||
|  |   exportPurchaseOrder, | ||||||
|  |   getPurchaseOrderPage, | ||||||
|  |   updatePurchaseOrderStatus, | ||||||
|  | } from '#/api/erp/purchase/order'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import PurchaseOrderForm from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | /** ERP 采购订单列表 */ | ||||||
|  | defineOptions({ name: 'ErpPurchaseOrder' }); | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: PurchaseOrderForm, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const checkedIds = ref<number[]>([]); | ||||||
|  | function handleRowCheckboxChange({ | ||||||
|  |   records, | ||||||
|  | }: { | ||||||
|  |   records: ErpPurchaseOrderApi.PurchaseOrder[]; | ||||||
|  | }) { | ||||||
|  |   checkedIds.value = records.map((item) => item.id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 详情 */ | ||||||
|  | function handleDetail(row: ErpPurchaseOrderApi.PurchaseOrder) { | ||||||
|  |   formModalApi.setData({ type: 'detail', id: row.id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增 */ | ||||||
|  | function handleCreate() { | ||||||
|  |   formModalApi.setData({ type: 'create' }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑 */ | ||||||
|  | function handleEdit(row: ErpPurchaseOrderApi.PurchaseOrder) { | ||||||
|  |   formModalApi.setData({ type: 'edit', id: row.id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除 */ | ||||||
|  | async function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrder) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting'), | ||||||
|  |     duration: 0, | ||||||
|  |     key: 'action_process_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     if (row.id) await deletePurchaseOrder(row.id); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess'), | ||||||
|  |       key: 'action_process_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } catch { | ||||||
|  |     // 处理错误 | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 批量删除 */ | ||||||
|  | async function handleBatchDelete() { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting'), | ||||||
|  |     duration: 0, | ||||||
|  |     key: 'action_process_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deletePurchaseOrderList(checkedIds.value); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess'), | ||||||
|  |       key: 'action_process_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } catch { | ||||||
|  |     // 处理错误 | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 审批/反审批操作 */ | ||||||
|  | function handleUpdateStatus( | ||||||
|  |   row: ErpPurchaseOrderApi.PurchaseOrder, | ||||||
|  |   status: number, | ||||||
|  | ) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: `确定${status === 20 ? '审批' : '反审批'}该订单吗?`, | ||||||
|  |     duration: 0, | ||||||
|  |     key: 'action_process_msg', | ||||||
|  |   }); | ||||||
|  |   updatePurchaseOrderStatus(row.id, status) | ||||||
|  |     .then(() => { | ||||||
|  |       message.success({ | ||||||
|  |         content: `${status === 20 ? '审批' : '反审批'}成功`, | ||||||
|  |         key: 'action_process_msg', | ||||||
|  |       }); | ||||||
|  |       onRefresh(); | ||||||
|  |     }) | ||||||
|  |     .catch(() => { | ||||||
|  |       // 处理错误 | ||||||
|  |     }) | ||||||
|  |     .finally(() => { | ||||||
|  |       hideLoading(); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportPurchaseOrder(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '采购订单.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getPurchaseOrderPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |       isHover: true, | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>, | ||||||
|  |   gridEvents: { | ||||||
|  |     checkboxAll: handleRowCheckboxChange, | ||||||
|  |     checkboxChange: handleRowCheckboxChange, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <template #doc> |     <template #doc> | ||||||
|       <DocAlert |       <DocAlert | ||||||
|         title="【采购】采购订单、入库、退货" |         title="【采购】采购订单、入库、退货" | ||||||
|         url="https://doc.iocoder.cn/erp/purchase/" |         url="https://doc.iocoder.cn/erp/purchase/" | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Button | 
 | ||||||
|       danger |     <FormModal @success="onRefresh" /> | ||||||
|       type="link" | 
 | ||||||
|       target="_blank" |     <Grid table-title="采购订单列表"> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |       <template #toolbar-tools> | ||||||
|     > |         <TableAction | ||||||
|       该功能支持 Vue3 + element-plus 版本! |           :actions="[ | ||||||
|     </Button> |             { | ||||||
|     <br /> |               label: $t('ui.actionTitle.create', ['采购订单']), | ||||||
|     <Button |               type: 'primary', | ||||||
|       type="link" |               icon: ACTION_ICON.ADD, | ||||||
|       target="_blank" |               auth: ['erp:purchase-order:create'], | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/order/index" |               onClick: handleCreate, | ||||||
|     > |             }, | ||||||
|       可参考 |             { | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/order/index |               label: $t('ui.actionTitle.export'), | ||||||
|       代码,pull request 贡献给我们! |               type: 'primary', | ||||||
|     </Button> |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|  |               auth: ['erp:purchase-order:export'], | ||||||
|  |               onClick: handleExport, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '批量删除', | ||||||
|  |               type: 'primary', | ||||||
|  |               danger: true, | ||||||
|  |               disabled: isEmpty(checkedIds), | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['erp:purchase-order:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: `是否删除所选中数据?`, | ||||||
|  |                 confirm: handleBatchDelete, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: $t('common.detail'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.VIEW, | ||||||
|  |               auth: ['erp:purchase-order:query'], | ||||||
|  |               onClick: handleDetail.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.edit'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['erp:purchase-order:update'], | ||||||
|  |               ifShow: () => row.status !== 20, | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |           :drop-down-actions="[ | ||||||
|  |             { | ||||||
|  |               label: row.status === 10 ? '审批' : '反审批', | ||||||
|  |               type: 'link', | ||||||
|  |               auth: ['erp:purchase-order:update-status'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: `确认${row.status === 10 ? '审批' : '反审批'}${row.no}吗?`, | ||||||
|  |                 confirm: handleUpdateStatus.bind( | ||||||
|  |                   null, | ||||||
|  |                   row, | ||||||
|  |                   row.status === 10 ? 20 : 10, | ||||||
|  |                 ), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.delete'), | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               color: 'error', | ||||||
|  |               auth: ['erp:purchase-order:delete'], | ||||||
|  |               onClick: handleDelete.bind(null, row), | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.no]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,358 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; | ||||||
|  | 
 | ||||||
|  | import { nextTick, onMounted, ref, watch } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { erpPriceMultiply } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { Input, InputNumber, Select } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { getProductSimpleList } from '#/api/erp/product/product'; | ||||||
|  | import { getStockCount } from '#/api/erp/stock/stock'; | ||||||
|  | 
 | ||||||
|  | import { usePurchaseOrderItemTableColumns } from '../data'; | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<Props>(), { | ||||||
|  |   items: () => [], | ||||||
|  |   disabled: false, | ||||||
|  |   discountPercent: 0, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits([ | ||||||
|  |   'update:items', | ||||||
|  |   'update:discount-price', | ||||||
|  |   'update:total-price', | ||||||
|  | ]); | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   items?: ErpPurchaseOrderApi.PurchaseOrderItem[]; | ||||||
|  |   disabled?: boolean; | ||||||
|  |   discountPercent?: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const tableData = ref<ErpPurchaseOrderApi.PurchaseOrderItem[]>([]); | ||||||
|  | const productOptions = ref<any[]>([]); | ||||||
|  | 
 | ||||||
|  | /** 表格配置 */ | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   gridOptions: { | ||||||
|  |     editConfig: { | ||||||
|  |       trigger: 'click', | ||||||
|  |       mode: 'cell', | ||||||
|  |     }, | ||||||
|  |     columns: usePurchaseOrderItemTableColumns(), | ||||||
|  |     data: tableData.value, | ||||||
|  |     border: true, | ||||||
|  |     showOverflow: true, | ||||||
|  |     autoResize: true, | ||||||
|  |     minHeight: 250, | ||||||
|  |     keepSource: true, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     pagerConfig: { | ||||||
|  |       enabled: false, | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       enabled: false, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 监听外部传入的列数据 */ | ||||||
|  | watch( | ||||||
|  |   () => props.items, | ||||||
|  |   async (items) => { | ||||||
|  |     if (!items) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     await nextTick(); | ||||||
|  |     tableData.value = [...items]; | ||||||
|  |     await nextTick(); | ||||||
|  |     gridApi.grid.reloadData(tableData.value); | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     immediate: true, | ||||||
|  |   }, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | /** 计算 discountPrice、totalPrice 价格 */ | ||||||
|  | watch( | ||||||
|  |   () => [tableData.value, props.discountPercent], | ||||||
|  |   () => { | ||||||
|  |     if (!tableData.value || tableData.value.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const totalPrice = tableData.value.reduce( | ||||||
|  |       (prev, curr) => prev + (curr.totalPrice || 0), | ||||||
|  |       0, | ||||||
|  |     ); | ||||||
|  |     const discountPrice = | ||||||
|  |       props.discountPercent === null | ||||||
|  |         ? 0 | ||||||
|  |         : erpPriceMultiply(totalPrice, props.discountPercent / 100); | ||||||
|  |     const finalTotalPrice = totalPrice - discountPrice; | ||||||
|  | 
 | ||||||
|  |     // 发送计算结果给父组件 | ||||||
|  |     emit('update:discount-price', discountPrice); | ||||||
|  |     emit('update:total-price', finalTotalPrice); | ||||||
|  |   }, | ||||||
|  |   { deep: true }, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | /** 初始化 */ | ||||||
|  | onMounted(async () => { | ||||||
|  |   productOptions.value = await getProductSimpleList(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function handleAdd() { | ||||||
|  |   const newRow = { | ||||||
|  |     productId: null, | ||||||
|  |     productName: '', | ||||||
|  |     productUnitId: null, | ||||||
|  |     productUnitName: '', | ||||||
|  |     productBarCode: '', | ||||||
|  |     count: 1, | ||||||
|  |     productPrice: 0, | ||||||
|  |     totalProductPrice: 0, | ||||||
|  |     taxPercent: 0, | ||||||
|  |     taxPrice: 0, | ||||||
|  |     totalPrice: 0, | ||||||
|  |     stockCount: 0, | ||||||
|  |     remark: '', | ||||||
|  |   }; | ||||||
|  |   tableData.value.push(newRow); | ||||||
|  |   gridApi.grid.insertAt(newRow, -1); | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrderItem) { | ||||||
|  |   gridApi.grid.remove(row); | ||||||
|  |   const index = tableData.value.findIndex((item) => item.id === row.id); | ||||||
|  |   if (index !== -1) { | ||||||
|  |     tableData.value.splice(index, 1); | ||||||
|  |   } | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function handleProductChange(productId: any, row: any) { | ||||||
|  |   const product = productOptions.value.find((p) => p.id === productId); | ||||||
|  |   if (!product) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const stockCount = await getStockCount(productId); | ||||||
|  | 
 | ||||||
|  |   row.productId = productId; | ||||||
|  |   row.productUnitId = product.unitId; | ||||||
|  |   row.productBarCode = product.barCode; | ||||||
|  |   row.productUnitName = product.unitName; | ||||||
|  |   row.productName = product.name; | ||||||
|  |   row.stockCount = stockCount || 0; | ||||||
|  |   row.productPrice = product.purchasePrice; | ||||||
|  |   row.count = row.count || 1; | ||||||
|  | 
 | ||||||
|  |   handlePriceChange(row); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handlePriceChange(row: any) { | ||||||
|  |   if (row.productPrice && row.count) { | ||||||
|  |     row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; | ||||||
|  |     row.taxPrice = | ||||||
|  |       erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0; | ||||||
|  |     row.totalPrice = row.totalProductPrice + row.taxPrice; | ||||||
|  |   } | ||||||
|  |   handleUpdateValue(row); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handleUpdateValue(row: any) { | ||||||
|  |   const index = tableData.value.findIndex((item) => item.id === row.id); | ||||||
|  |   if (index === -1) { | ||||||
|  |     tableData.value.push(row); | ||||||
|  |   } else { | ||||||
|  |     tableData.value[index] = row; | ||||||
|  |   } | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getSummaries = (): { | ||||||
|  |   count: number; | ||||||
|  |   productName: string; | ||||||
|  |   taxPrice: number; | ||||||
|  |   totalPrice: number; | ||||||
|  |   totalProductPrice: number; | ||||||
|  | } => { | ||||||
|  |   return { | ||||||
|  |     productName: '合计', | ||||||
|  |     count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0), | ||||||
|  |     totalProductPrice: tableData.value.reduce( | ||||||
|  |       (sum, item) => sum + (item.totalProductPrice || 0), | ||||||
|  |       0, | ||||||
|  |     ), | ||||||
|  |     taxPrice: tableData.value.reduce( | ||||||
|  |       (sum, item) => sum + (item.taxPrice || 0), | ||||||
|  |       0, | ||||||
|  |     ), | ||||||
|  |     totalPrice: tableData.value.reduce( | ||||||
|  |       (sum, item) => sum + (item.totalPrice || 0), | ||||||
|  |       0, | ||||||
|  |     ), | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const validate = async (): Promise<boolean> => { | ||||||
|  |   try { | ||||||
|  |     for (let i = 0; i < tableData.value.length; i++) { | ||||||
|  |       const item = tableData.value[i]; | ||||||
|  |       if (item) { | ||||||
|  |         if (!item.productId) { | ||||||
|  |           throw new Error(`第 ${i + 1} 行:产品不能为空`); | ||||||
|  |         } | ||||||
|  |         if (!item.count || item.count <= 0) { | ||||||
|  |           throw new Error(`第 ${i + 1} 行:产品数量不能为空`); | ||||||
|  |         } | ||||||
|  |         if (!item.productPrice || item.productPrice <= 0) { | ||||||
|  |           throw new Error(`第 ${i + 1} 行:产品单价不能为空`); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('验证失败:', error); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const getData = (): ErpPurchaseOrderApi.PurchaseOrderItem[] => tableData.value; | ||||||
|  | const init = ( | ||||||
|  |   items: ErpPurchaseOrderApi.PurchaseOrderItem[] | undefined, | ||||||
|  | ): void => { | ||||||
|  |   tableData.value = | ||||||
|  |     items && items.length > 0 | ||||||
|  |       ? items.map((item) => { | ||||||
|  |           const newItem = { ...item }; | ||||||
|  |           if (newItem.productPrice && newItem.count) { | ||||||
|  |             newItem.totalProductPrice = | ||||||
|  |               erpPriceMultiply(newItem.productPrice, newItem.count) ?? 0; | ||||||
|  |             newItem.taxPrice = | ||||||
|  |               erpPriceMultiply( | ||||||
|  |                 newItem.totalProductPrice, | ||||||
|  |                 (newItem.taxPercent || 0) / 100, | ||||||
|  |               ) ?? 0; | ||||||
|  |             newItem.totalPrice = newItem.totalProductPrice + newItem.taxPrice; | ||||||
|  |           } | ||||||
|  |           return newItem; | ||||||
|  |         }) | ||||||
|  |       : []; | ||||||
|  |   nextTick(() => { | ||||||
|  |     gridApi.grid.reloadData(tableData.value); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  |   validate, | ||||||
|  |   getData, | ||||||
|  |   init, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Grid class="w-full"> | ||||||
|  |     <template #productId="{ row }"> | ||||||
|  |       <Select | ||||||
|  |         v-if="!disabled" | ||||||
|  |         v-model:value="row.productId" | ||||||
|  |         :options="productOptions" | ||||||
|  |         :field-names="{ label: 'name', value: 'id' }" | ||||||
|  |         style="width: 100%" | ||||||
|  |         placeholder="请选择产品" | ||||||
|  |         show-search | ||||||
|  |         @change="handleProductChange($event, row)" | ||||||
|  |       /> | ||||||
|  |       <span v-else>{{ row.productName || '-' }}</span> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #count="{ row }"> | ||||||
|  |       <InputNumber | ||||||
|  |         v-if="!disabled" | ||||||
|  |         v-model:value="row.count" | ||||||
|  |         :min="0" | ||||||
|  |         :precision="2" | ||||||
|  |         @change="handlePriceChange(row)" | ||||||
|  |       /> | ||||||
|  |       <span v-else>{{ row.count || '-' }}</span> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #productPrice="{ row }"> | ||||||
|  |       <InputNumber | ||||||
|  |         v-if="!disabled" | ||||||
|  |         v-model:value="row.productPrice" | ||||||
|  |         :min="0" | ||||||
|  |         :precision="2" | ||||||
|  |         @change="handlePriceChange(row)" | ||||||
|  |       /> | ||||||
|  |       <span v-else>{{ row.productPrice || '-' }}</span> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #taxPercent="{ row }"> | ||||||
|  |       <InputNumber | ||||||
|  |         v-if="!disabled" | ||||||
|  |         v-model:value="row.taxPercent" | ||||||
|  |         :min="0" | ||||||
|  |         :max="100" | ||||||
|  |         :precision="2" | ||||||
|  |         @change="handlePriceChange(row)" | ||||||
|  |       /> | ||||||
|  |       <span v-else>{{ row.taxPercent || '-' }}</span> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #remark="{ row }"> | ||||||
|  |       <Input v-if="!disabled" v-model:value="row.remark" class="w-full" /> | ||||||
|  |       <span v-else>{{ row.remark || '-' }}</span> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #bottom> | ||||||
|  |       <div class="border-border bg-muted mt-2 rounded border p-2"> | ||||||
|  |         <div class="text-muted-foreground flex justify-between text-sm"> | ||||||
|  |           <span class="text-foreground font-medium">合计:</span> | ||||||
|  |           <div class="flex space-x-4"> | ||||||
|  |             <span>数量:{{ getSummaries().count }}</span> | ||||||
|  |             <span>金额:{{ getSummaries().totalProductPrice }}</span> | ||||||
|  |             <span>税额:{{ getSummaries().taxPrice }}</span> | ||||||
|  |             <span>税额合计:{{ getSummaries().totalPrice }}</span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <TableAction | ||||||
|  |         v-if="!disabled" | ||||||
|  |         class="mt-4 flex justify-center" | ||||||
|  |         :actions="[ | ||||||
|  |           { | ||||||
|  |             label: '添加产品', | ||||||
|  |             type: 'default', | ||||||
|  |             onClick: handleAdd, | ||||||
|  |           }, | ||||||
|  |         ]" | ||||||
|  |       /> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <template #actions="{ row }"> | ||||||
|  |       <TableAction | ||||||
|  |         v-if="!disabled" | ||||||
|  |         :actions="[ | ||||||
|  |           { | ||||||
|  |             label: '删除', | ||||||
|  |             type: 'link', | ||||||
|  |             danger: true, | ||||||
|  |             popConfirm: { | ||||||
|  |               title: '确认删除该产品吗?', | ||||||
|  |               confirm: handleDelete.bind(null, row), | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         ]" | ||||||
|  |       /> | ||||||
|  |     </template> | ||||||
|  |   </Grid> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,212 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; | ||||||
|  | 
 | ||||||
|  | import { computed, nextTick, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createPurchaseOrder, | ||||||
|  |   getPurchaseOrder, | ||||||
|  |   updatePurchaseOrder, | ||||||
|  | } from '#/api/erp/purchase/order'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | import PurchaseOrderItemForm from './PurchaseOrderItemForm.vue'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<ErpPurchaseOrderApi.PurchaseOrder>(); | ||||||
|  | const formType = ref(''); | ||||||
|  | const itemFormRef = ref(); | ||||||
|  | 
 | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   if (formType.value === 'create') return '添加采购订单'; | ||||||
|  |   if (formType.value === 'update') return '编辑采购订单'; | ||||||
|  |   return '采购订单详情'; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     labelWidth: 120, | ||||||
|  |   }, | ||||||
|  |   wrapperClass: 'grid-cols-3', | ||||||
|  |   layout: 'vertical', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  |   handleValuesChange: (values, changedFields) => { | ||||||
|  |     if (formData.value && changedFields.includes('discountPercent')) { | ||||||
|  |       formData.value.discountPercent = values.discountPercent; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const handleUpdateItems = (items: ErpPurchaseOrderApi.PurchaseOrderItem[]) => { | ||||||
|  |   formData.value = modalApi.getData<ErpPurchaseOrderApi.PurchaseOrder>(); | ||||||
|  |   if (formData.value) { | ||||||
|  |     formData.value.items = items; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const handleUpdateDiscountPrice = (discountPrice: number) => { | ||||||
|  |   if (formData.value) { | ||||||
|  |     formData.value.discountPrice = discountPrice; | ||||||
|  |     formApi.setValues({ | ||||||
|  |       discountPrice: formData.value.discountPrice, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const handleUpdateTotalPrice = (totalPrice: number) => { | ||||||
|  |   if (formData.value) { | ||||||
|  |     formData.value.totalPrice = totalPrice; | ||||||
|  |     formApi.setValues({ | ||||||
|  |       totalPrice: formData.value.totalPrice, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 创建或更新采购订单 | ||||||
|  |  */ | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     await nextTick(); | ||||||
|  | 
 | ||||||
|  |     const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |       ? itemFormRef.value[0] | ||||||
|  |       : itemFormRef.value; | ||||||
|  |     if (itemFormInstance && typeof itemFormInstance.validate === 'function') { | ||||||
|  |       try { | ||||||
|  |         const isValid = await itemFormInstance.validate(); | ||||||
|  |         if (!isValid) { | ||||||
|  |           message.error('子表单验证失败'); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } catch (error) { | ||||||
|  |         message.error(error.message || '子表单验证失败'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       message.error('子表单验证方法不存在'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 验证产品清单不能为空 | ||||||
|  |     if (!formData.value?.items || formData.value.items.length === 0) { | ||||||
|  |       message.error('产品清单不能为空,请至少添加一个产品'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = | ||||||
|  |       (await formApi.getValues()) as ErpPurchaseOrderApi.PurchaseOrder; | ||||||
|  |     data.items = formData.value?.items; | ||||||
|  |     // 将文件数组转换为字符串 | ||||||
|  |     if (data.fileUrl && Array.isArray(data.fileUrl)) { | ||||||
|  |       data.fileUrl = data.fileUrl.length > 0 ? data.fileUrl[0] : ''; | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       await (formType.value === 'create' | ||||||
|  |         ? createPurchaseOrder(data) | ||||||
|  |         : updatePurchaseOrder(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       message.success(formType.value === 'create' ? '新增成功' : '更新成功'); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<{ id?: number; type: string }>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     formType.value = data.type; | ||||||
|  | 
 | ||||||
|  |     if (!data.id) { | ||||||
|  |       // 初始化空的表单数据 | ||||||
|  |       formData.value = { items: [] } as ErpPurchaseOrderApi.PurchaseOrder; | ||||||
|  |       await nextTick(); | ||||||
|  |       const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |         ? itemFormRef.value[0] | ||||||
|  |         : itemFormRef.value; | ||||||
|  |       if (itemFormInstance && typeof itemFormInstance.init === 'function') { | ||||||
|  |         itemFormInstance.init([]); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = await getPurchaseOrder(data.id); | ||||||
|  |       // 将字符串形式的文件URL转换为数组形式以适配FileUpload组件 | ||||||
|  |       if ( | ||||||
|  |         formData.value.fileUrl && | ||||||
|  |         typeof formData.value.fileUrl === 'string' | ||||||
|  |       ) { | ||||||
|  |         formData.value.fileUrl = formData.value.fileUrl | ||||||
|  |           ? [formData.value.fileUrl] | ||||||
|  |           : []; | ||||||
|  |       } | ||||||
|  |       // 设置到 values | ||||||
|  |       await formApi.setValues(formData.value); | ||||||
|  |       // 初始化子表单 | ||||||
|  |       await nextTick(); | ||||||
|  |       const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |         ? itemFormRef.value[0] | ||||||
|  |         : itemFormRef.value; | ||||||
|  |       if (itemFormInstance && typeof itemFormInstance.init === 'function') { | ||||||
|  |         itemFormInstance.init(formData.value.items || []); | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | defineExpose({ modalApi }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal | ||||||
|  |     v-bind="$attrs" | ||||||
|  |     :title="getTitle" | ||||||
|  |     class="w-1/2" | ||||||
|  |     :closable="true" | ||||||
|  |     :mask-closable="true" | ||||||
|  |     :show-confirm-button="formType !== 'detail'" | ||||||
|  |   > | ||||||
|  |     <Form class="mx-3"> | ||||||
|  |       <template #product="slotProps"> | ||||||
|  |         <PurchaseOrderItemForm | ||||||
|  |           v-bind="slotProps" | ||||||
|  |           ref="itemFormRef" | ||||||
|  |           class="w-full" | ||||||
|  |           :items="formData?.items ?? []" | ||||||
|  |           :disabled="formType === 'detail'" | ||||||
|  |           :discount-percent="formData?.discountPercent ?? 0" | ||||||
|  |           @update:items="handleUpdateItems" | ||||||
|  |           @update:discount-price="handleUpdateDiscountPrice" | ||||||
|  |           @update:total-price="handleUpdateTotalPrice" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Form> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,233 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       fieldName: 'id', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '供应商名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入供应商名称', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'contact', | ||||||
|  |       label: '联系人', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入联系人', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'mobile', | ||||||
|  |       label: '手机号码', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入手机号码', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'telephone', | ||||||
|  |       label: '联系电话', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入联系电话', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'email', | ||||||
|  |       label: '电子邮箱', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入电子邮箱', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'fax', | ||||||
|  |       label: '传真', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入传真', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '开启状态', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |       defaultValue: 0, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'sort', | ||||||
|  |       label: '排序', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入排序', | ||||||
|  |         precision: 0, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |       defaultValue: 0, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'taxNo', | ||||||
|  |       label: '纳税人识别号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入纳税人识别号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'taxPercent', | ||||||
|  |       label: '税率(%)', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入税率', | ||||||
|  |         min: 0, | ||||||
|  |         precision: 2, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'bankName', | ||||||
|  |       label: '开户行', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入开户行', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'bankAccount', | ||||||
|  |       label: '开户账号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入开户账号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'bankAddress', | ||||||
|  |       label: '开户地址', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入开户地址', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         rows: 3, | ||||||
|  |       }, | ||||||
|  |       formItemClass: 'col-span-2', | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '供应商名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入供应商名称', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'mobile', | ||||||
|  |       label: '手机号码', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入手机号码', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'telephone', | ||||||
|  |       label: '联系电话', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入联系电话', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'name', | ||||||
|  |       title: '供应商名称', | ||||||
|  |       minWidth: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'contact', | ||||||
|  |       title: '联系人', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'mobile', | ||||||
|  |       title: '手机号码', | ||||||
|  |       minWidth: 130, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'telephone', | ||||||
|  |       title: '联系电话', | ||||||
|  |       minWidth: 130, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'email', | ||||||
|  |       title: '电子邮箱', | ||||||
|  |       minWidth: 180, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '状态', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.COMMON_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sort', | ||||||
|  |       title: '排序', | ||||||
|  |       width: 80, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'remark', | ||||||
|  |       title: '备注', | ||||||
|  |       minWidth: 150, | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'actions', | ||||||
|  |       title: '操作', | ||||||
|  |       fixed: 'right', | ||||||
|  |       width: 160, | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,34 +1,156 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpSupplierApi } from '#/api/erp/purchase/supplier'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteSupplier, | ||||||
|  |   exportSupplier, | ||||||
|  |   getSupplierPage, | ||||||
|  | } from '#/api/erp/purchase/supplier'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import SupplierForm from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | /** 供应商管理 */ | ||||||
|  | defineOptions({ name: 'ErpSupplier' }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 添加供应商 */ | ||||||
|  | function handleCreate() { | ||||||
|  |   formModalApi.setData({ type: 'create' }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑供应商 */ | ||||||
|  | function handleEdit(row: ErpSupplierApi.Supplier) { | ||||||
|  |   formModalApi.setData({ type: 'update', id: row.id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除供应商 */ | ||||||
|  | async function handleDelete(row: ErpSupplierApi.Supplier) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|  |     key: 'action_key_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteSupplier(row.id!); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||||
|  |       key: 'action_key_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } catch { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出供应商 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportSupplier(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '供应商.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: SupplierForm, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getSupplierPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpSupplierApi.Supplier>, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <template #doc> |     <template #doc> | ||||||
|       <DocAlert |       <DocAlert | ||||||
|         title="【采购】采购订单、入库、退货" |         title="【采购】采购订单、入库、退货" | ||||||
|         url="https://doc.iocoder.cn/erp/purchase/" |         url="https://doc.iocoder.cn/erp/purchase/" | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Button | 
 | ||||||
|       danger |     <FormModal @success="onRefresh" /> | ||||||
|       type="link" |     <Grid table-title="供应商列表"> | ||||||
|       target="_blank" |       <template #toolbar-tools> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |         <TableAction | ||||||
|     > |           :actions="[ | ||||||
|       该功能支持 Vue3 + element-plus 版本! |             { | ||||||
|     </Button> |               label: $t('ui.actionTitle.create', ['供应商']), | ||||||
|     <br /> |               type: 'primary', | ||||||
|     <Button |               icon: ACTION_ICON.ADD, | ||||||
|       type="link" |               auth: ['erp:supplier:create'], | ||||||
|       target="_blank" |               onClick: handleCreate, | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/supplier/index" |             }, | ||||||
|     > |             { | ||||||
|       可参考 |               label: $t('ui.actionTitle.export'), | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/supplier/index |               type: 'primary', | ||||||
|       代码,pull request 贡献给我们! |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|     </Button> |               auth: ['erp:supplier:export'], | ||||||
|  |               onClick: handleExport, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '编辑', | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['erp:supplier:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '删除', | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['erp:supplier:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,99 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpSupplierApi } from '#/api/erp/purchase/supplier'; | ||||||
|  | 
 | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createSupplier, | ||||||
|  |   getSupplier, | ||||||
|  |   updateSupplier, | ||||||
|  | } from '#/api/erp/purchase/supplier'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | 
 | ||||||
|  | const formType = ref<'create' | 'update'>('create'); | ||||||
|  | const supplierId = ref<number>(); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     labelWidth: 100, | ||||||
|  |   }, | ||||||
|  |   wrapperClass: 'grid-cols-2', | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = (await formApi.getValues()) as ErpSupplierApi.Supplier; | ||||||
|  |     try { | ||||||
|  |       if (formType.value === 'create') { | ||||||
|  |         await createSupplier(data); | ||||||
|  |         message.success($t('ui.actionMessage.createSuccess')); | ||||||
|  |       } else { | ||||||
|  |         await updateSupplier(data); | ||||||
|  |         message.success($t('ui.actionMessage.updateSuccess')); | ||||||
|  |       } | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<{ id?: number; type: 'create' | 'update' }>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     formType.value = data.type; | ||||||
|  |     supplierId.value = data.id; | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       if (data.type === 'update' && data.id) { | ||||||
|  |         // 编辑模式,加载数据 | ||||||
|  |         const supplierData = await getSupplier(data.id); | ||||||
|  |         await formApi.setValues(supplierData); | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  |   modalApi, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal | ||||||
|  |     :title="formType === 'create' ? '新增供应商' : '编辑供应商'" | ||||||
|  |     class="w-3/5" | ||||||
|  |   > | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,297 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { createRequiredValidation } from '#/adapter/vxe-table'; | ||||||
|  | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; | ||||||
|  | import { getSimpleUserList } from '#/api/system/user'; | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 表单的配置项 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         style: { display: 'none' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'id', | ||||||
|  |       label: 'ID', | ||||||
|  |       hideLabel: true, | ||||||
|  |       formItemClass: 'hidden', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '系统自动生成', | ||||||
|  |         disabled: true, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '入库单号', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择供应商', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSupplierSimpleList, | ||||||
|  |         fieldNames: { | ||||||
|  |           label: 'name', | ||||||
|  |           value: 'id', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'supplierId', | ||||||
|  |       label: '供应商', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'DatePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '选择入库时间', | ||||||
|  |         showTime: true, | ||||||
|  |         format: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |         valueFormat: 'x', | ||||||
|  |         style: { width: '100%' }, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'inTime', | ||||||
|  |       label: '入库时间', | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         autoSize: { minRows: 2, maxRows: 4 }, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       component: 'FileUpload', | ||||||
|  |       componentProps: { | ||||||
|  |         maxNumber: 1, | ||||||
|  |         maxSize: 10, | ||||||
|  |         accept: [ | ||||||
|  |           'pdf', | ||||||
|  |           'doc', | ||||||
|  |           'docx', | ||||||
|  |           'xls', | ||||||
|  |           'xlsx', | ||||||
|  |           'txt', | ||||||
|  |           'jpg', | ||||||
|  |           'jpeg', | ||||||
|  |           'png', | ||||||
|  |         ], | ||||||
|  |         showDescription: true, | ||||||
|  |       }, | ||||||
|  |       fieldName: 'fileUrl', | ||||||
|  |       label: '附件', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'product', | ||||||
|  |       label: '产品清单', | ||||||
|  |       component: 'Input', | ||||||
|  |       formItemClass: 'col-span-3', | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 入库产品清单表格列定义 */ | ||||||
|  | export function useStockInItemTableColumns( | ||||||
|  |   isValidating?: any, | ||||||
|  | ): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, | ||||||
|  |     { | ||||||
|  |       field: 'warehouseId', | ||||||
|  |       title: '仓库名称', | ||||||
|  |       minWidth: 150, | ||||||
|  |       slots: { default: 'warehouseId' }, | ||||||
|  |       className: createRequiredValidation(isValidating, 'warehouseId'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productId', | ||||||
|  |       title: '产品名称', | ||||||
|  |       minWidth: 200, | ||||||
|  |       slots: { default: 'productId' }, | ||||||
|  |       className: createRequiredValidation(isValidating, 'productId'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'stockCount', | ||||||
|  |       title: '库存', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productBarCode', | ||||||
|  |       title: '条码', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productUnitName', | ||||||
|  |       title: '单位', | ||||||
|  |       minWidth: 80, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'count', | ||||||
|  |       title: '数量', | ||||||
|  |       minWidth: 120, | ||||||
|  |       slots: { default: 'count' }, | ||||||
|  |       className: createRequiredValidation(isValidating, 'count'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productPrice', | ||||||
|  |       title: '产品单价', | ||||||
|  |       minWidth: 120, | ||||||
|  |       slots: { default: 'productPrice' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalPrice', | ||||||
|  |       title: '金额', | ||||||
|  |       minWidth: 120, | ||||||
|  |       formatter: 'formatAmount2', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'remark', | ||||||
|  |       title: '备注', | ||||||
|  |       minWidth: 150, | ||||||
|  |       slots: { default: 'remark' }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       width: 50, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'no', | ||||||
|  |       label: '入库单号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入入库单号', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'supplierId', | ||||||
|  |       label: '供应商', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择供应商', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSupplierSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'inTime', | ||||||
|  |       label: '入库时间', | ||||||
|  |       component: 'RangePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: ['开始日期', '结束日期'], | ||||||
|  |         showTime: true, | ||||||
|  |         format: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |         valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择状态', | ||||||
|  |         allowClear: true, | ||||||
|  |         options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'creator', | ||||||
|  |       label: '创建人', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择创建人', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getSimpleUserList, | ||||||
|  |         labelField: 'nickname', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       type: 'checkbox', | ||||||
|  |       width: 50, | ||||||
|  |       fixed: 'left', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'no', | ||||||
|  |       title: '入库单号', | ||||||
|  |       minWidth: 180, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'productNames', | ||||||
|  |       title: '产品信息', | ||||||
|  |       minWidth: 200, | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'supplierName', | ||||||
|  |       title: '供应商', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'inTime', | ||||||
|  |       title: '入库时间', | ||||||
|  |       minWidth: 180, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDateTime', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'creatorName', | ||||||
|  |       title: '创建人', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '状态', | ||||||
|  |       minWidth: 90, | ||||||
|  |       fixed: 'right', | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       width: 300, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,34 +1,220 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpStockInApi } from '#/api/erp/stock/in'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteStockIn, | ||||||
|  |   exportStockIn, | ||||||
|  |   getStockInPage, | ||||||
|  |   updateStockInStatus, | ||||||
|  | } from '#/api/erp/stock/in'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import StockInForm from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | /** 其它入库单管理 */ | ||||||
|  | defineOptions({ name: 'ErpStockIn' }); | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: StockInForm, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出入库单 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportStockIn(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '其它入库单.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 新增/编辑/详情 */ | ||||||
|  | function openForm(type: string, id?: number) { | ||||||
|  |   formModalApi.setData({ type, id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除 */ | ||||||
|  | async function handleDelete(ids: any[]) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: '删除中...', | ||||||
|  |     duration: 0, | ||||||
|  |     key: 'action_process_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteStockIn(ids); | ||||||
|  |     message.success({ | ||||||
|  |       content: '删除成功', | ||||||
|  |       key: 'action_process_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 审核/反审核 */ | ||||||
|  | async function handleUpdateStatus(id: any, status: number) { | ||||||
|  |   const statusText = status === 20 ? '审核' : '反审核'; | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: `${statusText}中...`, | ||||||
|  |     duration: 0, | ||||||
|  |     key: 'action_process_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await updateStockInStatus({ id, status }); | ||||||
|  |     message.success({ | ||||||
|  |       content: `${statusText}成功`, | ||||||
|  |       key: 'action_process_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getStockInPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |     checkboxConfig: { | ||||||
|  |       reserve: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpStockInApi.StockIn>, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <template #doc> |     <template #doc> | ||||||
|       <DocAlert |       <DocAlert | ||||||
|         title="【库存】其它入库、其它出库" |         title="【库存】其它入库、其它出库" | ||||||
|         url="https://doc.iocoder.cn/erp/stock-in-out/" |         url="https://doc.iocoder.cn/erp/stock-in-out/" | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Button | 
 | ||||||
|       danger |     <Grid table-title="其它入库单列表"> | ||||||
|       type="link" |       <template #toolbar-tools> | ||||||
|       target="_blank" |         <TableAction | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |           :actions="[ | ||||||
|     > |             { | ||||||
|       该功能支持 Vue3 + element-plus 版本! |               label: $t('ui.actionTitle.create', ['其它入库']), | ||||||
|     </Button> |               type: 'primary', | ||||||
|     <br /> |               icon: ACTION_ICON.ADD, | ||||||
|     <Button |               auth: ['erp:stock-in:create'], | ||||||
|       type="link" |               onClick: () => openForm('create'), | ||||||
|       target="_blank" |             }, | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/in/index" |             { | ||||||
|     > |               label: $t('ui.actionTitle.export'), | ||||||
|       可参考 |               type: 'default', | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/in/index |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|       代码,pull request 贡献给我们! |               auth: ['erp:stock-in:export'], | ||||||
|     </Button> |               onClick: handleExport, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '批量删除', | ||||||
|  |               type: 'default', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['erp:stock-in:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: '是否删除所选中数据?', | ||||||
|  |                 confirm: () => { | ||||||
|  |                   const checkboxRecords = gridApi.grid.getCheckboxRecords(); | ||||||
|  |                   if (checkboxRecords.length === 0) { | ||||||
|  |                     message.warning('请选择要删除的数据'); | ||||||
|  |                     return; | ||||||
|  |                   } | ||||||
|  |                   handleDelete(checkboxRecords.map((item) => item.id)); | ||||||
|  |                 }, | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '详情', | ||||||
|  |               icon: ACTION_ICON.VIEW, | ||||||
|  |               auth: ['erp:stock-in:query'], | ||||||
|  |               onClick: () => openForm('detail', row.id), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '编辑', | ||||||
|  |               auth: ['erp:stock-in:update'], | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               disabled: row.status !== 10, | ||||||
|  |               onClick: () => openForm('update', row.id), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '审核', | ||||||
|  |               auth: ['erp:stock-in:update'], | ||||||
|  |               ifShow: row.status === 10, | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: '确认要审核该入库单吗?', | ||||||
|  |                 confirm: () => handleUpdateStatus(row.id, 20), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '反审核', | ||||||
|  |               danger: true, | ||||||
|  |               auth: ['erp:stock-in:update'], | ||||||
|  |               ifShow: row.status === 20, | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: '确认要反审核该入库单吗?', | ||||||
|  |                 confirm: () => handleUpdateStatus(row.id, 10), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '删除', | ||||||
|  |               danger: true, | ||||||
|  |               auth: ['erp:stock-in:delete'], | ||||||
|  |               disabled: row.status !== 10, | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: '确认要删除该入库单吗?', | ||||||
|  |                 confirm: () => handleDelete([row.id]), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|  | 
 | ||||||
|  |     <!-- 表单弹窗 --> | ||||||
|  |     <FormModal @success="onRefresh" /> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,362 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpStockInApi } from '#/api/erp/stock/in'; | ||||||
|  | 
 | ||||||
|  | import { nextTick, onMounted, ref, watch } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { erpPriceMultiply } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { Input, InputNumber, Select } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { getProductSimpleList } from '#/api/erp/product/product'; | ||||||
|  | import { getStockCount } from '#/api/erp/stock/stock'; | ||||||
|  | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; | ||||||
|  | 
 | ||||||
|  | import { useStockInItemTableColumns } from '../data'; | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<Props>(), { | ||||||
|  |   items: () => [], | ||||||
|  |   disabled: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['update:items']); | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   items?: ErpStockInApi.StockInItem[]; | ||||||
|  |   disabled?: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const tableData = ref<ErpStockInApi.StockInItem[]>([]); | ||||||
|  | const productOptions = ref<any[]>([]); | ||||||
|  | const warehouseOptions = ref<any[]>([]); | ||||||
|  | const isValidating = ref(false); | ||||||
|  | 
 | ||||||
|  | /** 表格配置 */ | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   gridOptions: { | ||||||
|  |     editConfig: { | ||||||
|  |       trigger: 'click', | ||||||
|  |       mode: 'cell', | ||||||
|  |     }, | ||||||
|  |     columns: useStockInItemTableColumns(isValidating), | ||||||
|  |     data: tableData.value, | ||||||
|  |     border: true, | ||||||
|  |     showOverflow: true, | ||||||
|  |     autoResize: true, | ||||||
|  |     minHeight: 250, | ||||||
|  |     keepSource: true, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     pagerConfig: { | ||||||
|  |       enabled: false, | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       enabled: false, | ||||||
|  |     }, | ||||||
|  |     showFooter: true, | ||||||
|  |     footerCellClassName: 'stock-in-footer-cell', | ||||||
|  |     footerMethod: ({ columns }) => { | ||||||
|  |       const footers: any[][] = []; | ||||||
|  |       const sums = getSummaries(); | ||||||
|  |       const footerData: any[] = []; | ||||||
|  |       columns.forEach((column, columnIndex) => { | ||||||
|  |         if (columnIndex === 0) { | ||||||
|  |           footerData.push('合计'); | ||||||
|  |         } else if (column.field === 'count') { | ||||||
|  |           footerData.push(sums.count); | ||||||
|  |         } else if (column.field === 'totalPrice') { | ||||||
|  |           footerData.push(sums.totalPrice); | ||||||
|  |         } else { | ||||||
|  |           footerData.push(''); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       footers.push(footerData); | ||||||
|  |       return footers; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 监听外部传入的列数据 */ | ||||||
|  | watch( | ||||||
|  |   () => props.items, | ||||||
|  |   async (items) => { | ||||||
|  |     if (!items) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     await nextTick(); | ||||||
|  |     tableData.value = [...items]; | ||||||
|  |     await nextTick(); | ||||||
|  |     gridApi.grid.reloadData(tableData.value); | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     immediate: true, | ||||||
|  |   }, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | /** 初始化 */ | ||||||
|  | onMounted(async () => { | ||||||
|  |   productOptions.value = await getProductSimpleList(); | ||||||
|  |   warehouseOptions.value = await getWarehouseSimpleList(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function handleAdd() { | ||||||
|  |   const newRow = { | ||||||
|  |     warehouseId: null, | ||||||
|  |     productId: null, | ||||||
|  |     productName: '', | ||||||
|  |     productUnitId: null, | ||||||
|  |     productUnitName: '', | ||||||
|  |     productBarCode: '', | ||||||
|  |     count: 1, | ||||||
|  |     productPrice: 0, | ||||||
|  |     totalPrice: 0, | ||||||
|  |     stockCount: 0, | ||||||
|  |     remark: '', | ||||||
|  |   }; | ||||||
|  |   tableData.value.push(newRow); | ||||||
|  |   gridApi.grid.insertAt(newRow, -1); | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  |   // 触发表格重新渲染以更新cellClassName | ||||||
|  |   nextTick(() => { | ||||||
|  |     gridApi.grid.refreshColumn(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handleDelete(row: ErpStockInApi.StockInItem) { | ||||||
|  |   gridApi.grid.remove(row); | ||||||
|  |   const index = tableData.value.findIndex((item) => item.id === row.id); | ||||||
|  |   if (index !== -1) { | ||||||
|  |     tableData.value.splice(index, 1); | ||||||
|  |   } | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function handleWarehouseChange(warehouseId: any, row: any) { | ||||||
|  |   const warehouse = warehouseOptions.value.find((w) => w.id === warehouseId); | ||||||
|  |   if (!warehouse) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   row.warehouseId = warehouseId; | ||||||
|  | 
 | ||||||
|  |   // 如果已选择产品,重新获取库存 | ||||||
|  |   if (row.productId) { | ||||||
|  |     const stockCount = await getStockCount(row.productId, warehouseId); | ||||||
|  |     row.stockCount = stockCount || 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   handleUpdateValue(row); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function handleProductChange(productId: any, row: any) { | ||||||
|  |   const product = productOptions.value.find((p) => p.id === productId); | ||||||
|  |   if (!product) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // 获取库存数量 | ||||||
|  |   const stockCount = row.warehouseId | ||||||
|  |     ? await getStockCount(productId, row.warehouseId) | ||||||
|  |     : await getStockCount(productId); | ||||||
|  | 
 | ||||||
|  |   row.productId = productId; | ||||||
|  |   row.productUnitId = product.unitId; | ||||||
|  |   row.productBarCode = product.barCode; | ||||||
|  |   row.productUnitName = product.unitName; | ||||||
|  |   row.productName = product.name; | ||||||
|  |   row.stockCount = stockCount || 0; | ||||||
|  |   row.productPrice = product.purchasePrice || 0; | ||||||
|  |   row.count = row.count || 1; | ||||||
|  | 
 | ||||||
|  |   handlePriceChange(row); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handlePriceChange(row: any) { | ||||||
|  |   if (row.productPrice && row.count) { | ||||||
|  |     row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; | ||||||
|  |   } | ||||||
|  |   handleUpdateValue(row); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function handleUpdateValue(row: any) { | ||||||
|  |   const index = tableData.value.findIndex((item) => item.id === row.id); | ||||||
|  |   if (index === -1) { | ||||||
|  |     tableData.value.push(row); | ||||||
|  |   } else { | ||||||
|  |     tableData.value[index] = row; | ||||||
|  |   } | ||||||
|  |   emit('update:items', [...tableData.value]); | ||||||
|  |   // 触发表格重新渲染以更新cellClassName | ||||||
|  |   nextTick(() => { | ||||||
|  |     gridApi.grid.refreshColumn(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const getSummaries = (): { | ||||||
|  |   count: number; | ||||||
|  |   totalPrice: number; | ||||||
|  | } => { | ||||||
|  |   return { | ||||||
|  |     count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0), | ||||||
|  |     totalPrice: tableData.value.reduce( | ||||||
|  |       (sum, item) => sum + (item.totalPrice || 0), | ||||||
|  |       0, | ||||||
|  |     ), | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** 验证表单 */ | ||||||
|  | function validate(): Promise<boolean> { | ||||||
|  |   return new Promise((resolve) => { | ||||||
|  |     isValidating.value = true; | ||||||
|  | 
 | ||||||
|  |     // 触发表格重新渲染以显示验证错误 | ||||||
|  |     nextTick(() => { | ||||||
|  |       gridApi.grid.refreshColumn(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // 验证是否有产品清单 | ||||||
|  |     if (!tableData.value || tableData.value.length === 0) { | ||||||
|  |       resolve(false); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 验证每一行的必填字段 | ||||||
|  |     for (const item of tableData.value) { | ||||||
|  |       if ( | ||||||
|  |         !item.warehouseId || | ||||||
|  |         !item.productId || | ||||||
|  |         !item.count || | ||||||
|  |         item.count <= 0 | ||||||
|  |       ) { | ||||||
|  |         resolve(false); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 验证通过,清除验证状态 | ||||||
|  |     isValidating.value = false; | ||||||
|  |     nextTick(() => { | ||||||
|  |       gridApi.grid.refreshColumn(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     resolve(true); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 初始化表格数据 */ | ||||||
|  | function init(items: ErpStockInApi.StockInItem[]) { | ||||||
|  |   tableData.value = items || []; | ||||||
|  |   gridApi.grid.reloadData(tableData.value); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  |   validate, | ||||||
|  |   init, | ||||||
|  |   handleAdd, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div class="w-full"> | ||||||
|  |     <div class="mb-4 flex justify-between"> | ||||||
|  |       <span class="text-lg font-medium"></span> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <Grid> | ||||||
|  |       <template #warehouseId="{ row }"> | ||||||
|  |         <Select | ||||||
|  |           v-model:value="row.warehouseId" | ||||||
|  |           :options="warehouseOptions" | ||||||
|  |           :field-names="{ label: 'name', value: 'id' }" | ||||||
|  |           placeholder="请选择仓库" | ||||||
|  |           :disabled="disabled" | ||||||
|  |           show-search | ||||||
|  |           @change="(value) => handleWarehouseChange(value, row)" | ||||||
|  |           class="w-full" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #productId="{ row }"> | ||||||
|  |         <Select | ||||||
|  |           v-model:value="row.productId" | ||||||
|  |           :options="productOptions" | ||||||
|  |           :field-names="{ label: 'name', value: 'id' }" | ||||||
|  |           placeholder="请选择产品" | ||||||
|  |           :disabled="disabled" | ||||||
|  |           show-search | ||||||
|  |           @change="(value) => handleProductChange(value, row)" | ||||||
|  |           class="w-full" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #count="{ row }"> | ||||||
|  |         <InputNumber | ||||||
|  |           v-model:value="row.count" | ||||||
|  |           :disabled="disabled" | ||||||
|  |           :min="0.001" | ||||||
|  |           :precision="3" | ||||||
|  |           @change="() => handlePriceChange(row)" | ||||||
|  |           class="w-full" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #productPrice="{ row }"> | ||||||
|  |         <InputNumber | ||||||
|  |           v-model:value="row.productPrice" | ||||||
|  |           :disabled="disabled" | ||||||
|  |           :min="0.01" | ||||||
|  |           :precision="2" | ||||||
|  |           @change="() => handlePriceChange(row)" | ||||||
|  |           class="w-full" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #remark="{ row }"> | ||||||
|  |         <Input | ||||||
|  |           v-model:value="row.remark" | ||||||
|  |           :disabled="disabled" | ||||||
|  |           placeholder="请输入备注" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           v-if="!disabled" | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '删除', | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: '确认删除该产品吗?', | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #bottom> | ||||||
|  |         <TableAction | ||||||
|  |           v-if="!disabled" | ||||||
|  |           class="mt-4 flex justify-center" | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '添加产品', | ||||||
|  |               type: 'default', | ||||||
|  |               onClick: handleAdd, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | :deep(.vxe-table .vxe-footer--column.stock-in-footer-cell .vxe-cell) { | ||||||
|  |   background-color: #f5f5f5 !important; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,202 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpStockInApi } from '#/api/erp/stock/in'; | ||||||
|  | 
 | ||||||
|  | import { computed, nextTick, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createStockIn, | ||||||
|  |   getStockIn, | ||||||
|  |   updateStockIn, | ||||||
|  |   updateStockInStatus, | ||||||
|  | } from '#/api/erp/stock/in'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | import StockInItemForm from './StockInItemForm.vue'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<ErpStockInApi.StockIn>(); | ||||||
|  | const formType = ref(''); | ||||||
|  | const itemFormRef = ref(); | ||||||
|  | 
 | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   if (formType.value === 'create') return '添加其它入库单'; | ||||||
|  |   if (formType.value === 'update') return '编辑其它入库单'; | ||||||
|  |   return '其它入库单详情'; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     labelWidth: 120, | ||||||
|  |   }, | ||||||
|  |   wrapperClass: 'grid-cols-3', | ||||||
|  |   layout: 'vertical', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const handleUpdateItems = (items: ErpStockInApi.StockInItem[]) => { | ||||||
|  |   formData.value = modalApi.getData<ErpStockInApi.StockIn>(); | ||||||
|  |   if (formData.value) { | ||||||
|  |     formData.value.items = items; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 创建或更新其它入库单 | ||||||
|  |  */ | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     await nextTick(); | ||||||
|  | 
 | ||||||
|  |     const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |       ? itemFormRef.value[0] | ||||||
|  |       : itemFormRef.value; | ||||||
|  |     if (itemFormInstance && typeof itemFormInstance.validate === 'function') { | ||||||
|  |       try { | ||||||
|  |         const isValid = await itemFormInstance.validate(); | ||||||
|  |         if (!isValid) { | ||||||
|  |           message.error('产品清单验证失败,请检查必填项'); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } catch (error) { | ||||||
|  |         message.error(error.message || '产品清单验证失败'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       message.error('产品清单验证方法不存在'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 验证产品清单不能为空 | ||||||
|  |     if (!formData.value?.items || formData.value.items.length === 0) { | ||||||
|  |       message.error('产品清单不能为空,请至少添加一个产品'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = (await formApi.getValues()) as ErpStockInApi.StockIn; | ||||||
|  |     data.items = formData.value?.items; | ||||||
|  |     // 将文件数组转换为字符串 | ||||||
|  |     if (data.fileUrl && Array.isArray(data.fileUrl)) { | ||||||
|  |       data.fileUrl = data.fileUrl.length > 0 ? data.fileUrl[0] : ''; | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       await (formType.value === 'create' | ||||||
|  |         ? createStockIn(data) | ||||||
|  |         : updateStockIn(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       message.success(formType.value === 'create' ? '新增成功' : '更新成功'); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<{ id?: number; type: string }>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     formType.value = data.type; | ||||||
|  | 
 | ||||||
|  |     if (!data.id) { | ||||||
|  |       // 初始化空的表单数据 | ||||||
|  |       formData.value = { items: [] } as ErpStockInApi.StockIn; | ||||||
|  |       await nextTick(); | ||||||
|  |       const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |         ? itemFormRef.value[0] | ||||||
|  |         : itemFormRef.value; | ||||||
|  |       if (itemFormInstance && typeof itemFormInstance.init === 'function') { | ||||||
|  |         itemFormInstance.init([]); | ||||||
|  |       } | ||||||
|  |       // 如果是新增,自动添加一行 | ||||||
|  |       if (formType.value === 'create' && itemFormInstance) { | ||||||
|  |         itemFormInstance.handleAdd(); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = await getStockIn(data.id); | ||||||
|  |       // 将字符串形式的文件URL转换为数组形式以适配FileUpload组件 | ||||||
|  |       if ( | ||||||
|  |         formData.value.fileUrl && | ||||||
|  |         typeof formData.value.fileUrl === 'string' | ||||||
|  |       ) { | ||||||
|  |         formData.value.fileUrl = formData.value.fileUrl | ||||||
|  |           ? [formData.value.fileUrl] | ||||||
|  |           : []; | ||||||
|  |       } | ||||||
|  |       // 设置到 values | ||||||
|  |       await formApi.setValues(formData.value); | ||||||
|  |       // 初始化子表单 | ||||||
|  |       await nextTick(); | ||||||
|  |       const itemFormInstance = Array.isArray(itemFormRef.value) | ||||||
|  |         ? itemFormRef.value[0] | ||||||
|  |         : itemFormRef.value; | ||||||
|  |       if (itemFormInstance && typeof itemFormInstance.init === 'function') { | ||||||
|  |         itemFormInstance.init(formData.value.items || []); | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 审核/反审核 */ | ||||||
|  | async function handleUpdateStatus(id: number, status: number) { | ||||||
|  |   try { | ||||||
|  |     await updateStockInStatus({ id, status }); | ||||||
|  |     message.success(status === 20 ? '审核成功' : '反审核成功'); | ||||||
|  |     emit('success'); | ||||||
|  |     await modalApi.close(); | ||||||
|  |   } catch (error) { | ||||||
|  |     message.error(error.message || '操作失败'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | defineExpose({ modalApi, handleUpdateStatus }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal | ||||||
|  |     v-bind="$attrs" | ||||||
|  |     :title="getTitle" | ||||||
|  |     class="w-4/5" | ||||||
|  |     :closable="true" | ||||||
|  |     :mask-closable="true" | ||||||
|  |     :show-confirm-button="formType !== 'detail'" | ||||||
|  |   > | ||||||
|  |     <Form class="mx-3"> | ||||||
|  |       <template #product="slotProps"> | ||||||
|  |         <StockInItemForm | ||||||
|  |           v-bind="slotProps" | ||||||
|  |           ref="itemFormRef" | ||||||
|  |           class="w-full" | ||||||
|  |           :items="formData?.items ?? []" | ||||||
|  |           :disabled="formType === 'detail'" | ||||||
|  |           @update:items="handleUpdateItems" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Form> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,146 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { getProductSimpleList } from '#/api/erp/product/product'; | ||||||
|  | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'productId', | ||||||
|  |       label: '产品', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择产品', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getProductSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'warehouseId', | ||||||
|  |       label: '仓库', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择仓库', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getWarehouseSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'bizType', | ||||||
|  |       label: '类型', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择类型', | ||||||
|  |         allowClear: true, | ||||||
|  |         options: getDictOptions(DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE, 'number'), | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'bizNo', | ||||||
|  |       label: '业务单号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入业务单号', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'createTime', | ||||||
|  |       label: '创建时间', | ||||||
|  |       component: 'RangePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: ['开始日期', '结束日期'], | ||||||
|  |         showTime: true, | ||||||
|  |         format: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |         valueFormat: 'YYYY-MM-DD HH:mm:ss', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'productName', | ||||||
|  |       title: '产品名称', | ||||||
|  |       minWidth: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'categoryName', | ||||||
|  |       title: '产品分类', | ||||||
|  |       width: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'unitName', | ||||||
|  |       title: '产品单位', | ||||||
|  |       width: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'warehouseName', | ||||||
|  |       title: '仓库', | ||||||
|  |       width: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'bizType', | ||||||
|  |       title: '类型', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'bizNo', | ||||||
|  |       title: '出入库单号', | ||||||
|  |       width: 200, | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'createTime', | ||||||
|  |       title: '出入库日期', | ||||||
|  |       width: 180, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDateTime', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'count', | ||||||
|  |       title: '出入库数量', | ||||||
|  |       width: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellAmount', | ||||||
|  |         props: { | ||||||
|  |           digits: 2, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'totalCount', | ||||||
|  |       title: '库存量', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellAmount', | ||||||
|  |         props: { | ||||||
|  |           digits: 2, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'creatorName', | ||||||
|  |       title: '操作人', | ||||||
|  |       width: 100, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,32 +1,78 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpStockRecordApi } from '#/api/erp/stock/record'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { DocAlert, Page } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { exportStockRecord, getStockRecordPage } from '#/api/erp/stock/record'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | 
 | ||||||
|  | /** 产品库存明细管理 */ | ||||||
|  | defineOptions({ name: 'ErpStockRecord' }); | ||||||
|  | 
 | ||||||
|  | /** 导出库存明细 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportStockRecord(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '产品库存明细.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getStockRecordPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpStockRecordApi.StockRecord>, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <DocAlert |     <template #doc> | ||||||
|       title="【库存】产品库存、库存明细" |       <DocAlert | ||||||
|       url="https://doc.iocoder.cn/erp/stock/" |         title="【库存】产品库存、库存明细" | ||||||
|     /> |         url="https://doc.iocoder.cn/erp/stock/" | ||||||
|     <Button |       /> | ||||||
|       danger |     </template> | ||||||
|       type="link" | 
 | ||||||
|       target="_blank" |     <Grid table-title="产品库存明细列表"> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |       <template #toolbar-tools> | ||||||
|     > |         <TableAction | ||||||
|       该功能支持 Vue3 + element-plus 版本! |           :actions="[ | ||||||
|     </Button> |             { | ||||||
|     <br /> |               label: $t('ui.actionTitle.export'), | ||||||
|     <Button |               type: 'primary', | ||||||
|       type="link" |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|       target="_blank" |               auth: ['erp:stock-record:export'], | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/record/index" |               onClick: handleExport, | ||||||
|     > |             }, | ||||||
|       可参考 |           ]" | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/record/index |         /> | ||||||
|       代码,pull request 贡献给我们! |       </template> | ||||||
|     </Button> |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | 
 | ||||||
|  | import { getProductSimpleList } from '#/api/erp/product/product'; | ||||||
|  | import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; | ||||||
|  | 
 | ||||||
|  | /** 搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'productId', | ||||||
|  |       label: '产品', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择产品', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getProductSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'warehouseId', | ||||||
|  |       label: '仓库', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择仓库', | ||||||
|  |         allowClear: true, | ||||||
|  |         showSearch: true, | ||||||
|  |         api: getWarehouseSimpleList, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         filterOption: false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'productName', | ||||||
|  |       title: '产品名称', | ||||||
|  |       minWidth: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'unitName', | ||||||
|  |       title: '产品单位', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'categoryName', | ||||||
|  |       title: '产品分类', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'count', | ||||||
|  |       title: '库存量', | ||||||
|  |       minWidth: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellAmount', | ||||||
|  |         props: { | ||||||
|  |           digits: 2, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'warehouseName', | ||||||
|  |       title: '仓库', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,32 +1,78 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpStockApi } from '#/api/erp/stock/stock'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { DocAlert, Page } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { exportStock, getStockPage } from '#/api/erp/stock/stock'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | 
 | ||||||
|  | /** 产品库存管理 */ | ||||||
|  | defineOptions({ name: 'ErpStock' }); | ||||||
|  | 
 | ||||||
|  | /** 导出库存 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportStock(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '产品库存.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getStockPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpStockApi.Stock>, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <DocAlert |     <template #doc> | ||||||
|       title="【库存】产品库存、库存明细" |       <DocAlert | ||||||
|       url="https://doc.iocoder.cn/erp/stock/" |         title="【库存】产品库存、库存明细" | ||||||
|     /> |         url="https://doc.iocoder.cn/erp/stock/" | ||||||
|     <Button |       /> | ||||||
|       danger |     </template> | ||||||
|       type="link" | 
 | ||||||
|       target="_blank" |     <Grid table-title="产品库存列表"> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |       <template #toolbar-tools> | ||||||
|     > |         <TableAction | ||||||
|       该功能支持 Vue3 + element-plus 版本! |           :actions="[ | ||||||
|     </Button> |             { | ||||||
|     <br /> |               label: $t('ui.actionTitle.export'), | ||||||
|     <Button |               type: 'primary', | ||||||
|       type="link" |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|       target="_blank" |               auth: ['erp:stock:export'], | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/stock/index" |               onClick: handleExport, | ||||||
|     > |             }, | ||||||
|       可参考 |           ]" | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/stock/index |         /> | ||||||
|       代码,pull request 贡献给我们! |       </template> | ||||||
|     </Button> |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,214 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpWarehouseApi } from '#/api/erp/stock/warehouse'; | ||||||
|  | 
 | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       component: 'Input', | ||||||
|  |       fieldName: 'id', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '仓库名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入仓库名称', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'address', | ||||||
|  |       label: '仓库地址', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入仓库地址', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '开启状态', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |       defaultValue: 0, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'warehousePrice', | ||||||
|  |       label: '仓储费(元)', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入仓储费,单位:元/天/KG', | ||||||
|  |         min: 0, | ||||||
|  |         precision: 2, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'truckagePrice', | ||||||
|  |       label: '搬运费(元)', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入搬运费,单位:元', | ||||||
|  |         min: 0, | ||||||
|  |         precision: 2, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'principal', | ||||||
|  |       label: '负责人', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入负责人', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'sort', | ||||||
|  |       label: '排序', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入排序', | ||||||
|  |         precision: 0, | ||||||
|  |         class: 'w-full', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |       defaultValue: 0, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |         rows: 3, | ||||||
|  |       }, | ||||||
|  |       formItemClass: 'col-span-2', | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '仓库名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入仓库名称', | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '仓库状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请选择仓库状态', | ||||||
|  |         allowClear: true, | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns<T = ErpWarehouseApi.Warehouse>( | ||||||
|  |   onDefaultStatusChange?: ( | ||||||
|  |     newStatus: boolean, | ||||||
|  |     row: T, | ||||||
|  |   ) => PromiseLike<boolean | undefined>, | ||||||
|  | ): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'name', | ||||||
|  |       title: '仓库名称', | ||||||
|  |       minWidth: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'address', | ||||||
|  |       title: '仓库地址', | ||||||
|  |       minWidth: 200, | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'warehousePrice', | ||||||
|  |       title: '仓储费(元)', | ||||||
|  |       width: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellMoney', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'truckagePrice', | ||||||
|  |       title: '搬运费(元)', | ||||||
|  |       width: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellMoney', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'principal', | ||||||
|  |       title: '负责人', | ||||||
|  |       width: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sort', | ||||||
|  |       title: '排序', | ||||||
|  |       width: 80, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '状态', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.COMMON_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'defaultStatus', | ||||||
|  |       title: '是否默认', | ||||||
|  |       width: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         attrs: { beforeChange: onDefaultStatusChange }, | ||||||
|  |         name: 'CellSwitch', | ||||||
|  |         props: { | ||||||
|  |           checkedValue: true, | ||||||
|  |           unCheckedValue: false, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'remark', | ||||||
|  |       title: '备注', | ||||||
|  |       minWidth: 150, | ||||||
|  |       showOverflow: 'tooltip', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'createTime', | ||||||
|  |       title: '创建时间', | ||||||
|  |       width: 180, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDateTime', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'actions', | ||||||
|  |       title: '操作', | ||||||
|  |       width: 160, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,32 +1,180 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpWarehouseApi } from '#/api/erp/stock/warehouse'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { downloadFileFromBlobPart } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteWarehouse, | ||||||
|  |   exportWarehouse, | ||||||
|  |   getWarehousePage, | ||||||
|  |   updateWarehouseDefaultStatus, | ||||||
|  | } from '#/api/erp/stock/warehouse'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import WarehouseForm from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | /** 仓库管理 */ | ||||||
|  | defineOptions({ name: 'ErpWarehouse' }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 添加仓库 */ | ||||||
|  | function handleCreate() { | ||||||
|  |   formModalApi.setData({ type: 'create' }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑仓库 */ | ||||||
|  | function handleEdit(row: ErpWarehouseApi.Warehouse) { | ||||||
|  |   formModalApi.setData({ type: 'update', id: row.id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除仓库 */ | ||||||
|  | async function handleDelete(row: ErpWarehouseApi.Warehouse) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|  |     key: 'action_key_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteWarehouse(row.id!); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||||
|  |       key: 'action_key_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } catch { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 修改默认状态 */ | ||||||
|  | async function handleDefaultStatusChange( | ||||||
|  |   newStatus: boolean, | ||||||
|  |   row: ErpWarehouseApi.Warehouse, | ||||||
|  | ): Promise<boolean | undefined> { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     const text = newStatus ? '开启' : '取消'; | ||||||
|  | 
 | ||||||
|  |     confirm({ | ||||||
|  |       content: `确认要${text}"${row.name}"默认吗?`, | ||||||
|  |     }) | ||||||
|  |       .then(async () => { | ||||||
|  |         // 更新默认状态 | ||||||
|  |         await updateWarehouseDefaultStatus(row.id!, newStatus); | ||||||
|  |         message.success(`${text}默认状态成功`); | ||||||
|  |         resolve(true); | ||||||
|  |       }) | ||||||
|  |       .catch(() => { | ||||||
|  |         reject(new Error('取消操作')); | ||||||
|  |       }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 导出仓库 */ | ||||||
|  | async function handleExport() { | ||||||
|  |   const data = await exportWarehouse(await gridApi.formApi.getValues()); | ||||||
|  |   downloadFileFromBlobPart({ fileName: '仓库.xls', source: data }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: WarehouseForm, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(handleDefaultStatusChange), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getWarehousePage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpWarehouseApi.Warehouse>, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <DocAlert |     <template #doc> | ||||||
|       title="【库存】产品库存、库存明细" |       <DocAlert | ||||||
|       url="https://doc.iocoder.cn/erp/stock/" |         title="【库存】产品库存、库存明细" | ||||||
|     /> |         url="https://doc.iocoder.cn/erp/stock/" | ||||||
|     <Button |       /> | ||||||
|       danger |     </template> | ||||||
|       type="link" | 
 | ||||||
|       target="_blank" |     <FormModal @success="onRefresh" /> | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |     <Grid table-title="仓库列表"> | ||||||
|     > |       <template #toolbar-tools> | ||||||
|       该功能支持 Vue3 + element-plus 版本! |         <TableAction | ||||||
|     </Button> |           :actions="[ | ||||||
|     <br /> |             { | ||||||
|     <Button |               label: $t('ui.actionTitle.create', ['仓库']), | ||||||
|       type="link" |               type: 'primary', | ||||||
|       target="_blank" |               icon: ACTION_ICON.ADD, | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/warehouse/index" |               auth: ['erp:warehouse:create'], | ||||||
|     > |               onClick: handleCreate, | ||||||
|       可参考 |             }, | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/warehouse/index |             { | ||||||
|       代码,pull request 贡献给我们! |               label: $t('ui.actionTitle.export'), | ||||||
|     </Button> |               type: 'primary', | ||||||
|  |               icon: ACTION_ICON.DOWNLOAD, | ||||||
|  |               auth: ['erp:warehouse:export'], | ||||||
|  |               onClick: handleExport, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '编辑', | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['erp:warehouse:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: '删除', | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['erp:warehouse:delete'], | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpWarehouseApi } from '#/api/erp/stock/warehouse'; | ||||||
|  | 
 | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createWarehouse, | ||||||
|  |   getWarehouse, | ||||||
|  |   updateWarehouse, | ||||||
|  | } from '#/api/erp/stock/warehouse'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | 
 | ||||||
|  | const formType = ref<'create' | 'update'>('create'); | ||||||
|  | const warehouseId = ref<number>(); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     labelWidth: 100, | ||||||
|  |   }, | ||||||
|  |   wrapperClass: 'grid-cols-2', | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = (await formApi.getValues()) as ErpWarehouseApi.Warehouse; | ||||||
|  |     try { | ||||||
|  |       if (formType.value === 'create') { | ||||||
|  |         await createWarehouse(data); | ||||||
|  |         message.success($t('ui.actionMessage.createSuccess')); | ||||||
|  |       } else { | ||||||
|  |         await updateWarehouse(data); | ||||||
|  |         message.success($t('ui.actionMessage.updateSuccess')); | ||||||
|  |       } | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<{ id?: number; type: 'create' | 'update' }>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     formType.value = data.type; | ||||||
|  |     warehouseId.value = data.id; | ||||||
|  | 
 | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       if (data.type === 'update' && data.id) { | ||||||
|  |         const warehouseData = await getWarehouse(data.id); | ||||||
|  |         await formApi.setValues(warehouseData); | ||||||
|  |       } | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  |   modalApi, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="formType === 'create' ? '新增仓库' : '编辑仓库'" class="w-3/5"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/web-ele", |   "name": "@vben/web-ele", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://vben.pro", |   "homepage": "https://vben.pro", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import { requestClient } from '#/api/request'; | ||||||
| 
 | 
 | ||||||
| const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); | const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); | ||||||
| const accessStore = useAccessStore(); | const accessStore = useAccessStore(); | ||||||
|  | 
 | ||||||
| export namespace AiChatMessageApi { | export namespace AiChatMessageApi { | ||||||
|   export interface ChatMessage { |   export interface ChatMessage { | ||||||
|     id: number; // 编号
 |     id: number; // 编号
 | ||||||
|  | @ -82,6 +83,7 @@ export function deleteByConversationId(conversationId: number) { | ||||||
|     `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`, |     `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 获得消息分页
 | // 获得消息分页
 | ||||||
| export function getChatMessagePage(params: any) { | export function getChatMessagePage(params: any) { | ||||||
|   return requestClient.get<PageResult<AiChatMessageApi.ChatMessage>>( |   return requestClient.get<PageResult<AiChatMessageApi.ChatMessage>>( | ||||||
|  | @ -89,6 +91,7 @@ export function getChatMessagePage(params: any) { | ||||||
|     { params }, |     { params }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 管理员删除消息
 | // 管理员删除消息
 | ||||||
| export function deleteChatMessageByAdmin(id: number) { | export function deleteChatMessageByAdmin(id: number) { | ||||||
|   return requestClient.delete(`/ai/chat/message/delete-by-admin?id=${id}`); |   return requestClient.delete(`/ai/chat/message/delete-by-admin?id=${id}`); | ||||||
|  |  | ||||||
|  | @ -26,10 +26,12 @@ export function getKnowledgeDocumentPage(params: PageParam) { | ||||||
| export function getKnowledgeDocument(id: number) { | export function getKnowledgeDocument(id: number) { | ||||||
|   return requestClient.get(`/ai/knowledge/document/get?id=${id}`); |   return requestClient.get(`/ai/knowledge/document/get?id=${id}`); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 新增知识库文档(单个)
 | // 新增知识库文档(单个)
 | ||||||
| export function createKnowledge(data: any) { | export function createKnowledge(data: any) { | ||||||
|   return requestClient.post('/ai/knowledge/document/create', data); |   return requestClient.post('/ai/knowledge/document/create', data); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 新增知识库文档(多个)
 | // 新增知识库文档(多个)
 | ||||||
| export function createKnowledgeDocumentList(data: any) { | export function createKnowledgeDocumentList(data: any) { | ||||||
|   return requestClient.post('/ai/knowledge/document/create-list', data); |   return requestClient.post('/ai/knowledge/document/create-list', data); | ||||||
|  | @ -44,6 +46,7 @@ export function updateKnowledgeDocument(data: any) { | ||||||
| export function updateKnowledgeDocumentStatus(data: any) { | export function updateKnowledgeDocumentStatus(data: any) { | ||||||
|   return requestClient.put('/ai/knowledge/document/update-status', data); |   return requestClient.put('/ai/knowledge/document/update-status', data); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 删除知识库文档
 | // 删除知识库文档
 | ||||||
| export function deleteKnowledgeDocument(id: number) { | export function deleteKnowledgeDocument(id: number) { | ||||||
|   return requestClient.delete(`/ai/knowledge/document/delete?id=${id}`); |   return requestClient.delete(`/ai/knowledge/document/delete?id=${id}`); | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ export function getKnowledgeSegment(id: number) { | ||||||
|     `/ai/knowledge/segment/get?id=${id}`, |     `/ai/knowledge/segment/get?id=${id}`, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| // 新增知识库分段
 | // 新增知识库分段
 | ||||||
| export function createKnowledgeSegment( | export function createKnowledgeSegment( | ||||||
|   data: AiKnowledgeSegmentApi.KnowledgeSegment, |   data: AiKnowledgeSegmentApi.KnowledgeSegment, | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ import type { PageParam, PageResult } from '@vben/request'; | ||||||
| 
 | 
 | ||||||
| import { requestClient } from '#/api/request'; | import { requestClient } from '#/api/request'; | ||||||
| 
 | 
 | ||||||
|  | // TODO @xingyu:貌似模块不对
 | ||||||
|  | 
 | ||||||
| export namespace ProductUnitApi { | export namespace ProductUnitApi { | ||||||
|   /** 产品单位信息 */ |   /** 产品单位信息 */ | ||||||
|   export interface ProductUnit { |   export interface ProductUnit { | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ import type { PageParam, PageResult } from '@vben/request'; | ||||||
| 
 | 
 | ||||||
| import { requestClient } from '#/api/request'; | import { requestClient } from '#/api/request'; | ||||||
| 
 | 
 | ||||||
|  | // TODO @xingyu:貌似模块不对
 | ||||||
|  | 
 | ||||||
| export namespace ProductUnitGroupApi { | export namespace ProductUnitGroupApi { | ||||||
|   /** 产品单位组信息 */ |   /** 产品单位组信息 */ | ||||||
|   export interface ProductUnitGroup { |   export interface ProductUnitGroup { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/web-naive", |   "name": "@vben/web-naive", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://vben.pro", |   "homepage": "https://vben.pro", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/docs", |   "name": "@vben/docs", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "build": "vitepress build", |     "build": "vitepress build", | ||||||
|  |  | ||||||
|  | @ -56,6 +56,15 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda | ||||||
| 
 | 
 | ||||||
| <DemoPreview dir="demos/vben-modal/shared-data" /> | <DemoPreview dir="demos/vben-modal/shared-data" /> | ||||||
| 
 | 
 | ||||||
|  | ## 动画类型 | ||||||
|  | 
 | ||||||
|  | 通过 `animationType` 属性可以控制弹窗的动画效果: | ||||||
|  | 
 | ||||||
|  | - `slide`(默认):从顶部向下滑动进入/退出 | ||||||
|  | - `scale`:缩放淡入/淡出效果 | ||||||
|  | 
 | ||||||
|  | <DemoPreview dir="demos/vben-modal/animation-type" /> | ||||||
|  | 
 | ||||||
| ::: info 注意 | ::: info 注意 | ||||||
| 
 | 
 | ||||||
| - `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 | - `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 | ||||||
|  | @ -112,6 +121,7 @@ const [Modal, modalApi] = useVbenModal({ | ||||||
| | bordered | 是否显示border | `boolean` | `false` | | | bordered | 是否显示border | `boolean` | `false` | | ||||||
| | zIndex | 弹窗的ZIndex层级 | `number` | `1000` | | | zIndex | 弹窗的ZIndex层级 | `number` | `1000` | | ||||||
| | overlayBlur | 遮罩模糊度 | `number` | - | | | overlayBlur | 遮罩模糊度 | `number` | - | | ||||||
|  | | animationType | 动画类型 | `'slide' \| 'scale'` | `'slide'` | | ||||||
| | submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` | | | submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` | | ||||||
| 
 | 
 | ||||||
| ::: info appendToMain | ::: info appendToMain | ||||||
|  |  | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { useVbenModal, VbenButton } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | const [SlideModal, slideModalApi] = useVbenModal({ | ||||||
|  |   animationType: 'slide', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [ScaleModal, scaleModalApi] = useVbenModal({ | ||||||
|  |   animationType: 'scale', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function openSlideModal() { | ||||||
|  |   slideModalApi.open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function openScaleModal() { | ||||||
|  |   scaleModalApi.open(); | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div class="space-y-4"> | ||||||
|  |     <div class="flex gap-4"> | ||||||
|  |       <VbenButton @click="openSlideModal">滑动动画</VbenButton> | ||||||
|  |       <VbenButton @click="openScaleModal">缩放动画</VbenButton> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <SlideModal title="滑动动画示例" class="w-[500px]"> | ||||||
|  |       <p>这是使用滑动动画的弹窗,从顶部向下滑动进入。</p> | ||||||
|  |     </SlideModal> | ||||||
|  | 
 | ||||||
|  |     <ScaleModal title="缩放动画示例" class="w-[500px]"> | ||||||
|  |       <p>这是使用缩放动画的弹窗,以缩放淡入淡出的方式显示。</p> | ||||||
|  |     </ScaleModal> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/commitlint-config", |   "name": "@vben/commitlint-config", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/stylelint-config", |   "name": "@vben/stylelint-config", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/node-utils", |   "name": "@vben/node-utils", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/tailwind-config", |   "name": "@vben/tailwind-config", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/tsconfig", |   "name": "@vben/tsconfig", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/vite-config", |   "name": "@vben/vite-config", | ||||||
|   "version": "5.5.6", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "vben-admin-monorepo", |   "name": "vben-admin-monorepo", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "monorepo", |     "monorepo", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/design", |   "name": "@vben-core/design", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/icons", |   "name": "@vben-core/icons", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/shared", |   "name": "@vben-core/shared", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,82 @@ | ||||||
|  | import { beforeEach, describe, expect, it } from 'vitest'; | ||||||
|  | 
 | ||||||
|  | import { loadScript } from '../resources'; | ||||||
|  | 
 | ||||||
|  | const testJsPath = | ||||||
|  |   'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js'; | ||||||
|  | 
 | ||||||
|  | describe('loadScript', () => { | ||||||
|  |   beforeEach(() => { | ||||||
|  |     // 每个测试前清空 head,保证环境干净
 | ||||||
|  |     document.head.innerHTML = ''; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should resolve when the script loads successfully', async () => { | ||||||
|  |     const promise = loadScript(testJsPath); | ||||||
|  | 
 | ||||||
|  |     // 此时脚本元素已被创建并插入
 | ||||||
|  |     const script = document.querySelector( | ||||||
|  |       `script[src="${testJsPath}"]`, | ||||||
|  |     ) as HTMLScriptElement; | ||||||
|  |     expect(script).toBeTruthy(); | ||||||
|  | 
 | ||||||
|  |     // 模拟加载成功
 | ||||||
|  |     script.dispatchEvent(new Event('load')); | ||||||
|  | 
 | ||||||
|  |     // 等待 promise resolve
 | ||||||
|  |     await expect(promise).resolves.toBeUndefined(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should not insert duplicate script and resolve immediately if already loaded', async () => { | ||||||
|  |     // 先手动插入一个相同 src 的 script
 | ||||||
|  |     const existing = document.createElement('script'); | ||||||
|  |     existing.src = 'bar.js'; | ||||||
|  |     document.head.append(existing); | ||||||
|  | 
 | ||||||
|  |     // 再次调用
 | ||||||
|  |     const promise = loadScript('bar.js'); | ||||||
|  | 
 | ||||||
|  |     // 立即 resolve
 | ||||||
|  |     await expect(promise).resolves.toBeUndefined(); | ||||||
|  | 
 | ||||||
|  |     // head 中只保留一个
 | ||||||
|  |     const scripts = document.head.querySelectorAll('script[src="bar.js"]'); | ||||||
|  |     expect(scripts).toHaveLength(1); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should reject when the script fails to load', async () => { | ||||||
|  |     const promise = loadScript('error.js'); | ||||||
|  | 
 | ||||||
|  |     const script = document.querySelector( | ||||||
|  |       'script[src="error.js"]', | ||||||
|  |     ) as HTMLScriptElement; | ||||||
|  |     expect(script).toBeTruthy(); | ||||||
|  | 
 | ||||||
|  |     // 模拟加载失败
 | ||||||
|  |     script.dispatchEvent(new Event('error')); | ||||||
|  | 
 | ||||||
|  |     await expect(promise).rejects.toThrow('Failed to load script: error.js'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should handle multiple concurrent calls and only insert one script tag', async () => { | ||||||
|  |     const p1 = loadScript(testJsPath); | ||||||
|  |     const p2 = loadScript(testJsPath); | ||||||
|  | 
 | ||||||
|  |     const script = document.querySelector( | ||||||
|  |       `script[src="${testJsPath}"]`, | ||||||
|  |     ) as HTMLScriptElement; | ||||||
|  |     expect(script).toBeTruthy(); | ||||||
|  | 
 | ||||||
|  |     // 触发一次 load,两个 promise 都应该 resolve
 | ||||||
|  |     script.dispatchEvent(new Event('load')); | ||||||
|  | 
 | ||||||
|  |     await expect(p1).resolves.toBeUndefined(); | ||||||
|  |     await expect(p2).resolves.toBeUndefined(); | ||||||
|  | 
 | ||||||
|  |     // 只插入一次
 | ||||||
|  |     const scripts = document.head.querySelectorAll( | ||||||
|  |       `script[src="${testJsPath}"]`, | ||||||
|  |     ); | ||||||
|  |     expect(scripts).toHaveLength(1); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -8,6 +8,7 @@ export * from './inference'; | ||||||
| export * from './letter'; | export * from './letter'; | ||||||
| export * from './merge'; | export * from './merge'; | ||||||
| export * from './nprogress'; | export * from './nprogress'; | ||||||
|  | export * from './resources'; | ||||||
| export * from './state-handler'; | export * from './state-handler'; | ||||||
| export * from './time'; | export * from './time'; | ||||||
| export * from './to'; | export * from './to'; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | /** | ||||||
|  |  * 加载js文件 | ||||||
|  |  * @param src js文件地址 | ||||||
|  |  */ | ||||||
|  | function loadScript(src: string) { | ||||||
|  |   return new Promise<void>((resolve, reject) => { | ||||||
|  |     if (document.querySelector(`script[src="${src}"]`)) { | ||||||
|  |       // 如果已经加载过,直接 resolve
 | ||||||
|  |       return resolve(); | ||||||
|  |     } | ||||||
|  |     const script = document.createElement('script'); | ||||||
|  |     script.src = src; | ||||||
|  |     script.addEventListener('load', () => resolve()); | ||||||
|  |     script.addEventListener('error', () => | ||||||
|  |       reject(new Error(`Failed to load script: ${src}`)), | ||||||
|  |     ); | ||||||
|  |     document.head.append(script); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { loadScript }; | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/typings", |   "name": "@vben-core/typings", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/composables", |   "name": "@vben-core/composables", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/preferences", |   "name": "@vben-core/preferences", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/form-ui", |   "name": "@vben-core/form-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/layout-ui", |   "name": "@vben-core/layout-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/menu-ui", |   "name": "@vben-core/menu-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -59,6 +59,7 @@ export class ModalApi { | ||||||
|       showCancelButton: true, |       showCancelButton: true, | ||||||
|       showConfirmButton: true, |       showConfirmButton: true, | ||||||
|       title: '', |       title: '', | ||||||
|  |       animationType: 'slide', | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     this.store = new Store<ModalState>( |     this.store = new Store<ModalState>( | ||||||
|  |  | ||||||
|  | @ -5,6 +5,11 @@ import type { MaybePromise } from '@vben-core/typings'; | ||||||
| import type { ModalApi } from './modal-api'; | import type { ModalApi } from './modal-api'; | ||||||
| 
 | 
 | ||||||
| export interface ModalProps { | export interface ModalProps { | ||||||
|  |   /** | ||||||
|  |    * 动画类型 | ||||||
|  |    * @default 'slide' | ||||||
|  |    */ | ||||||
|  |   animationType?: 'scale' | 'slide'; | ||||||
|   /** |   /** | ||||||
|    * 是否要挂载到内容区域 |    * 是否要挂载到内容区域 | ||||||
|    * @default false |    * @default false | ||||||
|  |  | ||||||
|  | @ -94,12 +94,11 @@ const { | ||||||
|   submitting, |   submitting, | ||||||
|   title, |   title, | ||||||
|   titleTooltip, |   titleTooltip, | ||||||
|  |   animationType, | ||||||
|   zIndex, |   zIndex, | ||||||
| } = usePriorityValues(props, state); | } = usePriorityValues(props, state); | ||||||
| 
 | 
 | ||||||
| const shouldFullscreen = computed( | const shouldFullscreen = computed(() => fullscreen.value || isMobile.value); | ||||||
|   () => (fullscreen.value && header.value) || isMobile.value, |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| const shouldDraggable = computed( | const shouldDraggable = computed( | ||||||
|   () => draggable.value && !shouldFullscreen.value && header.value, |   () => draggable.value && !shouldFullscreen.value && header.value, | ||||||
|  | @ -244,6 +243,7 @@ function handleClosed() { | ||||||
|       :modal="modal" |       :modal="modal" | ||||||
|       :open="state?.isOpen" |       :open="state?.isOpen" | ||||||
|       :show-close="closable" |       :show-close="closable" | ||||||
|  |       :animation-type="animationType" | ||||||
|       :z-index="zIndex" |       :z-index="zIndex" | ||||||
|       :overlay-blur="overlayBlur" |       :overlay-blur="overlayBlur" | ||||||
|       close-class="top-3" |       close-class="top-3" | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/shadcn-ui", |   "name": "@vben-core/shadcn-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "#main": "./dist/index.mjs", |   "#main": "./dist/index.mjs", | ||||||
|   "#module": "./dist/index.mjs", |   "#module": "./dist/index.mjs", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import DialogOverlay from './DialogOverlay.vue'; | ||||||
| const props = withDefaults( | const props = withDefaults( | ||||||
|   defineProps< |   defineProps< | ||||||
|     DialogContentProps & { |     DialogContentProps & { | ||||||
|  |       animationType?: 'scale' | 'slide'; | ||||||
|       appendTo?: HTMLElement | string; |       appendTo?: HTMLElement | string; | ||||||
|       class?: ClassType; |       class?: ClassType; | ||||||
|       closeClass?: ClassType; |       closeClass?: ClassType; | ||||||
|  | @ -31,7 +32,12 @@ const props = withDefaults( | ||||||
|       zIndex?: number; |       zIndex?: number; | ||||||
|     } |     } | ||||||
|   >(), |   >(), | ||||||
|   { appendTo: 'body', closeDisabled: false, showClose: true }, |   { | ||||||
|  |     appendTo: 'body', | ||||||
|  |     animationType: 'slide', | ||||||
|  |     closeDisabled: false, | ||||||
|  |     showClose: true, | ||||||
|  |   }, | ||||||
| ); | ); | ||||||
| const emits = defineEmits< | const emits = defineEmits< | ||||||
|   DialogContentEmits & { close: []; closed: []; opened: [] } |   DialogContentEmits & { close: []; closed: []; opened: [] } | ||||||
|  | @ -43,6 +49,7 @@ const delegatedProps = computed(() => { | ||||||
|     modal: _modal, |     modal: _modal, | ||||||
|     open: _open, |     open: _open, | ||||||
|     showClose: __, |     showClose: __, | ||||||
|  |     animationType: ___, | ||||||
|     ...delegated |     ...delegated | ||||||
|   } = props; |   } = props; | ||||||
| 
 | 
 | ||||||
|  | @ -100,7 +107,11 @@ defineExpose({ | ||||||
|       v-bind="forwarded" |       v-bind="forwarded" | ||||||
|       :class=" |       :class=" | ||||||
|         cn( |         cn( | ||||||
|           'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl', |           'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full p-6 shadow-lg outline-none sm:rounded-xl', | ||||||
|  |           { | ||||||
|  |             'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]': | ||||||
|  |               animationType === 'slide', | ||||||
|  |           }, | ||||||
|           props.class, |           props.class, | ||||||
|         ) |         ) | ||||||
|       " |       " | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben-core/tabs-ui", |   "name": "@vben-core/tabs-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/constants", |   "name": "@vben/constants", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/access", |   "name": "@vben/access", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/common-ui", |   "name": "@vben/common-ui", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { useRoute } from 'vue-router'; | ||||||
|  | 
 | ||||||
|  | import { RiDingding } from '@vben/icons'; | ||||||
|  | import { $t } from '@vben/locales'; | ||||||
|  | 
 | ||||||
|  | import { alert, useVbenModal } from '@vben-core/popup-ui'; | ||||||
|  | import { VbenIconButton } from '@vben-core/shadcn-ui'; | ||||||
|  | import { loadScript } from '@vben-core/shared/utils'; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   clientId: string; | ||||||
|  |   corpId: string; | ||||||
|  |   // 登录回调地址 | ||||||
|  |   redirectUri?: string; | ||||||
|  |   // 是否内嵌二维码登录 | ||||||
|  |   isQrCode?: boolean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const props = defineProps<Props>(); | ||||||
|  | 
 | ||||||
|  | const route = useRoute(); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   header: false, | ||||||
|  |   footer: false, | ||||||
|  |   fullscreenButton: false, | ||||||
|  |   class: 'w-[302px] h-[302px] dingding-qrcode-login-modal', | ||||||
|  |   onOpened() { | ||||||
|  |     handleQrCodeLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const getRedirectUri = () => { | ||||||
|  |   const { redirectUri } = props; | ||||||
|  |   if (redirectUri) { | ||||||
|  |     return redirectUri; | ||||||
|  |   } | ||||||
|  |   return window.location.origin + route.fullPath; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 内嵌二维码登录 | ||||||
|  |  */ | ||||||
|  | const handleQrCodeLogin = async () => { | ||||||
|  |   const { clientId, corpId } = props; | ||||||
|  |   if (!(window as any).DTFrameLogin) { | ||||||
|  |     // 二维码登录 加载资源 | ||||||
|  |     await loadScript( | ||||||
|  |       'https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   (window as any).DTFrameLogin( | ||||||
|  |     { | ||||||
|  |       id: 'dingding_qrcode_login_element', | ||||||
|  |       width: 300, | ||||||
|  |       height: 300, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       // 注意:redirect_uri 需为完整URL,扫码后钉钉会带code跳转到这里 | ||||||
|  |       redirect_uri: encodeURIComponent(getRedirectUri()), | ||||||
|  |       client_id: clientId, | ||||||
|  |       scope: 'openid corpid', | ||||||
|  |       response_type: 'code', | ||||||
|  |       state: '1', | ||||||
|  |       prompt: 'consent', | ||||||
|  |       corpId, | ||||||
|  |     }, | ||||||
|  |     (loginResult: any) => { | ||||||
|  |       const { redirectUrl } = loginResult; | ||||||
|  |       // 这里可以直接进行重定向 | ||||||
|  |       window.location.href = redirectUrl; | ||||||
|  |     }, | ||||||
|  |     (errorMsg: string) => { | ||||||
|  |       // 这里一般需要展示登录失败的具体原因 | ||||||
|  |       alert(`Login Error: ${errorMsg}`); | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const handleLogin = () => { | ||||||
|  |   const { clientId, corpId, isQrCode } = props; | ||||||
|  |   if (isQrCode) { | ||||||
|  |     // 内嵌二维码登录 | ||||||
|  |     modalApi.open(); | ||||||
|  |   } else { | ||||||
|  |     window.location.href = `https://login.dingtalk.com/oauth2/auth?redirect_uri=${encodeURIComponent(getRedirectUri())}&response_type=code&client_id=${clientId}&scope=openid&corpid=${corpId}&prompt=consent`; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <VbenIconButton | ||||||
|  |       @click="handleLogin" | ||||||
|  |       :tooltip="$t('authentication.dingdingLogin')" | ||||||
|  |       tooltip-side="top" | ||||||
|  |     > | ||||||
|  |       <RiDingding /> | ||||||
|  |     </VbenIconButton> | ||||||
|  |     <Modal> | ||||||
|  |       <div id="dingding_qrcode_login_element"></div> | ||||||
|  |     </Modal> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | .dingding-qrcode-login-modal { | ||||||
|  |   .relative { | ||||||
|  |     padding: 0 !important; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { AntdDingTalk, MdiGithub, MdiQqchat, MdiWechat } from '@vben/icons'; | import { MdiGithub, MdiQqchat, MdiWechat, RiDingding } from '@vben/icons'; | ||||||
| import { $t } from '@vben/locales'; | import { $t } from '@vben/locales'; | ||||||
| 
 | 
 | ||||||
| import { VbenIconButton } from '@vben-core/shadcn-ui'; | import { VbenIconButton } from '@vben-core/shadcn-ui'; | ||||||
|  | @ -37,7 +37,7 @@ function handleThirdLogin(type: number) { | ||||||
|         <MdiWechat /> |         <MdiWechat /> | ||||||
|       </VbenIconButton> |       </VbenIconButton> | ||||||
|       <VbenIconButton class="mb-3" @click="handleThirdLogin(20)"> |       <VbenIconButton class="mb-3" @click="handleThirdLogin(20)"> | ||||||
|         <AntdDingTalk /> |         <RiDingding /> | ||||||
|       </VbenIconButton> |       </VbenIconButton> | ||||||
|       <VbenIconButton class="mb-3" @click="handleThirdLogin(0)"> |       <VbenIconButton class="mb-3" @click="handleThirdLogin(0)"> | ||||||
|         <MdiQqchat /> |         <MdiQqchat /> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "@vben/hooks", |   "name": "@vben/hooks", | ||||||
|   "version": "5.5.7", |   "version": "5.5.8", | ||||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", |   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||||
|   "repository": { |   "repository": { | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	 xingyu
						xingyu