Merge branch 'wms' of https://gitee.com/yudaocode/yudao-ui-admin-vue3
commit
9b8b1322b7
|
|
@ -0,0 +1,77 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 首页统计查询参数
|
||||
export interface WmsHomeStatisticsReqVO {
|
||||
warehouseId?: number
|
||||
goodsLimit?: number
|
||||
warehouseLimit?: number
|
||||
}
|
||||
|
||||
// WMS 首页单据状态统计 VO
|
||||
export interface WmsHomeOrderStatusVO {
|
||||
status: number
|
||||
count: number
|
||||
}
|
||||
|
||||
// WMS 首页单据汇总统计 VO
|
||||
export interface WmsHomeOrderSummaryVO {
|
||||
type: number
|
||||
total: number
|
||||
statuses: WmsHomeOrderStatusVO[]
|
||||
}
|
||||
|
||||
// WMS 首页单据趋势 VO
|
||||
export interface WmsHomeOrderTrendVO {
|
||||
time: string | number
|
||||
receiptCount: number
|
||||
shipmentCount: number
|
||||
movementCount: number
|
||||
checkCount: number
|
||||
}
|
||||
|
||||
// WMS 首页商品库存排行 VO
|
||||
export interface WmsHomeInventoryItemRankVO {
|
||||
id: number
|
||||
name: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
// WMS 首页仓库库存排行 VO
|
||||
export interface WmsHomeInventoryWarehouseRankVO {
|
||||
id: number
|
||||
name: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
// WMS 首页库存汇总统计 VO
|
||||
export interface WmsHomeInventorySummaryVO {
|
||||
totalQuantity: number
|
||||
goodsShareList: WmsHomeInventoryItemRankVO[]
|
||||
warehouseDistributionList: WmsHomeInventoryWarehouseRankVO[]
|
||||
}
|
||||
|
||||
// WMS 首页统计 API
|
||||
export const WmsHomeStatisticsApi = {
|
||||
// 获得首页单据汇总统计
|
||||
getOrderSummary: async (params?: WmsHomeStatisticsReqVO): Promise<WmsHomeOrderSummaryVO[]> => {
|
||||
return await request.get({ url: `/wms/home-statistics/order-summary`, params })
|
||||
},
|
||||
|
||||
// 获得首页单据趋势
|
||||
getOrderTrend: async (
|
||||
days?: number,
|
||||
params?: WmsHomeStatisticsReqVO
|
||||
): Promise<WmsHomeOrderTrendVO[]> => {
|
||||
return await request.get({
|
||||
url: `/wms/home-statistics/order-trend`,
|
||||
params: { ...params, days }
|
||||
})
|
||||
},
|
||||
|
||||
// 获得首页库存汇总统计
|
||||
getInventorySummary: async (
|
||||
params?: WmsHomeStatisticsReqVO
|
||||
): Promise<WmsHomeInventorySummaryVO> => {
|
||||
return await request.get({ url: `/wms/home-statistics/inventory-summary`, params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 库存记录 VO
|
||||
export interface InventoryHistoryVO {
|
||||
id?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
quantity?: number
|
||||
beforeQuantity?: number
|
||||
afterQuantity?: number
|
||||
price?: number
|
||||
totalPrice?: number
|
||||
remark?: string
|
||||
orderId?: number
|
||||
orderNo?: string
|
||||
orderType?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 库存记录 API
|
||||
export const InventoryHistoryApi = {
|
||||
// 查询库存记录分页
|
||||
getInventoryHistoryPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/inventory-history/page', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 库存统计 VO
|
||||
export interface InventoryVO {
|
||||
id?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
quantity?: number
|
||||
remark?: string
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 库存统计列表 Request VO
|
||||
export interface InventoryListReqVO {
|
||||
warehouseId: number
|
||||
}
|
||||
|
||||
// WMS 库存统计 API
|
||||
export const InventoryApi = {
|
||||
// 查询库存统计分页
|
||||
getInventoryPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/inventory/page', params })
|
||||
},
|
||||
|
||||
// 查询库存统计列表
|
||||
getInventoryList: async (params: InventoryListReqVO) => {
|
||||
return await request.get({ url: '/wms/inventory/list', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 商品品牌 VO
|
||||
export interface ItemBrandVO {
|
||||
id?: number
|
||||
code?: string
|
||||
name?: string
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 商品品牌 API
|
||||
export const ItemBrandApi = {
|
||||
// 查询商品品牌分页
|
||||
getItemBrandPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/item-brand/page', params })
|
||||
},
|
||||
|
||||
// 查询商品品牌精简列表
|
||||
getItemBrandSimpleList: async () => {
|
||||
return await request.get({ url: '/wms/item-brand/simple-list' })
|
||||
},
|
||||
|
||||
// 查询商品品牌详情
|
||||
getItemBrand: async (id: number) => {
|
||||
return await request.get({ url: '/wms/item-brand/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增商品品牌
|
||||
createItemBrand: async (data: ItemBrandVO) => {
|
||||
return await request.post({ url: '/wms/item-brand/create', data })
|
||||
},
|
||||
|
||||
// 修改商品品牌
|
||||
updateItemBrand: async (data: ItemBrandVO) => {
|
||||
return await request.put({ url: '/wms/item-brand/update', data })
|
||||
},
|
||||
|
||||
// 删除商品品牌
|
||||
deleteItemBrand: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/item-brand/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出商品品牌
|
||||
exportItemBrand: async (params) => {
|
||||
return await request.download({ url: '/wms/item-brand/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 商品分类 VO
|
||||
export interface ItemCategoryVO {
|
||||
id?: number
|
||||
parentId?: number
|
||||
code?: string
|
||||
name?: string
|
||||
sort?: number
|
||||
status?: number
|
||||
createTime?: Date
|
||||
children?: ItemCategoryVO[]
|
||||
}
|
||||
|
||||
// WMS 商品分类 API
|
||||
export const ItemCategoryApi = {
|
||||
// 查询商品分类列表
|
||||
getItemCategoryList: async (params?: any) => {
|
||||
return await request.get({ url: '/wms/item-category/list', params })
|
||||
},
|
||||
|
||||
// 查询商品分类精简列表
|
||||
getItemCategorySimpleList: async () => {
|
||||
return await request.get({ url: '/wms/item-category/simple-list' })
|
||||
},
|
||||
|
||||
// 查询商品分类详情
|
||||
getItemCategory: async (id: number) => {
|
||||
return await request.get({ url: '/wms/item-category/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增商品分类
|
||||
createItemCategory: async (data: ItemCategoryVO) => {
|
||||
return await request.post({ url: '/wms/item-category/create', data })
|
||||
},
|
||||
|
||||
// 修改商品分类
|
||||
updateItemCategory: async (data: ItemCategoryVO) => {
|
||||
return await request.put({ url: '/wms/item-category/update', data })
|
||||
},
|
||||
|
||||
// 删除商品分类
|
||||
deleteItemCategory: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/item-category/delete?id=' + id })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import request from '@/config/axios'
|
||||
import { ItemSkuVO } from './sku'
|
||||
|
||||
// WMS 商品 VO
|
||||
export interface ItemVO {
|
||||
id?: number
|
||||
code?: string
|
||||
name?: string
|
||||
categoryId?: number
|
||||
categoryName?: string
|
||||
unit?: string
|
||||
brandId?: number
|
||||
brandName?: string
|
||||
remark?: string
|
||||
skus?: ItemSkuVO[]
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 商品 API
|
||||
export const ItemApi = {
|
||||
// 查询商品分页
|
||||
getItemPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/item/page', params })
|
||||
},
|
||||
|
||||
// 查询商品精简列表
|
||||
getItemSimpleList: async (params?: any) => {
|
||||
return await request.get({ url: '/wms/item/simple-list', params })
|
||||
},
|
||||
|
||||
// 查询商品详情
|
||||
getItem: async (id: number) => {
|
||||
return await request.get({ url: '/wms/item/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增商品
|
||||
createItem: async (data: ItemVO) => {
|
||||
return await request.post({ url: '/wms/item/create', data })
|
||||
},
|
||||
|
||||
// 修改商品
|
||||
updateItem: async (data: ItemVO) => {
|
||||
return await request.put({ url: '/wms/item/update', data })
|
||||
},
|
||||
|
||||
// 删除商品
|
||||
deleteItem: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/item/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出商品
|
||||
exportItem: async (params: any) => {
|
||||
return await request.download({ url: '/wms/item/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 商品 SKU VO
|
||||
export interface ItemSkuVO {
|
||||
id?: number
|
||||
name?: string
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
categoryId?: number
|
||||
categoryName?: string
|
||||
unit?: string
|
||||
brandId?: number
|
||||
brandName?: string
|
||||
barCode?: string
|
||||
code?: string
|
||||
length?: number
|
||||
width?: number
|
||||
height?: number
|
||||
grossWeight?: number
|
||||
netWeight?: number
|
||||
costPrice?: number
|
||||
sellingPrice?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 商品 SKU API
|
||||
export const ItemSkuApi = {
|
||||
// 按 SKU 维度分页(支持商品 / 品牌 / 分类多表联查筛选)
|
||||
getItemSkuPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/item-sku/page', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 往来企业 VO
|
||||
export interface MerchantVO {
|
||||
id?: number
|
||||
code?: string
|
||||
name?: string
|
||||
type?: number
|
||||
level?: string
|
||||
bankName?: string
|
||||
bankAccount?: string
|
||||
address?: string
|
||||
mobile?: string
|
||||
telephone?: string
|
||||
contact?: string
|
||||
email?: string
|
||||
remark?: string
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
export interface MerchantSimpleListReqVO {
|
||||
types?: number[]
|
||||
}
|
||||
|
||||
// WMS 往来企业 API
|
||||
export const MerchantApi = {
|
||||
// 查询往来企业分页
|
||||
getMerchantPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/merchant/page', params })
|
||||
},
|
||||
|
||||
// 查询往来企业精简列表
|
||||
getMerchantSimpleList: async (params?: MerchantSimpleListReqVO) => {
|
||||
return await request.get({ url: '/wms/merchant/simple-list', params })
|
||||
},
|
||||
|
||||
// 查询往来企业详情
|
||||
getMerchant: async (id: number) => {
|
||||
return await request.get({ url: '/wms/merchant/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增往来企业
|
||||
createMerchant: async (data: MerchantVO) => {
|
||||
return await request.post({ url: '/wms/merchant/create', data })
|
||||
},
|
||||
|
||||
// 修改往来企业
|
||||
updateMerchant: async (data: MerchantVO) => {
|
||||
return await request.put({ url: '/wms/merchant/update', data })
|
||||
},
|
||||
|
||||
// 删除往来企业
|
||||
deleteMerchant: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/merchant/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出往来企业
|
||||
exportMerchant: async (params: any) => {
|
||||
return await request.download({ url: '/wms/merchant/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// WMS 仓库 VO
|
||||
export interface WarehouseVO {
|
||||
id?: number
|
||||
code?: string
|
||||
name?: string
|
||||
remark?: string
|
||||
sort?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
// WMS 仓库 API
|
||||
export const WarehouseApi = {
|
||||
// 查询仓库分页
|
||||
getWarehousePage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/warehouse/page', params })
|
||||
},
|
||||
|
||||
// 查询仓库精简列表
|
||||
getWarehouseSimpleList: async () => {
|
||||
return await request.get({ url: '/wms/warehouse/simple-list' })
|
||||
},
|
||||
|
||||
// 查询仓库详情
|
||||
getWarehouse: async (id: number) => {
|
||||
return await request.get({ url: '/wms/warehouse/get?id=' + id })
|
||||
},
|
||||
|
||||
// 新增仓库
|
||||
createWarehouse: async (data: WarehouseVO) => {
|
||||
return await request.post({ url: '/wms/warehouse/create', data })
|
||||
},
|
||||
|
||||
// 修改仓库
|
||||
updateWarehouse: async (data: WarehouseVO) => {
|
||||
return await request.put({ url: '/wms/warehouse/update', data })
|
||||
},
|
||||
|
||||
// 删除仓库
|
||||
deleteWarehouse: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/warehouse/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出仓库
|
||||
exportWarehouse: async (params) => {
|
||||
return await request.download({ url: '/wms/warehouse/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// WMS 盘库单明细 VO
|
||||
export interface CheckOrderDetailVO {
|
||||
id?: number
|
||||
orderId?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
inventoryId?: number
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
receiptTime?: Date
|
||||
quantity?: number
|
||||
checkQuantity?: number
|
||||
availableQuantity?: number
|
||||
price?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import request from '@/config/axios'
|
||||
import { CheckOrderDetailVO } from './detail'
|
||||
|
||||
// WMS 盘库单 VO
|
||||
export interface CheckOrderVO {
|
||||
id?: number
|
||||
no?: string
|
||||
orderTime?: string
|
||||
status?: number
|
||||
remark?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
totalQuantity?: number
|
||||
totalPrice?: number
|
||||
actualPrice?: number
|
||||
details?: CheckOrderDetailVO[]
|
||||
createTime?: Date
|
||||
creator?: string
|
||||
creatorName?: string
|
||||
updateTime?: Date
|
||||
updater?: string
|
||||
updaterName?: string
|
||||
}
|
||||
|
||||
// WMS 盘库单 API
|
||||
export const CheckOrderApi = {
|
||||
// 查询盘库单分页
|
||||
getCheckOrderPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/check-order/page', params })
|
||||
},
|
||||
|
||||
// 查询盘库单详情
|
||||
getCheckOrder: async (id: number) => {
|
||||
return await request.get({ url: '/wms/check-order/get?id=' + id })
|
||||
},
|
||||
|
||||
// 查询盘库单明细
|
||||
getCheckOrderDetailListByOrderId: async (orderId: number) => {
|
||||
return await request.get({
|
||||
url: '/wms/check-order-detail/list-by-order-id?orderId=' + orderId
|
||||
})
|
||||
},
|
||||
|
||||
// 新增盘库单
|
||||
createCheckOrder: async (data: CheckOrderVO) => {
|
||||
return await request.post({ url: '/wms/check-order/create', data })
|
||||
},
|
||||
|
||||
// 修改盘库单
|
||||
updateCheckOrder: async (data: CheckOrderVO) => {
|
||||
return await request.put({ url: '/wms/check-order/update', data })
|
||||
},
|
||||
|
||||
// 完成盘库
|
||||
completeCheckOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/check-order/complete?id=' + id })
|
||||
},
|
||||
|
||||
// 作废盘库单
|
||||
cancelCheckOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/check-order/cancel?id=' + id })
|
||||
},
|
||||
|
||||
// 删除盘库单
|
||||
deleteCheckOrder: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/check-order/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出盘库单
|
||||
exportCheckOrder: async (params: any) => {
|
||||
return await request.download({ url: '/wms/check-order/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// WMS 移库单明细 VO
|
||||
export interface MovementOrderDetailVO {
|
||||
id?: number
|
||||
orderId?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
sourceWarehouseId?: number
|
||||
sourceWarehouseName?: string
|
||||
targetWarehouseId?: number
|
||||
targetWarehouseName?: string
|
||||
quantity?: number
|
||||
availableQuantity?: number
|
||||
price?: number
|
||||
totalPrice?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import request from '@/config/axios'
|
||||
import { MovementOrderDetailVO } from './detail'
|
||||
|
||||
// WMS 移库单 VO
|
||||
export interface MovementOrderVO {
|
||||
id?: number
|
||||
no?: string
|
||||
orderTime?: string
|
||||
status?: number
|
||||
remark?: string
|
||||
sourceWarehouseId?: number
|
||||
sourceWarehouseName?: string
|
||||
targetWarehouseId?: number
|
||||
targetWarehouseName?: string
|
||||
totalQuantity?: number
|
||||
totalPrice?: number
|
||||
details?: MovementOrderDetailVO[]
|
||||
createTime?: Date
|
||||
creator?: string
|
||||
creatorName?: string
|
||||
updateTime?: Date
|
||||
updater?: string
|
||||
updaterName?: string
|
||||
}
|
||||
|
||||
// WMS 移库单 API
|
||||
export const MovementOrderApi = {
|
||||
// 查询移库单分页
|
||||
getMovementOrderPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/movement-order/page', params })
|
||||
},
|
||||
|
||||
// 查询移库单详情
|
||||
getMovementOrder: async (id: number) => {
|
||||
return await request.get({ url: '/wms/movement-order/get?id=' + id })
|
||||
},
|
||||
|
||||
// 查询移库单明细
|
||||
getMovementOrderDetailListByOrderId: async (orderId: number) => {
|
||||
return await request.get({
|
||||
url: '/wms/movement-order-detail/list-by-order-id?orderId=' + orderId
|
||||
})
|
||||
},
|
||||
|
||||
// 新增移库单
|
||||
createMovementOrder: async (data: MovementOrderVO) => {
|
||||
return await request.post({ url: '/wms/movement-order/create', data })
|
||||
},
|
||||
|
||||
// 修改移库单
|
||||
updateMovementOrder: async (data: MovementOrderVO) => {
|
||||
return await request.put({ url: '/wms/movement-order/update', data })
|
||||
},
|
||||
|
||||
// 完成移库
|
||||
completeMovementOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/movement-order/complete?id=' + id })
|
||||
},
|
||||
|
||||
// 作废移库单
|
||||
cancelMovementOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/movement-order/cancel?id=' + id })
|
||||
},
|
||||
|
||||
// 删除移库单
|
||||
deleteMovementOrder: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/movement-order/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出移库单
|
||||
exportMovementOrder: async (params: any) => {
|
||||
return await request.download({ url: '/wms/movement-order/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// WMS 入库单明细 VO
|
||||
export interface ReceiptOrderDetailVO {
|
||||
id?: number
|
||||
orderId?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
quantity?: number
|
||||
price?: number
|
||||
totalPrice?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import request from '@/config/axios'
|
||||
import { ReceiptOrderDetailVO } from './detail'
|
||||
|
||||
// WMS 入库单 VO
|
||||
export interface ReceiptOrderVO {
|
||||
id?: number
|
||||
no?: string
|
||||
type?: number
|
||||
orderTime?: string
|
||||
status?: number
|
||||
bizOrderNo?: string
|
||||
merchantId?: number
|
||||
merchantName?: string
|
||||
remark?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
totalQuantity?: number
|
||||
totalPrice?: number
|
||||
details?: ReceiptOrderDetailVO[]
|
||||
createTime?: Date
|
||||
creator?: string
|
||||
creatorName?: string
|
||||
updateTime?: Date
|
||||
updater?: string
|
||||
updaterName?: string
|
||||
}
|
||||
|
||||
// WMS 入库单 API
|
||||
export const ReceiptOrderApi = {
|
||||
// 查询入库单分页
|
||||
getReceiptOrderPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/receipt-order/page', params })
|
||||
},
|
||||
|
||||
// 查询入库单详情
|
||||
getReceiptOrder: async (id: number) => {
|
||||
return await request.get({ url: '/wms/receipt-order/get?id=' + id })
|
||||
},
|
||||
|
||||
// 查询入库单明细
|
||||
getReceiptOrderDetailListByOrderId: async (orderId: number) => {
|
||||
return await request.get({
|
||||
url: '/wms/receipt-order-detail/list-by-order-id?orderId=' + orderId
|
||||
})
|
||||
},
|
||||
|
||||
// 新增入库单
|
||||
createReceiptOrder: async (data: ReceiptOrderVO) => {
|
||||
return await request.post({ url: '/wms/receipt-order/create', data })
|
||||
},
|
||||
|
||||
// 修改入库单
|
||||
updateReceiptOrder: async (data: ReceiptOrderVO) => {
|
||||
return await request.put({ url: '/wms/receipt-order/update', data })
|
||||
},
|
||||
|
||||
// 完成入库
|
||||
completeReceiptOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/receipt-order/complete?id=' + id })
|
||||
},
|
||||
|
||||
// 作废入库单
|
||||
cancelReceiptOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/receipt-order/cancel?id=' + id })
|
||||
},
|
||||
|
||||
// 删除入库单
|
||||
deleteReceiptOrder: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/receipt-order/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出入库单
|
||||
exportReceiptOrder: async (params: any) => {
|
||||
return await request.download({ url: '/wms/receipt-order/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// WMS 出库单明细 VO
|
||||
export interface ShipmentOrderDetailVO {
|
||||
id?: number
|
||||
orderId?: number
|
||||
itemId?: number
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
unit?: string
|
||||
skuId?: number
|
||||
skuCode?: string
|
||||
skuName?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
quantity?: number
|
||||
availableQuantity?: number
|
||||
price?: number
|
||||
totalPrice?: number
|
||||
createTime?: Date
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import request from '@/config/axios'
|
||||
import { ShipmentOrderDetailVO } from './detail'
|
||||
|
||||
// WMS 出库单 VO
|
||||
export interface ShipmentOrderVO {
|
||||
id?: number
|
||||
no?: string
|
||||
type?: number
|
||||
orderTime?: string
|
||||
status?: number
|
||||
bizOrderNo?: string
|
||||
merchantId?: number
|
||||
merchantName?: string
|
||||
remark?: string
|
||||
warehouseId?: number
|
||||
warehouseName?: string
|
||||
totalQuantity?: number
|
||||
totalPrice?: number
|
||||
details?: ShipmentOrderDetailVO[]
|
||||
createTime?: Date
|
||||
creator?: string
|
||||
creatorName?: string
|
||||
updateTime?: Date
|
||||
updater?: string
|
||||
updaterName?: string
|
||||
}
|
||||
|
||||
// WMS 出库单 API
|
||||
export const ShipmentOrderApi = {
|
||||
// 查询出库单分页
|
||||
getShipmentOrderPage: async (params: any) => {
|
||||
return await request.get({ url: '/wms/shipment-order/page', params })
|
||||
},
|
||||
|
||||
// 查询出库单详情
|
||||
getShipmentOrder: async (id: number) => {
|
||||
return await request.get({ url: '/wms/shipment-order/get?id=' + id })
|
||||
},
|
||||
|
||||
// 查询出库单明细
|
||||
getShipmentOrderDetailListByOrderId: async (orderId: number) => {
|
||||
return await request.get({
|
||||
url: '/wms/shipment-order-detail/list-by-order-id?orderId=' + orderId
|
||||
})
|
||||
},
|
||||
|
||||
// 新增出库单
|
||||
createShipmentOrder: async (data: ShipmentOrderVO) => {
|
||||
return await request.post({ url: '/wms/shipment-order/create', data })
|
||||
},
|
||||
|
||||
// 修改出库单
|
||||
updateShipmentOrder: async (data: ShipmentOrderVO) => {
|
||||
return await request.put({ url: '/wms/shipment-order/update', data })
|
||||
},
|
||||
|
||||
// 完成出库
|
||||
completeShipmentOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/shipment-order/complete?id=' + id })
|
||||
},
|
||||
|
||||
// 作废出库单
|
||||
cancelShipmentOrder: async (id: number) => {
|
||||
return await request.put({ url: '/wms/shipment-order/cancel?id=' + id })
|
||||
},
|
||||
|
||||
// 删除出库单
|
||||
deleteShipmentOrder: async (id: number) => {
|
||||
return await request.delete({ url: '/wms/shipment-order/delete?id=' + id })
|
||||
},
|
||||
|
||||
// 导出出库单
|
||||
exportShipmentOrder: async (params: any) => {
|
||||
return await request.download({ url: '/wms/shipment-order/export-excel', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -216,6 +216,13 @@ export enum DICT_TYPE {
|
|||
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态
|
||||
ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型
|
||||
|
||||
// ========== WMS - 仓库管理模块 ==========
|
||||
WMS_MERCHANT_TYPE = 'merchant_type', // WMS 往来企业类型
|
||||
WMS_ORDER_TYPE = 'wms_order_type', // WMS 单据类型
|
||||
WMS_ORDER_STATUS = 'wms_order_status', // WMS 单据状态
|
||||
WMS_RECEIPT_ORDER_TYPE = 'wms_receipt_order_type', // WMS 入库单类型
|
||||
WMS_SHIPMENT_ORDER_TYPE = 'wms_shipment_order_type', // WMS 出库单类型
|
||||
|
||||
// ========== AI - 人工智能模块 ==========
|
||||
AI_PLATFORM = 'ai_platform', // AI 平台
|
||||
AI_MODEL_TYPE = 'ai_model_type', // AI 模型类型
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const defaultShortcuts = [
|
|||
* @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
|
||||
* @returns 返回拼接后的时间字符串
|
||||
*/
|
||||
export function formatDate(date: Date, format?: string): string {
|
||||
export function formatDate(date: Date | string, format?: string): string {
|
||||
// 日期不存在,则返回空
|
||||
if (!date) {
|
||||
return ''
|
||||
|
|
@ -72,6 +72,25 @@ export function formatDate(date: Date, format?: string): string {
|
|||
return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化可为空的时间日期
|
||||
*
|
||||
* @param date 当前时间,new Date() 格式或者字符串时间格式
|
||||
* @param format 需要转换的时间格式字符串
|
||||
* @param emptyText 空值展示文案
|
||||
* @returns 返回格式化后的时间字符串
|
||||
*/
|
||||
export function formatNullableDate(
|
||||
date?: Date | string | null,
|
||||
format = 'YYYY-MM-DD HH:mm:ss',
|
||||
emptyText = '-'
|
||||
): string {
|
||||
if (!date) {
|
||||
return emptyText
|
||||
}
|
||||
return formatDate(date, format) || emptyText
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的日期+时间
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<el-row :gutter="16">
|
||||
<el-col :lg="12" :md="24" :sm="24" :xl="12" :xs="24">
|
||||
<div
|
||||
class="mb-16px border border-[var(--el-border-color-light)] border-solid rounded-[var(--wms-card-radius)] bg-[var(--el-bg-color)] p-18px shadow-[0_8px_24px_rgba(15,23,42,0.04)]"
|
||||
>
|
||||
<div
|
||||
class="mb-12px flex items-center justify-between gap-12px lt-sm:flex-col lt-sm:items-start"
|
||||
>
|
||||
<div>
|
||||
<div class="text-16px font-600 text-[var(--el-text-color-primary)]">货物占比</div>
|
||||
<div class="text-13px text-[var(--el-text-color-secondary)]">
|
||||
按商品库存数量汇总 Top 5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="goodsShareChartOptions" />
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :lg="12" :md="24" :sm="24" :xl="12" :xs="24">
|
||||
<div
|
||||
class="mb-16px border border-[var(--el-border-color-light)] border-solid rounded-[var(--wms-card-radius)] bg-[var(--el-bg-color)] p-18px shadow-[0_8px_24px_rgba(15,23,42,0.04)]"
|
||||
>
|
||||
<div
|
||||
class="mb-12px flex items-center justify-between gap-12px lt-sm:flex-col lt-sm:items-start"
|
||||
>
|
||||
<div>
|
||||
<div class="text-16px font-600 text-[var(--el-text-color-primary)]">库存分布</div>
|
||||
<div class="text-13px text-[var(--el-text-color-secondary)]">按仓库库存数量汇总</div>
|
||||
</div>
|
||||
<div class="text-14px font-600 text-[var(--el-text-color-primary)]">
|
||||
总库存 {{ formatQuantityText(totalQuantity) }}
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="300" :options="inventoryDistributionChartOptions" />
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { WmsHomeStatisticsApi } from '@/api/wms/home'
|
||||
import { formatQuantity } from '@/views/wms/utils/format'
|
||||
|
||||
defineOptions({ name: 'WmsHomeInventoryCharts' })
|
||||
|
||||
interface ChartItem {
|
||||
name: string
|
||||
value: number
|
||||
}
|
||||
|
||||
const GOODS_SHARE_LIMIT = 5
|
||||
const WAREHOUSE_DISTRIBUTION_LIMIT = 8
|
||||
|
||||
const loading = ref(false)
|
||||
const totalQuantity = ref(0)
|
||||
const goodsShareList = ref<ChartItem[]>([])
|
||||
const inventoryDistributionList = ref<ChartItem[]>([])
|
||||
|
||||
/** 格式化库存数量展示 */
|
||||
function formatQuantityText(value: number) {
|
||||
return formatQuantity(value) || '0.00'
|
||||
}
|
||||
|
||||
const chartFontFamily =
|
||||
"Inter, 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif"
|
||||
const chartTextStyle = {
|
||||
color: '#303133',
|
||||
fontFamily: chartFontFamily,
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
textBorderWidth: 0,
|
||||
textShadowBlur: 0
|
||||
}
|
||||
const chartAxisLabelStyle = {
|
||||
color: '#8a9099',
|
||||
fontFamily: chartFontFamily,
|
||||
fontSize: 12,
|
||||
textBorderWidth: 0,
|
||||
textShadowBlur: 0
|
||||
}
|
||||
|
||||
/** 构建图表数据项,并过滤零库存数据 */
|
||||
const buildChartItemList = <T,>(
|
||||
list: T[] | undefined,
|
||||
nameGetter: (item: T) => string,
|
||||
valueGetter: (item: T) => number | undefined
|
||||
) => {
|
||||
return (list || [])
|
||||
.map((item) => ({
|
||||
name: nameGetter(item),
|
||||
value: Number(valueGetter(item) || 0)
|
||||
}))
|
||||
.filter((item) => item.value > 0)
|
||||
}
|
||||
|
||||
/** 格式化货物占比图例,补充库存占比 */
|
||||
const formatGoodsLegend = (name: string) => {
|
||||
const total = goodsShareList.value.reduce((sum, item) => sum + item.value, 0)
|
||||
const item = goodsShareList.value.find((goods) => goods.name === name)
|
||||
if (!total || !item) {
|
||||
return name
|
||||
}
|
||||
return `${name} ${((item.value / total) * 100).toFixed(1)}%`
|
||||
}
|
||||
|
||||
/** 加载库存汇总数据 */
|
||||
const load = async (warehouseId?: number) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await WmsHomeStatisticsApi.getInventorySummary({
|
||||
...(warehouseId ? { warehouseId } : {}),
|
||||
goodsLimit: GOODS_SHARE_LIMIT,
|
||||
warehouseLimit: WAREHOUSE_DISTRIBUTION_LIMIT
|
||||
})
|
||||
totalQuantity.value = Number(data.totalQuantity || 0)
|
||||
goodsShareList.value = buildChartItemList(
|
||||
data.goodsShareList,
|
||||
(item) => item.name || '未命名商品',
|
||||
(item) => item.quantity
|
||||
)
|
||||
inventoryDistributionList.value = buildChartItemList(
|
||||
data.warehouseDistributionList,
|
||||
(item) => item.name || '未指定仓库',
|
||||
(item) => item.quantity
|
||||
)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 货物占比图表配置 */
|
||||
const goodsShareChartOptions = computed<EChartsOption>(() => ({
|
||||
color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'],
|
||||
textStyle: chartTextStyle,
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}<br/>库存:{c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'middle',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
formatter: formatGoodsLegend,
|
||||
textStyle: chartTextStyle
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '货物占比',
|
||||
type: 'pie',
|
||||
radius: ['48%', '70%'],
|
||||
center: ['34%', '52%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
data: goodsShareList.value
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
/** 库存分布图表配置 */
|
||||
const inventoryDistributionChartOptions = computed<EChartsOption>(() => ({
|
||||
color: ['#2f7df6'],
|
||||
grid: { top: 12, left: 24, right: 40, bottom: 16, containLabel: true },
|
||||
textStyle: chartTextStyle,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: (params: any) => {
|
||||
const item = Array.isArray(params) ? params[0] : params
|
||||
return `${item.name}<br/>库存:${formatQuantityText(item.value)}`
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
axisLabel: chartAxisLabelStyle,
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: inventoryDistributionList.value.map((item) => item.name).reverse(),
|
||||
axisLabel: chartAxisLabelStyle,
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: false }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '库存',
|
||||
type: 'bar',
|
||||
barMaxWidth: 16,
|
||||
data: inventoryDistributionList.value.map((item) => item.value).reverse(),
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
...chartTextStyle,
|
||||
formatter: ({ value }) => formatQuantityText(Number(value))
|
||||
}
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
defineExpose({ load })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<template #template>
|
||||
<div class="mb-16px grid grid-cols-4 gap-16px lt-sm:grid-cols-1 lt-xl:grid-cols-2">
|
||||
<el-skeleton-item
|
||||
v-for="item in 4"
|
||||
:key="item"
|
||||
class="h-174px w-full rounded-[var(--wms-card-radius)]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mb-16px grid grid-cols-4 gap-16px lt-sm:grid-cols-1 lt-xl:grid-cols-2">
|
||||
<div
|
||||
v-for="item in summaryList"
|
||||
:key="item.orderType"
|
||||
class="min-h-154px border border-t-3 border-[var(--el-border-color-light)] border-t-[var(--theme-color)] border-solid rounded-[var(--wms-card-radius)] bg-[var(--el-bg-color)] px-18px py-16px shadow-[0_8px_24px_rgba(15,23,42,0.04)]"
|
||||
:style="{ '--theme-color': item.color }"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-12px">
|
||||
<div
|
||||
class="flex items-center gap-8px text-15px font-600 text-[var(--el-text-color-primary)]"
|
||||
>
|
||||
<span class="h-8px w-8px rounded-full" :style="{ backgroundColor: item.color }"></span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<el-button link type="primary" @click="handleNavigate(item.routeName)">查看</el-button>
|
||||
</div>
|
||||
<div class="mt-18px flex items-baseline gap-8px">
|
||||
<em class="not-italic text-[var(--el-text-color-secondary)]">合计</em>
|
||||
<span class="text-32px font-700 leading-38px text-[var(--el-text-color-primary)]">{{
|
||||
formatCount(item.total)
|
||||
}}</span>
|
||||
<em class="not-italic text-[var(--el-text-color-secondary)]">单</em>
|
||||
</div>
|
||||
<div
|
||||
class="mt-14px h-8px flex overflow-hidden rounded-full bg-[var(--el-fill-color-light)]"
|
||||
>
|
||||
<span
|
||||
v-for="status in statusList"
|
||||
:key="status.value"
|
||||
class="h-full"
|
||||
:style="{
|
||||
width: getStatusPercent(item, status.value),
|
||||
backgroundColor: status.color
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="mt-14px grid grid-cols-3 gap-8px">
|
||||
<div v-for="status in statusList" :key="status.value" class="min-w-0">
|
||||
<span class="block truncate text-12px text-[var(--el-text-color-secondary)]">
|
||||
{{ status.label }}
|
||||
</span>
|
||||
<strong class="mt-2px block text-16px" :style="{ color: status.color }">{{
|
||||
item.statusCounts[status.value]
|
||||
}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-skeleton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { WmsHomeStatisticsApi } from '@/api/wms/home'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { OrderStatusEnum } from '@/views/wms/utils/constants'
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderSummaryCards' })
|
||||
|
||||
interface StatusItem {
|
||||
label: string
|
||||
value: number
|
||||
color: string
|
||||
}
|
||||
|
||||
interface OrderDefinition {
|
||||
orderType: number
|
||||
title: string
|
||||
color: string
|
||||
routeName: string
|
||||
}
|
||||
|
||||
interface OrderSummaryItem extends OrderDefinition {
|
||||
total: number
|
||||
statusCounts: Record<number, number>
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const OrderTypeEnum = {
|
||||
RECEIPT: 1,
|
||||
SHIPMENT: 2,
|
||||
MOVEMENT: 3,
|
||||
CHECK: 4
|
||||
} as const
|
||||
|
||||
/** 获取单据类型名称 */
|
||||
const getOrderTypeTitle = (type: number, defaultTitle: string) => {
|
||||
const label = getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, type) || defaultTitle
|
||||
return label.endsWith('单') ? label.slice(0, -1) : label
|
||||
}
|
||||
|
||||
/** 获取单据状态名称 */
|
||||
const getOrderStatusLabel = (status: number, defaultLabel: string) => {
|
||||
return getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, status) || defaultLabel
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const statusList: StatusItem[] = [
|
||||
{
|
||||
label: getOrderStatusLabel(OrderStatusEnum.PREPARE, '草稿'),
|
||||
value: OrderStatusEnum.PREPARE,
|
||||
color: '#409eff'
|
||||
},
|
||||
{
|
||||
label: getOrderStatusLabel(OrderStatusEnum.FINISHED, '已完成'),
|
||||
value: OrderStatusEnum.FINISHED,
|
||||
color: '#67c23a'
|
||||
},
|
||||
{
|
||||
label: getOrderStatusLabel(OrderStatusEnum.CANCELED, '已作废'),
|
||||
value: OrderStatusEnum.CANCELED,
|
||||
color: '#909399'
|
||||
}
|
||||
]
|
||||
const orderDefinitions: OrderDefinition[] = [
|
||||
{
|
||||
orderType: OrderTypeEnum.RECEIPT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.RECEIPT, '入库'),
|
||||
color: '#2f7df6',
|
||||
routeName: 'WmsReceiptOrder'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.SHIPMENT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.SHIPMENT, '出库'),
|
||||
color: '#18a058',
|
||||
routeName: 'WmsShipmentOrder'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.MOVEMENT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.MOVEMENT, '移库'),
|
||||
color: '#f59e0b',
|
||||
routeName: 'WmsMovementOrder'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.CHECK,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.CHECK, '盘库'),
|
||||
color: '#7c3aed',
|
||||
routeName: 'WmsCheckOrder'
|
||||
}
|
||||
]
|
||||
|
||||
/** 构建默认的状态数量集合 */
|
||||
const buildEmptyStatusCounts = () => {
|
||||
return statusList.reduce(
|
||||
(result, status) => {
|
||||
result[status.value] = 0
|
||||
return result
|
||||
},
|
||||
{} as Record<number, number>
|
||||
)
|
||||
}
|
||||
|
||||
const summaryList = ref<OrderSummaryItem[]>(
|
||||
orderDefinitions.map((item) => ({
|
||||
...item,
|
||||
total: 0,
|
||||
statusCounts: buildEmptyStatusCounts()
|
||||
}))
|
||||
)
|
||||
|
||||
/** 加载单据汇总数据 */
|
||||
const load = async (warehouseId?: number) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await WmsHomeStatisticsApi.getOrderSummary(warehouseId ? { warehouseId } : {})
|
||||
summaryList.value = orderDefinitions.map((definition) => {
|
||||
const summary = data.find((item) => item.type === definition.orderType)
|
||||
const statusCounts = statusList.reduce((result, status) => {
|
||||
const statusItem = summary?.statuses?.find((item) => item.status === status.value)
|
||||
result[status.value] = statusItem?.count || 0
|
||||
return result
|
||||
}, buildEmptyStatusCounts())
|
||||
return {
|
||||
...definition,
|
||||
total: summary?.total || 0,
|
||||
statusCounts
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 跳转到对应单据列表 */
|
||||
const handleNavigate = async (name: string) => {
|
||||
try {
|
||||
await router.push({ name })
|
||||
} catch {
|
||||
message.warning('当前菜单尚未加载,请从左侧菜单进入对应页面')
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算状态进度条占比 */
|
||||
function getStatusPercent(item: OrderSummaryItem, status: number) {
|
||||
const count = item.statusCounts[status] || 0
|
||||
if (!item.total || !count) {
|
||||
return '0%'
|
||||
}
|
||||
return `${(count / item.total) * 100}%`
|
||||
}
|
||||
|
||||
/** 格式化单据数量 */
|
||||
function formatCount(value: number) {
|
||||
return value.toLocaleString()
|
||||
}
|
||||
|
||||
defineExpose({ load })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<div
|
||||
class="mb-16px border border-[var(--el-border-color-light)] border-solid rounded-[var(--wms-card-radius)] bg-[var(--el-bg-color)] p-18px shadow-[0_8px_24px_rgba(15,23,42,0.04)]"
|
||||
>
|
||||
<div
|
||||
class="mb-12px flex items-center justify-between gap-12px lt-sm:flex-col lt-sm:items-start"
|
||||
>
|
||||
<div>
|
||||
<div class="text-16px font-600 text-[var(--el-text-color-primary)]">单据趋势</div>
|
||||
<div class="text-13px text-[var(--el-text-color-secondary)]">
|
||||
入库、出库、移库、盘库单据数量
|
||||
</div>
|
||||
</div>
|
||||
<el-segmented
|
||||
v-model="trendDays"
|
||||
:options="trendDayOptions"
|
||||
@change="handleTrendDaysChange"
|
||||
/>
|
||||
</div>
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<Echart :height="330" :options="chartOptions" />
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { WmsHomeStatisticsApi, type WmsHomeOrderTrendVO } from '@/api/wms/home'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderTrendChart' })
|
||||
|
||||
interface OrderDefinition {
|
||||
orderType: number
|
||||
title: string
|
||||
color: string
|
||||
trendField: keyof Pick<
|
||||
WmsHomeOrderTrendVO,
|
||||
'receiptCount' | 'shipmentCount' | 'movementCount' | 'checkCount'
|
||||
>
|
||||
}
|
||||
|
||||
const OrderTypeEnum = {
|
||||
RECEIPT: 1,
|
||||
SHIPMENT: 2,
|
||||
MOVEMENT: 3,
|
||||
CHECK: 4
|
||||
} as const
|
||||
|
||||
/** 获取单据类型名称 */
|
||||
const getOrderTypeTitle = (type: number, defaultTitle: string) => {
|
||||
const label = getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, type) || defaultTitle
|
||||
return label.endsWith('单') ? label.slice(0, -1) : label
|
||||
}
|
||||
|
||||
const orderDefinitions: OrderDefinition[] = [
|
||||
{
|
||||
orderType: OrderTypeEnum.RECEIPT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.RECEIPT, '入库'),
|
||||
color: '#2f7df6',
|
||||
trendField: 'receiptCount'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.SHIPMENT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.SHIPMENT, '出库'),
|
||||
color: '#18a058',
|
||||
trendField: 'shipmentCount'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.MOVEMENT,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.MOVEMENT, '移库'),
|
||||
color: '#f59e0b',
|
||||
trendField: 'movementCount'
|
||||
},
|
||||
{
|
||||
orderType: OrderTypeEnum.CHECK,
|
||||
title: getOrderTypeTitle(OrderTypeEnum.CHECK, '盘库'),
|
||||
color: '#7c3aed',
|
||||
trendField: 'checkCount'
|
||||
}
|
||||
]
|
||||
const trendDayOptions = [
|
||||
{ label: '近7天', value: 7 },
|
||||
{ label: '近30天', value: 30 }
|
||||
]
|
||||
|
||||
const loading = ref(false)
|
||||
const warehouseId = ref<number>()
|
||||
const trendDays = ref(7)
|
||||
const trendLabels = ref<string[]>([])
|
||||
const trendSeriesMap = reactive<Record<number, number[]>>({
|
||||
[OrderTypeEnum.RECEIPT]: [],
|
||||
[OrderTypeEnum.SHIPMENT]: [],
|
||||
[OrderTypeEnum.MOVEMENT]: [],
|
||||
[OrderTypeEnum.CHECK]: []
|
||||
})
|
||||
|
||||
const chartFontFamily =
|
||||
"Inter, 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif"
|
||||
const chartTextStyle = {
|
||||
color: '#303133',
|
||||
fontFamily: chartFontFamily,
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
textBorderWidth: 0,
|
||||
textShadowBlur: 0
|
||||
}
|
||||
const chartAxisLabelStyle = {
|
||||
color: '#8a9099',
|
||||
fontFamily: chartFontFamily,
|
||||
fontSize: 12,
|
||||
textBorderWidth: 0,
|
||||
textShadowBlur: 0
|
||||
}
|
||||
|
||||
/** 加载单据趋势数据 */
|
||||
const load = async (selectedWarehouseId?: number) => {
|
||||
warehouseId.value = selectedWarehouseId
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await WmsHomeStatisticsApi.getOrderTrend(
|
||||
trendDays.value,
|
||||
selectedWarehouseId ? { warehouseId: selectedWarehouseId } : {}
|
||||
)
|
||||
trendLabels.value = data.map((item) => formatDate(new Date(item.time), 'MM-DD'))
|
||||
orderDefinitions.forEach((definition) => {
|
||||
trendSeriesMap[definition.orderType] = data.map((item) =>
|
||||
Number(item[definition.trendField] || 0)
|
||||
)
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换趋势统计天数 */
|
||||
const handleTrendDaysChange = async () => {
|
||||
await load(warehouseId.value)
|
||||
}
|
||||
|
||||
/** 单据趋势图表配置 */
|
||||
const chartOptions = computed<EChartsOption>(() => ({
|
||||
color: orderDefinitions.map((item) => item.color),
|
||||
grid: { top: 48, left: 28, right: 24, bottom: 24, containLabel: true },
|
||||
textStyle: chartTextStyle,
|
||||
legend: { top: 6, itemWidth: 10, itemHeight: 10, textStyle: chartTextStyle },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: trendLabels.value,
|
||||
axisLabel: chartAxisLabelStyle,
|
||||
axisTick: { show: false },
|
||||
axisLine: { lineStyle: { color: '#dcdfe6' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '单据数',
|
||||
nameTextStyle: chartAxisLabelStyle,
|
||||
axisLabel: chartAxisLabelStyle,
|
||||
minInterval: 1,
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } }
|
||||
},
|
||||
series: orderDefinitions.map((item) => ({
|
||||
name: item.title,
|
||||
type: 'bar',
|
||||
barMaxWidth: 18,
|
||||
data: trendSeriesMap[item.orderType],
|
||||
emphasis: { focus: 'series' }
|
||||
}))
|
||||
}))
|
||||
|
||||
defineExpose({ load })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div class="[--wms-card-radius:8px]">
|
||||
<div
|
||||
class="mb-16px flex flex-wrap justify-between gap-16px rounded-[var(--wms-card-radius)] border border-[var(--el-border-color-light)] border-solid bg-[var(--el-bg-color)] p-16px"
|
||||
>
|
||||
<div
|
||||
class="flex min-w-320px flex-1 flex-wrap items-center justify-between gap-16px lt-sm:w-full"
|
||||
>
|
||||
<div>
|
||||
<div class="text-20px font-600 leading-28px text-[var(--el-text-color-primary)]">
|
||||
WMS 首页
|
||||
</div>
|
||||
<div class="text-13px text-[var(--el-text-color-secondary)]">单据工作台 / 库存概览</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-8px lt-sm:w-full">
|
||||
<WarehouseSelect
|
||||
v-model="warehouseId"
|
||||
class="!w-220px"
|
||||
placeholder="全部仓库"
|
||||
@change="refresh()"
|
||||
/>
|
||||
<el-button :loading="loading" @click="refresh">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
|
||||
<WmsHomeOrderTrendChart ref="orderTrendChartRef" />
|
||||
<WmsHomeInventoryCharts ref="inventoryChartsRef" />
|
||||
|
||||
<div
|
||||
class="mt-2px flex items-center justify-center text-13px text-[var(--el-text-color-secondary)]"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:clock" />
|
||||
统计时间:{{ statTime }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import WmsHomeInventoryCharts from './components/WmsHomeInventoryCharts.vue'
|
||||
import WmsHomeOrderSummaryCards from './components/WmsHomeOrderSummaryCards.vue'
|
||||
import WmsHomeOrderTrendChart from './components/WmsHomeOrderTrendChart.vue'
|
||||
|
||||
/** WMS 首页 */
|
||||
defineOptions({ name: 'WmsHome' })
|
||||
|
||||
const loading = ref(false)
|
||||
const warehouseId = ref<number>()
|
||||
const statTime = ref(formatDate(new Date()))
|
||||
const orderSummaryCardsRef = ref<InstanceType<typeof WmsHomeOrderSummaryCards>>()
|
||||
const orderTrendChartRef = ref<InstanceType<typeof WmsHomeOrderTrendChart>>()
|
||||
const inventoryChartsRef = ref<InstanceType<typeof WmsHomeInventoryCharts>>()
|
||||
|
||||
/** 刷新:重新加载各个组件的数据(传入 warehouseId 以进行针对性查询),并更新时间戳 */
|
||||
const refresh = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await Promise.all([
|
||||
orderSummaryCardsRef.value?.load(warehouseId.value),
|
||||
orderTrendChartRef.value?.load(warehouseId.value),
|
||||
inventoryChartsRef.value?.load(warehouseId.value)
|
||||
])
|
||||
statTime.value = formatDate(new Date())
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
<!-- WMS 库存选择器 -->
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="dialogVisible"
|
||||
:append-to-body="true"
|
||||
:scroll="true"
|
||||
max-height="calc(100vh - 220px)"
|
||||
title="库存选择"
|
||||
width="80%"
|
||||
>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="itemName">
|
||||
<el-input
|
||||
v-model="queryParams.itemName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="itemCode">
|
||||
<el-input
|
||||
v-model="queryParams.itemCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="queryParams.skuName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格编号" prop="skuCode">
|
||||
<el-input
|
||||
v-model="queryParams.skuCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap class="!mb-0">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:row-key="getRowKey"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
border
|
||||
max-height="calc(100vh - 430px)"
|
||||
@row-dblclick="handleRowDblClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column :reserve-selection="true" align="center" type="selection" width="50" />
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.skuName || '-' }}</div>
|
||||
<div v-if="row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="仓库" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="可用库存" width="130">
|
||||
<template #default="{ row }">
|
||||
{{ formatQuantity(row.availableQuantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="overflow-hidden">
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElTable } from 'element-plus'
|
||||
import { InventoryApi, InventoryVO } from '@/api/wms/inventory'
|
||||
import { formatQuantity } from '@/views/wms/utils/format'
|
||||
|
||||
export interface InventorySelectRow extends InventoryVO {
|
||||
price?: number
|
||||
availableQuantity?: number
|
||||
}
|
||||
|
||||
/** WMS 库存选择器 */
|
||||
defineOptions({ name: 'WmsInventorySelect' })
|
||||
|
||||
const props = defineProps<{
|
||||
warehouseId?: number
|
||||
}>()
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const list = ref<InventorySelectRow[]>([]) // 库存列表
|
||||
const total = ref(0) // 列表的总条数
|
||||
const selectedMap = ref<Map<string, InventorySelectRow>>(new Map()) // 跨页已选择库存
|
||||
const tableRef = ref<InstanceType<typeof ElTable>>() // 表格 Ref
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
itemName: undefined as string | undefined,
|
||||
itemCode: undefined as string | undefined,
|
||||
skuName: undefined as string | undefined,
|
||||
skuCode: undefined as string | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [list: InventorySelectRow[]]
|
||||
}>()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
if (!props.warehouseId) {
|
||||
message.warning('请先选择仓库')
|
||||
return
|
||||
}
|
||||
dialogVisible.value = true
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
selectedMap.value = new Map()
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
await getList()
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 查询库存列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...queryParams,
|
||||
type: 'warehouse',
|
||||
warehouseId: props.warehouseId,
|
||||
onlyPositiveQuantity: true
|
||||
}
|
||||
const data = await InventoryApi.getInventoryPage(params)
|
||||
list.value = data.list.map((inventory: InventoryVO) => ({
|
||||
...inventory,
|
||||
availableQuantity: inventory.quantity
|
||||
}))
|
||||
total.value = data.total
|
||||
await nextTick()
|
||||
applySelection()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
queryFormRef.value?.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 获得行唯一标识 */
|
||||
const getRowKey = (row: InventorySelectRow) => {
|
||||
return `inventory-${row.id || `${row.skuId}-${row.warehouseId}`}`
|
||||
}
|
||||
|
||||
/** 恢复当前页选择状态 */
|
||||
const applySelection = () => {
|
||||
const table = tableRef.value
|
||||
if (!table || selectedMap.value.size === 0) {
|
||||
return
|
||||
}
|
||||
list.value.forEach((row) => {
|
||||
if (selectedMap.value.has(getRowKey(row))) {
|
||||
table.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 选择行变化 */
|
||||
const handleSelectionChange = (rows: InventorySelectRow[]) => {
|
||||
const currentKeys = new Set(list.value.map((row) => getRowKey(row)))
|
||||
currentKeys.forEach((key) => selectedMap.value.delete(key))
|
||||
rows.forEach((row) => selectedMap.value.set(getRowKey(row), row))
|
||||
}
|
||||
|
||||
/** 双击行直接选择 */
|
||||
const handleRowDblClick = (row: InventorySelectRow) => {
|
||||
selectedMap.value.set(getRowKey(row), row)
|
||||
handleConfirm()
|
||||
}
|
||||
|
||||
/** 确认选择 */
|
||||
const handleConfirm = () => {
|
||||
const selectedList = Array.from(selectedMap.value.values())
|
||||
if (selectedList.length === 0) {
|
||||
message.warning('请选择库存')
|
||||
return
|
||||
}
|
||||
emit('change', selectedList)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
<!-- WMS 库存流水 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="单据类型" prop="orderType">
|
||||
<el-select
|
||||
v-model="queryParams.orderType"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择单据类型"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据号" prop="orderNo">
|
||||
<el-input
|
||||
v-model="queryParams.orderNo"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入单据号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="queryParams.warehouseId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="itemCode">
|
||||
<el-input
|
||||
v-model="queryParams.itemCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="itemName">
|
||||
<el-input
|
||||
v-model="queryParams.itemName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格编号" prop="skuCode">
|
||||
<el-input
|
||||
v-model="queryParams.skuCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="queryParams.skuName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 库存流水列表 -->
|
||||
<ContentWrap>
|
||||
<div class="mb-12px text-16px font-500">库存流水</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
>
|
||||
<el-table-column fixed="left" label="单据号" prop="orderNo" width="180" />
|
||||
<el-table-column align="center" fixed="left" label="单据类型" width="110">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_ORDER_TYPE" :value="scope.row.orderType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div class="text-14px">{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="180">
|
||||
<template #default="scope">
|
||||
<div class="text-14px">{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="仓库" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="操作前" min-width="110">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.beforeQuantity) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="操作后" min-width="110">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.afterQuantity) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量/金额(元)" min-width="150">
|
||||
<template #default="scope">
|
||||
<div class="flex justify-between">
|
||||
<span>数量:</span>
|
||||
<span>{{ formatQuantity(scope.row.quantity) }}</span>
|
||||
</div>
|
||||
<div v-if="scope.row.price || scope.row.price === 0" class="flex justify-between">
|
||||
<span>单价:</span>
|
||||
<span>{{ formatPrice(scope.row.price) }}</span>
|
||||
</div>
|
||||
<div v-if="scope.row.totalPrice || scope.row.totalPrice === 0" class="flex justify-between">
|
||||
<span>金额:</span>
|
||||
<span>{{ formatPrice(scope.row.totalPrice) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
fixed="right"
|
||||
label="操作时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter, defaultShortcuts } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { InventoryHistoryApi, InventoryHistoryVO } from '@/api/wms/inventory/history'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { formatPrice, formatQuantity } from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 库存流水 */
|
||||
defineOptions({ name: 'WmsInventoryHistory' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<InventoryHistoryVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
orderType: undefined as number | undefined,
|
||||
orderNo: undefined as string | undefined,
|
||||
itemCode: undefined as string | undefined,
|
||||
itemName: undefined as string | undefined,
|
||||
skuCode: undefined as string | undefined,
|
||||
skuName: undefined as string | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
createTime: undefined as string[] | undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询库存流水列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await InventoryHistoryApi.getInventoryHistoryPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
<!-- WMS 库存统计 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="统计维度" prop="type">
|
||||
<el-radio-group v-model="queryParams.type" class="!w-240px" @change="handleTypeChange">
|
||||
<el-radio-button v-for="item in dimensionOptions" :key="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="queryParams.warehouseId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="itemName">
|
||||
<el-input
|
||||
v-model="queryParams.itemName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="itemCode">
|
||||
<el-input
|
||||
v-model="queryParams.itemCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="queryParams.skuName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格编号" prop="skuCode">
|
||||
<el-input
|
||||
v-model="queryParams.skuCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 库存列表 -->
|
||||
<ContentWrap>
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-16px font-500">库存统计</span>
|
||||
<el-checkbox v-model="filterZero" @change="handleFilterZeroChange">
|
||||
过滤掉库存为0的商品
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:span-method="spanMethod"
|
||||
border
|
||||
>
|
||||
<template v-if="warehouseDimensionList.includes(queryParams.type)">
|
||||
<el-table-column label="仓库" min-width="160" prop="warehouseId">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品信息" min-width="240" prop="warehouseItemId">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220" prop="skuId">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table-column label="商品信息" min-width="240" prop="itemId">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220" prop="skuId">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="仓库" min-width="160" prop="skuWarehouseId">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="right" label="库存" min-width="130" prop="quantity">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.quantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { InventoryApi, InventoryVO } from '@/api/wms/inventory'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { formatQuantity } from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 库存统计 */
|
||||
defineOptions({ name: 'WmsInventory' })
|
||||
|
||||
const INVENTORY_DIMENSION = {
|
||||
WAREHOUSE: 'warehouse',
|
||||
ITEM: 'item'
|
||||
} as const
|
||||
type InventoryDimension = (typeof INVENTORY_DIMENSION)[keyof typeof INVENTORY_DIMENSION]
|
||||
|
||||
const dimensionOptions = computed(() => [
|
||||
{ label: '仓库', value: INVENTORY_DIMENSION.WAREHOUSE },
|
||||
{ label: '商品', value: INVENTORY_DIMENSION.ITEM }
|
||||
])
|
||||
const warehouseDimensionList: InventoryDimension[] = [INVENTORY_DIMENSION.WAREHOUSE]
|
||||
|
||||
interface InventoryRow extends InventoryVO {
|
||||
warehouseItemId?: string
|
||||
skuWarehouseId?: string
|
||||
}
|
||||
|
||||
interface SpanMethodProps {
|
||||
column: {
|
||||
property?: string
|
||||
}
|
||||
rowIndex: number
|
||||
}
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<InventoryRow[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const filterZero = ref(false) // 是否过滤库存为 0 的商品
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: INVENTORY_DIMENSION.WAREHOUSE as InventoryDimension,
|
||||
itemCode: undefined as string | undefined,
|
||||
itemName: undefined as string | undefined,
|
||||
skuCode: undefined as string | undefined,
|
||||
skuName: undefined as string | undefined,
|
||||
warehouseId: undefined as number | undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询库存列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await InventoryApi.getInventoryPage({
|
||||
...queryParams,
|
||||
onlyPositiveQuantity: filterZero.value ? true : undefined
|
||||
})
|
||||
list.value = data.list.map((item: InventoryVO) => ({
|
||||
...item,
|
||||
// 展示字段
|
||||
warehouseItemId: `${item.warehouseId || 0}-${item.itemId || 0}`,
|
||||
skuWarehouseId: `${item.skuId || 0}-${item.warehouseId || 0}`
|
||||
}))
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryParams.type = INVENTORY_DIMENSION.WAREHOUSE
|
||||
filterZero.value = false
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 统计维度变化 */
|
||||
const handleTypeChange = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 是否过滤零库存变化 */
|
||||
const handleFilterZeroChange = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 合并库存统计的维度单元格 */
|
||||
const spanMethod = ({ column, rowIndex }: SpanMethodProps) => {
|
||||
const property = column.property
|
||||
if (!property || !getRowSpanProperties().includes(property)) {
|
||||
return { rowspan: 1, colspan: 1 }
|
||||
}
|
||||
const row = list.value[rowIndex]
|
||||
if (
|
||||
rowIndex > 0 &&
|
||||
getRowPropertyValue(list.value[rowIndex - 1], property) === getRowPropertyValue(row, property)
|
||||
) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
let rowspan = 1
|
||||
for (let index = rowIndex + 1; index < list.value.length; index++) {
|
||||
if (getRowPropertyValue(list.value[index], property) !== getRowPropertyValue(row, property)) {
|
||||
break
|
||||
}
|
||||
rowspan++
|
||||
}
|
||||
return { rowspan, colspan: 1 }
|
||||
}
|
||||
|
||||
const getRowPropertyValue = (row: InventoryRow | undefined, property: string) => {
|
||||
return row?.[property as keyof InventoryRow]
|
||||
}
|
||||
|
||||
const getRowSpanProperties = () => {
|
||||
if (queryParams.type === INVENTORY_DIMENSION.ITEM) {
|
||||
return ['itemId', 'skuId', 'skuWarehouseId']
|
||||
}
|
||||
return ['warehouseId', 'warehouseItemId']
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
<!-- WMS 商品表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1200px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="88px"
|
||||
>
|
||||
<!-- 基础信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="60" placeholder="请输入商品名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<ItemCategorySelect v-model="formData.categoryId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品编号" prop="code">
|
||||
<el-input v-model="formData.code" maxlength="20" placeholder="请输入商品编号">
|
||||
<template #append>
|
||||
<el-button @click="formData.code = generateWmsCode('I')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品单位" prop="unit">
|
||||
<el-input v-model="formData.unit" maxlength="20" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<ItemBrandSelect v-model="formData.brandId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" maxlength="255" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 规格信息 -->
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">规格</span>
|
||||
<el-button plain type="primary" @click="handleAddSku">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增规格
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="formData.skus" border>
|
||||
<el-table-column label="规格名称" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-form-item
|
||||
:prop="'skus.' + scope.$index + '.name'"
|
||||
:rules="skuNameRules"
|
||||
class="!mb-0 !w-1/1"
|
||||
label-width="0"
|
||||
>
|
||||
<el-input
|
||||
v-model="scope.row.name"
|
||||
maxlength="255"
|
||||
class="!w-1/1"
|
||||
placeholder="请输入规格名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编号/条码" width="260">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.code" maxlength="64" class="!w-1/1" placeholder="编号">
|
||||
<template #append>
|
||||
<el-button @click="scope.row.code = generateWmsCode('S')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="scope.row.barCode"
|
||||
maxlength="64"
|
||||
class="!w-1/1 mt-5px"
|
||||
placeholder="条码"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="scope.row.barCode = generateWmsCode()">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="长/宽/高(cm)" width="210">
|
||||
<template #default="scope">
|
||||
<div class="flex w-1/1 gap-5px">
|
||||
<el-input-number
|
||||
v-model="scope.row.length"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="DIMENSION_PRECISION"
|
||||
class="!w-1/3"
|
||||
placeholder="长"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="scope.row.width"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="DIMENSION_PRECISION"
|
||||
class="!w-1/3"
|
||||
placeholder="宽"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="scope.row.height"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="DIMENSION_PRECISION"
|
||||
class="!w-1/3"
|
||||
placeholder="高"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="净重/毛重(kg)" width="180">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.netWeight"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="WEIGHT_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="净重"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="scope.row.grossWeight"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="WEIGHT_PRECISION"
|
||||
class="!w-1/1 mt-5px"
|
||||
placeholder="毛重"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成本价/销售价" width="180">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.costPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="成本价"
|
||||
/>
|
||||
<el-input-number
|
||||
v-model="scope.row.sellingPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1 mt-5px"
|
||||
placeholder="销售价"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="handleDeleteSku(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormRules } from 'element-plus'
|
||||
import { ItemApi, ItemVO } from '@/api/wms/md/item'
|
||||
import { ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import { DIMENSION_PRECISION, PRICE_PRECISION, WEIGHT_PRECISION } from '@/views/wms/utils/format'
|
||||
import { generateWmsCode } from '@/views/wms/utils/constants'
|
||||
import ItemBrandSelect from './brand/components/ItemBrandSelect.vue'
|
||||
import ItemCategorySelect from './category/components/ItemCategorySelect.vue'
|
||||
|
||||
/** WMS 商品表单 */
|
||||
defineOptions({ name: 'WmsItemForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<ItemVO>({
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
categoryId: undefined,
|
||||
unit: undefined,
|
||||
brandId: undefined,
|
||||
remark: undefined,
|
||||
skus: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
code: [{ required: true, message: '商品编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }],
|
||||
categoryId: [{ required: true, message: '商品分类不能为空', trigger: 'change' }],
|
||||
skus: [{ required: true, message: '至少包含一个商品规格', trigger: 'change' }]
|
||||
})
|
||||
const skuNameRules = [{ required: true, message: '规格名称不能为空', trigger: 'blur' }]
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const item = await ItemApi.getItem(id)
|
||||
formData.value = {
|
||||
...item,
|
||||
skus: item.skus?.length ? item.skus : [buildEmptySku()]
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 构建空规格 */
|
||||
function buildEmptySku(): ItemSkuVO {
|
||||
return {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
barCode: undefined,
|
||||
code: undefined,
|
||||
length: undefined,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
grossWeight: undefined,
|
||||
netWeight: undefined,
|
||||
costPrice: undefined,
|
||||
sellingPrice: undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加规格 */
|
||||
const handleAddSku = () => {
|
||||
formData.value.skus?.push(buildEmptySku())
|
||||
}
|
||||
|
||||
/** 删除规格 */
|
||||
const handleDeleteSku = (index: number) => {
|
||||
if (!formData.value.skus || formData.value.skus.length <= 1) {
|
||||
message.error('至少包含一个商品规格')
|
||||
return
|
||||
}
|
||||
formData.value.skus.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ItemVO
|
||||
if (formType.value === 'create') {
|
||||
await ItemApi.createItem(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ItemApi.updateItem(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
categoryId: undefined,
|
||||
unit: undefined,
|
||||
brandId: undefined,
|
||||
remark: undefined,
|
||||
skus: [buildEmptySku()]
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<!-- WMS 商品品牌表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="品牌编号" prop="code">
|
||||
<el-input v-model="formData.code" maxlength="20" placeholder="请输入品牌编号">
|
||||
<template #append>
|
||||
<el-button @click="formData.code = generateWmsCode('B')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="30" placeholder="请输入品牌名称" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ItemBrandApi, ItemBrandVO } from '@/api/wms/md/item/brand'
|
||||
import { generateWmsCode } from '@/views/wms/utils/constants'
|
||||
|
||||
/** WMS 商品品牌表单 */
|
||||
defineOptions({ name: 'WmsItemBrandForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<ItemBrandVO>({
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
code: [{ required: true, message: '品牌编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ItemBrandApi.getItemBrand(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ItemBrandVO
|
||||
if (formType.value === 'create') {
|
||||
await ItemBrandApi.createItemBrand(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ItemBrandApi.updateItemBrand(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<!-- WMS 商品品牌选择器 -->
|
||||
<template>
|
||||
<el-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:filter-method="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-1/1"
|
||||
filterable
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="brand in filteredList"
|
||||
:key="brand.id!"
|
||||
:label="brand.name"
|
||||
:value="brand.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ItemBrandApi, ItemBrandVO } from '@/api/wms/md/item/brand'
|
||||
|
||||
/** WMS 商品品牌选择器 */
|
||||
defineOptions({ name: 'WmsItemBrandSelect', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: number
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
placeholder: '请选择商品品牌'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number | undefined]
|
||||
change: [brand: ItemBrandVO | undefined]
|
||||
}>()
|
||||
|
||||
const brandList = ref<ItemBrandVO[]>([]) // 品牌列表
|
||||
const filteredList = ref<ItemBrandVO[]>([]) // 过滤后的品牌列表
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/** 前端过滤 */
|
||||
const handleFilter = (query: string) => {
|
||||
if (!query) {
|
||||
filteredList.value = brandList.value
|
||||
return
|
||||
}
|
||||
const keyword = query.toLowerCase()
|
||||
filteredList.value = brandList.value.filter((brand) =>
|
||||
brand.name?.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
/** 选中变化 */
|
||||
const handleChange = (value: number | undefined) => {
|
||||
emit(
|
||||
'change',
|
||||
brandList.value.find((brand) => brand.id === value)
|
||||
)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
brandList.value = await ItemBrandApi.getItemBrandSimpleList()
|
||||
filteredList.value = brandList.value
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
<!-- WMS 商品品牌管理 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="品牌编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入品牌编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入品牌名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-brand:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-brand:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 品牌列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="品牌编号" prop="code" width="160" />
|
||||
<el-table-column align="center" label="品牌名称" prop="name" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-brand:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-brand:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ItemBrandForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ItemBrandApi, ItemBrandVO } from '@/api/wms/md/item/brand'
|
||||
import ItemBrandForm from './ItemBrandForm.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 商品品牌管理 */
|
||||
defineOptions({ name: 'WmsItemBrand' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ItemBrandVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
code: undefined,
|
||||
name: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询商品品牌列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ItemBrandApi.getItemBrandPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除品牌 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ItemBrandApi.deleteItemBrand(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ItemBrandApi.exportItemBrand(queryParams)
|
||||
download.excel(data, '商品品牌.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<!-- WMS 商品分类表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="上级分类" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="formData.parentId"
|
||||
:data="categoryTree"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
default-expand-all
|
||||
placeholder="请选择上级分类"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类编号" prop="code">
|
||||
<el-input v-model="formData.code" maxlength="20" placeholder="请输入分类编号">
|
||||
<template #append>
|
||||
<el-button @click="formData.code = generateWmsCode('C')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示排序" prop="sort">
|
||||
<el-input-number
|
||||
v-model="formData.sort"
|
||||
:min="0"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" clearable placeholder="请选择状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { ItemCategoryApi, ItemCategoryVO } from '@/api/wms/md/item/category'
|
||||
import { generateWmsCode } from '@/views/wms/utils/constants'
|
||||
|
||||
/** WMS 商品分类表单 */
|
||||
defineOptions({ name: 'WmsItemCategoryForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<ItemCategoryVO>({
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: 0,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
parentId: [{ required: true, message: '上级分类不能为空', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '分类编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const categoryTree = ref() // 树形结构
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number, parentId?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm(parentId)
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ItemCategoryApi.getItemCategory(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得分类树
|
||||
await getTree()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 获得分类树 */
|
||||
const getTree = async () => {
|
||||
categoryTree.value = []
|
||||
const data = await ItemCategoryApi.getItemCategorySimpleList()
|
||||
let category: Tree = { id: 0, name: '顶级分类', children: [] }
|
||||
category.children = handleTree(data, 'id', 'parentId')
|
||||
categoryTree.value.push(category)
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) {
|
||||
return
|
||||
}
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as ItemCategoryVO
|
||||
if (formType.value === 'create') {
|
||||
await ItemCategoryApi.createItemCategory(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ItemCategoryApi.updateItemCategory(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = (parentId?: number) => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
parentId,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: 0,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<!-- WMS 商品分类选择器 -->
|
||||
<template>
|
||||
<el-tree-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:data="categoryTree"
|
||||
:disabled="disabled"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="w-1/1"
|
||||
default-expand-all
|
||||
:placeholder="placeholder"
|
||||
value-key="id"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { ItemCategoryApi, ItemCategoryVO } from '@/api/wms/md/item/category'
|
||||
|
||||
/** WMS 商品分类选择器 */
|
||||
defineOptions({ name: 'WmsItemCategorySelect', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: number
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
placeholder: '请选择商品分类'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number | undefined]
|
||||
}>()
|
||||
|
||||
const categoryTree = ref<ItemCategoryVO[]>([]) // 分类树
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/** 获得分类树 */
|
||||
const getCategoryTree = async () => {
|
||||
const data = await ItemCategoryApi.getItemCategorySimpleList()
|
||||
categoryTree.value = handleTree(data, 'id', 'parentId')
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getCategoryTree()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<!-- WMS 商品分类树组件 -->
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<el-input v-model="filterText" class="p-[15px]" clearable :placeholder="filterPlaceholder">
|
||||
<template #prefix>
|
||||
<Icon icon="ep:search" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-scrollbar class="!h-[calc(100%-32px-30px)]">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="categoryList"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
@node-click="handleNodeClick"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElTree } from 'element-plus'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { ItemCategoryApi, ItemCategoryVO } from '@/api/wms/md/item/category'
|
||||
|
||||
/** WMS 商品分类树组件 */
|
||||
defineOptions({ name: 'WmsItemCategoryTree' })
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
filterPlaceholder?: string // 搜索输入框占位文字
|
||||
}>(),
|
||||
{
|
||||
filterPlaceholder: '请输入分类名称'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'node-click': [categoryId: number | undefined]
|
||||
}>()
|
||||
|
||||
const filterText = ref('') // 过滤关键字
|
||||
const categoryList = ref<ItemCategoryVO[]>([]) // 分类树数据
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>() // 树 Ref
|
||||
let currentNodeId: number | null = null // 当前选中的节点 ID
|
||||
|
||||
/** 加载分类树 */
|
||||
const loadTree = async () => {
|
||||
const data = await ItemCategoryApi.getItemCategorySimpleList()
|
||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||
}
|
||||
|
||||
/** 基于名字过滤 */
|
||||
const filterNode = (name: string, data: ItemCategoryVO) => {
|
||||
if (!name) {
|
||||
return true
|
||||
}
|
||||
return !!data.name?.includes(name)
|
||||
}
|
||||
|
||||
/** 监听过滤关键字 */
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value?.filter(val)
|
||||
})
|
||||
|
||||
/** 处理节点点击:支持点击同一节点取消选中 */
|
||||
const handleNodeClick = (row: ItemCategoryVO) => {
|
||||
if (currentNodeId === row.id) {
|
||||
treeRef.value?.setCurrentKey(undefined)
|
||||
currentNodeId = null
|
||||
emit('node-click', undefined)
|
||||
} else {
|
||||
currentNodeId = row.id!
|
||||
emit('node-click', row.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空选中状态 */
|
||||
const reset = () => {
|
||||
currentNodeId = null
|
||||
filterText.value = ''
|
||||
treeRef.value?.setCurrentKey(undefined)
|
||||
}
|
||||
|
||||
/** 设置当前选中分类 */
|
||||
const setCurrent = (categoryId: number) => {
|
||||
treeRef.value?.setCurrentKey(categoryId)
|
||||
}
|
||||
|
||||
defineExpose({ reset, setCurrent })
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await loadTree()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<!-- WMS 商品分类管理 -->
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="分类编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入分类编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入分类名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择分类状态"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-category:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button plain type="danger" @click="toggleExpandAll">
|
||||
<Icon class="mr-5px" icon="ep:sort" />
|
||||
展开/折叠
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 分类列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-if="refreshTable"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:default-expand-all="isExpandAll"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column label="分类名称" prop="name" />
|
||||
<el-table-column align="center" label="分类编号" prop="code" width="160" />
|
||||
<el-table-column align="center" label="排序" prop="sort" width="120" />
|
||||
<el-table-column align="center" label="状态" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作" width="240">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-category:create']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('create', undefined, scope.row.id)"
|
||||
>
|
||||
新增下级
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-category:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item-category:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ItemCategoryForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import { ItemCategoryApi, ItemCategoryVO } from '@/api/wms/md/item/category'
|
||||
import ItemCategoryForm from './ItemCategoryForm.vue'
|
||||
|
||||
/** WMS 商品分类管理 */
|
||||
defineOptions({ name: 'WmsItemCategory' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ItemCategoryVO[]>([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
status: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const isExpandAll = ref(true) // 是否展开,默认全部展开
|
||||
const refreshTable = ref(true) // 重新渲染表格状态
|
||||
|
||||
/** 查询商品分类列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ItemCategoryApi.getItemCategoryList(queryParams)
|
||||
list.value = handleTree(data, 'id', 'parentId')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = () => {
|
||||
refreshTable.value = false
|
||||
isExpandAll.value = !isExpandAll.value
|
||||
nextTick(() => {
|
||||
refreshTable.value = true
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number, parentId?: number) => {
|
||||
formRef.value.open(type, id, parentId)
|
||||
}
|
||||
|
||||
/** 删除分类 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ItemCategoryApi.deleteItemCategory(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
<!-- WMS 商品管理 -->
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4" :xs="24">
|
||||
<ContentWrap class="h-1/1">
|
||||
<ItemCategoryTree ref="categoryTreeRef" @node-click="handleCategoryNodeClick" />
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
<el-col :span="20" :xs="24">
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="商品编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品品牌" prop="brandId">
|
||||
<ItemBrandSelect v-model="queryParams.brandId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:span-method="spanMethod"
|
||||
>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div class="text-14px">{{ scope.row.itemName }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
{{ scope.row.itemCode }}
|
||||
</div>
|
||||
<div v-if="scope.row.brandName" class="text-12px text-gray-500">
|
||||
品牌:{{ scope.row.brandName }}
|
||||
</div>
|
||||
<div v-if="scope.row.categoryName" class="text-12px text-gray-500">
|
||||
分类:{{ scope.row.categoryName }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="180">
|
||||
<template #default="scope">
|
||||
<div class="text-14px">{{ scope.row.name }}</div>
|
||||
<div v-if="scope.row.code" class="text-12px text-gray-500">
|
||||
编号:{{ scope.row.code }}
|
||||
</div>
|
||||
<div v-if="scope.row.barCode" class="text-12px text-gray-500">
|
||||
条码:{{ scope.row.barCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" min-width="140">
|
||||
<template #default="scope">
|
||||
<div v-if="!isNullOrUnDef(scope.row.costPrice)">
|
||||
成本价:{{ formatPrice(scope.row.costPrice) }}
|
||||
</div>
|
||||
<div v-if="!isNullOrUnDef(scope.row.sellingPrice)">
|
||||
销售价:{{ formatPrice(scope.row.sellingPrice) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重量(kg)" min-width="140">
|
||||
<template #default="scope">
|
||||
<div v-if="!isNullOrUnDef(scope.row.netWeight)">
|
||||
净重:{{ formatWeight(scope.row.netWeight) }}
|
||||
</div>
|
||||
<div v-if="!isNullOrUnDef(scope.row.grossWeight)">
|
||||
毛重:{{ formatWeight(scope.row.grossWeight) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="长宽高(cm)" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDimensionText(scope.row.length, scope.row.width, scope.row.height) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['wms:item:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.itemId)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:item:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.itemId, scope.row.itemName)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ItemForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import download from '@/utils/download'
|
||||
import { ItemApi, ItemVO } from '@/api/wms/md/item'
|
||||
import { ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import ItemForm from './ItemForm.vue'
|
||||
import ItemCategoryTree from './category/components/ItemCategoryTree.vue'
|
||||
import ItemBrandSelect from './brand/components/ItemBrandSelect.vue'
|
||||
import { isNullOrUnDef } from '@/utils/is'
|
||||
import { formatDimensionText, formatPrice, formatWeight } from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 商品管理 */
|
||||
defineOptions({ name: 'WmsItem' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ItemSkuVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive<{
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
code?: string
|
||||
name?: string
|
||||
categoryId?: number
|
||||
brandId?: number
|
||||
}>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
categoryId: undefined,
|
||||
brandId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const categoryTreeRef = ref() // 分类树
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询商品列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ItemApi.getItemPage(queryParams)
|
||||
list.value = buildItemSkuRows(data.list || [])
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 展开商品 SKU 列表 */
|
||||
const buildItemSkuRows = (items: ItemVO[]) => {
|
||||
return items.flatMap((item) => {
|
||||
const skus = item.skus?.length ? item.skus : [{}]
|
||||
return skus.map((sku) => ({
|
||||
...sku,
|
||||
itemId: item.id,
|
||||
itemCode: item.code,
|
||||
itemName: item.name,
|
||||
categoryId: item.categoryId,
|
||||
categoryName: item.categoryName,
|
||||
unit: item.unit,
|
||||
brandId: item.brandId,
|
||||
brandName: item.brandName
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
categoryTreeRef.value?.reset()
|
||||
queryParams.categoryId = undefined
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 分类树点击 */
|
||||
const handleCategoryNodeClick = (categoryId: number | undefined) => {
|
||||
queryParams.categoryId = categoryId
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除商品 */
|
||||
const handleDelete = async (id: number, name?: string) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.confirm('确认删除商品【' + name + '】吗?')
|
||||
// 发起删除
|
||||
await ItemApi.deleteItem(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ItemApi.exportItem(queryParams)
|
||||
download.excel(data, '商品.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 合并商品维度的重复单元格 */
|
||||
const spanMethod = ({ rowIndex, columnIndex }: { rowIndex: number; columnIndex: number }) => {
|
||||
if (![0, 5].includes(columnIndex)) {
|
||||
return { rowspan: 1, colspan: 1 }
|
||||
}
|
||||
const row = list.value[rowIndex]
|
||||
if (rowIndex > 0 && list.value[rowIndex - 1]?.itemId === row.itemId) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
let rowspan = 1
|
||||
for (let index = rowIndex + 1; index < list.value.length; index++) {
|
||||
if (list.value[index]?.itemId !== row.itemId) {
|
||||
break
|
||||
}
|
||||
rowspan++
|
||||
}
|
||||
return { rowspan, colspan: 1 }
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
<!-- WMS 商品 SKU 选择器 -->
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="dialogVisible"
|
||||
:append-to-body="true"
|
||||
:scroll="true"
|
||||
max-height="calc(100vh - 220px)"
|
||||
title="商品选择"
|
||||
width="80%"
|
||||
>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="itemName">
|
||||
<el-input
|
||||
v-model="queryParams.itemName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="itemCode">
|
||||
<el-input
|
||||
v-model="queryParams.itemCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="条码" prop="barCode">
|
||||
<el-input
|
||||
v-model="queryParams.barCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入条码"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap class="!mb-0">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
border
|
||||
max-height="calc(100vh - 430px)"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleRowDblClick"
|
||||
>
|
||||
<el-table-column
|
||||
:reserve-selection="true"
|
||||
:selectable="isRowSelectable"
|
||||
align="center"
|
||||
type="selection"
|
||||
width="50"
|
||||
/>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
<div v-if="row.brandName" class="text-12px text-gray-500">
|
||||
品牌:{{ row.brandName }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.name || '-' }}</div>
|
||||
<div v-if="row.code" class="text-12px text-gray-500">编号:{{ row.code }}</div>
|
||||
<div v-if="row.barCode" class="text-12px text-gray-500">条码:{{ row.barCode }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.costPrice !== undefined">成本价:{{ formatPrice(row.costPrice) }}</div>
|
||||
<div v-if="row.sellingPrice !== undefined">
|
||||
销售价:{{ formatPrice(row.sellingPrice) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重量(kg)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.netWeight !== undefined">净重:{{ formatWeight(row.netWeight) }}</div>
|
||||
<div v-if="row.grossWeight !== undefined">
|
||||
毛重:{{ formatWeight(row.grossWeight) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="长宽高(cm)" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDimensionText(row.length, row.width, row.height) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="overflow-hidden">
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElTable } from 'element-plus'
|
||||
import { ItemSkuApi, ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import { formatDimensionText, formatPrice, formatWeight } from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 商品 SKU 选择器 */
|
||||
defineOptions({ name: 'WmsItemSkuSelect' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const list = ref<ItemSkuVO[]>([]) // 当前页 SKU 列表
|
||||
const total = ref(0) // 列表的总条数
|
||||
const selectedList = ref<ItemSkuVO[]>([]) // 已选择 SKU 列表
|
||||
const selectedMap = ref<Map<number, ItemSkuVO>>(new Map()) // 跨页已选择 SKU
|
||||
const preSelectedIds = ref<number[]>([]) // 打开弹窗时传入的已选 SKU 编号
|
||||
const disabledSelectedIds = ref<Set<number>>(new Set()) // 已在业务明细中使用的 SKU 编号
|
||||
const multiple = ref(true) // 是否允许多选
|
||||
const preselectDisabled = ref(true) // 是否回显已禁用的 SKU
|
||||
const syncingSingleSelection = ref(false) // 单选模式同步表格勾选状态中
|
||||
const tableRef = ref<InstanceType<typeof ElTable>>() // 表格 Ref
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
itemName: undefined as string | undefined,
|
||||
itemCode: undefined as string | undefined,
|
||||
name: undefined as string | undefined,
|
||||
code: undefined as string | undefined,
|
||||
barCode: undefined as string | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [list: ItemSkuVO[]]
|
||||
}>()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (
|
||||
selectedIds?: number[],
|
||||
options?: { multiple?: boolean; preselectDisabled?: boolean }
|
||||
) => {
|
||||
dialogVisible.value = true
|
||||
multiple.value = options?.multiple ?? true
|
||||
preselectDisabled.value = options?.preselectDisabled ?? true
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
selectedList.value = []
|
||||
selectedMap.value = new Map()
|
||||
preSelectedIds.value = selectedIds || []
|
||||
disabledSelectedIds.value = new Set(preSelectedIds.value)
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
await getList()
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 查询 SKU 列表(服务端分页 + 服务端筛选) */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ItemSkuApi.getItemSkuPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
await nextTick()
|
||||
applyPreSelection()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
queryFormRef.value?.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 恢复预选状态(当前页可见范围内) */
|
||||
const applyPreSelection = () => {
|
||||
const table = tableRef.value
|
||||
if (!table || selectedMap.value.size === 0) {
|
||||
return
|
||||
}
|
||||
list.value.forEach((row) => {
|
||||
if (row.id && selectedMap.value.has(row.id)) {
|
||||
table.toggleRowSelection(row, true, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 是否允许勾选 SKU */
|
||||
const isRowSelectable = (row: ItemSkuVO) => {
|
||||
return row.id === undefined || !disabledSelectedIds.value.has(row.id)
|
||||
}
|
||||
|
||||
/** 选择变化 */
|
||||
const handleSelectionChange = async (rows: ItemSkuVO[]) => {
|
||||
if (syncingSingleSelection.value) {
|
||||
return
|
||||
}
|
||||
if (!multiple.value) {
|
||||
let row: ItemSkuVO | undefined
|
||||
for (let i = rows.length - 1; i >= 0; i--) {
|
||||
const item = rows[i]
|
||||
if (item.id && !disabledSelectedIds.value.has(item.id)) {
|
||||
row = item
|
||||
break
|
||||
}
|
||||
}
|
||||
selectedMap.value = new Map()
|
||||
if (row?.id) {
|
||||
selectedMap.value.set(row.id, row)
|
||||
}
|
||||
selectedList.value = Array.from(selectedMap.value.values())
|
||||
syncingSingleSelection.value = true
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
if (row) {
|
||||
tableRef.value?.toggleRowSelection(row, true)
|
||||
}
|
||||
await nextTick()
|
||||
syncingSingleSelection.value = false
|
||||
return
|
||||
}
|
||||
const currentPageIds = list.value.map((row) => row.id).filter((id): id is number => !!id)
|
||||
currentPageIds.forEach((id) => {
|
||||
if (!disabledSelectedIds.value.has(id)) {
|
||||
selectedMap.value.delete(id)
|
||||
}
|
||||
})
|
||||
rows.forEach((row) => {
|
||||
if (row.id) {
|
||||
selectedMap.value.set(row.id, row)
|
||||
}
|
||||
})
|
||||
selectedList.value = Array.from(selectedMap.value.values())
|
||||
}
|
||||
|
||||
/** 双击行:切换勾选 */
|
||||
const handleRowDblClick = (row: ItemSkuVO) => {
|
||||
if (row.id && disabledSelectedIds.value.has(row.id)) {
|
||||
return
|
||||
}
|
||||
if (!multiple.value) {
|
||||
emit('change', [row])
|
||||
dialogVisible.value = false
|
||||
return
|
||||
}
|
||||
tableRef.value?.toggleRowSelection(row)
|
||||
}
|
||||
|
||||
/** 确认选择 */
|
||||
const handleConfirm = () => {
|
||||
const newSelectedList = selectedList.value.filter(
|
||||
(sku) => sku.id !== undefined && !disabledSelectedIds.value.has(sku.id)
|
||||
)
|
||||
if (newSelectedList.length === 0) {
|
||||
message.warning('请至少选择一条数据')
|
||||
return
|
||||
}
|
||||
emit('change', newSelectedList)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
<!-- WMS 往来企业表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1000px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="往来企业编号" prop="code">
|
||||
<el-input v-model="formData.code" maxlength="20" placeholder="请输入往来企业编号">
|
||||
<template #append>
|
||||
<el-button @click="formData.code = generateWmsCode('M')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="往来企业名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="60" placeholder="请输入往来企业名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="往来企业类型" prop="type">
|
||||
<el-select v-model="formData.type" class="w-1/1" placeholder="请选择往来企业类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_MERCHANT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="级别" prop="level">
|
||||
<el-input v-model="formData.level" maxlength="10" placeholder="请输入级别" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行" prop="bankName">
|
||||
<el-input v-model="formData.bankName" maxlength="255" placeholder="请输入开户行" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行账户" prop="bankAccount">
|
||||
<el-input v-model="formData.bankAccount" maxlength="40" placeholder="请输入银行账户" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="formData.address" maxlength="200" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人" prop="contact">
|
||||
<el-input v-model="formData.contact" maxlength="30" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="formData.mobile" maxlength="13" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="座机号" prop="telephone">
|
||||
<el-input v-model="formData.telephone" maxlength="13" placeholder="请输入座机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Email" prop="email">
|
||||
<el-input v-model="formData.email" maxlength="50" placeholder="请输入 Email" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" maxlength="255" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { MerchantApi, MerchantVO } from '@/api/wms/md/merchant'
|
||||
import { generateWmsCode } from '@/views/wms/utils/constants'
|
||||
|
||||
/** WMS 往来企业表单 */
|
||||
defineOptions({ name: 'WmsMerchantForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<MerchantVO>({
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
level: undefined,
|
||||
bankName: undefined,
|
||||
bankAccount: undefined,
|
||||
address: undefined,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
contact: undefined,
|
||||
email: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
code: [{ required: true, message: '往来企业编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '往来企业名称不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '往来企业类型不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await MerchantApi.getMerchant(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as MerchantVO
|
||||
if (formType.value === 'create') {
|
||||
await MerchantApi.createMerchant(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MerchantApi.updateMerchant(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
level: undefined,
|
||||
bankName: undefined,
|
||||
bankAccount: undefined,
|
||||
address: undefined,
|
||||
mobile: undefined,
|
||||
telephone: undefined,
|
||||
contact: undefined,
|
||||
email: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<!-- WMS 往来企业选择器 -->
|
||||
<template>
|
||||
<el-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:filter-method="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-1/1"
|
||||
filterable
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="merchant in filteredList"
|
||||
:key="merchant.id!"
|
||||
:label="merchant.name"
|
||||
:value="merchant.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MerchantApi, MerchantVO } from '@/api/wms/md/merchant'
|
||||
import { CustomerMerchantTypeList, SupplierMerchantTypeList } from '@/views/wms/utils/constants'
|
||||
|
||||
/** WMS 往来企业选择器 */
|
||||
defineOptions({ name: 'WmsMerchantSelect', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: number
|
||||
supplier?: boolean
|
||||
customer?: boolean
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
placeholder: '请选择往来企业'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number | undefined]
|
||||
change: [merchant: MerchantVO | undefined]
|
||||
}>()
|
||||
|
||||
const merchantList = ref<MerchantVO[]>([]) // 往来企业列表
|
||||
const filteredList = ref<MerchantVO[]>([]) // 过滤后的往来企业列表
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/** 获得可选往来企业列表 */
|
||||
const loadSelectableList = async () => {
|
||||
const types = props.supplier
|
||||
? SupplierMerchantTypeList
|
||||
: props.customer
|
||||
? CustomerMerchantTypeList
|
||||
: undefined
|
||||
merchantList.value = await MerchantApi.getMerchantSimpleList(types ? { types } : undefined)
|
||||
handleFilter('')
|
||||
}
|
||||
|
||||
/** 前端过滤 */
|
||||
const handleFilter = (query: string) => {
|
||||
if (!query) {
|
||||
filteredList.value = merchantList.value
|
||||
return
|
||||
}
|
||||
const keyword = query.toLowerCase()
|
||||
filteredList.value = merchantList.value.filter((merchant) =>
|
||||
merchant.name?.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
/** 选中变化 */
|
||||
const handleChange = (value: number | undefined) => {
|
||||
emit(
|
||||
'change',
|
||||
merchantList.value.find((merchant) => merchant.id === value)
|
||||
)
|
||||
}
|
||||
|
||||
/** 供应商开关变化时,重新加载可选列表 */
|
||||
watch(
|
||||
() => [props.supplier, props.customer],
|
||||
() => loadSelectableList()
|
||||
)
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => loadSelectableList())
|
||||
</script>
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
<!-- WMS 往来企业 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="往来企业编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入往来企业编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="往来企业名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入往来企业名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="往来企业类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择往来企业类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_MERCHANT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:merchant:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:merchant:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 往来企业列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="往来企业编号" prop="code" width="160" />
|
||||
<el-table-column align="center" label="往来企业名称" prop="name" min-width="160" />
|
||||
<el-table-column align="center" label="往来企业类型" prop="type" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_MERCHANT_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="级别" prop="level" width="100" />
|
||||
<el-table-column align="center" label="联系人" prop="contact" width="120" />
|
||||
<el-table-column align="center" label="备注" prop="remark" min-width="160" />
|
||||
<el-table-column align="center" label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['wms:merchant:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:merchant:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<MerchantForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { MerchantApi, MerchantVO } from '@/api/wms/md/merchant'
|
||||
import MerchantForm from './MerchantForm.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 往来企业 */
|
||||
defineOptions({ name: 'WmsMerchant' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<MerchantVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
type: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询往来企业列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await MerchantApi.getMerchantPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除往来企业 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await MerchantApi.deleteMerchant(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await MerchantApi.exportMerchant(queryParams)
|
||||
download.excel(data, '往来企业.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<!-- WMS 仓库表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="仓库名称" prop="name">
|
||||
<el-input v-model="formData.name" maxlength="50" placeholder="请输入仓库名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库编号" prop="code">
|
||||
<el-input v-model="formData.code" maxlength="20" placeholder="请输入仓库编号">
|
||||
<template #append>
|
||||
<el-button @click="formData.code = generateWmsCode('W')">生成</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number
|
||||
v-model="formData.sort"
|
||||
:min="0"
|
||||
class="!w-1/1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WarehouseApi, WarehouseVO } from '@/api/wms/md/warehouse'
|
||||
import { generateWmsCode } from '@/views/wms/utils/constants'
|
||||
|
||||
/** WMS 仓库表单 */
|
||||
defineOptions({ name: 'WmsWarehouseForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<WarehouseVO>({
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: 0,
|
||||
remark: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
code: [{ required: true, message: '仓库编号不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '仓库名称不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await WarehouseApi.getWarehouse(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as WarehouseVO
|
||||
if (formType.value === 'create') {
|
||||
await WarehouseApi.createWarehouse(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await WarehouseApi.updateWarehouse(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
code: undefined,
|
||||
name: undefined,
|
||||
sort: 0,
|
||||
remark: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<!-- WMS 仓库选择器 -->
|
||||
<template>
|
||||
<el-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectValue"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:filter-method="handleFilter"
|
||||
:placeholder="placeholder"
|
||||
class="w-1/1"
|
||||
filterable
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="warehouse in filteredList"
|
||||
:key="warehouse.id!"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WarehouseApi, WarehouseVO } from '@/api/wms/md/warehouse'
|
||||
|
||||
/** WMS 仓库选择器 */
|
||||
defineOptions({ name: 'WmsWarehouseSelect', inheritAttrs: false })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: number
|
||||
disabled?: boolean
|
||||
clearable?: boolean
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
placeholder: '请选择仓库'
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number | undefined]
|
||||
change: [warehouse: WarehouseVO | undefined]
|
||||
}>()
|
||||
|
||||
const warehouseList = ref<WarehouseVO[]>([]) // 仓库列表
|
||||
const filteredList = ref<WarehouseVO[]>([]) // 过滤后的仓库列表
|
||||
|
||||
const selectValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/** 前端过滤 */
|
||||
const handleFilter = (query: string) => {
|
||||
if (!query) {
|
||||
filteredList.value = warehouseList.value
|
||||
return
|
||||
}
|
||||
const keyword = query.toLowerCase()
|
||||
filteredList.value = warehouseList.value.filter((warehouse) =>
|
||||
warehouse.name?.toLowerCase().includes(keyword)
|
||||
)
|
||||
}
|
||||
|
||||
/** 选中变化 */
|
||||
const handleChange = (value: number | undefined) => {
|
||||
emit(
|
||||
'change',
|
||||
warehouseList.value.find((warehouse) => warehouse.id === value)
|
||||
)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
||||
filteredList.value = warehouseList.value
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<!-- WMS 仓库管理 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="仓库名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入仓库名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库编号" prop="code">
|
||||
<el-input
|
||||
v-model="queryParams.code"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入仓库编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:warehouse:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openWarehouseForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:warehouse:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 仓库列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||
<el-table-column align="center" label="仓库名称" prop="name" />
|
||||
<el-table-column align="center" label="仓库编号" prop="code" />
|
||||
<el-table-column align="center" label="备注" prop="remark" />
|
||||
<el-table-column align="center" label="排序" prop="sort" width="100" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column align="center" label="操作" width="210">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPermi="['wms:warehouse:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openWarehouseForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:warehouse:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<WarehouseForm ref="formRef" @success="handleWarehouseSuccess" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { WarehouseApi, WarehouseVO } from '@/api/wms/md/warehouse'
|
||||
import WarehouseForm from './WarehouseForm.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 仓库管理 */
|
||||
defineOptions({ name: 'WmsWarehouse' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<WarehouseVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
code: undefined,
|
||||
name: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询仓库列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await WarehouseApi.getWarehousePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openWarehouseForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 仓库保存成功 */
|
||||
const handleWarehouseSuccess = async () => {
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 删除仓库 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await WarehouseApi.deleteWarehouse(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await handleWarehouseSuccess()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await WarehouseApi.exportWarehouse(queryParams)
|
||||
download.excel(data, '仓库.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
<!-- WMS 盘库单详情 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="盘库单详情" width="1200px">
|
||||
<div v-loading="loading">
|
||||
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="盘库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">
|
||||
{{ detailData.warehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据日期">
|
||||
{{ formatNullableDate(detailData.orderTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据状态">
|
||||
<dict-tag
|
||||
v-if="detailData.status !== undefined"
|
||||
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||
:value="detailData.status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="盈亏数量">
|
||||
<span :class="getLossClass(detailData.totalQuantity)">
|
||||
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总金额">
|
||||
{{ formatPrice(detailData.totalPrice) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实际金额">
|
||||
{{ formatPrice(detailData.actualPrice) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实际盈亏金额">
|
||||
<span :class="getLossClass(getOrderDifferencePrice(detailData))">
|
||||
{{ formatPrice(getOrderDifferencePrice(detailData)) || '-' }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatNullableDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatNullableDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="备注">
|
||||
{{ detailData.remark || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mb-16px mt-24px text-18px font-bold">商品明细</div>
|
||||
<el-table :data="detailData.details || []" :summary-method="getSummaries" border show-summary>
|
||||
<el-table-column label="商品信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.skuName || '-' }}</div>
|
||||
<div v-if="row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="账面数量" prop="quantity" width="120">
|
||||
<template #default="{ row }">{{ formatQuantity(row.quantity) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" prop="price" width="120">
|
||||
<template #default="{ row }">{{ formatPrice(row.price) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实盘数量" prop="checkQuantity" width="120">
|
||||
<template #default="{ row }">{{ formatQuantity(row.checkQuantity) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际金额(元)" prop="actualPrice" width="140">
|
||||
<template #default="{ row }">{{ formatPrice(getActualPrice(row)) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="盈亏数量" prop="differenceQuantity" width="120">
|
||||
<template #default="{ row }">
|
||||
<span :class="getLossClass(getDifferenceQuantity(row))">
|
||||
{{ formatQuantity(getDifferenceQuantity(row)) || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际盈亏金额(元)" prop="differencePrice" width="160">
|
||||
<template #default="{ row }">
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{
|
||||
formatPrice(getDifferencePrice(row)) || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue'
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
getLossClass,
|
||||
roundPrice,
|
||||
sumPrice,
|
||||
sumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 盘库单详情 */
|
||||
defineOptions({ name: 'WmsCheckOrderDetail' })
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const detailData = ref<CheckOrderVO>({})
|
||||
|
||||
function getOrderDifferencePrice(order: CheckOrderVO) {
|
||||
return roundPrice(Number(order.actualPrice || 0) - Number(order.totalPrice || 0))
|
||||
}
|
||||
|
||||
function getDifferenceQuantity(detail: CheckOrderDetailVO) {
|
||||
return Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
}
|
||||
|
||||
function getActualPrice(detail: CheckOrderDetailVO) {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
}
|
||||
function getDifferencePrice(detail: CheckOrderDetailVO) {
|
||||
if (detail.price === undefined || detail.price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(getDifferenceQuantity(detail) * Number(detail.price))
|
||||
}
|
||||
function renderLossText(
|
||||
value: number | string | null | undefined,
|
||||
formatter: (value?: number | string | null) => string
|
||||
) {
|
||||
return h('span', { class: getLossClass(value) }, formatter(value))
|
||||
}
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getSummaries({ columns, data }: { columns: any[]; data: CheckOrderDetailVO[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'checkQuantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.checkQuantity)
|
||||
}
|
||||
if (column.property === 'actualPrice') {
|
||||
return formatSumPrice(data, (detail) => getActualPrice(detail))
|
||||
}
|
||||
if (column.property === 'differenceQuantity') {
|
||||
return renderLossText(
|
||||
sumQuantity(data, (detail) => getDifferenceQuantity(detail)),
|
||||
formatQuantity
|
||||
)
|
||||
}
|
||||
if (column.property === 'differencePrice') {
|
||||
return renderLossText(
|
||||
sumPrice(data, (detail) => getDifferencePrice(detail)),
|
||||
formatPrice
|
||||
)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
detailData.value = await CheckOrderApi.getCheckOrder(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,668 @@
|
|||
<!-- WMS 盘库单表单 -->
|
||||
<template>
|
||||
<Dialog v-model="warehouseDialogVisible" title="选择盘库仓库" width="420px">
|
||||
<el-form
|
||||
ref="warehouseFormRef"
|
||||
:model="warehouseFormData"
|
||||
:rules="warehouseFormRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="warehouseFormData.warehouseId" @change="handleWarehouseSelect" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleStartCheck">开始盘库</el-button>
|
||||
<el-button @click="warehouseDialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="92px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="盘库单号" prop="no">
|
||||
<el-input v-model="formData.no" maxlength="64" placeholder="请输入盘库单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="formData.warehouseId" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="formData.orderTime"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择单据日期"
|
||||
type="date"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="实际金额">
|
||||
<el-input :model-value="formatPrice(actualPrice)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">盘库明细</span>
|
||||
<el-tooltip content="请先选择仓库" :disabled="!!formData.warehouseId" placement="top">
|
||||
<span>
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleImportAllInventory"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导入仓库库存
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddSkuInventory"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加盘点商品
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table
|
||||
:data="formData.details"
|
||||
:summary-method="getDetailSummaries"
|
||||
border
|
||||
empty-text="暂无商品明细"
|
||||
show-summary
|
||||
>
|
||||
<el-table-column label="商品信息" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.skuName || '-' }}</div>
|
||||
<div v-if="row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="账面库存" prop="quantity" width="120">
|
||||
<template #default="{ row }">{{ formatQuantity(row.quantity) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价(元)" prop="price" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.price"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="单价"
|
||||
@change="handleDetailPriceChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际库存" prop="checkQuantity" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.checkQuantity"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="数量"
|
||||
@change="handleDetailCheckQuantityChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际金额(元)" prop="actualPrice" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.actualPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="金额"
|
||||
@change="handleDetailActualPriceChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="盈亏数" prop="differenceQuantity" width="120">
|
||||
<template #default="{ row }">
|
||||
<span :class="getLossClass(getDifferenceQuantity(row))">{{
|
||||
formatQuantity(getDifferenceQuantity(row))
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际盈亏金额(元)" prop="differencePrice" width="160">
|
||||
<template #default="{ row }">
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{
|
||||
formatPrice(getDifferencePrice(row))
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="handleDeleteDetail($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ItemSkuSelect ref="skuSelectRef" @change="handleSelectSku" />
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:check-order:complete']"
|
||||
:disabled="formLoading"
|
||||
type="success"
|
||||
@click="handleComplete"
|
||||
>
|
||||
完成盘库
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:check-order:cancel']"
|
||||
:disabled="formLoading"
|
||||
type="danger"
|
||||
@click="handleCancel"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormRules } from 'element-plus'
|
||||
import { h } from 'vue'
|
||||
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||
import { InventoryApi, InventoryVO } from '@/api/wms/inventory'
|
||||
import { ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import { WarehouseVO } from '@/api/wms/md/warehouse'
|
||||
import ItemSkuSelect from '@/views/wms/md/item/sku/components/ItemSkuSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||
import {
|
||||
dividePrice,
|
||||
getLossClass,
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION,
|
||||
roundPrice,
|
||||
sumPrice,
|
||||
sumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
import { generateOrderNo } from '@/views/wms/utils/order'
|
||||
|
||||
/** WMS 盘库单表单 */
|
||||
defineOptions({ name: 'WmsCheckOrderForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formType = ref('')
|
||||
const originalFormData = ref('')
|
||||
const warehouseDialogVisible = ref(false)
|
||||
|
||||
type CheckOrderFormDetail = CheckOrderDetailVO & { actualPrice?: number }
|
||||
type CheckOrderFormData = Omit<CheckOrderVO, 'details'> & { details?: CheckOrderFormDetail[] }
|
||||
type CheckInventoryRow = InventoryVO & { price?: number; availableQuantity?: number }
|
||||
|
||||
const formData = ref<CheckOrderFormData>({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
no: [{ required: true, message: '盘库单号不能为空', trigger: 'blur' }],
|
||||
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref()
|
||||
const skuSelectRef = ref()
|
||||
const warehouseFormRef = ref()
|
||||
const warehouseFormData = reactive({
|
||||
warehouseId: undefined as number | undefined,
|
||||
warehouseName: undefined as string | undefined
|
||||
})
|
||||
const warehouseFormRules = reactive<FormRules>({
|
||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
function getDifferenceQuantity(detail: CheckOrderFormDetail) {
|
||||
return Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
}
|
||||
|
||||
function getBookPrice(detail: CheckOrderFormDetail) {
|
||||
return multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
function getActualPrice(detail: CheckOrderFormDetail) {
|
||||
return detail.actualPrice ?? multiplyPrice(detail.checkQuantity, detail.price)
|
||||
}
|
||||
|
||||
function getDifferencePrice(detail: CheckOrderFormDetail) {
|
||||
if (detail.price === undefined || detail.price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(getDifferenceQuantity(detail) * Number(detail.price))
|
||||
}
|
||||
|
||||
function renderLossText(
|
||||
value: number | string | null | undefined,
|
||||
formatter: (value?: number | string | null) => string
|
||||
) {
|
||||
return h('span', { class: getLossClass(value) }, formatter(value))
|
||||
}
|
||||
const totalQuantity = computed(() =>
|
||||
sumQuantity(formData.value.details || [], (detail) => getDifferenceQuantity(detail))
|
||||
)
|
||||
const totalPrice = computed(() =>
|
||||
sumPrice(formData.value.details || [], (detail) => getBookPrice(detail))
|
||||
)
|
||||
const actualPrice = computed(() =>
|
||||
sumPrice(formData.value.details || [], (detail) => getActualPrice(detail))
|
||||
)
|
||||
const differencePrice = computed(() => roundPrice(actualPrice.value - totalPrice.value) || 0)
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
(formData.value.status !== undefined && OrderUpdateStatusList.includes(formData.value.status))
|
||||
)
|
||||
const isSavedPrepareOrder = computed(
|
||||
() =>
|
||||
!!formData.value.id &&
|
||||
formData.value.status !== undefined &&
|
||||
OrderUpdateStatusList.includes(formData.value.status)
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
formType.value = type
|
||||
if (type === 'create') {
|
||||
dialogVisible.value = false
|
||||
resetForm()
|
||||
warehouseDialogVisible.value = true
|
||||
warehouseFormData.warehouseId = undefined
|
||||
warehouseFormData.warehouseName = undefined
|
||||
await nextTick()
|
||||
warehouseFormRef.value?.clearValidate()
|
||||
return
|
||||
}
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
resetForm()
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const order = await CheckOrderApi.getCheckOrder(id)
|
||||
formData.value = { ...order, details: normalizeDetails(order.details || []) }
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 选择仓库后开始盘库 */
|
||||
const handleStartCheck = async () => {
|
||||
await warehouseFormRef.value.validate()
|
||||
warehouseDialogVisible.value = false
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.create')
|
||||
resetForm()
|
||||
formData.value.warehouseId = warehouseFormData.warehouseId
|
||||
formData.value.warehouseName = warehouseFormData.warehouseName
|
||||
await nextTick()
|
||||
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||
}
|
||||
|
||||
/** 记录选中的盘库仓库 */
|
||||
const handleWarehouseSelect = (warehouse: WarehouseVO | undefined) => {
|
||||
warehouseFormData.warehouseName = warehouse?.name
|
||||
}
|
||||
|
||||
/** 构建盘库明细 */
|
||||
function buildDetail(inventory: CheckInventoryRow): CheckOrderFormDetail {
|
||||
return {
|
||||
id: undefined,
|
||||
itemId: inventory.itemId,
|
||||
itemCode: inventory.itemCode,
|
||||
itemName: inventory.itemName,
|
||||
unit: inventory.unit,
|
||||
skuId: inventory.skuId,
|
||||
skuCode: inventory.skuCode,
|
||||
skuName: inventory.skuName,
|
||||
inventoryId: inventory.id,
|
||||
warehouseId: inventory.warehouseId,
|
||||
warehouseName: inventory.warehouseName,
|
||||
quantity: inventory.availableQuantity,
|
||||
checkQuantity: inventory.availableQuantity,
|
||||
availableQuantity: inventory.availableQuantity,
|
||||
price: inventory.price,
|
||||
actualPrice: multiplyPrice(inventory.availableQuantity, inventory.price)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetails(details: CheckOrderDetailVO[]) {
|
||||
return details.map((detail) => ({
|
||||
...detail,
|
||||
actualPrice: multiplyPrice(detail.checkQuantity, detail.price)
|
||||
}))
|
||||
}
|
||||
|
||||
/** 导入当前仓库的全部库存余额 */
|
||||
const handleImportAllInventory = async () => {
|
||||
if (!formData.value.warehouseId) {
|
||||
message.warning('请先选择仓库')
|
||||
return
|
||||
}
|
||||
if (formData.value.details?.length) {
|
||||
await message.confirm('导入仓库库存会覆盖当前盘库明细,是否继续?')
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const inventories = await loadWarehouseInventoryList()
|
||||
formData.value.details = inventories.map((inventory) =>
|
||||
buildDetail({ ...inventory, availableQuantity: inventory.quantity })
|
||||
)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开盘点商品添加弹窗 */
|
||||
const handleAddSkuInventory = () => {
|
||||
if (!formData.value.warehouseId) {
|
||||
message.warning('请先选择仓库')
|
||||
return
|
||||
}
|
||||
skuSelectRef.value?.open(getSelectedSkuIds(), { multiple: false, preselectDisabled: false })
|
||||
}
|
||||
|
||||
/** 选择商品 SKU */
|
||||
const handleSelectSku = async (skus: ItemSkuVO[]) => {
|
||||
if (!skus.length) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const warehouseInventoryMap = await getWarehouseInventoryMap()
|
||||
formData.value.details = formData.value.details || []
|
||||
const selectedSkuIds = new Set(getSelectedSkuIds())
|
||||
skus.forEach((sku) => {
|
||||
if (!sku.id || selectedSkuIds.has(sku.id)) {
|
||||
return
|
||||
}
|
||||
const inventory = warehouseInventoryMap.get(sku.id)
|
||||
formData.value.details!.push(
|
||||
inventory
|
||||
? buildDetail({ ...inventory, availableQuantity: inventory.quantity })
|
||||
: buildZeroInventoryDetail(sku)
|
||||
)
|
||||
selectedSkuIds.add(sku.id)
|
||||
})
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获得已选 SKU 编号 */
|
||||
const getSelectedSkuIds = () => {
|
||||
return (formData.value.details || [])
|
||||
.map((detail) => detail.skuId)
|
||||
.filter((id): id is number => !!id)
|
||||
}
|
||||
|
||||
/** 查询当前仓库全部库存余额 */
|
||||
const loadWarehouseInventoryList = async (): Promise<InventoryVO[]> => {
|
||||
return await InventoryApi.getInventoryList({ warehouseId: formData.value.warehouseId! })
|
||||
}
|
||||
|
||||
/** 获得当前仓库 SKU 对应库存余额 */
|
||||
const getWarehouseInventoryMap = async (): Promise<Map<number, InventoryVO>> => {
|
||||
const inventories = await loadWarehouseInventoryList()
|
||||
return new Map(
|
||||
inventories
|
||||
.filter((inventory) => !!inventory.skuId)
|
||||
.map((inventory) => [inventory.skuId!, inventory] as const)
|
||||
)
|
||||
}
|
||||
|
||||
/** 构建零库存盘库明细 */
|
||||
function buildZeroInventoryDetail(sku: ItemSkuVO): CheckOrderFormDetail {
|
||||
return {
|
||||
id: undefined,
|
||||
itemId: sku.itemId,
|
||||
itemCode: sku.itemCode,
|
||||
itemName: sku.itemName,
|
||||
unit: sku.unit,
|
||||
skuId: sku.id,
|
||||
skuCode: sku.code,
|
||||
skuName: sku.name,
|
||||
inventoryId: undefined,
|
||||
warehouseId: formData.value.warehouseId,
|
||||
warehouseName: formData.value.warehouseName,
|
||||
quantity: 0,
|
||||
checkQuantity: 0,
|
||||
availableQuantity: 0,
|
||||
price: sku.costPrice,
|
||||
actualPrice: 0
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
formData.value.details?.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleDetailCheckQuantityChange = (detail: CheckOrderFormDetail) => {
|
||||
if (detail.price !== undefined && detail.price !== null) {
|
||||
detail.actualPrice = multiplyPrice(detail.checkQuantity, detail.price)
|
||||
return
|
||||
}
|
||||
detail.price = dividePrice(detail.actualPrice, detail.checkQuantity)
|
||||
}
|
||||
|
||||
const handleDetailPriceChange = (detail: CheckOrderFormDetail) => {
|
||||
detail.actualPrice = multiplyPrice(detail.checkQuantity, detail.price)
|
||||
}
|
||||
|
||||
const handleDetailActualPriceChange = (detail: CheckOrderFormDetail) => {
|
||||
detail.price = dividePrice(detail.actualPrice, detail.checkQuantity)
|
||||
}
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getDetailSummaries({ columns, data }: { columns: any[]; data: CheckOrderFormDetail[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'checkQuantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.checkQuantity)
|
||||
}
|
||||
if (column.property === 'actualPrice') {
|
||||
return formatSumPrice(data, (detail) => getActualPrice(detail))
|
||||
}
|
||||
if (column.property === 'differenceQuantity') {
|
||||
return renderLossText(totalQuantity.value, formatQuantity)
|
||||
}
|
||||
if (column.property === 'differencePrice') {
|
||||
return renderLossText(differencePrice.value, formatPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 校验明细 */
|
||||
const validateDetails = (required: boolean) => {
|
||||
if (!formData.value.details?.length) {
|
||||
if (required) {
|
||||
message.error('至少包含一条盘库明细')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < formData.value.details.length; i++) {
|
||||
const detail = formData.value.details[i]
|
||||
if (detail.checkQuantity === undefined || detail.checkQuantity < 0) {
|
||||
message.error(`第 ${i + 1} 行明细实盘数量不能小于 0`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
actualPrice: _actualPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: (details || []).map(
|
||||
({ actualPrice: _rowActualPrice, availableQuantity: _availableQuantity, ...detail }) => detail
|
||||
)
|
||||
} as CheckOrderVO
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(false)) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = buildSubmitData()
|
||||
if (formType.value === 'create') {
|
||||
await CheckOrderApi.createCheckOrder(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CheckOrderApi.updateCheckOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 完成盘库:表单修改过则先保存,再完成 */
|
||||
const handleComplete = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(true)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm('确认完成盘库?完成后将更新库存。')
|
||||
formLoading.value = true
|
||||
const data = buildSubmitData()
|
||||
if (JSON.stringify(data) !== originalFormData.value) {
|
||||
await CheckOrderApi.updateCheckOrder(data)
|
||||
}
|
||||
await CheckOrderApi.completeCheckOrder(formData.value.id!)
|
||||
message.success('盘库成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
// 用户点击确认框取消按钮、或网络异常时跳过
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 作废盘库单 */
|
||||
const handleCancel = async () => {
|
||||
try {
|
||||
await message.confirm('确认作废该盘库单?作废后不可恢复。')
|
||||
formLoading.value = true
|
||||
await CheckOrderApi.cancelCheckOrder(formData.value.id!)
|
||||
message.success('作废成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
// 用户点击确认框取消按钮、或网络异常时跳过
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: generateOrderNo('PK'),
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
}
|
||||
originalFormData.value = ''
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
<!-- WMS 盘库单打印 -->
|
||||
<template>
|
||||
<div class="hidden">
|
||||
<button ref="printButtonRef" v-print="printObj" type="button"></button>
|
||||
<div id="wmsCheckOrderPrint" class="color-#303133">
|
||||
<div class="relative mb-8px">
|
||||
<h2 class="text-center">盘库单</h2>
|
||||
<div v-if="printData.no" class="absolute right-0 top-0">
|
||||
<Barcode
|
||||
:content="printData.no"
|
||||
:display-value="false"
|
||||
:format="BarcodeFormatEnum.CODE39"
|
||||
:height="40"
|
||||
:width="180"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-12px grid grid-cols-3 gap-x-24px gap-y-8px text-14px">
|
||||
<div>盘库单号:{{ printData.no || '-' }}</div>
|
||||
<div>仓库:{{ printData.warehouseName || '-' }}</div>
|
||||
<div>盘库状态:{{ getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, printData.status) || '-' }}</div>
|
||||
<div>单据日期:{{ formatNullableDate(printData.orderTime, 'YYYY-MM-DD') }}</div>
|
||||
<div>
|
||||
盈亏数量:<span :class="getLossClass(printData.totalQuantity)">
|
||||
{{ formatQuantity(printData.totalQuantity) || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div>总金额:{{ formatPrice(printData.totalPrice) || '-' }}</div>
|
||||
<div>实际金额:{{ formatPrice(printData.actualPrice) || '-' }}</div>
|
||||
<div>
|
||||
实际盈亏金额:<span :class="getLossClass(getOrderDifferencePrice(printData))">
|
||||
{{ formatPrice(getOrderDifferencePrice(printData)) || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-3 grid grid-cols-2 gap-x-24px">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(printData.createTime) }} /
|
||||
{{ printData.creatorName || printData.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(printData.updateTime) }} /
|
||||
{{ printData.updaterName || printData.updater || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3">备注:{{ printData.remark || '-' }}</div>
|
||||
</div>
|
||||
<table class="w-full border-collapse text-13px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">商品信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">规格信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">账面库存</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">单价(元)</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">实际库存</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">
|
||||
实际金额(元)
|
||||
</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">盈亏数</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">
|
||||
实际盈亏金额(元)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="detail in printRows" :key="detail.id">
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px">编号:{{ detail.itemCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px">编号:{{ detail.skuCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.quantity) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.price) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.checkQuantity) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.actualPrice) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
<span :class="getLossClass(detail.differenceQuantity)">
|
||||
{{ formatQuantity(detail.differenceQuantity) || '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
<span :class="getLossClass(detail.differencePrice)">
|
||||
{{ formatPrice(detail.differencePrice) || '-' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="printRows.length">
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px" colspan="2">合计</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumQuantity(printRows, (detail) => detail.quantity) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumQuantity(printRows, (detail) => detail.checkQuantity) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumPrice(printRows, (detail) => detail.actualPrice) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
<span :class="getLossClass(totalDifferenceQuantity)">
|
||||
{{ formatQuantity(totalDifferenceQuantity) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
<span :class="getLossClass(totalDifferencePrice)">
|
||||
{{ formatPrice(totalDifferencePrice) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!printRows.length">
|
||||
<td
|
||||
class="border border-solid border-#dcdfe6 p-8px text-center"
|
||||
:colspan="tableColumnCount"
|
||||
>
|
||||
暂无明细
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||
import Barcode from '@/views/mes/wm/barcode/components/Barcode.vue'
|
||||
import { BarcodeFormatEnum } from '@/views/mes/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
getLossClass,
|
||||
roundPrice,
|
||||
sumPrice,
|
||||
sumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 盘库单打印 */
|
||||
defineOptions({ name: 'WmsCheckOrderPrint' })
|
||||
|
||||
const printData = ref<CheckOrderVO>({}) // 打印数据
|
||||
const printButtonRef = ref<HTMLButtonElement>() // 打印按钮
|
||||
const tableColumnCount = 8
|
||||
const printObj = ref({
|
||||
id: 'wmsCheckOrderPrint',
|
||||
popTitle: ' ',
|
||||
extraCss: '/print.css',
|
||||
extraHead: '',
|
||||
zIndex: 20003
|
||||
})
|
||||
|
||||
interface PrintRow extends CheckOrderDetailVO {
|
||||
actualPrice?: number
|
||||
differenceQuantity?: number
|
||||
differencePrice?: number
|
||||
}
|
||||
|
||||
function getDifferenceQuantity(detail: CheckOrderDetailVO) {
|
||||
return Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
}
|
||||
|
||||
function getActualPrice(detail: CheckOrderDetailVO) {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
}
|
||||
function getDifferencePrice(detail: CheckOrderDetailVO, differenceQuantity: number) {
|
||||
if (detail.price === undefined || detail.price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(differenceQuantity * Number(detail.price))
|
||||
}
|
||||
function getOrderDifferencePrice(order: CheckOrderVO) {
|
||||
return roundPrice(Number(order.actualPrice || 0) - Number(order.totalPrice || 0))
|
||||
}
|
||||
const printRows = computed<PrintRow[]>(() =>
|
||||
(printData.value.details || []).map((detail) => {
|
||||
const differenceQuantity = getDifferenceQuantity(detail)
|
||||
return {
|
||||
...detail,
|
||||
actualPrice: getActualPrice(detail),
|
||||
differenceQuantity,
|
||||
differencePrice: getDifferencePrice(detail, differenceQuantity)
|
||||
}
|
||||
})
|
||||
)
|
||||
const totalDifferenceQuantity = computed(() =>
|
||||
sumQuantity(printRows.value, (detail) => detail.differenceQuantity)
|
||||
)
|
||||
const totalDifferencePrice = computed(() =>
|
||||
sumPrice(printRows.value, (detail) => detail.differencePrice)
|
||||
)
|
||||
|
||||
/** 打印盘库单 */
|
||||
const print = async (id: number) => {
|
||||
printData.value = await CheckOrderApi.getCheckOrder(id)
|
||||
await nextTick()
|
||||
printButtonRef.value?.click()
|
||||
}
|
||||
defineExpose({ print })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,618 @@
|
|||
<!-- WMS 盘库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="盘库单号" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入盘库单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择单据状态"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="queryParams.warehouseId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.orderTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="盈亏数量" prop="totalQuantityMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMin"
|
||||
:controls="false"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMax"
|
||||
:controls="false"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额" prop="totalPriceMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="实际金额" prop="actualPriceMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.actualPriceMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.actualPriceMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="更新用户" prop="updater">
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.updateTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
表格设置
|
||||
</el-button>
|
||||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="grid grid-cols-3 gap-y-14px rounded bg-[var(--el-fill-color-light)] p-16px"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="column in tableColumnOptions"
|
||||
:key="column.value"
|
||||
class="!h-28px !mr-0 [&_.el-checkbox__label]:font-600 [&_.el-checkbox__label]:text-16px"
|
||||
:label="column.value"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-popover>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 盘库单列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<el-table-column type="expand" width="48">
|
||||
<template #default="scope">
|
||||
<el-table :data="detailMap[scope.row.id] || []" border>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.itemName || '-' }}</div>
|
||||
<div v-if="detailScope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ detailScope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.skuName || '-' }}</div>
|
||||
<div v-if="detailScope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ detailScope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="账面数量" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatQuantity(detailScope.row.quantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实盘数量" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatQuantity(detailScope.row.checkQuantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(detailScope.row.price) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际金额(元)" width="140">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(getDetailActualPrice(detailScope.row)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="盈亏数量" width="120">
|
||||
<template #default="detailScope">
|
||||
<span :class="getLossClass(getDetailDifferenceQuantity(detailScope.row))">
|
||||
{{ formatQuantity(getDetailDifferenceQuantity(detailScope.row)) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际盈亏金额(元)" width="160">
|
||||
<template #default="detailScope">
|
||||
<span :class="getLossClass(getDetailDifferencePrice(detailScope.row))">
|
||||
{{ formatPrice(getDetailDifferencePrice(detailScope.row)) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('no')" fixed="left" label="单号" width="210">
|
||||
<template #default="scope">
|
||||
单号:
|
||||
<el-button link type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.no }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('status')"
|
||||
align="center"
|
||||
fixed="left"
|
||||
label="盘库状态"
|
||||
width="110"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="盈亏/金额(元)"
|
||||
min-width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>盈亏数:</span>
|
||||
<span :class="getLossClass(scope.row.totalQuantity)">{{
|
||||
formatQuantity(scope.row.totalQuantity)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>总金额:</span>
|
||||
<span>{{ formatPrice(scope.row.totalPrice) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>实际金额:</span>
|
||||
<span>{{ formatPrice(scope.row.actualPrice) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>盈亏金额:</span>
|
||||
<span :class="getLossClass(getDifferencePrice(scope.row))">{{
|
||||
formatPrice(getDifferencePrice(scope.row))
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(scope.row.createTime) }} /
|
||||
{{ scope.row.creatorName || scope.row.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(scope.row.updateTime) }} /
|
||||
{{ scope.row.updaterName || scope.row.updater || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('remark')"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
prop="remark"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:content="getCheckOrderUpdateTip(scope.row.status)"
|
||||
:disabled="canUpdateCheckOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:update']"
|
||||
:disabled="!canUpdateCheckOrder(scope.row.status)"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="getCheckOrderDeleteTip(scope.row.status)"
|
||||
:disabled="canDeleteCheckOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:delete']"
|
||||
:disabled="!canDeleteCheckOrder(scope.row.status)"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrint(scope.row.id)"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CheckOrderForm ref="formRef" @success="getList" />
|
||||
<CheckOrderDetail ref="detailRef" />
|
||||
<CheckOrderPrint ref="printRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
getLossClass,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION,
|
||||
roundPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||
import CheckOrderDetail from './CheckOrderDetail.vue'
|
||||
import CheckOrderForm from './CheckOrderForm.vue'
|
||||
import CheckOrderPrint from './CheckOrderPrint.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 盘库单 */
|
||||
defineOptions({ name: 'WmsCheckOrder' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
type TableColumnKey = 'no' | 'status' | 'warehouse' | 'quantityAmount' | 'operateInfo' | 'remark'
|
||||
|
||||
const tableColumnOptions: Array<{ label: string; value: TableColumnKey }> = [
|
||||
{ label: '单号', value: 'no' },
|
||||
{ label: '盘库状态', value: 'status' },
|
||||
{ label: '仓库', value: 'warehouse' },
|
||||
{ label: '盈亏/金额', value: 'quantityAmount' },
|
||||
{ label: '操作信息', value: 'operateInfo' },
|
||||
{ label: '备注', value: 'remark' }
|
||||
]
|
||||
const checkedTableColumns = ref<TableColumnKey[]>([
|
||||
'no',
|
||||
'status',
|
||||
'warehouse',
|
||||
'quantityAmount',
|
||||
'operateInfo',
|
||||
'remark'
|
||||
])
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<CheckOrderVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
no: undefined as string | undefined,
|
||||
status: undefined as number | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
orderTime: undefined as string[] | undefined,
|
||||
totalQuantityMin: undefined as number | undefined,
|
||||
totalQuantityMax: undefined as number | undefined,
|
||||
totalPriceMin: undefined as number | undefined,
|
||||
totalPriceMax: undefined as number | undefined,
|
||||
actualPriceMin: undefined as number | undefined,
|
||||
actualPriceMax: undefined as number | undefined,
|
||||
creator: undefined as number | undefined,
|
||||
updater: undefined as number | undefined,
|
||||
createTime: undefined as string[] | undefined,
|
||||
updateTime: undefined as string[] | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const detailMap = reactive<Record<number, CheckOrderDetailVO[]>>({}) // 盘库单明细数据
|
||||
|
||||
/** 判断表格列是否可见 */
|
||||
function isTableColumnVisible(column: TableColumnKey) {
|
||||
return checkedTableColumns.value.includes(column)
|
||||
}
|
||||
|
||||
/** 是否允许修改盘库单 */
|
||||
const canUpdateCheckOrder = (status?: number) => {
|
||||
return status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 是否允许删除盘库单 */
|
||||
const canDeleteCheckOrder = (status?: number) => {
|
||||
return status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 获得盘库单修改禁用提示 */
|
||||
const getCheckOrderUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已盘库,无法修改'
|
||||
}
|
||||
if (status === OrderStatusEnum.CANCELED) {
|
||||
return '已作废,无法修改'
|
||||
}
|
||||
return '当前状态无法修改'
|
||||
}
|
||||
|
||||
/** 获得盘库单删除禁用提示 */
|
||||
const getCheckOrderDeleteTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已盘库,无法删除'
|
||||
}
|
||||
return '当前状态无法删除'
|
||||
}
|
||||
|
||||
/** 计算盘库单盈亏金额 */
|
||||
const getDifferencePrice = (row: CheckOrderVO) => {
|
||||
return roundPrice(Number(row.actualPrice || 0) - Number(row.totalPrice || 0))
|
||||
}
|
||||
|
||||
/** 计算明细盈亏数量 */
|
||||
const getDetailDifferenceQuantity = (detail: CheckOrderDetailVO) => {
|
||||
return Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
}
|
||||
|
||||
/** 计算明细实际金额 */
|
||||
const getDetailActualPrice = (detail: CheckOrderDetailVO) => {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
}
|
||||
|
||||
/** 计算明细盈亏金额 */
|
||||
const getDetailDifferencePrice = (detail: CheckOrderDetailVO) => {
|
||||
if (detail.price === undefined || detail.price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(getDetailDifferenceQuantity(detail) * Number(detail.price))
|
||||
}
|
||||
|
||||
/** 查询盘库单列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CheckOrderApi.getCheckOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 展开明细 */
|
||||
const handleExpandChange = async (row: CheckOrderVO, expandedRows: CheckOrderVO[]) => {
|
||||
if (!row.id || !expandedRows.some((item) => item.id === row.id)) {
|
||||
return
|
||||
}
|
||||
delete detailMap[row.id]
|
||||
detailMap[row.id] = await CheckOrderApi.getCheckOrderDetailListByOrderId(row.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 查看盘库单详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 打印盘库单 */
|
||||
const printRef = ref()
|
||||
const handlePrint = (id: number) => {
|
||||
printRef.value.print(id)
|
||||
}
|
||||
|
||||
/** 删除盘库单 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await CheckOrderApi.deleteCheckOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await CheckOrderApi.exportCheckOrder(queryParams)
|
||||
download.excel(data, '盘库单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<!-- WMS 移库单详情 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="移库单详情" width="1200px">
|
||||
<div v-loading="loading">
|
||||
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="移库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="来源仓库">
|
||||
{{ detailData.sourceWarehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="目标仓库">
|
||||
{{ detailData.targetWarehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据日期">
|
||||
{{ formatNullableDate(detailData.orderTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据状态">
|
||||
<dict-tag
|
||||
v-if="detailData.status !== undefined"
|
||||
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||
:value="detailData.status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总数量">
|
||||
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总金额">
|
||||
{{ formatPrice(detailData.totalPrice) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatNullableDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatNullableDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="备注">
|
||||
{{ detailData.remark || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mb-16px mt-24px text-18px font-bold">商品明细</div>
|
||||
<el-table :data="detailRows" :summary-method="getSummaries" border show-summary>
|
||||
<el-table-column label="商品信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.skuName || '-' }}</div>
|
||||
<div v-if="row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="数量" prop="quantity" width="120">
|
||||
<template #default="{ row }">{{ formatQuantity(row.quantity) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" prop="price" width="140">
|
||||
<template #default="{ row }">{{ formatPrice(row.price) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="金额(元)" prop="totalPrice" width="140">
|
||||
<template #default="{ row }">{{ formatPrice(row.totalPrice) || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 移库单详情 */
|
||||
defineOptions({ name: 'WmsMovementOrderDetail' })
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const detailData = ref<MovementOrderVO>({})
|
||||
|
||||
interface DetailRow extends MovementOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const detailRows = computed<DetailRow[]>(() =>
|
||||
(detailData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getSummaries({ columns, data }: { columns: any[]; data: DetailRow[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
detailData.value = await MovementOrderApi.getMovementOrder(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,507 @@
|
|||
<!-- WMS 移库单表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="98px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="移库单号" prop="no">
|
||||
<el-input v-model="formData.no" maxlength="64" placeholder="请输入移库单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来源仓库" prop="sourceWarehouseId">
|
||||
<WarehouseSelect
|
||||
v-model="formData.sourceWarehouseId"
|
||||
@change="handleSourceWarehouseChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="目标仓库" prop="targetWarehouseId">
|
||||
<WarehouseSelect
|
||||
v-model="formData.targetWarehouseId"
|
||||
@change="handleTargetWarehouseChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="formData.orderTime"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择单据日期"
|
||||
type="date"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">移库明细</span>
|
||||
<el-tooltip
|
||||
content="请先选择来源仓库和目标仓库"
|
||||
:disabled="canAddDetail"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
:disabled="!canAddDetail"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table
|
||||
:data="formData.details"
|
||||
:summary-method="getDetailSummaries"
|
||||
border
|
||||
empty-text="暂无商品明细"
|
||||
show-summary
|
||||
>
|
||||
<el-table-column label="商品信息" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.skuName || '-' }}</div>
|
||||
<div v-if="row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="可用库存" width="120">
|
||||
<template #default="{ row }">{{ formatQuantity(row.availableQuantity) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="移库数量" prop="quantity" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.quantity"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="数量"
|
||||
@change="handleDetailQuantityChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价(元)" prop="price" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.price"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="单价"
|
||||
@change="handleDetailPriceChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" prop="totalPrice" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.totalPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="金额"
|
||||
@change="handleDetailTotalPriceChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" @click="handleDeleteDetail($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<InventorySelect
|
||||
ref="inventorySelectRef"
|
||||
:warehouse-id="formData.sourceWarehouseId"
|
||||
@change="handleSelectInventory"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:movement-order:complete']"
|
||||
:disabled="formLoading"
|
||||
type="success"
|
||||
@click="handleComplete"
|
||||
>
|
||||
完成移库
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:movement-order:cancel']"
|
||||
:disabled="formLoading"
|
||||
type="danger"
|
||||
@click="handleCancel"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormRules } from 'element-plus'
|
||||
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||
import InventorySelect, {
|
||||
InventorySelectRow
|
||||
} from '@/views/wms/inventory/components/InventorySelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||
import {
|
||||
dividePrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import { generateOrderNo } from '@/views/wms/utils/order'
|
||||
|
||||
/** WMS 移库单表单 */
|
||||
defineOptions({ name: 'WmsMovementOrderForm' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formType = ref('')
|
||||
const originalFormData = ref('')
|
||||
const formData = ref<MovementOrderVO>({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
sourceWarehouseId: undefined,
|
||||
targetWarehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
no: [{ required: true, message: '移库单号不能为空', trigger: 'blur' }],
|
||||
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
sourceWarehouseId: [{ required: true, message: '来源仓库不能为空', trigger: 'change' }],
|
||||
targetWarehouseId: [{ required: true, message: '目标仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref()
|
||||
const inventorySelectRef = ref()
|
||||
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
(formData.value.status !== undefined && OrderUpdateStatusList.includes(formData.value.status))
|
||||
)
|
||||
const isSavedPrepareOrder = computed(
|
||||
() =>
|
||||
!!formData.value.id &&
|
||||
formData.value.status !== undefined &&
|
||||
OrderUpdateStatusList.includes(formData.value.status)
|
||||
)
|
||||
const canAddDetail = computed(
|
||||
() => !!formData.value.sourceWarehouseId && !!formData.value.targetWarehouseId
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const order = await MovementOrderApi.getMovementOrder(id)
|
||||
formData.value = { ...order, details: normalizeDetails(order.details || []) }
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 构建移库明细 */
|
||||
function buildDetail(inventory: InventorySelectRow): MovementOrderDetailVO {
|
||||
return {
|
||||
id: undefined,
|
||||
itemId: inventory.itemId,
|
||||
itemCode: inventory.itemCode,
|
||||
itemName: inventory.itemName,
|
||||
unit: inventory.unit,
|
||||
skuId: inventory.skuId,
|
||||
skuCode: inventory.skuCode,
|
||||
skuName: inventory.skuName,
|
||||
sourceWarehouseId: inventory.warehouseId,
|
||||
sourceWarehouseName: inventory.warehouseName,
|
||||
targetWarehouseId: formData.value.targetWarehouseId,
|
||||
quantity: undefined,
|
||||
availableQuantity: inventory.availableQuantity,
|
||||
price: undefined,
|
||||
totalPrice: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetails(details: MovementOrderDetailVO[]) {
|
||||
return details.map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
}
|
||||
|
||||
/** 打开库存选择弹窗 */
|
||||
const handleAddDetail = () => {
|
||||
if (!canAddDetail.value) {
|
||||
return
|
||||
}
|
||||
inventorySelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 选择库存 */
|
||||
const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
||||
if (!inventories.length) {
|
||||
return
|
||||
}
|
||||
formData.value.details = formData.value.details || []
|
||||
inventories.forEach((inventory) => {
|
||||
if (isInventorySelected(inventory)) {
|
||||
return
|
||||
}
|
||||
formData.value.details!.push(buildDetail(inventory))
|
||||
})
|
||||
}
|
||||
|
||||
/** 判断库存是否已选择 */
|
||||
function isInventorySelected(inventory: InventorySelectRow) {
|
||||
return (formData.value.details || []).some((detail) => {
|
||||
return detail.skuId === inventory.skuId && detail.sourceWarehouseId === inventory.warehouseId
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
formData.value.details?.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleSourceWarehouseChange = () => {
|
||||
formData.value.details = []
|
||||
}
|
||||
const handleTargetWarehouseChange = () => {
|
||||
refreshTargetToDetails()
|
||||
}
|
||||
const refreshTargetToDetails = () => {
|
||||
const details = formData.value.details || []
|
||||
details.forEach((detail) => {
|
||||
detail.targetWarehouseId = formData.value.targetWarehouseId
|
||||
})
|
||||
}
|
||||
|
||||
const handleDetailQuantityChange = (detail: MovementOrderDetailVO) => {
|
||||
if (detail.price !== undefined && detail.price !== null) {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
return
|
||||
}
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
const handleDetailPriceChange = (detail: MovementOrderDetailVO) => {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
const handleDetailTotalPriceChange = (detail: MovementOrderDetailVO) => {
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getDetailSummaries({
|
||||
columns,
|
||||
data
|
||||
}: {
|
||||
columns: any[]
|
||||
data: MovementOrderDetailVO[]
|
||||
}) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 校验明细 */
|
||||
const validateDetails = (required: boolean) => {
|
||||
if (formData.value.sourceWarehouseId === formData.value.targetWarehouseId) {
|
||||
message.error('来源仓库和目标仓库不能相同')
|
||||
return false
|
||||
}
|
||||
if (!formData.value.details?.length) {
|
||||
if (required) {
|
||||
message.error('至少包含一条移库明细')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < formData.value.details.length; i++) {
|
||||
const detail = formData.value.details[i]
|
||||
if (!detail.quantity || detail.quantity <= 0) {
|
||||
message.error(`第 ${i + 1} 行明细移库数量必须大于 0`)
|
||||
return false
|
||||
}
|
||||
if (detail.availableQuantity !== undefined && detail.quantity > detail.availableQuantity) {
|
||||
message.error(`第 ${i + 1} 行明细移库数量不能大于可用库存`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: details || []
|
||||
} as MovementOrderVO
|
||||
}
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(false)) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = buildSubmitData()
|
||||
if (formType.value === 'create') {
|
||||
await MovementOrderApi.createMovementOrder(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MovementOrderApi.updateMovementOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 完成移库:表单修改过则先保存,再完成 */
|
||||
const handleComplete = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(true)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm('确认完成移库?完成后将更新库存。')
|
||||
formLoading.value = true
|
||||
const data = buildSubmitData()
|
||||
if (JSON.stringify(data) !== originalFormData.value) {
|
||||
await MovementOrderApi.updateMovementOrder(data)
|
||||
}
|
||||
await MovementOrderApi.completeMovementOrder(formData.value.id!)
|
||||
message.success('移库成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
// 用户点击确认框取消按钮、或网络异常时跳过
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 作废移库单 */
|
||||
const handleCancel = async () => {
|
||||
try {
|
||||
await message.confirm('确认作废该移库单?作废后不可恢复。')
|
||||
formLoading.value = true
|
||||
await MovementOrderApi.cancelMovementOrder(formData.value.id!)
|
||||
message.success('作废成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
// 用户点击确认框取消按钮、或网络异常时跳过
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: generateOrderNo('YK'),
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
sourceWarehouseId: undefined,
|
||||
targetWarehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
}
|
||||
originalFormData.value = ''
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<!-- WMS 移库单打印 -->
|
||||
<template>
|
||||
<div class="hidden">
|
||||
<button ref="printButtonRef" v-print="printObj" type="button"></button>
|
||||
<div id="wmsMovementOrderPrint" class="color-#303133">
|
||||
<div class="relative mb-8px">
|
||||
<h2 class="text-center">移库单</h2>
|
||||
<div v-if="printData.no" class="absolute right-0 top-0">
|
||||
<Barcode
|
||||
:content="printData.no"
|
||||
:display-value="false"
|
||||
:format="BarcodeFormatEnum.CODE39"
|
||||
:height="40"
|
||||
:width="180"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-12px grid grid-cols-3 gap-x-24px gap-y-8px text-14px">
|
||||
<div>移库单号:{{ printData.no || '-' }}</div>
|
||||
<div>来源仓库:{{ printData.sourceWarehouseName || '-' }}</div>
|
||||
<div>目标仓库:{{ printData.targetWarehouseName || '-' }}</div>
|
||||
<div>移库状态:{{ getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, printData.status) || '-' }}</div>
|
||||
<div>单据日期:{{ formatNullableDate(printData.orderTime, 'YYYY-MM-DD') }}</div>
|
||||
<div>总数量:{{ formatQuantity(printData.totalQuantity) || '-' }}</div>
|
||||
<div>总金额:{{ formatPrice(printData.totalPrice) || '-' }}</div>
|
||||
<div class="col-span-3 grid grid-cols-2 gap-x-24px">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(printData.createTime) }} /
|
||||
{{ printData.creatorName || printData.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(printData.updateTime) }} /
|
||||
{{ printData.updaterName || printData.updater || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3">备注:{{ printData.remark || '-' }}</div>
|
||||
</div>
|
||||
<table class="w-full border-collapse text-13px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">商品信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">规格信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">数量</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">单价(元)</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">金额(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="detail in printRows" :key="detail.id">
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px">编号:{{ detail.itemCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px">编号:{{ detail.skuCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.quantity) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.price) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.totalPrice) || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="printRows.length">
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px" colspan="2">合计</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumQuantity(printRows, (detail) => detail.quantity) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumPrice(printRows, (detail) => detail.totalPrice) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!printRows.length">
|
||||
<td
|
||||
class="border border-solid border-#dcdfe6 p-8px text-center"
|
||||
:colspan="tableColumnCount"
|
||||
>
|
||||
暂无明细
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
import Barcode from '@/views/mes/wm/barcode/components/Barcode.vue'
|
||||
import { BarcodeFormatEnum } from '@/views/mes/utils/constants'
|
||||
|
||||
/** WMS 移库单打印 */
|
||||
defineOptions({ name: 'WmsMovementOrderPrint' })
|
||||
|
||||
const printData = ref<MovementOrderVO>({}) // 打印数据
|
||||
const printButtonRef = ref<HTMLButtonElement>() // 打印按钮
|
||||
const tableColumnCount = 5
|
||||
const printObj = ref({
|
||||
id: 'wmsMovementOrderPrint',
|
||||
popTitle: ' ',
|
||||
extraCss: '/print.css',
|
||||
extraHead: '',
|
||||
zIndex: 20003
|
||||
})
|
||||
|
||||
interface PrintRow extends MovementOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const printRows = computed<PrintRow[]>(() =>
|
||||
(printData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 打印移库单 */
|
||||
const print = async (id: number) => {
|
||||
printData.value = await MovementOrderApi.getMovementOrder(id)
|
||||
await nextTick()
|
||||
printButtonRef.value?.click()
|
||||
}
|
||||
defineExpose({ print })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,568 @@
|
|||
<!-- WMS 移库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="移库单号" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入移库单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择单据状态"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源仓库" prop="sourceWarehouseId">
|
||||
<WarehouseSelect v-model="queryParams.sourceWarehouseId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="目标仓库" prop="targetWarehouseId">
|
||||
<WarehouseSelect v-model="queryParams.targetWarehouseId" class="!w-240px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.orderTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="totalQuantityMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额" prop="totalPriceMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="更新用户" prop="updater">
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.updateTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
表格设置
|
||||
</el-button>
|
||||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="grid grid-cols-3 gap-y-14px rounded bg-[var(--el-fill-color-light)] p-16px"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="column in tableColumnOptions"
|
||||
:key="column.value"
|
||||
class="!h-28px !mr-0 [&_.el-checkbox__label]:font-600 [&_.el-checkbox__label]:text-16px"
|
||||
:label="column.value"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-popover>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 移库单列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<el-table-column type="expand" width="48">
|
||||
<template #default="scope">
|
||||
<el-table :data="detailMap[scope.row.id] || []" border>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.itemName || '-' }}</div>
|
||||
<div v-if="detailScope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ detailScope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.skuName || '-' }}</div>
|
||||
<div v-if="detailScope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ detailScope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="移库数量" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatQuantity(detailScope.row.quantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(detailScope.row.price) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="金额(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(getDetailTotalPrice(detailScope.row)) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('no')" fixed="left" label="单号" width="210">
|
||||
<template #default="scope">
|
||||
单号:
|
||||
<el-button link type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.no }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('status')"
|
||||
align="center"
|
||||
fixed="left"
|
||||
label="移库状态"
|
||||
width="110"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('sourceWarehouse')"
|
||||
label="来源仓库"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.sourceWarehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('targetWarehouse')"
|
||||
label="目标仓库"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.targetWarehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="总数量/总金额(元)"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>数量:</span>
|
||||
<span>{{ formatQuantity(scope.row.totalQuantity) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>金额:</span>
|
||||
<span>{{ formatPrice(scope.row.totalPrice) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(scope.row.createTime) }} /
|
||||
{{ scope.row.creatorName || scope.row.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(scope.row.updateTime) }} /
|
||||
{{ scope.row.updaterName || scope.row.updater || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('remark')"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
prop="remark"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:content="getMovementOrderUpdateTip(scope.row.status)"
|
||||
:disabled="canUpdateMovementOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:update']"
|
||||
:disabled="!canUpdateMovementOrder(scope.row.status)"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="getMovementOrderDeleteTip(scope.row.status)"
|
||||
:disabled="canDeleteMovementOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:delete']"
|
||||
:disabled="!canDeleteMovementOrder(scope.row.status)"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrint(scope.row.id)"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<MovementOrderForm ref="formRef" @success="getList" />
|
||||
<MovementOrderDetail ref="detailRef" />
|
||||
<MovementOrderPrint ref="printRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||
import MovementOrderDetail from './MovementOrderDetail.vue'
|
||||
import MovementOrderForm from './MovementOrderForm.vue'
|
||||
import MovementOrderPrint from './MovementOrderPrint.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 移库单 */
|
||||
defineOptions({ name: 'WmsMovementOrder' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
type TableColumnKey =
|
||||
| 'no'
|
||||
| 'status'
|
||||
| 'sourceWarehouse'
|
||||
| 'targetWarehouse'
|
||||
| 'quantityAmount'
|
||||
| 'operateInfo'
|
||||
| 'remark'
|
||||
|
||||
const tableColumnOptions: Array<{ label: string; value: TableColumnKey }> = [
|
||||
{ label: '单号', value: 'no' },
|
||||
{ label: '移库状态', value: 'status' },
|
||||
{ label: '来源仓库', value: 'sourceWarehouse' },
|
||||
{ label: '目标仓库', value: 'targetWarehouse' },
|
||||
{ label: '数量/金额(元)', value: 'quantityAmount' },
|
||||
{ label: '操作信息', value: 'operateInfo' },
|
||||
{ label: '备注', value: 'remark' }
|
||||
]
|
||||
const checkedTableColumns = ref<TableColumnKey[]>([
|
||||
'no',
|
||||
'status',
|
||||
'sourceWarehouse',
|
||||
'targetWarehouse',
|
||||
'quantityAmount',
|
||||
'operateInfo',
|
||||
'remark'
|
||||
])
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<MovementOrderVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
no: undefined as string | undefined,
|
||||
status: undefined as number | undefined,
|
||||
sourceWarehouseId: undefined as number | undefined,
|
||||
targetWarehouseId: undefined as number | undefined,
|
||||
orderTime: undefined as string[] | undefined,
|
||||
totalQuantityMin: undefined as number | undefined,
|
||||
totalQuantityMax: undefined as number | undefined,
|
||||
totalPriceMin: undefined as number | undefined,
|
||||
totalPriceMax: undefined as number | undefined,
|
||||
creator: undefined as number | undefined,
|
||||
updater: undefined as number | undefined,
|
||||
createTime: undefined as string[] | undefined,
|
||||
updateTime: undefined as string[] | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const detailMap = reactive<Record<number, MovementOrderDetailVO[]>>({}) // 移库单明细数据
|
||||
|
||||
/** 判断表格列是否可见 */
|
||||
function isTableColumnVisible(column: TableColumnKey) {
|
||||
return checkedTableColumns.value.includes(column)
|
||||
}
|
||||
|
||||
/** 是否允许修改移库单 */
|
||||
const canUpdateMovementOrder = (status?: number) => {
|
||||
return status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 是否允许删除移库单 */
|
||||
const canDeleteMovementOrder = (status?: number) => {
|
||||
return status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 获得移库单修改禁用提示 */
|
||||
const getMovementOrderUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已移库,无法修改'
|
||||
}
|
||||
if (status === OrderStatusEnum.CANCELED) {
|
||||
return '已作废,无法修改'
|
||||
}
|
||||
return '当前状态无法修改'
|
||||
}
|
||||
|
||||
/** 获得移库单删除禁用提示 */
|
||||
const getMovementOrderDeleteTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已移库,无法删除'
|
||||
}
|
||||
return '当前状态无法删除'
|
||||
}
|
||||
|
||||
/** 查询移库单列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await MovementOrderApi.getMovementOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 计算明细金额 */
|
||||
const getDetailTotalPrice = (detail: MovementOrderDetailVO) => {
|
||||
if (detail.totalPrice !== undefined && detail.totalPrice !== null) {
|
||||
return detail.totalPrice
|
||||
}
|
||||
return multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
/** 展开明细 */
|
||||
const handleExpandChange = async (row: MovementOrderVO, expandedRows: MovementOrderVO[]) => {
|
||||
if (!row.id || !expandedRows.some((item) => item.id === row.id)) {
|
||||
return
|
||||
}
|
||||
delete detailMap[row.id]
|
||||
detailMap[row.id] = await MovementOrderApi.getMovementOrderDetailListByOrderId(row.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 查看移库单详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 打印移库单 */
|
||||
const printRef = ref()
|
||||
const handlePrint = (id: number) => {
|
||||
printRef.value.print(id)
|
||||
}
|
||||
|
||||
/** 删除移库单 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await MovementOrderApi.deleteMovementOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await MovementOrderApi.exportMovementOrder(queryParams)
|
||||
download.excel(data, '移库单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<!-- WMS 入库单详情 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="入库单详情" width="1200px">
|
||||
<div v-loading="loading">
|
||||
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="入库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="入库类型">
|
||||
<dict-tag
|
||||
v-if="detailData.type !== undefined"
|
||||
:type="DICT_TYPE.WMS_RECEIPT_ORDER_TYPE"
|
||||
:value="detailData.type"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">
|
||||
{{ detailData.warehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据状态">
|
||||
<dict-tag
|
||||
v-if="detailData.status !== undefined"
|
||||
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||
:value="detailData.status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据日期">
|
||||
{{ formatNullableDate(detailData.orderTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="供应商">
|
||||
{{ detailData.merchantName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务单号">
|
||||
{{ detailData.bizOrderNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总数量">
|
||||
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总金额">
|
||||
{{ formatPrice(detailData.totalPrice) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatNullableDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatNullableDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="备注">
|
||||
{{ detailData.remark || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mb-16px mt-24px text-18px font-bold">商品明细</div>
|
||||
<el-table :data="detailRows" :summary-method="getSummaries" border show-summary>
|
||||
<el-table-column label="商品名称" min-width="180" prop="itemName">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格名称" min-width="180" prop="skuName">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="数量" prop="quantity" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.quantity) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="单位" prop="unit" width="100" />
|
||||
<el-table-column align="right" label="单价" prop="price" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatPrice(scope.row.price) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="总价" prop="totalPrice" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatPrice(scope.row.totalPrice) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 入库单详情 */
|
||||
defineOptions({ name: 'WmsReceiptOrderDetail' })
|
||||
|
||||
interface DetailRow extends ReceiptOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailData = ref<ReceiptOrderVO>({}) // 详情数据
|
||||
const detailRows = computed<DetailRow[]>(() =>
|
||||
(detailData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getSummaries({ columns, data }: { columns: any[]; data: DetailRow[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
detailData.value = await ReceiptOrderApi.getReceiptOrder(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
<!-- WMS 入库单表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="92px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入库单号" prop="no">
|
||||
<el-input v-model="formData.no" maxlength="64" placeholder="请输入入库单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="入库类型" prop="type">
|
||||
<el-select v-model="formData.type" class="w-1/1" placeholder="请选择入库类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="formData.warehouseId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="formData.orderTime"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择单据日期"
|
||||
type="date"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="供应商" prop="merchantId">
|
||||
<MerchantSelect v-model="formData.merchantId" placeholder="请选择供应商" supplier />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="业务单号" prop="bizOrderNo">
|
||||
<el-input v-model="formData.bizOrderNo" maxlength="64" placeholder="请输入业务单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">入库明细</span>
|
||||
<el-tooltip content="请先选择仓库" :disabled="!!formData.warehouseId" placement="top">
|
||||
<span>
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table
|
||||
:data="formData.details"
|
||||
:summary-method="getDetailSummaries"
|
||||
border
|
||||
empty-text="暂无商品明细"
|
||||
show-summary
|
||||
>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入库数量" prop="quantity" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.quantity"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="数量"
|
||||
@change="handleDetailQuantityChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价(元)" prop="price" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.price"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="单价"
|
||||
@change="handleDetailPriceChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" prop="totalPrice" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.totalPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="金额"
|
||||
@change="handleDetailTotalPriceChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="handleDeleteDetail(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ItemSkuSelect ref="skuSelectRef" @change="handleSelectSku" />
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:receipt-order:complete']"
|
||||
:disabled="formLoading"
|
||||
type="success"
|
||||
@click="handleComplete"
|
||||
>
|
||||
完成入库
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:receipt-order:cancel']"
|
||||
:disabled="formLoading"
|
||||
type="danger"
|
||||
@click="handleCancel"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormRules } from 'element-plus'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
import { ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import ItemSkuSelect from '@/views/wms/md/item/sku/components/ItemSkuSelect.vue'
|
||||
import MerchantSelect from '@/views/wms/md/merchant/components/MerchantSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||
import {
|
||||
dividePrice,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import { generateOrderNo } from '@/views/wms/utils/order'
|
||||
|
||||
/** WMS 入库单表单 */
|
||||
defineOptions({ name: 'WmsReceiptOrderForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const originalFormData = ref('') // 表单原始数据快照
|
||||
const formData = ref<ReceiptOrderVO>({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
type: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
no: [{ required: true, message: '入库单号不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '入库类型不能为空', trigger: 'change' }],
|
||||
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const skuSelectRef = ref() // 商品 SKU 选择弹窗 Ref
|
||||
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
(formData.value.status !== undefined && OrderUpdateStatusList.includes(formData.value.status))
|
||||
)
|
||||
const isSavedPrepareOrder = computed(
|
||||
() =>
|
||||
!!formData.value.id &&
|
||||
formData.value.status !== undefined &&
|
||||
OrderUpdateStatusList.includes(formData.value.status)
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const order = await ReceiptOrderApi.getReceiptOrder(id)
|
||||
formData.value = {
|
||||
...order,
|
||||
details: normalizeDetails(order.details || [])
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 构建入库明细 */
|
||||
function buildDetail(sku: ItemSkuVO): ReceiptOrderDetailVO {
|
||||
return {
|
||||
id: undefined,
|
||||
itemId: sku.itemId,
|
||||
itemCode: sku.itemCode,
|
||||
itemName: sku.itemName,
|
||||
unit: sku.unit,
|
||||
skuId: sku.id,
|
||||
skuCode: sku.code,
|
||||
skuName: sku.name,
|
||||
quantity: undefined,
|
||||
price: undefined,
|
||||
totalPrice: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetails(details: ReceiptOrderDetailVO[]) {
|
||||
return details.map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
}
|
||||
|
||||
/** 添加商品 */
|
||||
const handleAddDetail = () => {
|
||||
skuSelectRef.value?.open(getSelectedSkuIds())
|
||||
}
|
||||
|
||||
/** 选择商品 SKU */
|
||||
const handleSelectSku = (skus: ItemSkuVO[]) => {
|
||||
if (!skus.length) {
|
||||
return
|
||||
}
|
||||
formData.value.details = formData.value.details || []
|
||||
const selectedSkuIds = new Set(getSelectedSkuIds())
|
||||
skus.forEach((sku) => {
|
||||
if (!sku.id || selectedSkuIds.has(sku.id)) {
|
||||
return
|
||||
}
|
||||
formData.value.details!.push(buildDetail(sku))
|
||||
selectedSkuIds.add(sku.id)
|
||||
})
|
||||
}
|
||||
|
||||
/** 获得已选 SKU 编号 */
|
||||
const getSelectedSkuIds = () => {
|
||||
return (formData.value.details || [])
|
||||
.map((detail) => detail.skuId)
|
||||
.filter((id): id is number => !!id)
|
||||
}
|
||||
|
||||
/** 删除明细 */
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
formData.value.details?.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 明细数量变化 */
|
||||
const handleDetailQuantityChange = (detail: ReceiptOrderDetailVO) => {
|
||||
if (detail.price !== undefined && detail.price !== null) {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
return
|
||||
}
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
/** 明细单价变化 */
|
||||
const handleDetailPriceChange = (detail: ReceiptOrderDetailVO) => {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
/** 明细金额变化 */
|
||||
const handleDetailTotalPriceChange = (detail: ReceiptOrderDetailVO) => {
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getDetailSummaries({ columns, data }: { columns: any[]; data: ReceiptOrderDetailVO[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 校验明细 */
|
||||
const validateDetails = (required: boolean) => {
|
||||
if (!formData.value.details?.length) {
|
||||
if (required) {
|
||||
message.error('至少包含一条入库明细')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < formData.value.details.length; i++) {
|
||||
const detail = formData.value.details[i]
|
||||
if (!detail.skuId) {
|
||||
message.error(`第 ${i + 1} 行明细请选择商品规格`)
|
||||
return false
|
||||
}
|
||||
if (!detail.quantity || detail.quantity <= 0) {
|
||||
message.error(`第 ${i + 1} 行明细入库数量必须大于 0`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: details || []
|
||||
} as ReceiptOrderVO
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(false)) {
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = buildSubmitData()
|
||||
if (formType.value === 'create') {
|
||||
await ReceiptOrderApi.createReceiptOrder(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ReceiptOrderApi.updateReceiptOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 完成入库:表单修改过则先保存,再完成 */
|
||||
const handleComplete = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(true)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm('确认完成入库?完成后将更新库存。')
|
||||
formLoading.value = true
|
||||
const data = buildSubmitData()
|
||||
if (JSON.stringify(data) !== originalFormData.value) {
|
||||
await ReceiptOrderApi.updateReceiptOrder(data)
|
||||
}
|
||||
await ReceiptOrderApi.completeReceiptOrder(formData.value.id!)
|
||||
message.success('入库成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 作废入库单 */
|
||||
const handleCancel = async () => {
|
||||
try {
|
||||
await message.confirm('确认作废该入库单?作废后不可恢复。')
|
||||
formLoading.value = true
|
||||
await ReceiptOrderApi.cancelReceiptOrder(formData.value.id!)
|
||||
message.success('作废成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: generateOrderNo('RK'),
|
||||
type: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
}
|
||||
originalFormData.value = ''
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<!-- WMS 入库单打印 -->
|
||||
<template>
|
||||
<div class="hidden">
|
||||
<button ref="printButtonRef" v-print="printObj" type="button"></button>
|
||||
<div id="wmsReceiptOrderPrint" class="color-#303133">
|
||||
<div class="relative mb-8px">
|
||||
<h2 class="text-center">入库单</h2>
|
||||
<div v-if="printData.no" class="absolute right-0 top-0">
|
||||
<Barcode
|
||||
:content="printData.no"
|
||||
:display-value="false"
|
||||
:format="BarcodeFormatEnum.CODE39"
|
||||
:height="40"
|
||||
:width="180"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-12px grid grid-cols-3 gap-x-24px gap-y-8px text-14px">
|
||||
<div>入库单号:{{ printData.no || '-' }}</div>
|
||||
<div>
|
||||
入库类型:{{ getDictLabel(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE, printData.type) || '-' }}
|
||||
</div>
|
||||
<div>仓库:{{ printData.warehouseName || '-' }}</div>
|
||||
<div>入库状态:{{ getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, printData.status) || '-' }}</div>
|
||||
<div>单据日期:{{ formatNullableDate(printData.orderTime, 'YYYY-MM-DD') }}</div>
|
||||
<div>供应商:{{ printData.merchantName || '-' }}</div>
|
||||
<div>业务单号:{{ printData.bizOrderNo || '-' }}</div>
|
||||
<div>总数量:{{ formatQuantity(printData.totalQuantity) || '-' }}</div>
|
||||
<div>总金额:{{ formatPrice(printData.totalPrice) || '-' }}</div>
|
||||
<div class="col-span-3 grid grid-cols-2 gap-x-24px">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(printData.createTime) }} /
|
||||
{{ printData.creatorName || printData.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(printData.updateTime) }} /
|
||||
{{ printData.updaterName || printData.updater || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3">备注:{{ printData.remark || '-' }}</div>
|
||||
</div>
|
||||
<table class="w-full border-collapse text-13px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">商品信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">规格信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">数量</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">单价(元)</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">金额(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="detail in printRows" :key="detail.id">
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px">编号:{{ detail.itemCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px">编号:{{ detail.skuCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.quantity) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.price) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.totalPrice) || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="printRows.length">
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px" colspan="2">合计</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumQuantity(printRows, (detail) => detail.quantity) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumPrice(printRows, (detail) => detail.totalPrice) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!printRows.length">
|
||||
<td
|
||||
class="border border-solid border-#dcdfe6 p-8px text-center"
|
||||
:colspan="tableColumnCount"
|
||||
>
|
||||
暂无明细
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
import Barcode from '@/views/mes/wm/barcode/components/Barcode.vue'
|
||||
import { BarcodeFormatEnum } from '@/views/mes/utils/constants'
|
||||
|
||||
/** WMS 入库单打印 */
|
||||
defineOptions({ name: 'WmsReceiptOrderPrint' })
|
||||
|
||||
const printData = ref<ReceiptOrderVO>({}) // 打印数据
|
||||
const printButtonRef = ref<HTMLButtonElement>() // 打印按钮
|
||||
const tableColumnCount = 5
|
||||
const printObj = ref({
|
||||
id: 'wmsReceiptOrderPrint',
|
||||
popTitle: ' ',
|
||||
extraCss: '/print.css',
|
||||
extraHead: '',
|
||||
zIndex: 20003
|
||||
})
|
||||
|
||||
interface PrintRow extends ReceiptOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const printRows = computed<PrintRow[]>(() =>
|
||||
(printData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 打印入库单 */
|
||||
const print = async (id: number) => {
|
||||
printData.value = await ReceiptOrderApi.getReceiptOrder(id)
|
||||
await nextTick()
|
||||
printButtonRef.value?.click()
|
||||
}
|
||||
defineExpose({ print })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,625 @@
|
|||
<!-- WMS 入库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="入库单号" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入入库单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务单号" prop="bizOrderNo">
|
||||
<el-input
|
||||
v-model="queryParams.bizOrderNo"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入业务单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择单据状态"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect
|
||||
v-model="queryParams.warehouseId"
|
||||
class="!w-240px"
|
||||
@change="handleWarehouseChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商" prop="merchantId">
|
||||
<MerchantSelect
|
||||
v-model="queryParams.merchantId"
|
||||
class="!w-240px"
|
||||
placeholder="请选择供应商"
|
||||
supplier
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.orderTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="totalQuantityMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额" prop="totalPriceMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="入库类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择入库类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="更新用户" prop="updater">
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.updateTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
表格设置
|
||||
</el-button>
|
||||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="grid grid-cols-3 gap-y-14px rounded bg-[var(--el-fill-color-light)] p-16px"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="column in tableColumnOptions"
|
||||
:key="column.value"
|
||||
class="!h-28px !mr-0 [&_.el-checkbox__label]:font-600 [&_.el-checkbox__label]:text-16px"
|
||||
:label="column.value"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-popover>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 入库单列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<el-table-column type="expand" width="48">
|
||||
<template #default="scope">
|
||||
<el-table :data="detailMap[scope.row.id] || []" border>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.itemName || '-' }}</div>
|
||||
<div v-if="detailScope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ detailScope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.skuName || '-' }}</div>
|
||||
<div v-if="detailScope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ detailScope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="入库数量" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatQuantity(detailScope.row.quantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(detailScope.row.price) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="金额(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(getDetailTotalPrice(detailScope.row)) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('no')"
|
||||
fixed="left"
|
||||
label="单号/业务单号"
|
||||
width="290"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
单号:
|
||||
<el-button link type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.no }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="scope.row.bizOrderNo" class="text-12px text-gray-500">
|
||||
业务:{{ scope.row.bizOrderNo }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('status')"
|
||||
align="center"
|
||||
fixed="left"
|
||||
label="入库状态"
|
||||
width="110"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('type')"
|
||||
align="center"
|
||||
label="入库类型"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_RECEIPT_ORDER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="总数量/总金额(元)"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>数量:</span>
|
||||
<span>{{ formatQuantity(scope.row.totalQuantity) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>金额:</span>
|
||||
<span>{{ formatPrice(scope.row.totalPrice) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('merchant')"
|
||||
label="供应商"
|
||||
min-width="160"
|
||||
prop="merchantName"
|
||||
/>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(scope.row.createTime) }} /
|
||||
{{ scope.row.creatorName || scope.row.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(scope.row.updateTime) }} /
|
||||
{{ scope.row.updaterName || scope.row.updater || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('remark')"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
prop="remark"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:content="getReceiptOrderUpdateTip(scope.row.status)"
|
||||
:disabled="canUpdateReceiptOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:update']"
|
||||
:disabled="!canUpdateReceiptOrder(scope.row.status)"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="getReceiptOrderDeleteTip(scope.row.status)"
|
||||
:disabled="canDeleteReceiptOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:delete']"
|
||||
:disabled="!canDeleteReceiptOrder(scope.row.status)"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrint(scope.row.id)"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ReceiptOrderForm ref="formRef" @success="getList" />
|
||||
<ReceiptOrderDetail ref="detailRef" />
|
||||
<ReceiptOrderPrint ref="printRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
import MerchantSelect from '@/views/wms/md/merchant/components/MerchantSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||
import ReceiptOrderDetail from './ReceiptOrderDetail.vue'
|
||||
import ReceiptOrderForm from './ReceiptOrderForm.vue'
|
||||
import ReceiptOrderPrint from './ReceiptOrderPrint.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 入库单 */
|
||||
defineOptions({ name: 'WmsReceiptOrder' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
type TableColumnKey =
|
||||
| 'no'
|
||||
| 'status'
|
||||
| 'type'
|
||||
| 'warehouse'
|
||||
| 'quantityAmount'
|
||||
| 'merchant'
|
||||
| 'operateInfo'
|
||||
| 'remark'
|
||||
|
||||
const tableColumnOptions: Array<{ label: string; value: TableColumnKey }> = [
|
||||
{ label: '单号/业务单号', value: 'no' },
|
||||
{ label: '入库状态', value: 'status' },
|
||||
{ label: '入库类型', value: 'type' },
|
||||
{ label: '仓库', value: 'warehouse' },
|
||||
{ label: '数量/金额(元)', value: 'quantityAmount' },
|
||||
{ label: '供应商', value: 'merchant' },
|
||||
{ label: '操作信息', value: 'operateInfo' },
|
||||
{ label: '备注', value: 'remark' }
|
||||
]
|
||||
const checkedTableColumns = ref<TableColumnKey[]>([
|
||||
'no',
|
||||
'status',
|
||||
'type',
|
||||
'warehouse',
|
||||
'quantityAmount',
|
||||
'merchant',
|
||||
'operateInfo',
|
||||
'remark'
|
||||
])
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ReceiptOrderVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
no: undefined as string | undefined,
|
||||
status: undefined as number | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
merchantId: undefined as number | undefined,
|
||||
orderTime: undefined as string[] | undefined,
|
||||
totalQuantityMin: undefined as number | undefined,
|
||||
totalQuantityMax: undefined as number | undefined,
|
||||
totalPriceMin: undefined as number | undefined,
|
||||
totalPriceMax: undefined as number | undefined,
|
||||
type: undefined as number | undefined,
|
||||
bizOrderNo: undefined as string | undefined,
|
||||
creator: undefined as number | undefined,
|
||||
updater: undefined as number | undefined,
|
||||
createTime: undefined as string[] | undefined,
|
||||
updateTime: undefined as string[] | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const detailMap = reactive<Record<number, ReceiptOrderDetailVO[]>>({}) // 入库单明细数据
|
||||
|
||||
/** 判断表格列是否可见 */
|
||||
function isTableColumnVisible(column: TableColumnKey) {
|
||||
return checkedTableColumns.value.includes(column)
|
||||
}
|
||||
|
||||
/** 是否允许修改入库单 */
|
||||
const canUpdateReceiptOrder = (status?: number) => {
|
||||
return status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 是否允许删除入库单 */
|
||||
const canDeleteReceiptOrder = (status?: number) => {
|
||||
return status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 获得入库单修改禁用提示 */
|
||||
const getReceiptOrderUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已入库,无法修改'
|
||||
}
|
||||
if (status === OrderStatusEnum.CANCELED) {
|
||||
return '已作废,无法修改'
|
||||
}
|
||||
return '当前状态无法修改'
|
||||
}
|
||||
|
||||
/** 获得入库单删除禁用提示 */
|
||||
const getReceiptOrderDeleteTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已入库,无法删除'
|
||||
}
|
||||
return '当前状态无法删除'
|
||||
}
|
||||
|
||||
/** 查询入库单列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ReceiptOrderApi.getReceiptOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 仓库变化 */
|
||||
const handleWarehouseChange = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 计算明细金额 */
|
||||
const getDetailTotalPrice = (detail: ReceiptOrderDetailVO) => {
|
||||
if (detail.totalPrice !== undefined && detail.totalPrice !== null) {
|
||||
return detail.totalPrice
|
||||
}
|
||||
return multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
/** 展开明细 */
|
||||
const handleExpandChange = async (row: ReceiptOrderVO, expandedRows: ReceiptOrderVO[]) => {
|
||||
if (!row.id || !expandedRows.some((item) => item.id === row.id)) {
|
||||
return
|
||||
}
|
||||
delete detailMap[row.id]
|
||||
detailMap[row.id] = await ReceiptOrderApi.getReceiptOrderDetailListByOrderId(row.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 查看入库单详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 打印入库单 */
|
||||
const printRef = ref()
|
||||
const handlePrint = (id: number) => {
|
||||
printRef.value.print(id)
|
||||
}
|
||||
|
||||
/** 删除入库单 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ReceiptOrderApi.deleteReceiptOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ReceiptOrderApi.exportReceiptOrder(queryParams)
|
||||
download.excel(data, '入库单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<!-- WMS 出库单详情 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="出库单详情" width="1200px">
|
||||
<div v-loading="loading">
|
||||
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="出库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出库类型">
|
||||
<dict-tag
|
||||
v-if="detailData.type !== undefined"
|
||||
:type="DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE"
|
||||
:value="detailData.type"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">
|
||||
{{ detailData.warehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据状态">
|
||||
<dict-tag
|
||||
v-if="detailData.status !== undefined"
|
||||
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||
:value="detailData.status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据日期">
|
||||
{{ formatNullableDate(detailData.orderTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="客户">
|
||||
{{ detailData.merchantName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务单号">
|
||||
{{ detailData.bizOrderNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总数量">
|
||||
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="总金额">
|
||||
{{ formatPrice(detailData.totalPrice) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatNullableDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatNullableDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="备注">
|
||||
{{ detailData.remark || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mb-16px mt-24px text-18px font-bold">商品明细</div>
|
||||
<el-table :data="detailRows" :summary-method="getSummaries" border show-summary>
|
||||
<el-table-column label="商品名称" min-width="180" prop="itemName">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格名称" min-width="180" prop="skuName">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="数量" prop="quantity" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.quantity) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="单位" prop="unit" width="100" />
|
||||
<el-table-column align="right" label="单价" prop="price" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatPrice(scope.row.price) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="总价" prop="totalPrice" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatPrice(scope.row.totalPrice) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { ShipmentOrderApi, ShipmentOrderVO } from '@/api/wms/order/shipment'
|
||||
import { ShipmentOrderDetailVO } from '@/api/wms/order/shipment/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 出库单详情 */
|
||||
defineOptions({ name: 'WmsShipmentOrderDetail' })
|
||||
|
||||
interface DetailRow extends ShipmentOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const loading = ref(false) // 加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailData = ref<ShipmentOrderVO>({}) // 详情数据
|
||||
const detailRows = computed<DetailRow[]>(() =>
|
||||
(detailData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getSummaries({ columns, data }: { columns: any[]; data: DetailRow[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
detailData.value = await ShipmentOrderApi.getShipmentOrder(id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,511 @@
|
|||
<!-- WMS 出库单表单 -->
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="92px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出库单号" prop="no">
|
||||
<el-input v-model="formData.no" maxlength="64" placeholder="请输入出库单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出库类型" prop="type">
|
||||
<el-select v-model="formData.type" class="w-1/1" placeholder="请选择出库类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect v-model="formData.warehouseId" @change="handleWarehouseChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="formData.orderTime"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择单据日期"
|
||||
type="date"
|
||||
value-format="x"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户" prop="merchantId">
|
||||
<MerchantSelect v-model="formData.merchantId" placeholder="请选择客户" customer />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="业务单号" prop="bizOrderNo">
|
||||
<el-input v-model="formData.bizOrderNo" maxlength="64" placeholder="请输入业务单号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">出库明细</span>
|
||||
<el-tooltip content="请先选择仓库" :disabled="!!formData.warehouseId" placement="top">
|
||||
<span>
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-table
|
||||
:data="formData.details"
|
||||
:summary-method="getDetailSummaries"
|
||||
border
|
||||
empty-text="暂无商品明细"
|
||||
show-summary
|
||||
>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.itemName || '-' }}</div>
|
||||
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ scope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.skuName || '-' }}</div>
|
||||
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ scope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="可用库存" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatQuantity(scope.row.availableQuantity) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出库数量" prop="quantity" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.quantity"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="数量"
|
||||
@change="handleDetailQuantityChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价(元)" prop="price" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.price"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="单价"
|
||||
@change="handleDetailPriceChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" prop="totalPrice" width="160">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.totalPrice"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-1/1"
|
||||
placeholder="金额"
|
||||
@change="handleDetailTotalPriceChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" @click="handleDeleteDetail(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<InventorySelect
|
||||
ref="inventorySelectRef"
|
||||
:warehouse-id="formData.warehouseId"
|
||||
@change="handleSelectInventory"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:shipment-order:complete']"
|
||||
:disabled="formLoading"
|
||||
type="success"
|
||||
@click="handleComplete"
|
||||
>
|
||||
完成出库
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSavedPrepareOrder"
|
||||
v-hasPermi="['wms:shipment-order:cancel']"
|
||||
:disabled="formLoading"
|
||||
type="danger"
|
||||
@click="handleCancel"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormRules } from 'element-plus'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ShipmentOrderApi, ShipmentOrderVO } from '@/api/wms/order/shipment'
|
||||
import { ShipmentOrderDetailVO } from '@/api/wms/order/shipment/detail'
|
||||
import InventorySelect, {
|
||||
InventorySelectRow
|
||||
} from '@/views/wms/inventory/components/InventorySelect.vue'
|
||||
import MerchantSelect from '@/views/wms/md/merchant/components/MerchantSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||
import {
|
||||
dividePrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import { generateOrderNo } from '@/views/wms/utils/order'
|
||||
|
||||
/** WMS 出库单表单 */
|
||||
defineOptions({ name: 'WmsShipmentOrderForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const originalFormData = ref('') // 表单原始数据快照
|
||||
const formData = ref<ShipmentOrderVO>({
|
||||
id: undefined,
|
||||
no: undefined,
|
||||
type: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
no: [{ required: true, message: '出库单号不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '出库类型不能为空', trigger: 'change' }],
|
||||
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
|
||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const inventorySelectRef = ref() // 库存选择弹窗 Ref
|
||||
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
(formData.value.status !== undefined && OrderUpdateStatusList.includes(formData.value.status))
|
||||
)
|
||||
const isSavedPrepareOrder = computed(
|
||||
() =>
|
||||
!!formData.value.id &&
|
||||
formData.value.status !== undefined &&
|
||||
OrderUpdateStatusList.includes(formData.value.status)
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const order = await ShipmentOrderApi.getShipmentOrder(id)
|
||||
formData.value = {
|
||||
...order,
|
||||
details: normalizeDetails(order.details || [])
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 构建出库明细 */
|
||||
function buildDetail(inventory: InventorySelectRow): ShipmentOrderDetailVO {
|
||||
return {
|
||||
id: undefined,
|
||||
itemId: inventory.itemId,
|
||||
itemCode: inventory.itemCode,
|
||||
itemName: inventory.itemName,
|
||||
unit: inventory.unit,
|
||||
skuId: inventory.skuId,
|
||||
skuCode: inventory.skuCode,
|
||||
skuName: inventory.skuName,
|
||||
warehouseId: inventory.warehouseId,
|
||||
warehouseName: inventory.warehouseName,
|
||||
quantity: undefined,
|
||||
availableQuantity: inventory.availableQuantity,
|
||||
price: undefined,
|
||||
totalPrice: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetails(details: ShipmentOrderDetailVO[]) {
|
||||
return details.map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
}
|
||||
|
||||
/** 添加商品 */
|
||||
const handleAddDetail = () => {
|
||||
inventorySelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 选择库存 */
|
||||
const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
||||
if (!inventories.length) {
|
||||
return
|
||||
}
|
||||
formData.value.details = formData.value.details || []
|
||||
inventories.forEach((inventory) => {
|
||||
if (isInventorySelected(inventory)) {
|
||||
return
|
||||
}
|
||||
formData.value.details!.push(buildDetail(inventory))
|
||||
})
|
||||
}
|
||||
|
||||
/** 判断库存是否已选择 */
|
||||
const isInventorySelected = (inventory: InventorySelectRow) => {
|
||||
return (formData.value.details || []).some((detail) => {
|
||||
return detail.skuId === inventory.skuId && detail.warehouseId === inventory.warehouseId
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除明细 */
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
formData.value.details?.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 仓库变化 */
|
||||
const handleWarehouseChange = () => {
|
||||
formData.value.details = []
|
||||
}
|
||||
|
||||
/** 明细数量变化 */
|
||||
const handleDetailQuantityChange = (detail: ShipmentOrderDetailVO) => {
|
||||
if (detail.price !== undefined && detail.price !== null) {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
return
|
||||
}
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
/** 明细单价变化 */
|
||||
const handleDetailPriceChange = (detail: ShipmentOrderDetailVO) => {
|
||||
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
/** 明细金额变化 */
|
||||
const handleDetailTotalPriceChange = (detail: ShipmentOrderDetailVO) => {
|
||||
detail.price = dividePrice(detail.totalPrice, detail.quantity)
|
||||
}
|
||||
|
||||
/** 计算表格的合计行数据 */
|
||||
function getDetailSummaries({ columns, data }: { columns: any[]; data: ShipmentOrderDetailVO[] }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
return '合计'
|
||||
}
|
||||
if (column.property === 'quantity') {
|
||||
return formatSumQuantity(data, (detail) => detail.quantity)
|
||||
}
|
||||
if (column.property === 'totalPrice') {
|
||||
return formatSumPrice(data, (detail) => detail.totalPrice)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 校验明细 */
|
||||
const validateDetails = (required: boolean) => {
|
||||
if (!formData.value.details?.length) {
|
||||
if (required) {
|
||||
message.error('至少包含一条出库明细')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < formData.value.details.length; i++) {
|
||||
const detail = formData.value.details[i]
|
||||
if (!detail.skuId) {
|
||||
message.error(`第 ${i + 1} 行明细请选择商品规格`)
|
||||
return false
|
||||
}
|
||||
if (!detail.quantity || detail.quantity <= 0) {
|
||||
message.error(`第 ${i + 1} 行明细出库数量必须大于 0`)
|
||||
return false
|
||||
}
|
||||
if (detail.availableQuantity !== undefined && detail.quantity > detail.availableQuantity) {
|
||||
message.error(`第 ${i + 1} 行明细出库数量不能大于可用库存`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: details || []
|
||||
} as ShipmentOrderVO
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(false)) {
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = buildSubmitData()
|
||||
if (formType.value === 'create') {
|
||||
await ShipmentOrderApi.createShipmentOrder(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ShipmentOrderApi.updateShipmentOrder(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 完成出库:表单修改过则先保存,再完成 */
|
||||
const handleComplete = async () => {
|
||||
await formRef.value.validate()
|
||||
if (!validateDetails(true)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await message.confirm('确认完成出库?完成后将更新库存。')
|
||||
formLoading.value = true
|
||||
const data = buildSubmitData()
|
||||
if (JSON.stringify(data) !== originalFormData.value) {
|
||||
await ShipmentOrderApi.updateShipmentOrder(data)
|
||||
}
|
||||
await ShipmentOrderApi.completeShipmentOrder(formData.value.id!)
|
||||
message.success('出库成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 作废出库单 */
|
||||
const handleCancel = async () => {
|
||||
try {
|
||||
await message.confirm('确认作废该出库单?作废后不可恢复。')
|
||||
formLoading.value = true
|
||||
await ShipmentOrderApi.cancelShipmentOrder(formData.value.id!)
|
||||
message.success('作废成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch {
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
no: generateOrderNo('CK'),
|
||||
type: undefined,
|
||||
orderTime: undefined,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
remark: undefined,
|
||||
details: []
|
||||
}
|
||||
originalFormData.value = ''
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<!-- WMS 出库单打印 -->
|
||||
<template>
|
||||
<div class="hidden">
|
||||
<button ref="printButtonRef" v-print="printObj" type="button"></button>
|
||||
<div id="wmsShipmentOrderPrint" class="color-#303133">
|
||||
<div class="relative mb-8px">
|
||||
<h2 class="text-center">出库单</h2>
|
||||
<div v-if="printData.no" class="absolute right-0 top-0">
|
||||
<Barcode
|
||||
:content="printData.no"
|
||||
:display-value="false"
|
||||
:format="BarcodeFormatEnum.CODE39"
|
||||
:height="40"
|
||||
:width="180"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-12px grid grid-cols-3 gap-x-24px gap-y-8px text-14px">
|
||||
<div>出库单号:{{ printData.no || '-' }}</div>
|
||||
<div>
|
||||
出库类型:{{ getDictLabel(DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE, printData.type) || '-' }}
|
||||
</div>
|
||||
<div>仓库:{{ printData.warehouseName || '-' }}</div>
|
||||
<div>出库状态:{{ getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, printData.status) || '-' }}</div>
|
||||
<div>单据日期:{{ formatNullableDate(printData.orderTime, 'YYYY-MM-DD') }}</div>
|
||||
<div>客户:{{ printData.merchantName || '-' }}</div>
|
||||
<div>业务单号:{{ printData.bizOrderNo || '-' }}</div>
|
||||
<div>总数量:{{ formatQuantity(printData.totalQuantity) || '-' }}</div>
|
||||
<div>总金额:{{ formatPrice(printData.totalPrice) || '-' }}</div>
|
||||
<div class="col-span-3 grid grid-cols-2 gap-x-24px">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(printData.createTime) }} /
|
||||
{{ printData.creatorName || printData.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(printData.updateTime) }} /
|
||||
{{ printData.updaterName || printData.updater || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-3">备注:{{ printData.remark || '-' }}</div>
|
||||
</div>
|
||||
<table class="w-full border-collapse text-13px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">商品信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">规格信息</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">数量</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">单价(元)</th>
|
||||
<th class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-left">金额(元)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="detail in printRows" :key="detail.id">
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px">编号:{{ detail.itemCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px">编号:{{ detail.skuCode }}</div>
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.quantity) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.price) || '-' }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatPrice(detail.totalPrice) || '-' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="printRows.length">
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px" colspan="2">合计</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumQuantity(printRows, (detail) => detail.quantity) }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 bg-#f5f7fa p-8px text-right">
|
||||
{{ formatSumPrice(printRows, (detail) => detail.totalPrice) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!printRows.length">
|
||||
<td
|
||||
class="border border-solid border-#dcdfe6 p-8px text-center"
|
||||
:colspan="tableColumnCount"
|
||||
>
|
||||
暂无明细
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { ShipmentOrderApi, ShipmentOrderVO } from '@/api/wms/order/shipment'
|
||||
import { ShipmentOrderDetailVO } from '@/api/wms/order/shipment/detail'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity,
|
||||
multiplyPrice
|
||||
} from '@/views/wms/utils/format'
|
||||
import Barcode from '@/views/mes/wm/barcode/components/Barcode.vue'
|
||||
import { BarcodeFormatEnum } from '@/views/mes/utils/constants'
|
||||
|
||||
/** WMS 出库单打印 */
|
||||
defineOptions({ name: 'WmsShipmentOrderPrint' })
|
||||
|
||||
const printData = ref<ShipmentOrderVO>({}) // 打印数据
|
||||
const printButtonRef = ref<HTMLButtonElement>() // 打印按钮
|
||||
const tableColumnCount = 5
|
||||
const printObj = ref({
|
||||
id: 'wmsShipmentOrderPrint',
|
||||
popTitle: ' ',
|
||||
extraCss: '/print.css',
|
||||
extraHead: '',
|
||||
zIndex: 20003
|
||||
})
|
||||
|
||||
interface PrintRow extends ShipmentOrderDetailVO {
|
||||
totalPrice?: number
|
||||
}
|
||||
|
||||
const printRows = computed<PrintRow[]>(() =>
|
||||
(printData.value.details || []).map((detail) => ({
|
||||
...detail,
|
||||
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
|
||||
}))
|
||||
)
|
||||
|
||||
/** 打印出库单 */
|
||||
const print = async (id: number) => {
|
||||
printData.value = await ShipmentOrderApi.getShipmentOrder(id)
|
||||
await nextTick()
|
||||
printButtonRef.value?.click()
|
||||
}
|
||||
defineExpose({ print })
|
||||
</script>
|
||||
|
|
@ -0,0 +1,625 @@
|
|||
<!-- WMS 出库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="出库单号" prop="no">
|
||||
<el-input
|
||||
v-model="queryParams.no"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入出库单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务单号" prop="bizOrderNo">
|
||||
<el-input
|
||||
v-model="queryParams.bizOrderNo"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入业务单号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择单据状态"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" prop="warehouseId">
|
||||
<WarehouseSelect
|
||||
v-model="queryParams.warehouseId"
|
||||
class="!w-240px"
|
||||
@change="handleWarehouseChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户" prop="merchantId">
|
||||
<MerchantSelect
|
||||
v-model="queryParams.merchantId"
|
||||
class="!w-240px"
|
||||
placeholder="请选择客户"
|
||||
customer
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据日期" prop="orderTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.orderTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="totalQuantityMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalQuantityMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="QUANTITY_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额" prop="totalPriceMin">
|
||||
<div class="flex w-240px items-center gap-8px">
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMin"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最小值"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-input-number
|
||||
v-model="queryParams.totalPriceMax"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="PRICE_PRECISION"
|
||||
class="!w-105px"
|
||||
placeholder="最大值"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="出库类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请选择出库类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="更新用户" prop="updater">
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新时间" prop="updateTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.updateTime"
|
||||
:shortcuts="defaultShortcuts"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束时间"
|
||||
start-placeholder="开始时间"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
表格设置
|
||||
</el-button>
|
||||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="grid grid-cols-3 gap-y-14px rounded bg-[var(--el-fill-color-light)] p-16px"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="column in tableColumnOptions"
|
||||
:key="column.value"
|
||||
class="!h-28px !mr-0 [&_.el-checkbox__label]:font-600 [&_.el-checkbox__label]:text-16px"
|
||||
:label="column.value"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-popover>
|
||||
<el-button
|
||||
v-hasPermi="['wms:shipment-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:shipment-order:export']"
|
||||
:loading="exportLoading"
|
||||
plain
|
||||
type="success"
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:download" />
|
||||
导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 出库单列表 -->
|
||||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<el-table-column type="expand" width="48">
|
||||
<template #default="scope">
|
||||
<el-table :data="detailMap[scope.row.id] || []" border>
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.itemName || '-' }}</div>
|
||||
<div v-if="detailScope.row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ detailScope.row.itemCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="detailScope">
|
||||
<div>{{ detailScope.row.skuName || '-' }}</div>
|
||||
<div v-if="detailScope.row.skuCode" class="text-12px text-gray-500">
|
||||
规格编号:{{ detailScope.row.skuCode }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="出库数量" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatQuantity(detailScope.row.quantity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(detailScope.row.price) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="金额(元)" width="120">
|
||||
<template #default="detailScope">
|
||||
{{ formatPrice(getDetailTotalPrice(detailScope.row)) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('no')"
|
||||
fixed="left"
|
||||
label="单号/业务单号"
|
||||
width="290"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
单号:
|
||||
<el-button link type="primary" @click="openDetail(scope.row.id)">
|
||||
{{ scope.row.no }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="scope.row.bizOrderNo" class="text-12px text-gray-500">
|
||||
业务:{{ scope.row.bizOrderNo }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('status')"
|
||||
align="center"
|
||||
fixed="left"
|
||||
label="出库状态"
|
||||
width="110"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('type')"
|
||||
align="center"
|
||||
label="出库类型"
|
||||
width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.WMS_SHIPMENT_ORDER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="总数量/总金额(元)"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>数量:</span>
|
||||
<span>{{ formatQuantity(scope.row.totalQuantity) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>金额:</span>
|
||||
<span>{{ formatPrice(scope.row.totalPrice) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('merchant')"
|
||||
label="客户"
|
||||
min-width="160"
|
||||
prop="merchantName"
|
||||
/>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
创建:{{ formatNullableDate(scope.row.createTime) }} /
|
||||
{{ scope.row.creatorName || scope.row.creator || '-' }}
|
||||
</div>
|
||||
<div>
|
||||
更新:{{ formatNullableDate(scope.row.updateTime) }} /
|
||||
{{ scope.row.updaterName || scope.row.updater || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('remark')"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
prop="remark"
|
||||
/>
|
||||
<el-table-column align="center" fixed="right" label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:content="getShipmentOrderUpdateTip(scope.row.status)"
|
||||
:disabled="canUpdateShipmentOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:shipment-order:update']"
|
||||
:disabled="!canUpdateShipmentOrder(scope.row.status)"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="getShipmentOrderDeleteTip(scope.row.status)"
|
||||
:disabled="canDeleteShipmentOrder(scope.row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:shipment-order:delete']"
|
||||
:disabled="!canDeleteShipmentOrder(scope.row.status)"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
v-hasPermi="['wms:shipment-order:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrint(scope.row.id)"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ShipmentOrderForm ref="formRef" @success="getList" />
|
||||
<ShipmentOrderDetail ref="detailRef" />
|
||||
<ShipmentOrderPrint ref="printRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ShipmentOrderApi, ShipmentOrderVO } from '@/api/wms/order/shipment'
|
||||
import { ShipmentOrderDetailVO } from '@/api/wms/order/shipment/detail'
|
||||
import MerchantSelect from '@/views/wms/md/merchant/components/MerchantSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
multiplyPrice,
|
||||
PRICE_PRECISION,
|
||||
QUANTITY_PRECISION
|
||||
} from '@/views/wms/utils/format'
|
||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||
import ShipmentOrderDetail from './ShipmentOrderDetail.vue'
|
||||
import ShipmentOrderForm from './ShipmentOrderForm.vue'
|
||||
import ShipmentOrderPrint from './ShipmentOrderPrint.vue'
|
||||
import download from '@/utils/download'
|
||||
|
||||
/** WMS 出库单 */
|
||||
defineOptions({ name: 'WmsShipmentOrder' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
type TableColumnKey =
|
||||
| 'no'
|
||||
| 'status'
|
||||
| 'type'
|
||||
| 'warehouse'
|
||||
| 'quantityAmount'
|
||||
| 'merchant'
|
||||
| 'operateInfo'
|
||||
| 'remark'
|
||||
|
||||
const tableColumnOptions: Array<{ label: string; value: TableColumnKey }> = [
|
||||
{ label: '单号/业务单号', value: 'no' },
|
||||
{ label: '出库状态', value: 'status' },
|
||||
{ label: '出库类型', value: 'type' },
|
||||
{ label: '仓库', value: 'warehouse' },
|
||||
{ label: '数量/金额(元)', value: 'quantityAmount' },
|
||||
{ label: '客户', value: 'merchant' },
|
||||
{ label: '操作信息', value: 'operateInfo' },
|
||||
{ label: '备注', value: 'remark' }
|
||||
]
|
||||
const checkedTableColumns = ref<TableColumnKey[]>([
|
||||
'no',
|
||||
'status',
|
||||
'type',
|
||||
'warehouse',
|
||||
'quantityAmount',
|
||||
'merchant',
|
||||
'operateInfo',
|
||||
'remark'
|
||||
])
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ShipmentOrderVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
/** 获得默认的查询参数 */
|
||||
function getDefaultQueryParams() {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
no: undefined as string | undefined,
|
||||
status: undefined as number | undefined,
|
||||
warehouseId: undefined as number | undefined,
|
||||
merchantId: undefined as number | undefined,
|
||||
orderTime: undefined as string[] | undefined,
|
||||
totalQuantityMin: undefined as number | undefined,
|
||||
totalQuantityMax: undefined as number | undefined,
|
||||
totalPriceMin: undefined as number | undefined,
|
||||
totalPriceMax: undefined as number | undefined,
|
||||
type: undefined as number | undefined,
|
||||
bizOrderNo: undefined as string | undefined,
|
||||
creator: undefined as number | undefined,
|
||||
updater: undefined as number | undefined,
|
||||
createTime: undefined as string[] | undefined,
|
||||
updateTime: undefined as string[] | undefined
|
||||
}
|
||||
}
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const detailMap = reactive<Record<number, ShipmentOrderDetailVO[]>>({}) // 出库单明细数据
|
||||
|
||||
/** 判断表格列是否可见 */
|
||||
function isTableColumnVisible(column: TableColumnKey) {
|
||||
return checkedTableColumns.value.includes(column)
|
||||
}
|
||||
|
||||
/** 是否允许修改出库单 */
|
||||
const canUpdateShipmentOrder = (status?: number) => {
|
||||
return status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 是否允许删除出库单 */
|
||||
const canDeleteShipmentOrder = (status?: number) => {
|
||||
return status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
}
|
||||
|
||||
/** 获得出库单修改禁用提示 */
|
||||
const getShipmentOrderUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已出库,无法修改'
|
||||
}
|
||||
if (status === OrderStatusEnum.CANCELED) {
|
||||
return '已作废,无法修改'
|
||||
}
|
||||
return '当前状态无法修改'
|
||||
}
|
||||
|
||||
/** 获得出库单删除禁用提示 */
|
||||
const getShipmentOrderDeleteTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) {
|
||||
return '已出库,无法删除'
|
||||
}
|
||||
return '当前状态无法删除'
|
||||
}
|
||||
|
||||
/** 查询出库单列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ShipmentOrderApi.getShipmentOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 仓库变化 */
|
||||
const handleWarehouseChange = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 计算明细金额 */
|
||||
const getDetailTotalPrice = (detail: ShipmentOrderDetailVO) => {
|
||||
if (detail.totalPrice !== undefined && detail.totalPrice !== null) {
|
||||
return detail.totalPrice
|
||||
}
|
||||
return multiplyPrice(detail.quantity, detail.price)
|
||||
}
|
||||
|
||||
/** 展开明细 */
|
||||
const handleExpandChange = async (row: ShipmentOrderVO, expandedRows: ShipmentOrderVO[]) => {
|
||||
if (!row.id || !expandedRows.some((item) => item.id === row.id)) {
|
||||
return
|
||||
}
|
||||
delete detailMap[row.id]
|
||||
detailMap[row.id] = await ShipmentOrderApi.getShipmentOrderDetailListByOrderId(row.id)
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 查看出库单详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 打印出库单 */
|
||||
const printRef = ref()
|
||||
const handlePrint = (id: number) => {
|
||||
printRef.value.print(id)
|
||||
}
|
||||
|
||||
/** 删除出库单 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ShipmentOrderApi.deleteShipmentOrder(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await ShipmentOrderApi.exportShipmentOrder(queryParams)
|
||||
download.excel(data, '出库单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/** 单据状态枚举 */
|
||||
export const OrderStatusEnum = {
|
||||
PREPARE: 0, // 草稿
|
||||
FINISHED: 4, // 已完成
|
||||
CANCELED: 5 // 已作废
|
||||
} as const
|
||||
|
||||
/** 可修改的单据状态 */
|
||||
export const OrderUpdateStatusList: number[] = [OrderStatusEnum.PREPARE]
|
||||
|
||||
/** 可删除的单据状态 */
|
||||
export const OrderDeleteStatusList: number[] = [OrderStatusEnum.PREPARE, OrderStatusEnum.CANCELED]
|
||||
|
||||
/** 往来企业类型枚举 */
|
||||
export const MerchantTypeEnum = {
|
||||
CUSTOMER: 1, // 客户
|
||||
SUPPLIER: 2, // 供应商
|
||||
CUSTOMER_SUPPLIER: 3 // 客户/供应商
|
||||
} as const
|
||||
|
||||
/** 供应商类型的往来企业 */
|
||||
export const SupplierMerchantTypeList = [
|
||||
MerchantTypeEnum.SUPPLIER,
|
||||
MerchantTypeEnum.CUSTOMER_SUPPLIER
|
||||
]
|
||||
|
||||
/** 客户类型的往来企业 */
|
||||
export const CustomerMerchantTypeList = [
|
||||
MerchantTypeEnum.CUSTOMER,
|
||||
MerchantTypeEnum.CUSTOMER_SUPPLIER
|
||||
]
|
||||
|
||||
/**
|
||||
* 生成 WMS 编号 / 条码。
|
||||
*
|
||||
* @param prefix 可选前缀,按业务域区分编号种类:
|
||||
* - `I` 商品编号
|
||||
* - `S` SKU 编号
|
||||
* - `W` 仓库编号
|
||||
* - `M` 往来企业编号
|
||||
* - 不传 / 空串 用于 SKU 条码(条码业务习惯纯数字)
|
||||
* @returns 前缀 + 8 位随机数字串。
|
||||
*
|
||||
* 由前端在表单【生成】按钮上调用,避免后端兜底生成造成编号不可控。
|
||||
*/
|
||||
export function generateWmsCode(prefix: string = ''): string {
|
||||
let result = ''
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += Math.floor(Math.random() * 10).toString()
|
||||
}
|
||||
return prefix + result
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* WMS 展示格式化和表单配置
|
||||
*/
|
||||
|
||||
import { isNullOrUnDef } from '@/utils/is'
|
||||
|
||||
type DecimalValue = number | string | null | undefined
|
||||
|
||||
/** 数量小数位 */
|
||||
export const QUANTITY_PRECISION = 2
|
||||
/** 金额小数位 */
|
||||
export const PRICE_PRECISION = 2
|
||||
/** 重量小数位 */
|
||||
export const WEIGHT_PRECISION = 3
|
||||
/** 长宽高小数位 */
|
||||
export const DIMENSION_PRECISION = 1
|
||||
|
||||
function toFiniteDecimal(value: DecimalValue) {
|
||||
if (isNullOrUnDef(value)) {
|
||||
return undefined
|
||||
}
|
||||
if (typeof value === 'string' && value.trim() === '') {
|
||||
return undefined
|
||||
}
|
||||
const decimalValue = typeof value === 'string' ? Number(value) : value
|
||||
if (!Number.isFinite(decimalValue)) {
|
||||
return undefined
|
||||
}
|
||||
return decimalValue
|
||||
}
|
||||
|
||||
function sumDecimal<T>(list: T[], getter: (item: T) => DecimalValue) {
|
||||
return list.reduce((sum, item) => {
|
||||
const decimalValue = toFiniteDecimal(getter(item))
|
||||
return decimalValue === undefined ? sum : sum + decimalValue
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/** 格式化数量 */
|
||||
export function formatQuantity(value?: number | string | null) {
|
||||
const decimalValue = toFiniteDecimal(value)
|
||||
return decimalValue === undefined ? '' : decimalValue.toFixed(QUANTITY_PRECISION)
|
||||
}
|
||||
|
||||
/** 格式化金额 */
|
||||
export function formatPrice(value?: number | string | null) {
|
||||
const decimalValue = toFiniteDecimal(value)
|
||||
return decimalValue === undefined ? '' : decimalValue.toFixed(PRICE_PRECISION)
|
||||
}
|
||||
|
||||
/** 金额四舍五入 */
|
||||
export function roundPrice(value: number) {
|
||||
return Number.isFinite(value) ? Number(value.toFixed(PRICE_PRECISION)) : undefined
|
||||
}
|
||||
|
||||
/** 亏损数字样式 */
|
||||
export function getLossClass(value?: number | string | null) {
|
||||
const decimalValue = toFiniteDecimal(value)
|
||||
return decimalValue !== undefined && decimalValue < 0 ? 'text-red-500' : ''
|
||||
}
|
||||
|
||||
/** 数量 * 单价,计算金额 */
|
||||
export function multiplyPrice(quantity?: number, price?: number) {
|
||||
if (quantity === undefined || quantity === null || price === undefined || price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(quantity) * Number(price))
|
||||
}
|
||||
|
||||
/** 金额 / 数量,反算单价 */
|
||||
export function dividePrice(totalPrice?: number, quantity?: number) {
|
||||
if (totalPrice === undefined || totalPrice === null || !quantity) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(totalPrice) / Number(quantity))
|
||||
}
|
||||
|
||||
/** 汇总数量 */
|
||||
export function sumQuantity<T>(list: T[], getter: (item: T) => DecimalValue) {
|
||||
return sumDecimal(list, getter)
|
||||
}
|
||||
|
||||
/** 汇总金额 */
|
||||
export function sumPrice<T>(list: T[], getter: (item: T) => DecimalValue) {
|
||||
return sumDecimal(list, getter)
|
||||
}
|
||||
|
||||
/** 格式化汇总数量 */
|
||||
export function formatSumQuantity<T>(list: T[], getter: (item: T) => DecimalValue) {
|
||||
return formatQuantity(sumQuantity(list, getter))
|
||||
}
|
||||
|
||||
/** 格式化汇总金额 */
|
||||
export function formatSumPrice<T>(list: T[], getter: (item: T) => DecimalValue) {
|
||||
return formatPrice(sumPrice(list, getter))
|
||||
}
|
||||
|
||||
/** 格式化重量 */
|
||||
export function formatWeight(value?: number | string | null) {
|
||||
const decimalValue = toFiniteDecimal(value)
|
||||
return decimalValue === undefined ? '' : decimalValue.toFixed(WEIGHT_PRECISION)
|
||||
}
|
||||
|
||||
/** 格式化长宽高 */
|
||||
export function formatDimension(value?: number | string | null) {
|
||||
const decimalValue = toFiniteDecimal(value)
|
||||
return decimalValue === undefined ? '' : decimalValue.toFixed(DIMENSION_PRECISION)
|
||||
}
|
||||
|
||||
/** 格式化长宽高组合 */
|
||||
export function formatDimensionText(
|
||||
length?: number | string | null,
|
||||
width?: number | string | null,
|
||||
height?: number | string | null
|
||||
) {
|
||||
if (!isNullOrUnDef(length) && !isNullOrUnDef(width) && !isNullOrUnDef(height)) {
|
||||
return [formatDimension(length), formatDimension(width), formatDimension(height)].join(' * ')
|
||||
}
|
||||
return [
|
||||
!isNullOrUnDef(length) ? `长:${formatDimension(length)}` : undefined,
|
||||
!isNullOrUnDef(width) ? `宽:${formatDimension(width)}` : undefined,
|
||||
!isNullOrUnDef(height) ? `高:${formatDimension(height)}` : undefined
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* WMS 订单工具
|
||||
*/
|
||||
|
||||
/** 生成业务单号:前缀 + 月日 + 4 位随机数 */
|
||||
export function generateOrderNo(prefix: string) {
|
||||
const now = new Date()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const randomNo = String(Math.floor(Math.random() * 10000)).padStart(4, '0')
|
||||
return `${prefix}${month}${day}${randomNo}`
|
||||
}
|
||||
Loading…
Reference in New Issue