fix(wms): 优化 WMS 首页卡片样式和区块间距

pull/345/head
YunaiV 2026-05-19 14:46:05 +08:00
parent 9d54f60b10
commit 0bc1981675
12 changed files with 124 additions and 70 deletions

View File

@ -4,7 +4,7 @@ import { onMounted, ref } from 'vue';
import { DocAlert, Page } from '@vben/common-ui'; import { DocAlert, Page } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import { Button } from 'ant-design-vue'; import { Button, Card } from 'ant-design-vue';
import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components';
@ -45,26 +45,28 @@ onMounted(() => {
<template #doc> <template #doc>
<DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" /> <DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" />
</template> </template>
<div> <div class="flex flex-col gap-2">
<div class="mb-4 flex flex-wrap items-center justify-between gap-4 rounded border bg-card p-4"> <Card :body-style="{ padding: '16px' }">
<div> <div class="flex flex-wrap items-center justify-between gap-4">
<div class="text-xl font-semibold">WMS 首页</div> <div>
<div class="text-sm text-muted-foreground">单据工作台 / 库存概览</div> <div class="text-xl font-semibold">WMS 首页</div>
<div class="text-sm text-muted-foreground">单据工作台 / 库存概览</div>
</div>
<div class="flex items-center gap-2">
<WmsWarehouseSelect
v-model="warehouseId"
class="!w-[220px]"
placeholder="全部仓库"
@change="refresh"
/>
<Button :loading="loading" @click="refresh"></Button>
</div>
</div> </div>
<div class="flex items-center gap-2"> </Card>
<WmsWarehouseSelect
v-model="warehouseId"
class="!w-[220px]"
placeholder="全部仓库"
@change="refresh"
/>
<Button :loading="loading" @click="refresh"></Button>
</div>
</div>
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" /> <WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
<WmsHomeOrderTrendChart ref="orderTrendChartRef" /> <WmsHomeOrderTrendChart ref="orderTrendChartRef" />
<WmsHomeInventoryCharts ref="inventoryChartsRef" /> <WmsHomeInventoryCharts ref="inventoryChartsRef" />
<div class="mt-3 text-center text-sm text-muted-foreground"> <div class="text-center text-sm text-muted-foreground">
统计时间{{ statTime }} 统计时间{{ statTime }}
</div> </div>
</div> </div>

View File

@ -1,7 +1,13 @@
import type { useEcharts } from '@vben/plugins/echarts';
import type { WmsHomeStatisticsApi } from '#/api/wms/home'; import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { formatQuantity } from '#/views/wms/utils/format'; import { formatQuantity } from '#/views/wms/utils/format';
type WmsHomeChartOption = Parameters<
ReturnType<typeof useEcharts>['renderEcharts']
>[0];
export interface InventoryChartItem { export interface InventoryChartItem {
name: string; name: string;
value: number; value: number;
@ -36,7 +42,9 @@ function formatGoodsLegend(name: string, goodsShareList: InventoryChartItem[]) {
} }
/** 货物占比图表配置 */ /** 货物占比图表配置 */
export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]): any { export function getGoodsShareChartOptions(
goodsShareList: InventoryChartItem[],
): WmsHomeChartOption {
return { return {
color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'], color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'],
legend: { legend: {
@ -70,7 +78,7 @@ export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]):
/** 库存分布图表配置 */ /** 库存分布图表配置 */
export function getWarehouseDistributionChartOptions( export function getWarehouseDistributionChartOptions(
warehouseDistributionList: InventoryChartItem[], warehouseDistributionList: InventoryChartItem[],
): any { ): WmsHomeChartOption {
const sortedList = warehouseDistributionList.toReversed(); const sortedList = warehouseDistributionList.toReversed();
return { return {
color: ['#2f7df6'], color: ['#2f7df6'],
@ -80,7 +88,7 @@ export function getWarehouseDistributionChartOptions(
barMaxWidth: 16, barMaxWidth: 16,
data: sortedList.map((item) => item.value), data: sortedList.map((item) => item.value),
label: { label: {
formatter: ({ value }: { value?: number }) => formatQuantityText(Number(value || 0)), formatter: ({ value }) => formatQuantityText(Number(value || 0)),
position: 'right', position: 'right',
show: true, show: true,
}, },

View File

@ -5,6 +5,8 @@ import { nextTick, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card } from 'ant-design-vue';
import { getInventorySummary } from '#/api/wms/home'; import { getInventorySummary } from '#/api/wms/home';
import { import {
@ -68,7 +70,7 @@ defineExpose({ load });
<template> <template>
<div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1"> <div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
<div class="rounded border bg-card p-4 shadow-sm"> <Card :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3"> <div class="mb-3">
<div class="font-semibold">货物占比</div> <div class="font-semibold">货物占比</div>
<div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div> <div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div>
@ -82,8 +84,8 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </Card>
<div class="rounded border bg-card p-4 shadow-sm"> <Card :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3 flex justify-between"> <div class="mb-3 flex justify-between">
<div> <div>
<div class="font-semibold">库存分布</div> <div class="font-semibold">库存分布</div>
@ -100,6 +102,6 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </Card>
</div> </div>
</template> </template>

View File

@ -120,11 +120,11 @@ defineExpose({ load });
</script> </script>
<template> <template>
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1"> <div class="grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
<Card <Card
v-for="item in summaryList" v-for="item in summaryList"
:key="item.type" :key="item.type"
:body-style="{ padding: '16px' }" :body-style="{ padding: '12px 16px 16px' }"
class="h-full shadow-sm" class="h-full shadow-sm"
:loading="loading" :loading="loading"
:style="{ borderTop: `3px solid ${item.color}` }" :style="{ borderTop: `3px solid ${item.color}` }"

View File

@ -1,3 +1,5 @@
import type { useEcharts } from '@vben/plugins/echarts';
import type { WmsHomeStatisticsApi } from '#/api/wms/home'; import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
@ -6,6 +8,10 @@ import { formatDate } from '@vben/utils';
import { OrderTypeEnum } from '#/views/wms/utils/constants'; import { OrderTypeEnum } from '#/views/wms/utils/constants';
type WmsHomeChartOption = Parameters<
ReturnType<typeof useEcharts>['renderEcharts']
>[0];
interface OrderDefinition { interface OrderDefinition {
color: string; color: string;
title: string; title: string;
@ -50,7 +56,9 @@ function formatTrendTime(time: number | string) {
} }
/** 单据趋势图表配置 */ /** 单据趋势图表配置 */
export function getOrderTrendChartOptions(list: WmsHomeStatisticsApi.OrderTrend[]): any { export function getOrderTrendChartOptions(
list: WmsHomeStatisticsApi.OrderTrend[],
): WmsHomeChartOption {
const labels = list.map((item) => formatTrendTime(item.time)); const labels = list.map((item) => formatTrendTime(item.time));
return { return {
color: orderDefinitions.map((item) => item.color), color: orderDefinitions.map((item) => item.color),

View File

@ -5,6 +5,8 @@ import { nextTick, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card, Segmented } from 'ant-design-vue';
import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home'; import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home';
import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options'; import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options';
@ -17,6 +19,10 @@ const trendDays = ref(7);
const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]); const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]);
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);
const trendDayOptions = [
{ label: '近7天', value: 7 },
{ label: '近30天', value: 30 },
];
/** 使用最新趋势数据渲染单据趋势图 */ /** 使用最新趋势数据渲染单据趋势图 */
async function renderChart() { async function renderChart() {
@ -40,8 +46,8 @@ async function load(selectedWarehouseId?: number) {
} }
/** 切换趋势统计时间范围并刷新图表 */ /** 切换趋势统计时间范围并刷新图表 */
async function setTrendDays(days: number) { async function handleTrendDaysChange(value: number | string) {
trendDays.value = days; trendDays.value = Number(value);
await load(warehouseId.value); await load(warehouseId.value);
} }
@ -49,16 +55,17 @@ defineExpose({ load });
</script> </script>
<template> <template>
<div class="mb-4 rounded border bg-card p-4 shadow-sm"> <Card :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3 flex items-center justify-between"> <div class="mb-3 flex items-center justify-between">
<div> <div>
<div class="font-semibold">单据趋势</div> <div class="font-semibold">单据趋势</div>
<div class="text-sm text-muted-foreground">入库出库移库盘库单据数量</div> <div class="text-sm text-muted-foreground">入库出库移库盘库单据数量</div>
</div> </div>
<div class="flex gap-2"> <Segmented
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 7 }" @click="setTrendDays(7)">7</button> :options="trendDayOptions"
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 30 }" @click="setTrendDays(30)">30</button> :value="trendDays"
</div> @change="handleTrendDaysChange"
/>
</div> </div>
<div class="relative min-h-[330px]"> <div class="relative min-h-[330px]">
<EchartsUI ref="chartRef" height="330px" /> <EchartsUI ref="chartRef" height="330px" />
@ -69,5 +76,5 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </Card>
</template> </template>

View File

@ -4,7 +4,7 @@ import { onMounted, ref } from 'vue';
import { DocAlert, Page } from '@vben/common-ui'; import { DocAlert, Page } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils'; import { formatDateTime } from '@vben/utils';
import { ElButton } from 'element-plus'; import { ElButton, ElCard } from 'element-plus';
import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components'; import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components';
@ -45,26 +45,28 @@ onMounted(() => {
<template #doc> <template #doc>
<DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" /> <DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" />
</template> </template>
<div> <div class="flex flex-col gap-2">
<div class="mb-4 flex flex-wrap items-center justify-between gap-4 rounded border bg-card p-4"> <ElCard :body-style="{ padding: '16px' }">
<div> <div class="flex flex-wrap items-center justify-between gap-4">
<div class="text-xl font-semibold">WMS 首页</div> <div>
<div class="text-sm text-muted-foreground">单据工作台 / 库存概览</div> <div class="text-xl font-semibold">WMS 首页</div>
<div class="text-sm text-muted-foreground">单据工作台 / 库存概览</div>
</div>
<div class="flex items-center gap-2">
<WmsWarehouseSelect
v-model="warehouseId"
class="!w-[220px]"
placeholder="全部仓库"
@change="refresh"
/>
<ElButton :loading="loading" @click="refresh"></ElButton>
</div>
</div> </div>
<div class="flex items-center gap-2"> </ElCard>
<WmsWarehouseSelect
v-model="warehouseId"
class="!w-[220px]"
placeholder="全部仓库"
@change="refresh"
/>
<ElButton :loading="loading" @click="refresh"></ElButton>
</div>
</div>
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" /> <WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
<WmsHomeOrderTrendChart ref="orderTrendChartRef" /> <WmsHomeOrderTrendChart ref="orderTrendChartRef" />
<WmsHomeInventoryCharts ref="inventoryChartsRef" /> <WmsHomeInventoryCharts ref="inventoryChartsRef" />
<div class="mt-3 text-center text-sm text-muted-foreground"> <div class="text-center text-sm text-muted-foreground">
统计时间{{ statTime }} 统计时间{{ statTime }}
</div> </div>
</div> </div>

View File

@ -1,7 +1,13 @@
import type { useEcharts } from '@vben/plugins/echarts';
import type { WmsHomeStatisticsApi } from '#/api/wms/home'; import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { formatQuantity } from '#/views/wms/utils/format'; import { formatQuantity } from '#/views/wms/utils/format';
type WmsHomeChartOption = Parameters<
ReturnType<typeof useEcharts>['renderEcharts']
>[0];
export interface InventoryChartItem { export interface InventoryChartItem {
name: string; name: string;
value: number; value: number;
@ -36,7 +42,9 @@ function formatGoodsLegend(name: string, goodsShareList: InventoryChartItem[]) {
} }
/** 货物占比图表配置 */ /** 货物占比图表配置 */
export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]): any { export function getGoodsShareChartOptions(
goodsShareList: InventoryChartItem[],
): WmsHomeChartOption {
return { return {
color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'], color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'],
legend: { legend: {
@ -70,7 +78,7 @@ export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]):
/** 库存分布图表配置 */ /** 库存分布图表配置 */
export function getWarehouseDistributionChartOptions( export function getWarehouseDistributionChartOptions(
warehouseDistributionList: InventoryChartItem[], warehouseDistributionList: InventoryChartItem[],
): any { ): WmsHomeChartOption {
const sortedList = warehouseDistributionList.toReversed(); const sortedList = warehouseDistributionList.toReversed();
return { return {
color: ['#2f7df6'], color: ['#2f7df6'],
@ -80,7 +88,7 @@ export function getWarehouseDistributionChartOptions(
barMaxWidth: 16, barMaxWidth: 16,
data: sortedList.map((item) => item.value), data: sortedList.map((item) => item.value),
label: { label: {
formatter: ({ value }: { value?: number }) => formatQuantityText(Number(value || 0)), formatter: ({ value }) => formatQuantityText(Number(value || 0)),
position: 'right', position: 'right',
show: true, show: true,
}, },

View File

@ -5,6 +5,8 @@ import { nextTick, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { ElCard } from 'element-plus';
import { getInventorySummary } from '#/api/wms/home'; import { getInventorySummary } from '#/api/wms/home';
import { import {
@ -68,7 +70,7 @@ defineExpose({ load });
<template> <template>
<div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1"> <div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
<div class="rounded border bg-card p-4 shadow-sm"> <ElCard :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3"> <div class="mb-3">
<div class="font-semibold">货物占比</div> <div class="font-semibold">货物占比</div>
<div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div> <div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div>
@ -82,8 +84,8 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </ElCard>
<div class="rounded border bg-card p-4 shadow-sm"> <ElCard :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3 flex justify-between"> <div class="mb-3 flex justify-between">
<div> <div>
<div class="font-semibold">库存分布</div> <div class="font-semibold">库存分布</div>
@ -100,6 +102,6 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </ElCard>
</div> </div>
</template> </template>

View File

@ -120,11 +120,11 @@ defineExpose({ load });
</script> </script>
<template> <template>
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1"> <div class="grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
<ElCard <ElCard
v-for="item in summaryList" v-for="item in summaryList"
:key="item.type" :key="item.type"
:body-style="{ padding: '16px' }" :body-style="{ padding: '12px 16px 16px' }"
class="h-full" class="h-full"
shadow="never" shadow="never"
:style="{ borderTop: `3px solid ${item.color}` }" :style="{ borderTop: `3px solid ${item.color}` }"

View File

@ -1,3 +1,5 @@
import type { useEcharts } from '@vben/plugins/echarts';
import type { WmsHomeStatisticsApi } from '#/api/wms/home'; import type { WmsHomeStatisticsApi } from '#/api/wms/home';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
@ -6,6 +8,10 @@ import { formatDate } from '@vben/utils';
import { OrderTypeEnum } from '#/views/wms/utils/constants'; import { OrderTypeEnum } from '#/views/wms/utils/constants';
type WmsHomeChartOption = Parameters<
ReturnType<typeof useEcharts>['renderEcharts']
>[0];
interface OrderDefinition { interface OrderDefinition {
color: string; color: string;
title: string; title: string;
@ -50,7 +56,9 @@ function formatTrendTime(time: number | string) {
} }
/** 单据趋势图表配置 */ /** 单据趋势图表配置 */
export function getOrderTrendChartOptions(list: WmsHomeStatisticsApi.OrderTrend[]): any { export function getOrderTrendChartOptions(
list: WmsHomeStatisticsApi.OrderTrend[],
): WmsHomeChartOption {
const labels = list.map((item) => formatTrendTime(item.time)); const labels = list.map((item) => formatTrendTime(item.time));
return { return {
color: orderDefinitions.map((item) => item.color), color: orderDefinitions.map((item) => item.color),

View File

@ -5,6 +5,8 @@ import { nextTick, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { ElCard, ElSegmented } from 'element-plus';
import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home'; import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home';
import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options'; import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options';
@ -17,6 +19,10 @@ const trendDays = ref(7);
const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]); const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]);
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);
const trendDayOptions = [
{ label: '近7天', value: 7 },
{ label: '近30天', value: 30 },
];
/** 使用最新趋势数据渲染单据趋势图 */ /** 使用最新趋势数据渲染单据趋势图 */
async function renderChart() { async function renderChart() {
@ -40,8 +46,8 @@ async function load(selectedWarehouseId?: number) {
} }
/** 切换趋势统计时间范围并刷新图表 */ /** 切换趋势统计时间范围并刷新图表 */
async function setTrendDays(days: number) { async function handleTrendDaysChange(value: number | string) {
trendDays.value = days; trendDays.value = Number(value);
await load(warehouseId.value); await load(warehouseId.value);
} }
@ -49,16 +55,17 @@ defineExpose({ load });
</script> </script>
<template> <template>
<div class="mb-4 rounded border bg-card p-4 shadow-sm"> <ElCard :body-style="{ padding: '12px 16px 16px' }">
<div class="mb-3 flex items-center justify-between"> <div class="mb-3 flex items-center justify-between">
<div> <div>
<div class="font-semibold">单据趋势</div> <div class="font-semibold">单据趋势</div>
<div class="text-sm text-muted-foreground">入库出库移库盘库单据数量</div> <div class="text-sm text-muted-foreground">入库出库移库盘库单据数量</div>
</div> </div>
<div class="flex gap-2"> <ElSegmented
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 7 }" @click="setTrendDays(7)">7</button> :model-value="trendDays"
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 30 }" @click="setTrendDays(30)">30</button> :options="trendDayOptions"
</div> @change="handleTrendDaysChange"
/>
</div> </div>
<div class="relative min-h-[330px]"> <div class="relative min-h-[330px]">
<EchartsUI ref="chartRef" height="330px" /> <EchartsUI ref="chartRef" height="330px" />
@ -69,5 +76,5 @@ defineExpose({ load });
加载中 加载中
</div> </div>
</div> </div>
</div> </ElCard>
</template> </template>