feat(wms):优化首页的代码实现
parent
58537a34c7
commit
ae54f938cf
|
|
@ -3,26 +3,26 @@ import request from '@/config/axios'
|
|||
// WMS 首页统计查询参数
|
||||
export interface WmsHomeStatisticsReqVO {
|
||||
warehouseId?: number
|
||||
goodsLimit?: number
|
||||
warehouseLimit?: number
|
||||
}
|
||||
|
||||
// WMS 首页单据状态统计 VO
|
||||
export interface WmsHomeOrderStatusVO {
|
||||
status: number
|
||||
statusName: string
|
||||
count: number
|
||||
}
|
||||
|
||||
// WMS 首页单据汇总统计 VO
|
||||
export interface WmsHomeOrderSummaryVO {
|
||||
orderType: number
|
||||
orderTypeName: string
|
||||
type: number
|
||||
total: number
|
||||
statusList: WmsHomeOrderStatusVO[]
|
||||
statuses: WmsHomeOrderStatusVO[]
|
||||
}
|
||||
|
||||
// WMS 首页单据趋势 VO
|
||||
export interface WmsHomeOrderTrendVO {
|
||||
date: string
|
||||
time: string | number
|
||||
receiptCount: number
|
||||
shipmentCount: number
|
||||
movementCount: number
|
||||
|
|
@ -31,15 +31,15 @@ export interface WmsHomeOrderTrendVO {
|
|||
|
||||
// WMS 首页商品库存排行 VO
|
||||
export interface WmsHomeInventoryItemRankVO {
|
||||
itemId: number
|
||||
itemName: string
|
||||
id: number
|
||||
name: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
// WMS 首页仓库库存排行 VO
|
||||
export interface WmsHomeInventoryWarehouseRankVO {
|
||||
warehouseId: number
|
||||
warehouseName: string
|
||||
id: number
|
||||
name: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
<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[]>([])
|
||||
|
||||
/** 格式化库存数量展示 */
|
||||
const formatQuantityText = (value: number) => 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,219 @@
|
|||
<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('当前菜单尚未加载,请从左侧菜单进入对应页面')
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算状态进度条占比 */
|
||||
const getStatusPercent = (item: OrderSummaryItem, status: number) => {
|
||||
const count = item.statusCounts[status] || 0
|
||||
if (!item.total || !count) {
|
||||
return '0%'
|
||||
}
|
||||
return `${Math.max((count / item.total) * 100, 4)}%`
|
||||
}
|
||||
|
||||
/** 格式化单据数量 */
|
||||
const formatCount = (value: number) => 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>
|
||||
|
|
@ -1,29 +1,25 @@
|
|||
<!-- TODO @AI:组件拆分下。 -->
|
||||
<template>
|
||||
<div class="wms-home">
|
||||
<div class="wms-home__toolbar">
|
||||
<div class="wms-home__toolbar-main">
|
||||
<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="wms-home__title">WMS 首页</div>
|
||||
<div class="wms-home__subtitle">单据工作台 / 库存概览</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="wms-home__filters">
|
||||
<el-select
|
||||
<div class="flex flex-wrap items-center gap-8px lt-sm:w-full">
|
||||
<WarehouseSelect
|
||||
v-model="warehouseId"
|
||||
class="!w-220px"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部仓库"
|
||||
@change="refresh"
|
||||
>
|
||||
<el-option
|
||||
v-for="warehouse in warehouseList"
|
||||
:key="warehouse.id!"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id!"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :loading="loading || trendLoading || inventoryLoading" @click="refresh">
|
||||
@change="refresh()"
|
||||
/>
|
||||
<el-button :loading="loading" @click="refresh">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
刷新
|
||||
</el-button>
|
||||
|
|
@ -31,97 +27,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<el-skeleton :loading="loading" animated>
|
||||
<template #template>
|
||||
<div class="wms-home__summary-grid">
|
||||
<el-skeleton-item v-for="item in 4" :key="item" class="wms-home__summary-skeleton" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="wms-home__summary-grid">
|
||||
<div
|
||||
v-for="item in orderSummaries"
|
||||
:key="item.key"
|
||||
class="wms-home__summary-card"
|
||||
:style="{ '--theme-color': item.color }"
|
||||
>
|
||||
<div class="wms-home__summary-head">
|
||||
<div class="wms-home__summary-title">
|
||||
<span :style="{ backgroundColor: item.color }"></span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<el-button link type="primary" @click="handleNavigate(item.routeName)">查看</el-button>
|
||||
</div>
|
||||
<div class="wms-home__summary-total">
|
||||
<em>合计</em>
|
||||
<span>{{ formatCount(item.total) }}</span>
|
||||
<em>单</em>
|
||||
</div>
|
||||
<div class="wms-home__status-bar">
|
||||
<span
|
||||
v-for="status in statusList"
|
||||
:key="status.key"
|
||||
:style="{
|
||||
width: getStatusPercent(item, status.key),
|
||||
backgroundColor: status.color
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="wms-home__status-list">
|
||||
<div v-for="status in statusList" :key="status.key">
|
||||
<span>{{ status.label }}</span>
|
||||
<strong :style="{ color: status.color }">{{ item.statusCounts[status.key] }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-skeleton>
|
||||
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
|
||||
<WmsHomeOrderTrendChart ref="orderTrendChartRef" />
|
||||
<WmsHomeInventoryCharts ref="inventoryChartsRef" />
|
||||
|
||||
<div class="wms-home__chart-card">
|
||||
<div class="wms-home__card-head">
|
||||
<div>
|
||||
<div class="wms-home__card-title">单据趋势</div>
|
||||
<div class="wms-home__card-subtitle">入库、出库、移库、盘库单据数量</div>
|
||||
</div>
|
||||
<el-segmented v-model="trendDays" :options="trendDayOptions" @change="loadTrendData" />
|
||||
</div>
|
||||
<el-skeleton :loading="trendLoading" animated>
|
||||
<Echart :height="330" :options="trendChartOptions" />
|
||||
</el-skeleton>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :lg="12" :md="24" :sm="24" :xl="12" :xs="24">
|
||||
<div class="wms-home__chart-card">
|
||||
<div class="wms-home__card-head">
|
||||
<div>
|
||||
<div class="wms-home__card-title">货物占比</div>
|
||||
<div class="wms-home__card-subtitle">按商品库存数量汇总 Top 5</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-skeleton :loading="inventoryLoading" 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="wms-home__chart-card">
|
||||
<div class="wms-home__card-head">
|
||||
<div>
|
||||
<div class="wms-home__card-title">库存分布</div>
|
||||
<div class="wms-home__card-subtitle">按仓库库存数量汇总</div>
|
||||
</div>
|
||||
<div class="wms-home__total-quantity"
|
||||
>总库存 {{ formatQuantityText(totalQuantity) }}</div
|
||||
>
|
||||
</div>
|
||||
<el-skeleton :loading="inventoryLoading" animated>
|
||||
<Echart :height="300" :options="inventoryDistributionChartOptions" />
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="wms-home__stat-time">
|
||||
<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>
|
||||
|
|
@ -129,562 +41,38 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { WmsHomeOrderTrendVO, WmsHomeStatisticsApi, WmsHomeStatisticsReqVO } from '@/api/wms/home'
|
||||
import { WarehouseApi, WarehouseVO } from '@/api/wms/md/warehouse'
|
||||
import { OrderStatusEnum } from '@/views/wms/utils/constants'
|
||||
import { formatQuantity } from '@/views/wms/utils/format'
|
||||
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' })
|
||||
|
||||
type StatusKey = 'prepare' | 'finished' | 'canceled'
|
||||
type OrderKey = 'receipt' | 'shipment' | 'movement' | 'check'
|
||||
|
||||
interface OrderDefinition {
|
||||
key: OrderKey
|
||||
orderType: number
|
||||
title: string
|
||||
color: string
|
||||
routeName: string
|
||||
trendField: keyof Pick<
|
||||
WmsHomeOrderTrendVO,
|
||||
'receiptCount' | 'shipmentCount' | 'movementCount' | 'checkCount'
|
||||
>
|
||||
}
|
||||
|
||||
interface OrderSummary extends OrderDefinition {
|
||||
total: number
|
||||
statusCounts: Record<StatusKey, number>
|
||||
}
|
||||
|
||||
interface ChartItem {
|
||||
name: string
|
||||
value: number
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const statusList: Array<{ key: StatusKey; label: string; value: number; color: string }> = [
|
||||
{ key: 'prepare', label: '草稿', value: OrderStatusEnum.PREPARE, color: '#409eff' },
|
||||
{ key: 'finished', label: '已完成', value: OrderStatusEnum.FINISHED, color: '#67c23a' },
|
||||
{ key: 'canceled', label: '已作废', value: OrderStatusEnum.CANCELED, color: '#909399' }
|
||||
]
|
||||
|
||||
const OrderTypeEnum = {
|
||||
RECEIPT: 1,
|
||||
SHIPMENT: 2,
|
||||
MOVEMENT: 3,
|
||||
CHECK: 4
|
||||
} as const
|
||||
|
||||
const orderDefinitions: OrderDefinition[] = [
|
||||
{
|
||||
key: 'receipt',
|
||||
orderType: OrderTypeEnum.RECEIPT,
|
||||
title: '入库',
|
||||
color: '#2f7df6',
|
||||
routeName: 'WmsReceiptOrder',
|
||||
trendField: 'receiptCount'
|
||||
},
|
||||
{
|
||||
key: 'shipment',
|
||||
orderType: OrderTypeEnum.SHIPMENT,
|
||||
title: '出库',
|
||||
color: '#18a058',
|
||||
routeName: 'WmsShipmentOrder',
|
||||
trendField: 'shipmentCount'
|
||||
},
|
||||
{
|
||||
key: 'movement',
|
||||
orderType: OrderTypeEnum.MOVEMENT,
|
||||
title: '移库',
|
||||
color: '#f59e0b',
|
||||
routeName: 'WmsMovementOrder',
|
||||
trendField: 'movementCount'
|
||||
},
|
||||
{
|
||||
key: 'check',
|
||||
orderType: OrderTypeEnum.CHECK,
|
||||
title: '盘库',
|
||||
color: '#7c3aed',
|
||||
routeName: 'WmsCheckOrder',
|
||||
trendField: 'checkCount'
|
||||
}
|
||||
]
|
||||
|
||||
const trendDayOptions = [
|
||||
{ label: '近7天', value: 7 },
|
||||
{ label: '近30天', value: 30 }
|
||||
]
|
||||
|
||||
const loading = ref(false)
|
||||
const trendLoading = ref(false)
|
||||
const inventoryLoading = ref(false)
|
||||
const warehouseId = ref<number>()
|
||||
const warehouseList = ref<WarehouseVO[]>([])
|
||||
const statTime = ref(formatDate(new Date()))
|
||||
const orderSummaries = ref<OrderSummary[]>(
|
||||
orderDefinitions.map((item) => ({
|
||||
...item,
|
||||
total: 0,
|
||||
statusCounts: { prepare: 0, finished: 0, canceled: 0 }
|
||||
}))
|
||||
)
|
||||
const trendDays = ref(7)
|
||||
const trendLabels = ref<string[]>([])
|
||||
const trendSeriesMap = reactive<Record<OrderKey, number[]>>({
|
||||
receipt: [],
|
||||
shipment: [],
|
||||
movement: [],
|
||||
check: []
|
||||
})
|
||||
const totalQuantity = ref(0)
|
||||
const goodsShareList = ref<ChartItem[]>([])
|
||||
const inventoryDistributionList = ref<ChartItem[]>([])
|
||||
|
||||
const getStatisticsParams = (): WmsHomeStatisticsReqVO => {
|
||||
return warehouseId.value ? { warehouseId: warehouseId.value } : {}
|
||||
}
|
||||
|
||||
const loadOrderSummaries = async () => {
|
||||
const data = await WmsHomeStatisticsApi.getOrderSummary(getStatisticsParams())
|
||||
orderSummaries.value = orderDefinitions.map((definition) => {
|
||||
const summary = data.find((item) => item.orderType === definition.orderType)
|
||||
const statusCounts = statusList.reduce(
|
||||
(result, status) => {
|
||||
const statusItem = summary?.statusList?.find((item) => item.status === status.value)
|
||||
result[status.key] = statusItem?.count || 0
|
||||
return result
|
||||
},
|
||||
{ prepare: 0, finished: 0, canceled: 0 } as Record<StatusKey, number>
|
||||
)
|
||||
return {
|
||||
...definition,
|
||||
total: summary?.total || 0,
|
||||
statusCounts
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 loadTrendData = async () => {
|
||||
trendLoading.value = true
|
||||
try {
|
||||
const data = await WmsHomeStatisticsApi.getOrderTrend(trendDays.value, getStatisticsParams())
|
||||
trendLabels.value = data.map((item) => item.date.substring(5))
|
||||
orderDefinitions.forEach((definition) => {
|
||||
trendSeriesMap[definition.key] = data.map((item) => Number(item[definition.trendField] || 0))
|
||||
})
|
||||
} finally {
|
||||
trendLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadInventoryData = async () => {
|
||||
inventoryLoading.value = true
|
||||
try {
|
||||
const data = await WmsHomeStatisticsApi.getInventorySummary(getStatisticsParams())
|
||||
totalQuantity.value = Number(data.totalQuantity || 0)
|
||||
goodsShareList.value = buildChartItemList(
|
||||
data.goodsShareList,
|
||||
(item) => item.itemName || '未命名商品',
|
||||
(item) => item.quantity
|
||||
)
|
||||
inventoryDistributionList.value = buildChartItemList(
|
||||
data.warehouseDistributionList,
|
||||
(item) => item.warehouseName || '未指定仓库',
|
||||
(item) => item.quantity
|
||||
)
|
||||
} finally {
|
||||
inventoryLoading.value = false
|
||||
}
|
||||
}
|
||||
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([loadOrderSummaries(), loadTrendData(), loadInventoryData()])
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
const loadWarehouseList = async () => {
|
||||
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
||||
}
|
||||
|
||||
const handleNavigate = async (name: string) => {
|
||||
try {
|
||||
await router.push({ name })
|
||||
} catch {
|
||||
message.warning('当前菜单尚未加载,请从左侧菜单进入对应页面')
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusPercent = (item: OrderSummary, key: StatusKey) => {
|
||||
if (!item.total) {
|
||||
return `${100 / statusList.length}%`
|
||||
}
|
||||
return `${Math.max((item.statusCounts[key] / item.total) * 100, 4)}%`
|
||||
}
|
||||
|
||||
const formatCount = (value: number) => value.toLocaleString()
|
||||
const formatQuantityText = (value: number) => 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 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 trendChartOptions = 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.key],
|
||||
emphasis: { focus: 'series' }
|
||||
}))
|
||||
}))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
]
|
||||
}))
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await loadWarehouseList()
|
||||
await refresh()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wms-home {
|
||||
--wms-card-radius: 8px;
|
||||
}
|
||||
|
||||
.wms-home__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: var(--wms-card-radius);
|
||||
}
|
||||
|
||||
.wms-home__toolbar-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.wms-home__title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wms-home__subtitle,
|
||||
.wms-home__card-subtitle,
|
||||
.wms-home__stat-time {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.wms-home__filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wms-home__summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.wms-home__summary-skeleton {
|
||||
width: 100%;
|
||||
height: 174px;
|
||||
border-radius: var(--wms-card-radius);
|
||||
}
|
||||
|
||||
.wms-home__summary-card,
|
||||
.wms-home__chart-card {
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: var(--wms-card-radius);
|
||||
box-shadow: 0 8px 24px rgb(15 23 42 / 4%);
|
||||
}
|
||||
|
||||
.wms-home__summary-card {
|
||||
min-height: 154px;
|
||||
padding: 16px 18px;
|
||||
border-top: 3px solid var(--theme-color);
|
||||
}
|
||||
|
||||
.wms-home__summary-head,
|
||||
.wms-home__card-head {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wms-home__summary-title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wms-home__summary-title span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.wms-home__summary-total {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.wms-home__summary-total span {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
line-height: 38px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wms-home__summary-total em {
|
||||
font-style: normal;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.wms-home__status-bar {
|
||||
display: flex;
|
||||
height: 8px;
|
||||
margin-top: 14px;
|
||||
overflow: hidden;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.wms-home__status-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.wms-home__status-list div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wms-home__status-list span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wms-home__status-list strong {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.wms-home__chart-card {
|
||||
margin-bottom: 16px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.wms-home__card-head {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.wms-home__card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wms-home__total-quantity {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.wms-home__stat-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.wms-home__summary-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.wms-home__toolbar-main,
|
||||
.wms-home__filters {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wms-home__summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.wms-home__card-head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@
|
|||
<div class="mb-12px text-16px font-500">库存流水</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-inventory-history-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
|
|
@ -231,9 +231,3 @@ onMounted(async () => {
|
|||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-inventory-history-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@
|
|||
>
|
||||
<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"
|
||||
>
|
||||
<el-radio-button v-for="item in dimensionOptions" :key="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
|
@ -82,7 +78,7 @@
|
|||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-inventory-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:span-method="spanMethod"
|
||||
|
|
@ -283,9 +279,3 @@ onMounted(async () => {
|
|||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-inventory-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@
|
|||
<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>
|
||||
<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">
|
||||
|
|
@ -104,13 +106,17 @@
|
|||
<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>
|
||||
<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>
|
||||
<div v-if="row.grossWeight !== undefined">
|
||||
毛重:{{ formatWeight(row.grossWeight) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="长宽高(cm)" min-width="180">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
<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="仓库">
|
||||
{{ detailData.warehouseName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据日期">
|
||||
{{ formatNullableDate(detailData.orderTime, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
|
|
@ -45,7 +47,9 @@
|
|||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="备注">{{ detailData.remark || '-' }}</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>
|
||||
|
|
@ -53,13 +57,17 @@
|
|||
<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>
|
||||
<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>
|
||||
<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">
|
||||
|
|
@ -83,7 +91,9 @@
|
|||
</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>
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{
|
||||
formatPrice(getDifferencePrice(row)) || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
@ -116,9 +126,15 @@ const dialogVisible = ref(false)
|
|||
const detailData = ref<CheckOrderVO>({})
|
||||
const getOrderDifferencePrice = (order: CheckOrderVO) =>
|
||||
roundPrice(Number(order.actualPrice || 0) - Number(order.totalPrice || 0))
|
||||
const getDifferenceQuantity = (detail: CheckOrderDetailVO) => Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getDifferenceQuantity = (detail: CheckOrderDetailVO) =>
|
||||
Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getActualPrice = (detail: CheckOrderDetailVO) => {
|
||||
if (detail.checkQuantity === undefined || detail.checkQuantity === null || detail.price === undefined || detail.price === null) {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
|
|
@ -129,20 +145,36 @@ const getDifferencePrice = (detail: CheckOrderDetailVO) => {
|
|||
}
|
||||
return roundPrice(getDifferenceQuantity(detail) * Number(detail.price))
|
||||
}
|
||||
const renderLossText = (value: number | string | null | undefined, formatter: (value?: number | string | null) => string) =>
|
||||
h('span', { class: getLossClass(value) }, formatter(value))
|
||||
const renderLossText = (
|
||||
value: number | string | null | undefined,
|
||||
formatter: (value?: number | string | null) => string
|
||||
) => h('span', { class: getLossClass(value) }, formatter(value))
|
||||
|
||||
const getSummaries = ({ columns, data }: { columns: any[]; data: CheckOrderDetailVO[] }) =>
|
||||
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 (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)
|
||||
return renderLossText(
|
||||
sumQuantity(data, (detail) => getDifferenceQuantity(detail)),
|
||||
formatQuantity
|
||||
)
|
||||
}
|
||||
if (column.property === 'differencePrice') {
|
||||
return renderLossText(sumPrice(data, (detail) => getDifferencePrice(detail)), formatPrice)
|
||||
return renderLossText(
|
||||
sumPrice(data, (detail) => getDifferencePrice(detail)),
|
||||
formatPrice
|
||||
)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
<!-- 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-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">
|
||||
|
|
@ -31,7 +37,13 @@
|
|||
</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-input
|
||||
v-model="formData.remark"
|
||||
maxlength="255"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
@ -40,7 +52,12 @@
|
|||
<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">
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
|
|
@ -57,13 +74,17 @@
|
|||
<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>
|
||||
<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>
|
||||
<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">
|
||||
|
|
@ -110,12 +131,16 @@
|
|||
</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>
|
||||
<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>
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{
|
||||
formatPrice(getDifferencePrice(row))
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="操作" width="80">
|
||||
|
|
@ -153,7 +178,13 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button v-if="isPrepareOrder" :disabled="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>保存</el-button
|
||||
>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -166,7 +197,9 @@ 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 InventorySelect, { InventorySelectRow } from '@/views/wms/inventory/components/InventorySelect.vue'
|
||||
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 {
|
||||
|
|
@ -217,20 +250,30 @@ const formRules = reactive<FormRules>({
|
|||
const formRef = ref()
|
||||
const inventorySelectRef = ref()
|
||||
|
||||
const getDifferenceQuantity = (detail: CheckOrderFormDetail) => Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getDifferenceQuantity = (detail: CheckOrderFormDetail) =>
|
||||
Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getBookPrice = (detail: CheckOrderFormDetail) => multiplyPrice(detail.quantity, detail.price)
|
||||
const getActualPrice = (detail: CheckOrderFormDetail) => detail.actualPrice ?? multiplyPrice(detail.checkQuantity, detail.price)
|
||||
const getActualPrice = (detail: CheckOrderFormDetail) =>
|
||||
detail.actualPrice ?? multiplyPrice(detail.checkQuantity, detail.price)
|
||||
const getDifferencePrice = (detail: CheckOrderFormDetail) => {
|
||||
if (detail.price === undefined || detail.price === null) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(getDifferenceQuantity(detail) * Number(detail.price))
|
||||
}
|
||||
const renderLossText = (value: number | string | null | undefined, formatter: (value?: number | string | null) => string) =>
|
||||
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 renderLossText = (
|
||||
value: number | string | null | undefined,
|
||||
formatter: (value?: number | string | null) => string
|
||||
) => 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(
|
||||
() =>
|
||||
|
|
@ -329,10 +372,14 @@ const getDetailSummaries = ({ columns, data }: { columns: any[]; data: CheckOrde
|
|||
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)
|
||||
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 ''
|
||||
})
|
||||
|
||||
|
|
@ -357,14 +404,18 @@ const validateDetails = (required: boolean) => {
|
|||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const { totalQuantity: _totalQuantity, totalPrice: _totalPrice, actualPrice: _actualPrice, details, ...order } = formData.value
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
actualPrice: _actualPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: (details || []).map(({
|
||||
actualPrice: _rowActualPrice,
|
||||
availableQuantity: _availableQuantity,
|
||||
...detail
|
||||
}) => detail)
|
||||
details: (details || []).map(
|
||||
({ actualPrice: _rowActualPrice, availableQuantity: _availableQuantity, ...detail }) => detail
|
||||
)
|
||||
} as CheckOrderVO
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,13 @@
|
|||
<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>
|
||||
|
|
@ -145,9 +149,15 @@ interface PrintRow extends CheckOrderDetailVO {
|
|||
differencePrice?: number
|
||||
}
|
||||
|
||||
const getDifferenceQuantity = (detail: CheckOrderDetailVO) => Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getDifferenceQuantity = (detail: CheckOrderDetailVO) =>
|
||||
Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||
const getActualPrice = (detail: CheckOrderDetailVO) => {
|
||||
if (detail.checkQuantity === undefined || detail.checkQuantity === null || detail.price === undefined || detail.price === null) {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
|
|
@ -171,8 +181,12 @@ const printRows = computed<PrintRow[]>(() =>
|
|||
}
|
||||
})
|
||||
)
|
||||
const totalDifferenceQuantity = computed(() => sumQuantity(printRows.value, (detail) => detail.differenceQuantity))
|
||||
const totalDifferencePrice = computed(() => sumPrice(printRows.value, (detail) => detail.differencePrice))
|
||||
const totalDifferenceQuantity = computed(() =>
|
||||
sumQuantity(printRows.value, (detail) => detail.differenceQuantity)
|
||||
)
|
||||
const totalDifferencePrice = computed(() =>
|
||||
sumPrice(printRows.value, (detail) => detail.differencePrice)
|
||||
)
|
||||
|
||||
/** 打印盘库单 */
|
||||
const print = async (id: number) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,29 @@
|
|||
<!-- WMS 盘库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="90px">
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="盘库单号" prop="no">
|
||||
<el-input v-model="queryParams.no" class="!w-240px" clearable placeholder="请输入盘库单号" @keyup.enter="handleQuery" />
|
||||
<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-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"
|
||||
|
|
@ -127,7 +144,7 @@
|
|||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="wms-check-order-table-setting-popover" trigger="click" width="520">
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
|
|
@ -136,14 +153,24 @@
|
|||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="wms-check-order-table-setting grid grid-cols-3 gap-y-14px rounded p-16px"
|
||||
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" :label="column.value">
|
||||
<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')">
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
|
|
@ -164,7 +191,7 @@
|
|||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-check-order-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
|
|
@ -176,26 +203,34 @@
|
|||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="{ row: detail }">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px text-gray-500">商品编号:{{ detail.itemCode }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px text-gray-500"
|
||||
>商品编号:{{ detail.itemCode }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="{ row: detail }">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px text-gray-500">规格编号:{{ detail.skuCode }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px text-gray-500"
|
||||
>规格编号:{{ detail.skuCode }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="账面数量" width="120">
|
||||
<template #default="{ row: detail }">{{ formatQuantity(detail.quantity) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实盘数量" width="120">
|
||||
<template #default="{ row: detail }">{{ formatQuantity(detail.checkQuantity) }}</template>
|
||||
<template #default="{ row: detail }">{{
|
||||
formatQuantity(detail.checkQuantity)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="单价(元)" width="120">
|
||||
<template #default="{ row: detail }">{{ formatPrice(detail.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="实际金额(元)" width="140">
|
||||
<template #default="{ row: detail }">{{ formatPrice(getDetailActualPrice(detail)) }}</template>
|
||||
<template #default="{ row: detail }">{{
|
||||
formatPrice(getDetailActualPrice(detail))
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="盈亏数量" width="120">
|
||||
<template #default="{ row: detail }">
|
||||
|
|
@ -231,20 +266,22 @@
|
|||
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('warehouse')"
|
||||
label="仓库"
|
||||
min-width="180"
|
||||
>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.warehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('quantityAmount')" label="盈亏/金额(元)" min-width="200">
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="盈亏/金额(元)"
|
||||
min-width="200"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>盈亏数:</span>
|
||||
<span :class="getLossClass(row.totalQuantity)">{{ formatQuantity(row.totalQuantity) }}</span>
|
||||
<span :class="getLossClass(row.totalQuantity)">{{
|
||||
formatQuantity(row.totalQuantity)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>总金额:</span>
|
||||
|
|
@ -256,20 +293,37 @@
|
|||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>盈亏金额:</span>
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{ formatPrice(getDifferencePrice(row)) }}</span>
|
||||
<span :class="getLossClass(getDifferencePrice(row))">{{
|
||||
formatPrice(getDifferencePrice(row))
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div>创建:{{ formatNullableDate(row.createTime) }} / {{ row.creatorName || row.creator || '-' }}</div>
|
||||
<div>更新:{{ formatNullableDate(row.updateTime) }} / {{ row.updaterName || row.updater || '-' }}</div>
|
||||
<div
|
||||
>创建:{{ formatNullableDate(row.createTime) }} /
|
||||
{{ row.creatorName || row.creator || '-' }}</div
|
||||
>
|
||||
<div
|
||||
>更新:{{ formatNullableDate(row.updateTime) }} /
|
||||
{{ row.updaterName || row.updater || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('remark')" label="备注" min-width="160" prop="remark" />
|
||||
<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="{ row }">
|
||||
<el-tooltip :content="getUpdateTip(row.status)" :disabled="canUpdate(row.status)" placement="top">
|
||||
<el-tooltip
|
||||
:content="getUpdateTip(row.status)"
|
||||
:disabled="canUpdate(row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:update']"
|
||||
|
|
@ -282,7 +336,11 @@
|
|||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="getDeleteTip(row.status)" :disabled="canDelete(row.status)" placement="top">
|
||||
<el-tooltip
|
||||
:content="getDeleteTip(row.status)"
|
||||
:disabled="canDelete(row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:check-order:delete']"
|
||||
|
|
@ -306,7 +364,12 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total" @pagination="getList" />
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<CheckOrderForm ref="formRef" @success="getList" />
|
||||
|
|
@ -320,7 +383,11 @@ 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 {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
|
|
@ -387,8 +454,10 @@ const queryFormRef = ref()
|
|||
const exportLoading = ref(false)
|
||||
const detailMap = reactive<Record<number, CheckOrderDetailVO[]>>({})
|
||||
|
||||
const canUpdate = (status?: number) => status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
const canDelete = (status?: number) => status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
const canUpdate = (status?: number) =>
|
||||
status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
const canDelete = (status?: number) =>
|
||||
status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
const getUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) return '已盘库,无法修改'
|
||||
if (status === OrderStatusEnum.CANCELED) return '已作废,无法修改'
|
||||
|
|
@ -398,11 +467,17 @@ const getDeleteTip = (status?: number) => {
|
|||
if (status === OrderStatusEnum.FINISHED) return '已盘库,无法删除'
|
||||
return '当前状态无法删除'
|
||||
}
|
||||
const getDifferencePrice = (row: CheckOrderVO) => roundPrice(Number(row.actualPrice || 0) - Number(row.totalPrice || 0))
|
||||
const getDifferencePrice = (row: CheckOrderVO) =>
|
||||
roundPrice(Number(row.actualPrice || 0) - Number(row.totalPrice || 0))
|
||||
const getDetailDifferenceQuantity = (detail: CheckOrderDetailVO) =>
|
||||
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) {
|
||||
if (
|
||||
detail.checkQuantity === undefined ||
|
||||
detail.checkQuantity === null ||
|
||||
detail.price === undefined ||
|
||||
detail.price === null
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
return roundPrice(Number(detail.checkQuantity) * Number(detail.price))
|
||||
|
|
@ -465,27 +540,3 @@ const handleExport = async () => {
|
|||
|
||||
onMounted(() => getList())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-check-order-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:global(.wms-check-order-table-setting-popover) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:global(.wms-check-order-table-setting) {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:global(.wms-check-order-table-setting .el-checkbox) {
|
||||
height: 28px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:global(.wms-check-order-table-setting .el-checkbox__label) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -50,13 +50,17 @@
|
|||
<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>
|
||||
<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>
|
||||
<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">
|
||||
|
|
@ -78,7 +82,12 @@ 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 } from '@/views/wms/utils/format'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 移库单详情 */
|
||||
defineOptions({ name: 'WmsMovementOrderDetail' })
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
<!-- 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-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">
|
||||
|
|
@ -10,12 +16,18 @@
|
|||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来源仓库" prop="sourceWarehouseId">
|
||||
<WarehouseSelect v-model="formData.sourceWarehouseId" @change="handleSourceWarehouseChange" />
|
||||
<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" />
|
||||
<WarehouseSelect
|
||||
v-model="formData.targetWarehouseId"
|
||||
@change="handleTargetWarehouseChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
|
|
@ -31,16 +43,31 @@
|
|||
</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-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.sourceWarehouseId" placement="top">
|
||||
<el-tooltip
|
||||
content="请先选择来源仓库"
|
||||
:disabled="!!formData.sourceWarehouseId"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button :disabled="!formData.sourceWarehouseId" plain type="primary" @click="handleAddDetail">
|
||||
<el-button
|
||||
:disabled="!formData.sourceWarehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
|
|
@ -57,13 +84,17 @@
|
|||
<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>
|
||||
<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>
|
||||
<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">
|
||||
|
|
@ -143,7 +174,13 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button v-if="isPrepareOrder" :disabled="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>保存</el-button
|
||||
>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -155,7 +192,9 @@
|
|||
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 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 {
|
||||
|
|
@ -201,7 +240,9 @@ const formRules = reactive<FormRules>({
|
|||
const formRef = ref()
|
||||
const inventorySelectRef = ref()
|
||||
|
||||
const detailPriceSum = computed(() => sumPrice(formData.value.details || [], (detail) => detail.price))
|
||||
const detailPriceSum = computed(() =>
|
||||
sumPrice(formData.value.details || [], (detail) => detail.price)
|
||||
)
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
|
|
@ -273,10 +314,7 @@ const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
|||
/** 判断库存是否已选择 */
|
||||
const isInventorySelected = (inventory: InventorySelectRow) =>
|
||||
(formData.value.details || []).some((detail) => {
|
||||
return (
|
||||
detail.skuId === inventory.skuId &&
|
||||
detail.sourceWarehouseId === inventory.warehouseId
|
||||
)
|
||||
return detail.skuId === inventory.skuId && detail.sourceWarehouseId === inventory.warehouseId
|
||||
})
|
||||
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
|
|
@ -358,7 +396,12 @@ const validateDetails = (required: boolean) => {
|
|||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const { totalQuantity: _totalQuantity, totalPrice: _totalPrice, details, ...order } = formData.value
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: (details || []).map(({ totalPrice: _rowTotalPrice, ...detail }) => detail)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,29 @@
|
|||
<!-- WMS 移库单 -->
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="90px">
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="移库单号" prop="no">
|
||||
<el-input v-model="queryParams.no" class="!w-240px" clearable placeholder="请输入移库单号" @keyup.enter="handleQuery" />
|
||||
<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-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"
|
||||
|
|
@ -111,7 +128,7 @@
|
|||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover popper-class="wms-movement-order-table-setting-popover" trigger="click" width="520">
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
|
|
@ -120,14 +137,24 @@
|
|||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="wms-movement-order-table-setting grid grid-cols-3 gap-y-14px rounded p-16px"
|
||||
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" :label="column.value">
|
||||
<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')">
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
|
|
@ -148,7 +175,7 @@
|
|||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-movement-order-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
|
|
@ -160,13 +187,17 @@
|
|||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="{ row: detail }">
|
||||
<div>{{ detail.itemName || '-' }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px text-gray-500">商品编号:{{ detail.itemCode }}</div>
|
||||
<div v-if="detail.itemCode" class="text-12px text-gray-500"
|
||||
>商品编号:{{ detail.itemCode }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="{ row: detail }">
|
||||
<div>{{ detail.skuName || '-' }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px text-gray-500">规格编号:{{ detail.skuCode }}</div>
|
||||
<div v-if="detail.skuCode" class="text-12px text-gray-500"
|
||||
>规格编号:{{ detail.skuCode }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="移库数量" width="120">
|
||||
|
|
@ -176,7 +207,9 @@
|
|||
<template #default="{ row: detail }">{{ formatPrice(detail.price) || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="金额(元)" width="120">
|
||||
<template #default="{ row: detail }">{{ formatPrice(getDetailTotalPrice(detail)) || '-' }}</template>
|
||||
<template #default="{ row: detail }">{{
|
||||
formatPrice(getDetailTotalPrice(detail)) || '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
|
@ -216,7 +249,11 @@
|
|||
{{ row.targetWarehouseName || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('quantityAmount')" label="总数量/总金额(元)" min-width="180">
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('quantityAmount')"
|
||||
label="总数量/总金额(元)"
|
||||
min-width="180"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>数量:</span>
|
||||
|
|
@ -230,14 +267,29 @@
|
|||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('operateInfo')" label="操作信息" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div>创建:{{ formatNullableDate(row.createTime) }} / {{ row.creatorName || row.creator || '-' }}</div>
|
||||
<div>更新:{{ formatNullableDate(row.updateTime) }} / {{ row.updaterName || row.updater || '-' }}</div>
|
||||
<div
|
||||
>创建:{{ formatNullableDate(row.createTime) }} /
|
||||
{{ row.creatorName || row.creator || '-' }}</div
|
||||
>
|
||||
<div
|
||||
>更新:{{ formatNullableDate(row.updateTime) }} /
|
||||
{{ row.updaterName || row.updater || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('remark')" label="备注" min-width="160" prop="remark" />
|
||||
<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="{ row }">
|
||||
<el-tooltip :content="getUpdateTip(row.status)" :disabled="canUpdate(row.status)" placement="top">
|
||||
<el-tooltip
|
||||
:content="getUpdateTip(row.status)"
|
||||
:disabled="canUpdate(row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:update']"
|
||||
|
|
@ -250,7 +302,11 @@
|
|||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="getDeleteTip(row.status)" :disabled="canDelete(row.status)" placement="top">
|
||||
<el-tooltip
|
||||
:content="getDeleteTip(row.status)"
|
||||
:disabled="canDelete(row.status)"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<el-button
|
||||
v-hasPermi="['wms:movement-order:delete']"
|
||||
|
|
@ -266,7 +322,12 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total" @pagination="getList" />
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<MovementOrderForm ref="formRef" @success="getList" />
|
||||
|
|
@ -279,8 +340,17 @@ 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, PRICE_PRECISION, QUANTITY_PRECISION } from '@/views/wms/utils/format'
|
||||
import {
|
||||
OrderDeleteStatusList,
|
||||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
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'
|
||||
|
|
@ -346,8 +416,10 @@ const queryFormRef = ref()
|
|||
const exportLoading = ref(false)
|
||||
const detailMap = reactive<Record<number, MovementOrderDetailVO[]>>({})
|
||||
|
||||
const canUpdate = (status?: number) => status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
const canDelete = (status?: number) => status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
const canUpdate = (status?: number) =>
|
||||
status !== undefined && OrderUpdateStatusList.includes(status)
|
||||
const canDelete = (status?: number) =>
|
||||
status !== undefined && OrderDeleteStatusList.includes(status)
|
||||
const getUpdateTip = (status?: number) => {
|
||||
if (status === OrderStatusEnum.FINISHED) return '已移库,无法修改'
|
||||
if (status === OrderStatusEnum.CANCELED) return '已作废,无法修改'
|
||||
|
|
@ -413,27 +485,3 @@ const handleExport = async () => {
|
|||
|
||||
onMounted(() => getList())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-movement-order-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:global(.wms-movement-order-table-setting-popover) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:global(.wms-movement-order-table-setting) {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:global(.wms-movement-order-table-setting .el-checkbox) {
|
||||
height: 28px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:global(.wms-movement-order-table-setting .el-checkbox__label) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -44,11 +44,7 @@
|
|||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="供应商" prop="merchantId">
|
||||
<MerchantSelect
|
||||
v-model="formData.merchantId"
|
||||
placeholder="请选择供应商"
|
||||
supplier
|
||||
/>
|
||||
<MerchantSelect v-model="formData.merchantId" placeholder="请选择供应商" supplier />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
|
|
@ -71,13 +67,14 @@
|
|||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">入库明细</span>
|
||||
<el-tooltip
|
||||
content="请先选择仓库"
|
||||
:disabled="!!formData.warehouseId"
|
||||
placement="top"
|
||||
>
|
||||
<el-tooltip content="请先选择仓库" :disabled="!!formData.warehouseId" placement="top">
|
||||
<span>
|
||||
<el-button :disabled="!formData.warehouseId" plain type="primary" @click="handleAddDetail">
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
|
|
@ -177,7 +174,12 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button v-if="isPrepareOrder" :disabled="formLoading" type="primary" @click="submitForm">
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
|
|
@ -241,7 +243,9 @@ const formRules = reactive<FormRules>({
|
|||
const formRef = ref() // 表单 Ref
|
||||
const skuSelectRef = ref() // 商品 SKU 选择弹窗 Ref
|
||||
|
||||
const detailPriceSum = computed(() => sumPrice(formData.value.details || [], (detail) => detail.price))
|
||||
const detailPriceSum = computed(() =>
|
||||
sumPrice(formData.value.details || [], (detail) => detail.price)
|
||||
)
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
|
|
@ -393,7 +397,12 @@ const validateDetails = (required: boolean) => {
|
|||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const { totalQuantity: _totalQuantity, totalPrice: _totalPrice, details, ...order } = formData.value
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: (details || []).map(({ totalPrice: _rowTotalPrice, ...detail }) => detail)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,12 @@ 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 } from '@/views/wms/utils/format'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 入库单打印 */
|
||||
defineOptions({ name: 'WmsReceiptOrderPrint' })
|
||||
|
|
|
|||
|
|
@ -117,18 +117,10 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2
|
||||
v-model="queryParams.creator"
|
||||
class="!w-240px"
|
||||
placeholder="请选择创建用户"
|
||||
/>
|
||||
<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="请选择更新用户"
|
||||
/>
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
|
|
@ -161,11 +153,7 @@
|
|||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover
|
||||
popper-class="wms-receipt-order-table-setting-popover"
|
||||
trigger="click"
|
||||
width="520"
|
||||
>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
|
|
@ -174,11 +162,12 @@
|
|||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="wms-receipt-order-table-setting grid grid-cols-3 gap-y-14px rounded p-16px"
|
||||
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 }}
|
||||
|
|
@ -212,7 +201,7 @@
|
|||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-receipt-order-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
|
|
@ -255,7 +244,12 @@
|
|||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('no')" fixed="left" label="单号/业务单号" width="290">
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('no')"
|
||||
fixed="left"
|
||||
label="单号/业务单号"
|
||||
width="290"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
单号:
|
||||
|
|
@ -289,11 +283,7 @@
|
|||
<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"
|
||||
>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
|
|
@ -332,7 +322,12 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('remark')" label="备注" min-width="160" prop="remark" />
|
||||
<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
|
||||
|
|
@ -407,7 +402,12 @@ import {
|
|||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import { formatPrice, formatQuantity, PRICE_PRECISION, QUANTITY_PRECISION } from '@/views/wms/utils/format'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
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'
|
||||
|
|
@ -604,27 +604,3 @@ onMounted(async () => {
|
|||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-receipt-order-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:global(.wms-receipt-order-table-setting-popover) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:global(.wms-receipt-order-table-setting) {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:global(.wms-receipt-order-table-setting .el-checkbox) {
|
||||
height: 28px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:global(.wms-receipt-order-table-setting .el-checkbox__label) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -44,11 +44,7 @@
|
|||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户" prop="merchantId">
|
||||
<MerchantSelect
|
||||
v-model="formData.merchantId"
|
||||
placeholder="请选择客户"
|
||||
customer
|
||||
/>
|
||||
<MerchantSelect v-model="formData.merchantId" placeholder="请选择客户" customer />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
|
|
@ -71,13 +67,14 @@
|
|||
|
||||
<div class="mb-12px flex items-center justify-between">
|
||||
<span class="text-14px font-bold">出库明细</span>
|
||||
<el-tooltip
|
||||
content="请先选择仓库"
|
||||
:disabled="!!formData.warehouseId"
|
||||
placement="top"
|
||||
>
|
||||
<el-tooltip content="请先选择仓库" :disabled="!!formData.warehouseId" placement="top">
|
||||
<span>
|
||||
<el-button :disabled="!formData.warehouseId" plain type="primary" @click="handleAddDetail">
|
||||
<el-button
|
||||
:disabled="!formData.warehouseId"
|
||||
plain
|
||||
type="primary"
|
||||
@click="handleAddDetail"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
|
|
@ -186,7 +183,12 @@
|
|||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button v-if="isPrepareOrder" :disabled="formLoading" type="primary" @click="submitForm">
|
||||
<el-button
|
||||
v-if="isPrepareOrder"
|
||||
:disabled="formLoading"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
|
|
@ -252,7 +254,9 @@ const formRules = reactive<FormRules>({
|
|||
const formRef = ref() // 表单 Ref
|
||||
const inventorySelectRef = ref() // 库存选择弹窗 Ref
|
||||
|
||||
const detailPriceSum = computed(() => sumPrice(formData.value.details || [], (detail) => detail.price))
|
||||
const detailPriceSum = computed(() =>
|
||||
sumPrice(formData.value.details || [], (detail) => detail.price)
|
||||
)
|
||||
const isPrepareOrder = computed(
|
||||
() =>
|
||||
!formData.value.id ||
|
||||
|
|
@ -334,10 +338,7 @@ const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
|||
/** 判断库存是否已选择 */
|
||||
const isInventorySelected = (inventory: InventorySelectRow) => {
|
||||
return (formData.value.details || []).some((detail) => {
|
||||
return (
|
||||
detail.skuId === inventory.skuId &&
|
||||
detail.warehouseId === inventory.warehouseId
|
||||
)
|
||||
return detail.skuId === inventory.skuId && detail.warehouseId === inventory.warehouseId
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +418,12 @@ const validateDetails = (required: boolean) => {
|
|||
|
||||
/** 构建提交数据 */
|
||||
const buildSubmitData = () => {
|
||||
const { totalQuantity: _totalQuantity, totalPrice: _totalPrice, details, ...order } = formData.value
|
||||
const {
|
||||
totalQuantity: _totalQuantity,
|
||||
totalPrice: _totalPrice,
|
||||
details,
|
||||
...order
|
||||
} = formData.value
|
||||
return {
|
||||
...order,
|
||||
details: (details || []).map(({ totalPrice: _rowTotalPrice, ...detail }) => detail)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,12 @@ 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 } from '@/views/wms/utils/format'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
formatSumPrice,
|
||||
formatSumQuantity
|
||||
} from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 出库单打印 */
|
||||
defineOptions({ name: 'WmsShipmentOrderPrint' })
|
||||
|
|
|
|||
|
|
@ -117,18 +117,10 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建用户" prop="creator">
|
||||
<UserSelectV2
|
||||
v-model="queryParams.creator"
|
||||
class="!w-240px"
|
||||
placeholder="请选择创建用户"
|
||||
/>
|
||||
<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="请选择更新用户"
|
||||
/>
|
||||
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
|
|
@ -161,11 +153,7 @@
|
|||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-popover
|
||||
popper-class="wms-shipment-order-table-setting-popover"
|
||||
trigger="click"
|
||||
width="520"
|
||||
>
|
||||
<el-popover popper-class="!p-12px" trigger="click" width="520">
|
||||
<template #reference>
|
||||
<el-button>
|
||||
<Icon class="mr-5px" icon="ep:setting" />
|
||||
|
|
@ -174,11 +162,12 @@
|
|||
</template>
|
||||
<el-checkbox-group
|
||||
v-model="checkedTableColumns"
|
||||
class="wms-shipment-order-table-setting grid grid-cols-3 gap-y-14px rounded p-16px"
|
||||
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 }}
|
||||
|
|
@ -212,7 +201,7 @@
|
|||
<ContentWrap>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:cell-class-name="'wms-shipment-order-cell'"
|
||||
cell-class-name="!align-top"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
border
|
||||
|
|
@ -255,7 +244,12 @@
|
|||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('no')" fixed="left" label="单号/业务单号" width="290">
|
||||
<el-table-column
|
||||
v-if="isTableColumnVisible('no')"
|
||||
fixed="left"
|
||||
label="单号/业务单号"
|
||||
width="290"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
单号:
|
||||
|
|
@ -289,11 +283,7 @@
|
|||
<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"
|
||||
>
|
||||
<el-table-column v-if="isTableColumnVisible('warehouse')" label="仓库" min-width="160">
|
||||
<template #default="scope">
|
||||
{{ scope.row.warehouseName || '-' }}
|
||||
</template>
|
||||
|
|
@ -332,7 +322,12 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('remark')" label="备注" min-width="160" prop="remark" />
|
||||
<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
|
||||
|
|
@ -407,7 +402,12 @@ import {
|
|||
OrderStatusEnum,
|
||||
OrderUpdateStatusList
|
||||
} from '@/views/wms/utils/constants'
|
||||
import { formatPrice, formatQuantity, PRICE_PRECISION, QUANTITY_PRECISION } from '@/views/wms/utils/format'
|
||||
import {
|
||||
formatPrice,
|
||||
formatQuantity,
|
||||
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'
|
||||
|
|
@ -604,27 +604,3 @@ onMounted(async () => {
|
|||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.wms-shipment-order-cell) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:global(.wms-shipment-order-table-setting-popover) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
:global(.wms-shipment-order-table-setting) {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:global(.wms-shipment-order-table-setting .el-checkbox) {
|
||||
height: 28px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:global(.wms-shipment-order-table-setting .el-checkbox__label) {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue