Merge pull request !189 from xingyu/devpull/190/MERGE
						commit
						3f1c3a283f
					
				|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/web-antd", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://vben.pro", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { IconifyIcon } from '@vben/icons'; | |||
| import { $te } from '@vben/locales'; | ||||
| import { | ||||
|   AsyncComponents, | ||||
|   createRequiredValidation, | ||||
|   setupVbenVxeTable, | ||||
|   useVbenVxeGrid, | ||||
| } from '@vben/plugins/vxe-table'; | ||||
|  | @ -354,7 +355,7 @@ setupVbenVxeTable({ | |||
|   useVbenForm, | ||||
| }); | ||||
| 
 | ||||
| export { useVbenVxeGrid }; | ||||
| export { createRequiredValidation, useVbenVxeGrid }; | ||||
| 
 | ||||
| const [VxeTable, VxeColumn, VxeToolbar] = AsyncComponents; | ||||
| export { VxeColumn, VxeTable, VxeToolbar }; | ||||
|  |  | |||
|  | @ -40,6 +40,7 @@ export namespace BpmProcessInstanceApi { | |||
|     nodeType: BpmNodeTypeEnum; | ||||
|     startTime?: Date; | ||||
|     status: number; | ||||
|     processInstanceId?: string; | ||||
|     tasks: ApprovalTaskInfo[]; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -130,3 +130,8 @@ export const getChildrenTaskList = async (id: string) => { | |||
|     `/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( | ||||
|   () => [props.actions, props.dropDownActions], | ||||
|   () => { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| <script lang="ts" setup> | ||||
| // TODO @gjd:https://t.zsxq.com/pmNb1 AI 对话、绘图底部没对齐 | ||||
| import type { AiChatConversationApi } from '#/api/ai/chat/conversation'; | ||||
| 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 MessageLoading from './components/message/MessageLoading.vue'; | ||||
| import MessageNewConversation from './components/message/MessageNewConversation.vue'; | ||||
| 
 | ||||
| /** AI 聊天对话 列表 */ | ||||
| defineOptions({ name: 'AiChat' }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -123,6 +123,7 @@ const formData: any = ref({ | |||
|     enable: false, | ||||
|     summary: [], | ||||
|   }, | ||||
|   allowWithdrawTask: false, | ||||
| }); | ||||
| 
 | ||||
| // 流程数据 | ||||
|  | @ -178,6 +179,16 @@ async function initData() { | |||
|     // 特殊:复制场景 | ||||
|     if (route.params.type === 'copy') { | ||||
|       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.key += '_copy'; | ||||
|     } | ||||
|  |  | |||
|  | @ -69,7 +69,27 @@ const selectedUsers = ref<number[]>(); | |||
| 
 | ||||
| const rules: Record<string, Rule[]> = { | ||||
|   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' }], | ||||
|   type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }], | ||||
|   visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }], | ||||
|  |  | |||
|  | @ -217,6 +217,9 @@ function initData() { | |||
|   if (modelData.value.taskAfterTriggerSetting) { | ||||
|     taskAfterTriggerEnable.value = true; | ||||
|   } | ||||
|   if (modelData.value.allowWithdrawTask === undefined) { | ||||
|     modelData.value.allowWithdrawTask = false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 监听表单 ID 变化,加载表单数据 */ | ||||
|  | @ -267,6 +270,18 @@ defineExpose({ initData, validate }); | |||
|         </div> | ||||
|       </div> | ||||
|     </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="流程编码"> | ||||
|       <Row :gutter="8" align="middle"> | ||||
|         <Col :span="1"> | ||||
|  |  | |||
|  | @ -415,6 +415,7 @@ const handleRenameSuccess = () => { | |||
|     > | ||||
|       <div class="flex h-12 items-center"> | ||||
|         <!-- 头部:分类名 --> | ||||
|         <!-- TODO @jason:1)无法拖动排序;2)拖动后,直接请求排序,不用有个【保存】;排序模型分类,和排序分类里的模型,交互有点不同哈。 --> | ||||
|         <div class="flex items-center"> | ||||
|           <Tooltip v-if="isCategorySorting" title="拖动排序"> | ||||
|             <span | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ | |||
| import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | ||||
| import type { SystemUserApi } from '#/api/system/user'; | ||||
| 
 | ||||
| // TODO @jason:业务表单审批时,读取不到界面,参见 https://t.zsxq.com/eif2e | ||||
| 
 | ||||
| import { nextTick, onMounted, ref, shallowRef, watch } from 'vue'; | ||||
| 
 | ||||
| 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 { computed, reactive, ref, watch } from 'vue'; | ||||
| import { computed, nextTick, reactive, ref, watch } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
|  | @ -102,6 +102,7 @@ const approveSignFormRef = ref(); | |||
| const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>( | ||||
|   [], | ||||
| ); // 下一个审批节点信息 | ||||
| const nextAssigneesTimelineRef = ref(); // 下一个节点审批人时间线组件的引用 | ||||
| const approveReasonForm: any = reactive({ | ||||
|   reason: '', | ||||
|   signPicUrl: '', | ||||
|  | @ -278,6 +279,10 @@ function closePopover(type: string, formRef: any | FormInstance) { | |||
|   } | ||||
|   if (popOverVisible.value[type]) popOverVisible.value[type] = false; | ||||
|   nextAssigneesActivityNode.value = []; | ||||
|   // 清理 Timeline 组件中的自定义审批人数据 | ||||
|   if (nextAssigneesTimelineRef.value) { | ||||
|     nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({}); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */ | ||||
|  | @ -290,6 +295,7 @@ async function initNextAssigneesFormField() { | |||
|     processVariablesStr: JSON.stringify(variables), | ||||
|   }); | ||||
|   if (data && data.length > 0) { | ||||
|     const customApproveUsersData: Record<string, any[]> = {}; // 用于收集需要设置到 Timeline 组件的自定义审批人数据 | ||||
|     data.forEach((node: BpmProcessInstanceApi.ApprovalNodeInfo) => { | ||||
|       if ( | ||||
|         // 情况一:当前节点没有审批人,并且是发起人自选 | ||||
|  | @ -302,7 +308,23 @@ async function initNextAssigneesFormField() { | |||
|       ) { | ||||
|         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); | ||||
|       popOverVisible.value.approve = false; | ||||
|       nextAssigneesActivityNode.value = []; | ||||
|       // 清理 Timeline 组件中的自定义审批人数据 | ||||
|       if (nextAssigneesTimelineRef.value) { | ||||
|         nextAssigneesTimelineRef.value.batchSetCustomApproveUsers({}); | ||||
|       } | ||||
|       message.success('审批通过成功'); | ||||
|     } else { | ||||
|       // 审批不通过数据 | ||||
|  | @ -733,9 +759,10 @@ defineExpose({ loadTodoTask }); | |||
|               > | ||||
|                 <div class="-mb-8 -mt-3.5 ml-2.5"> | ||||
|                   <ProcessInstanceTimeline | ||||
|                     ref="nextAssigneesTimelineRef" | ||||
|                     :activity-nodes="nextAssigneesActivityNode" | ||||
|                     :show-status-icon="false" | ||||
|                     :use-next-assignees="true" | ||||
|                     :enable-approve-user-select="true" | ||||
|                     @select-user-confirm="selectNextAssigneesConfirm" | ||||
|                   /> | ||||
|                 </div> | ||||
|  |  | |||
|  | @ -23,12 +23,12 @@ defineOptions({ name: 'BpmProcessInstanceTimeline' }); | |||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     activityNodes: BpmProcessInstanceApi.ApprovalNodeInfo[]; // 审批节点信息 | ||||
|     enableApproveUserSelect?: boolean; // 是否开启审批人自选功能 | ||||
|     showStatusIcon?: boolean; // 是否显示头像右下角状态图标 | ||||
|     useNextAssignees?: boolean; //  是否用于下一个节点审批人选择 | ||||
|   }>(), | ||||
|   { | ||||
|     showStatusIcon: true, // 默认值为 true | ||||
|     useNextAssignees: false, // 默认值为 false | ||||
|     enableApproveUserSelect: false, // 默认值为 false | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
|  | @ -181,6 +181,9 @@ function handleUserSelectConfirm(userList: any[]) { | |||
| 
 | ||||
| /** 跳转子流程 */ | ||||
| function handleChildProcess(activity: any) { | ||||
|   if (!activity.processInstanceId) { | ||||
|     return; | ||||
|   } | ||||
|   push({ | ||||
|     name: 'BpmProcessInstanceDetail', | ||||
|     query: { | ||||
|  | @ -195,12 +198,12 @@ function shouldShowCustomUserSelect( | |||
| ) { | ||||
|   return ( | ||||
|     isEmpty(activity.tasks) && | ||||
|     isEmpty(activity.candidateUsers) && | ||||
|     (BpmCandidateStrategyEnum.START_USER_SELECT === | ||||
|       activity.candidateStrategy || | ||||
|       (BpmCandidateStrategyEnum.APPROVE_USER_SELECT === | ||||
|         activity.candidateStrategy && | ||||
|         props.useNextAssignees)) | ||||
|     ((BpmCandidateStrategyEnum.START_USER_SELECT === | ||||
|       activity.candidateStrategy && | ||||
|       isEmpty(activity.candidateUsers)) || | ||||
|       (props.enableApproveUserSelect && | ||||
|         BpmCandidateStrategyEnum.APPROVE_USER_SELECT === | ||||
|           activity.candidateStrategy)) | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
|  | @ -223,6 +226,21 @@ function handleUserSelectClosed() { | |||
| function handleUserSelectCancel() { | ||||
|   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> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -284,6 +302,7 @@ function handleUserSelectCancel() { | |||
|               ghost | ||||
|               size="small" | ||||
|               @click="handleChildProcess(activity)" | ||||
|               :disabled="!activity.processInstanceId" | ||||
|             > | ||||
|               查看子流程 | ||||
|             </Button> | ||||
|  |  | |||
|  | @ -4,8 +4,10 @@ import type { BpmTaskApi } from '#/api/bpm/task'; | |||
| 
 | ||||
| import { DocAlert, Page } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| 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 { 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: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|  | @ -52,7 +62,7 @@ const [Grid] = useVbenVxeGrid({ | |||
|     cellConfig: { | ||||
|       height: 64, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<BpmTaskApi.Task>, | ||||
|   } as VxeTableGridOptions<BpmTaskApi.TaskManager>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  | @ -75,6 +85,13 @@ const [Grid] = useVbenVxeGrid({ | |||
|       <template #actions="{ row }"> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: '撤回', | ||||
|               type: 'link', | ||||
|               icon: ACTION_ICON.EDIT, | ||||
|               color: 'warning', | ||||
|               onClick: handleWithdraw.bind(null, row), | ||||
|             }, | ||||
|             { | ||||
|               label: '历史', | ||||
|               type: 'link', | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ function handleUpdateValue(row: any) { | |||
|   } else { | ||||
|     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> | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -12,23 +26,28 @@ import { Button } from 'ant-design-vue'; | |||
|         url="https://doc.iocoder.cn/erp/build/" | ||||
|       /> | ||||
|     </template> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/home/index.vue" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/home/index.vue | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
| 
 | ||||
|     <Spin :spinning="loading"> | ||||
|       <div class="flex flex-col gap-4"> | ||||
|         <!-- 销售/采购的全局统计 --> | ||||
|         <SummaryCard /> | ||||
| 
 | ||||
|         <!-- 销售/采购的时段统计 --> | ||||
|         <Row :gutter="16"> | ||||
|           <!-- 销售统计 --> | ||||
|           <Col :md="12" :sm="12" :xs="24"> | ||||
|             <TimeSummaryChart ref="saleChartRef" title="销售统计" type="sale" /> | ||||
|           </Col> | ||||
|           <!-- 采购统计 --> | ||||
|           <Col :md="12" :sm="12" :xs="24"> | ||||
|             <TimeSummaryChart | ||||
|               ref="purchaseChartRef" | ||||
|               title="采购统计" | ||||
|               type="purchase" | ||||
|             /> | ||||
|           </Col> | ||||
|         </Row> | ||||
|       </div> | ||||
|     </Spin> | ||||
|   </Page> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【采购】采购订单、入库、退货" | ||||
|         url="https://doc.iocoder.cn/erp/purchase/" | ||||
|       /> | ||||
|     </template> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/order/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/order/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
| 
 | ||||
|     <FormModal @success="onRefresh" /> | ||||
| 
 | ||||
|     <Grid table-title="采购订单列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.create', ['采购订单']), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['erp:purchase-order:create'], | ||||
|               onClick: handleCreate, | ||||
|             }, | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               type: 'primary', | ||||
|               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> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【采购】采购订单、入库、退货" | ||||
|         url="https://doc.iocoder.cn/erp/purchase/" | ||||
|       /> | ||||
|     </template> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/supplier/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/purchase/supplier/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
| 
 | ||||
|     <FormModal @success="onRefresh" /> | ||||
|     <Grid table-title="供应商列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.create', ['供应商']), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['erp:supplier:create'], | ||||
|               onClick: handleCreate, | ||||
|             }, | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.DOWNLOAD, | ||||
|               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> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【库存】其它入库、其它出库" | ||||
|         url="https://doc.iocoder.cn/erp/stock-in-out/" | ||||
|       /> | ||||
|     </template> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/in/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/in/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
| 
 | ||||
|     <Grid table-title="其它入库单列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.create', ['其它入库']), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['erp:stock-in:create'], | ||||
|               onClick: () => openForm('create'), | ||||
|             }, | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               type: 'default', | ||||
|               icon: ACTION_ICON.DOWNLOAD, | ||||
|               auth: ['erp:stock-in:export'], | ||||
|               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> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="【库存】产品库存、库存明细" | ||||
|       url="https://doc.iocoder.cn/erp/stock/" | ||||
|     /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/record/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/record/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【库存】产品库存、库存明细" | ||||
|         url="https://doc.iocoder.cn/erp/stock/" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|     <Grid table-title="产品库存明细列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.DOWNLOAD, | ||||
|               auth: ['erp:stock-record:export'], | ||||
|               onClick: handleExport, | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="【库存】产品库存、库存明细" | ||||
|       url="https://doc.iocoder.cn/erp/stock/" | ||||
|     /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/stock/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/stock/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【库存】产品库存、库存明细" | ||||
|         url="https://doc.iocoder.cn/erp/stock/" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|     <Grid table-title="产品库存列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.DOWNLOAD, | ||||
|               auth: ['erp:stock:export'], | ||||
|               onClick: handleExport, | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </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> | ||||
| 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> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|     <DocAlert | ||||
|       title="【库存】产品库存、库存明细" | ||||
|       url="https://doc.iocoder.cn/erp/stock/" | ||||
|     /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/warehouse/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/stock/warehouse/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【库存】产品库存、库存明细" | ||||
|         url="https://doc.iocoder.cn/erp/stock/" | ||||
|       /> | ||||
|     </template> | ||||
| 
 | ||||
|     <FormModal @success="onRefresh" /> | ||||
|     <Grid table-title="仓库列表"> | ||||
|       <template #toolbar-tools> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('ui.actionTitle.create', ['仓库']), | ||||
|               type: 'primary', | ||||
|               icon: ACTION_ICON.ADD, | ||||
|               auth: ['erp:warehouse:create'], | ||||
|               onClick: handleCreate, | ||||
|             }, | ||||
|             { | ||||
|               label: $t('ui.actionTitle.export'), | ||||
|               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> | ||||
| </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", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://vben.pro", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import { requestClient } from '#/api/request'; | |||
| 
 | ||||
| const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); | ||||
| const accessStore = useAccessStore(); | ||||
| 
 | ||||
| export namespace AiChatMessageApi { | ||||
|   export interface ChatMessage { | ||||
|     id: number; // 编号
 | ||||
|  | @ -82,6 +83,7 @@ export function deleteByConversationId(conversationId: number) { | |||
|     `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| // 获得消息分页
 | ||||
| export function getChatMessagePage(params: any) { | ||||
|   return requestClient.get<PageResult<AiChatMessageApi.ChatMessage>>( | ||||
|  | @ -89,6 +91,7 @@ export function getChatMessagePage(params: any) { | |||
|     { params }, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| // 管理员删除消息
 | ||||
| export function deleteChatMessageByAdmin(id: number) { | ||||
|   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) { | ||||
|   return requestClient.get(`/ai/knowledge/document/get?id=${id}`); | ||||
| } | ||||
| 
 | ||||
| // 新增知识库文档(单个)
 | ||||
| export function createKnowledge(data: any) { | ||||
|   return requestClient.post('/ai/knowledge/document/create', data); | ||||
| } | ||||
| 
 | ||||
| // 新增知识库文档(多个)
 | ||||
| export function createKnowledgeDocumentList(data: any) { | ||||
|   return requestClient.post('/ai/knowledge/document/create-list', data); | ||||
|  | @ -44,6 +46,7 @@ export function updateKnowledgeDocument(data: any) { | |||
| export function updateKnowledgeDocumentStatus(data: any) { | ||||
|   return requestClient.put('/ai/knowledge/document/update-status', data); | ||||
| } | ||||
| 
 | ||||
| // 删除知识库文档
 | ||||
| export function deleteKnowledgeDocument(id: number) { | ||||
|   return requestClient.delete(`/ai/knowledge/document/delete?id=${id}`); | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ export function getKnowledgeSegment(id: number) { | |||
|     `/ai/knowledge/segment/get?id=${id}`, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| // 新增知识库分段
 | ||||
| export function createKnowledgeSegment( | ||||
|   data: AiKnowledgeSegmentApi.KnowledgeSegment, | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ import type { PageParam, PageResult } from '@vben/request'; | |||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| // TODO @xingyu:貌似模块不对
 | ||||
| 
 | ||||
| export namespace ProductUnitApi { | ||||
|   /** 产品单位信息 */ | ||||
|   export interface ProductUnit { | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ import type { PageParam, PageResult } from '@vben/request'; | |||
| 
 | ||||
| import { requestClient } from '#/api/request'; | ||||
| 
 | ||||
| // TODO @xingyu:貌似模块不对
 | ||||
| 
 | ||||
| export namespace ProductUnitGroupApi { | ||||
|   /** 产品单位组信息 */ | ||||
|   export interface ProductUnitGroup { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/web-naive", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://vben.pro", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/docs", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "build": "vitepress build", | ||||
|  |  | |||
|  | @ -56,6 +56,15 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda | |||
| 
 | ||||
| <DemoPreview dir="demos/vben-modal/shared-data" /> | ||||
| 
 | ||||
| ## 动画类型 | ||||
| 
 | ||||
| 通过 `animationType` 属性可以控制弹窗的动画效果: | ||||
| 
 | ||||
| - `slide`(默认):从顶部向下滑动进入/退出 | ||||
| - `scale`:缩放淡入/淡出效果 | ||||
| 
 | ||||
| <DemoPreview dir="demos/vben-modal/animation-type" /> | ||||
| 
 | ||||
| ::: info 注意 | ||||
| 
 | ||||
| - `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 | ||||
|  | @ -112,6 +121,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
| | bordered | 是否显示border | `boolean` | `false` | | ||||
| | zIndex | 弹窗的ZIndex层级 | `number` | `1000` | | ||||
| | overlayBlur | 遮罩模糊度 | `number` | - | | ||||
| | animationType | 动画类型 | `'slide' \| 'scale'` | `'slide'` | | ||||
| | submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` | | ||||
| 
 | ||||
| ::: 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", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/stylelint-config", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/node-utils", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/tailwind-config", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/tsconfig", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/vite-config", | ||||
|   "version": "5.5.6", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "vben-admin-monorepo", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "private": true, | ||||
|   "keywords": [ | ||||
|     "monorepo", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/design", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/icons", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/shared", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "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 './merge'; | ||||
| export * from './nprogress'; | ||||
| export * from './resources'; | ||||
| export * from './state-handler'; | ||||
| export * from './time'; | ||||
| 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", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/composables", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/preferences", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/form-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/layout-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/menu-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -59,6 +59,7 @@ export class ModalApi { | |||
|       showCancelButton: true, | ||||
|       showConfirmButton: true, | ||||
|       title: '', | ||||
|       animationType: 'slide', | ||||
|     }; | ||||
| 
 | ||||
|     this.store = new Store<ModalState>( | ||||
|  |  | |||
|  | @ -5,6 +5,11 @@ import type { MaybePromise } from '@vben-core/typings'; | |||
| import type { ModalApi } from './modal-api'; | ||||
| 
 | ||||
| export interface ModalProps { | ||||
|   /** | ||||
|    * 动画类型 | ||||
|    * @default 'slide' | ||||
|    */ | ||||
|   animationType?: 'scale' | 'slide'; | ||||
|   /** | ||||
|    * 是否要挂载到内容区域 | ||||
|    * @default false | ||||
|  |  | |||
|  | @ -94,12 +94,11 @@ const { | |||
|   submitting, | ||||
|   title, | ||||
|   titleTooltip, | ||||
|   animationType, | ||||
|   zIndex, | ||||
| } = usePriorityValues(props, state); | ||||
| 
 | ||||
| const shouldFullscreen = computed( | ||||
|   () => (fullscreen.value && header.value) || isMobile.value, | ||||
| ); | ||||
| const shouldFullscreen = computed(() => fullscreen.value || isMobile.value); | ||||
| 
 | ||||
| const shouldDraggable = computed( | ||||
|   () => draggable.value && !shouldFullscreen.value && header.value, | ||||
|  | @ -244,6 +243,7 @@ function handleClosed() { | |||
|       :modal="modal" | ||||
|       :open="state?.isOpen" | ||||
|       :show-close="closable" | ||||
|       :animation-type="animationType" | ||||
|       :z-index="zIndex" | ||||
|       :overlay-blur="overlayBlur" | ||||
|       close-class="top-3" | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/shadcn-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "#main": "./dist/index.mjs", | ||||
|   "#module": "./dist/index.mjs", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import DialogOverlay from './DialogOverlay.vue'; | |||
| const props = withDefaults( | ||||
|   defineProps< | ||||
|     DialogContentProps & { | ||||
|       animationType?: 'scale' | 'slide'; | ||||
|       appendTo?: HTMLElement | string; | ||||
|       class?: ClassType; | ||||
|       closeClass?: ClassType; | ||||
|  | @ -31,7 +32,12 @@ const props = withDefaults( | |||
|       zIndex?: number; | ||||
|     } | ||||
|   >(), | ||||
|   { appendTo: 'body', closeDisabled: false, showClose: true }, | ||||
|   { | ||||
|     appendTo: 'body', | ||||
|     animationType: 'slide', | ||||
|     closeDisabled: false, | ||||
|     showClose: true, | ||||
|   }, | ||||
| ); | ||||
| const emits = defineEmits< | ||||
|   DialogContentEmits & { close: []; closed: []; opened: [] } | ||||
|  | @ -43,6 +49,7 @@ const delegatedProps = computed(() => { | |||
|     modal: _modal, | ||||
|     open: _open, | ||||
|     showClose: __, | ||||
|     animationType: ___, | ||||
|     ...delegated | ||||
|   } = props; | ||||
| 
 | ||||
|  | @ -100,7 +107,11 @@ defineExpose({ | |||
|       v-bind="forwarded" | ||||
|       :class=" | ||||
|         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, | ||||
|         ) | ||||
|       " | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben-core/tabs-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/constants", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/access", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/common-ui", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "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"> | ||||
| import { AntdDingTalk, MdiGithub, MdiQqchat, MdiWechat } from '@vben/icons'; | ||||
| import { MdiGithub, MdiQqchat, MdiWechat, RiDingding } from '@vben/icons'; | ||||
| import { $t } from '@vben/locales'; | ||||
| 
 | ||||
| import { VbenIconButton } from '@vben-core/shadcn-ui'; | ||||
|  | @ -37,7 +37,7 @@ function handleThirdLogin(type: number) { | |||
|         <MdiWechat /> | ||||
|       </VbenIconButton> | ||||
|       <VbenIconButton class="mb-3" @click="handleThirdLogin(20)"> | ||||
|         <AntdDingTalk /> | ||||
|         <RiDingding /> | ||||
|       </VbenIconButton> | ||||
|       <VbenIconButton class="mb-3" @click="handleThirdLogin(0)"> | ||||
|         <MdiQqchat /> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "@vben/hooks", | ||||
|   "version": "5.5.7", | ||||
|   "version": "5.5.8", | ||||
|   "homepage": "https://github.com/vbenjs/vue-vben-admin", | ||||
|   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||||
|   "repository": { | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	 xingyu
						xingyu