fix(wms): 修复首页汇总卡展示跳转和库存选择跨页勾选

pull/345/head
YunaiV 2026-05-19 10:59:14 +08:00
parent a098c201e1
commit a1b66588ec
2 changed files with 178 additions and 31 deletions

View File

@ -2,57 +2,101 @@
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { DICT_TYPE } from '@vben/constants';
import { getDictLabel } from '@vben/hooks';
import { Button, Card, message } from 'ant-design-vue';
import { getOrderSummary } from '#/api/wms/home';
import { OrderStatusEnum, OrderTypeEnum } from '#/views/wms/utils/constants';
defineOptions({ name: 'WmsHomeOrderSummaryCards' });
interface StatusItem {
color: string;
label: string;
value: number;
}
interface OrderSummaryItem {
color: string;
routeName: string;
statuses?: WmsHomeStatisticsApi.OrderStatus[];
title: string;
total?: number;
type: number;
}
const router = useRouter();
const orderDefinitions: OrderSummaryItem[] = [
{
color: '#2f7df6',
routeName: 'WmsReceiptOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.RECEIPT).replace(/单$/, ''),
type: OrderTypeEnum.RECEIPT,
},
{
color: '#18a058',
routeName: 'WmsShipmentOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.SHIPMENT).replace(/单$/, ''),
type: OrderTypeEnum.SHIPMENT,
},
{
color: '#f59e0b',
routeName: 'WmsMovementOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.MOVEMENT).replace(/单$/, ''),
type: OrderTypeEnum.MOVEMENT,
},
{
color: '#7c3aed',
routeName: 'WmsCheckOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.CHECK).replace(/单$/, ''),
type: OrderTypeEnum.CHECK,
},
];
const statusList = [
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.PREPARE) || '草稿', value: OrderStatusEnum.PREPARE },
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.FINISHED) || '已完成', value: OrderStatusEnum.FINISHED },
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.CANCELED) || '已作废', value: OrderStatusEnum.CANCELED },
const statusList: StatusItem[] = [
{
color: '#409eff',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.PREPARE) || '草稿',
value: OrderStatusEnum.PREPARE,
},
{
color: '#67c23a',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.FINISHED) || '已完成',
value: OrderStatusEnum.FINISHED,
},
{
color: '#909399',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.CANCELED) || '已作废',
value: OrderStatusEnum.CANCELED,
},
];
const loading = ref(false);
const summaryList = ref<OrderSummaryItem[]>(orderDefinitions);
function getStatusCount(item: OrderSummaryItem, status: number) {
return item.statuses?.find((row) => row.status === status)?.count;
return item.statuses?.find((row) => row.status === status)?.count ?? 0;
}
function getStatusPercent(item: OrderSummaryItem, status: number) {
const total = item.total ?? 0;
if (total <= 0) {
return '0%';
}
return `${(getStatusCount(item, status) / total) * 100}%`;
}
async function handleNavigate(routeName: string) {
try {
await router.push({ name: routeName });
} catch {
message.warning('当前菜单尚未加载,请从左侧菜单进入对应页面');
}
}
async function load(warehouseId?: number) {
@ -77,23 +121,49 @@ defineExpose({ load });
<template>
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
<div
<Card
v-for="item in summaryList"
:key="item.type"
class="rounded border bg-card p-4 shadow-sm"
:body-style="{ padding: '16px' }"
class="h-full shadow-sm"
:loading="loading"
:style="{ borderTop: `3px solid ${item.color}` }"
>
<div class="mb-3 flex items-center justify-between">
<span class="font-semibold">{{ item.title }}</span>
<span v-if="loading" class="text-xs text-muted-foreground"></span>
<div class="flex items-center gap-2 font-semibold">
<span class="h-2 w-2 rounded-full" :style="{ backgroundColor: item.color }"></span>
{{ item.title }}
</div>
<Button size="small" type="link" @click="handleNavigate(item.routeName)">
查看
</Button>
</div>
<div class="mb-1 flex items-baseline gap-2">
<span class="text-muted-foreground">合计</span>
<span class="text-3xl font-bold leading-9">
{{ item.total?.toLocaleString() ?? 0 }}
</span>
<span class="text-muted-foreground"></span>
</div>
<div class="my-3 flex h-2 overflow-hidden rounded-full bg-muted">
<span
v-for="status in statusList"
:key="status.value"
class="h-full"
:style="{
backgroundColor: status.color,
width: getStatusPercent(item, status.value),
}"
></span>
</div>
<div class="mb-3 text-3xl font-bold">{{ item.total }}</div>
<div class="grid grid-cols-3 gap-2 text-sm">
<div v-for="status in statusList" :key="status.value">
<div class="truncate text-muted-foreground">{{ status.label }}</div>
<div class="font-semibold">{{ getStatusCount(item, status.value) }}</div>
<div class="font-semibold" :style="{ color: status.color }">
{{ getStatusCount(item, status.value).toLocaleString() }}
</div>
</div>
</div>
</div>
</Card>
</div>
</template>

View File

@ -2,57 +2,101 @@
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { DICT_TYPE } from '@vben/constants';
import { getDictLabel } from '@vben/hooks';
import { ElButton, ElCard, ElMessage, ElSkeleton } from 'element-plus';
import { getOrderSummary } from '#/api/wms/home';
import { OrderStatusEnum, OrderTypeEnum } from '#/views/wms/utils/constants';
defineOptions({ name: 'WmsHomeOrderSummaryCards' });
interface StatusItem {
color: string;
label: string;
value: number;
}
interface OrderSummaryItem {
color: string;
routeName: string;
statuses?: WmsHomeStatisticsApi.OrderStatus[];
title: string;
total?: number;
type: number;
}
const router = useRouter();
const orderDefinitions: OrderSummaryItem[] = [
{
color: '#2f7df6',
routeName: 'WmsReceiptOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.RECEIPT).replace(/单$/, ''),
type: OrderTypeEnum.RECEIPT,
},
{
color: '#18a058',
routeName: 'WmsShipmentOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.SHIPMENT).replace(/单$/, ''),
type: OrderTypeEnum.SHIPMENT,
},
{
color: '#f59e0b',
routeName: 'WmsMovementOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.MOVEMENT).replace(/单$/, ''),
type: OrderTypeEnum.MOVEMENT,
},
{
color: '#7c3aed',
routeName: 'WmsCheckOrder',
title: getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, OrderTypeEnum.CHECK).replace(/单$/, ''),
type: OrderTypeEnum.CHECK,
},
];
const statusList = [
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.PREPARE) || '草稿', value: OrderStatusEnum.PREPARE },
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.FINISHED) || '已完成', value: OrderStatusEnum.FINISHED },
{ label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.CANCELED) || '已作废', value: OrderStatusEnum.CANCELED },
const statusList: StatusItem[] = [
{
color: '#409eff',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.PREPARE) || '草稿',
value: OrderStatusEnum.PREPARE,
},
{
color: '#67c23a',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.FINISHED) || '已完成',
value: OrderStatusEnum.FINISHED,
},
{
color: '#909399',
label: getDictLabel(DICT_TYPE.WMS_ORDER_STATUS, OrderStatusEnum.CANCELED) || '已作废',
value: OrderStatusEnum.CANCELED,
},
];
const loading = ref(false);
const summaryList = ref<OrderSummaryItem[]>(orderDefinitions);
function getStatusCount(item: OrderSummaryItem, status: number) {
return item.statuses?.find((row) => row.status === status)?.count;
return item.statuses?.find((row) => row.status === status)?.count ?? 0;
}
function getStatusPercent(item: OrderSummaryItem, status: number) {
const total = item.total ?? 0;
if (total <= 0) {
return '0%';
}
return `${(getStatusCount(item, status) / total) * 100}%`;
}
async function handleNavigate(routeName: string) {
try {
await router.push({ name: routeName });
} catch {
ElMessage.warning('当前菜单尚未加载,请从左侧菜单进入对应页面');
}
}
async function load(warehouseId?: number) {
@ -77,23 +121,56 @@ defineExpose({ load });
<template>
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
<div
<ElCard
v-for="item in summaryList"
:key="item.type"
class="rounded border bg-card p-4 shadow-sm"
:body-style="{ padding: '16px' }"
class="h-full"
shadow="never"
:style="{ borderTop: `3px solid ${item.color}` }"
>
<div class="mb-3 flex items-center justify-between">
<span class="font-semibold">{{ item.title }}</span>
<span v-if="loading" class="text-xs text-muted-foreground"></span>
</div>
<div class="mb-3 text-3xl font-bold">{{ item.total }}</div>
<div class="grid grid-cols-3 gap-2 text-sm">
<div v-for="status in statusList" :key="status.value">
<div class="truncate text-muted-foreground">{{ status.label }}</div>
<div class="font-semibold">{{ getStatusCount(item, status.value) }}</div>
</div>
</div>
</div>
<ElSkeleton :loading="loading" animated :rows="4">
<template #default>
<div class="mb-3 flex items-center justify-between">
<div class="flex items-center gap-2 font-semibold">
<span
class="h-2 w-2 rounded-full"
:style="{ backgroundColor: item.color }"
></span>
{{ item.title }}
</div>
<ElButton link type="primary" @click="handleNavigate(item.routeName)">
查看
</ElButton>
</div>
<div class="mb-1 flex items-baseline gap-2">
<span class="text-muted-foreground">合计</span>
<span class="text-3xl font-bold leading-9">
{{ item.total?.toLocaleString() ?? 0 }}
</span>
<span class="text-muted-foreground"></span>
</div>
<div class="my-3 flex h-2 overflow-hidden rounded-full bg-muted">
<span
v-for="status in statusList"
:key="status.value"
class="h-full"
:style="{
backgroundColor: status.color,
width: getStatusPercent(item, status.value),
}"
></span>
</div>
<div class="grid grid-cols-3 gap-2 text-sm">
<div v-for="status in statusList" :key="status.value">
<div class="truncate text-muted-foreground">{{ status.label }}</div>
<div class="font-semibold" :style="{ color: status.color }">
{{ getStatusCount(item, status.value).toLocaleString() }}
</div>
</div>
</div>
</template>
</ElSkeleton>
</ElCard>
</div>
</template>