feat(wms):增加 home 统计的迁移
parent
0163794e3f
commit
735ff018be
|
|
@ -0,0 +1,72 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components';
|
||||
|
||||
import WmsHomeInventoryCharts from './modules/wms-home-inventory-charts.vue';
|
||||
import WmsHomeOrderSummaryCards from './modules/wms-home-order-summary-cards.vue';
|
||||
import WmsHomeOrderTrendChart from './modules/wms-home-order-trend-chart.vue';
|
||||
|
||||
defineOptions({ name: 'WmsHome' });
|
||||
|
||||
const loading = ref(false);
|
||||
const warehouseId = ref<number>();
|
||||
const statTime = ref(formatDateTime(new Date()));
|
||||
const orderSummaryCardsRef = ref<InstanceType<typeof WmsHomeOrderSummaryCards>>();
|
||||
const orderTrendChartRef = ref<InstanceType<typeof WmsHomeOrderTrendChart>>();
|
||||
const inventoryChartsRef = ref<InstanceType<typeof WmsHomeInventoryCharts>>();
|
||||
|
||||
async function refresh() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await Promise.all([
|
||||
orderSummaryCardsRef.value?.load(warehouseId.value),
|
||||
orderTrendChartRef.value?.load(warehouseId.value),
|
||||
inventoryChartsRef.value?.load(warehouseId.value),
|
||||
]);
|
||||
statTime.value = formatDateTime(new Date());
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<template #doc>
|
||||
<DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" />
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-4 rounded border bg-card p-4">
|
||||
<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>
|
||||
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
|
||||
<WmsHomeOrderTrendChart ref="orderTrendChartRef" />
|
||||
<WmsHomeInventoryCharts ref="inventoryChartsRef" />
|
||||
<div class="mt-3 text-center text-sm text-muted-foreground">
|
||||
统计时间:{{ statTime }}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { formatQuantity } from '#/views/wms/utils/format';
|
||||
|
||||
export interface InventoryChartItem {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
/** 格式化库存数量展示文本 */
|
||||
export function formatQuantityText(value?: number) {
|
||||
return formatQuantity(value || 0) || '0.00';
|
||||
}
|
||||
|
||||
/** 转换库存排行接口数据为 ECharts 可消费的 name/value 数据 */
|
||||
export function buildInventoryChartItemList(
|
||||
list: undefined | WmsHomeStatisticsApi.InventoryRankItem[],
|
||||
emptyName: string,
|
||||
) {
|
||||
return (list || [])
|
||||
.map((item) => ({
|
||||
name: item.name || emptyName,
|
||||
value: Number(item.quantity || 0),
|
||||
}))
|
||||
.filter((item) => item.value > 0);
|
||||
}
|
||||
|
||||
/** 格式化货物占比图例,补充当前商品库存占比 */
|
||||
function formatGoodsLegend(name: string, goodsShareList: InventoryChartItem[]) {
|
||||
const total = goodsShareList.reduce((sum, item) => sum + item.value, 0);
|
||||
const item = goodsShareList.find((goods) => goods.name === name);
|
||||
if (!total || !item) {
|
||||
return name;
|
||||
}
|
||||
return `${name} ${((item.value / total) * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/** 货物占比图表配置 */
|
||||
export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]): any {
|
||||
return {
|
||||
color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'],
|
||||
legend: {
|
||||
formatter: (name: string) => formatGoodsLegend(name, goodsShareList),
|
||||
itemHeight: 10,
|
||||
itemWidth: 10,
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'middle',
|
||||
type: 'scroll',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
avoidLabelOverlap: true,
|
||||
center: ['34%', '52%'],
|
||||
data: goodsShareList,
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
name: '货物占比',
|
||||
radius: ['48%', '70%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
formatter: '{b}<br/>库存:{c} ({d}%)',
|
||||
trigger: 'item',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 库存分布图表配置 */
|
||||
export function getWarehouseDistributionChartOptions(
|
||||
warehouseDistributionList: InventoryChartItem[],
|
||||
): any {
|
||||
const sortedList = warehouseDistributionList.toReversed();
|
||||
return {
|
||||
color: ['#2f7df6'],
|
||||
grid: { bottom: 16, containLabel: true, left: 24, right: 40, top: 12 },
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 16,
|
||||
data: sortedList.map((item) => item.value),
|
||||
label: {
|
||||
formatter: ({ value }: { value?: number }) => formatQuantityText(Number(value || 0)),
|
||||
position: 'right',
|
||||
show: true,
|
||||
},
|
||||
name: '库存',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
formatter: (params: unknown) => {
|
||||
const item = (Array.isArray(params) ? params[0] : params) as {
|
||||
name?: string;
|
||||
value?: number;
|
||||
};
|
||||
return `${item.name || '-'}<br/>库存:${formatQuantityText(item.value)}`;
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } },
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
data: sortedList.map((item) => item.name),
|
||||
type: 'category',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { getInventorySummary } from '#/api/wms/home';
|
||||
|
||||
import {
|
||||
buildInventoryChartItemList,
|
||||
formatQuantityText,
|
||||
getGoodsShareChartOptions,
|
||||
getWarehouseDistributionChartOptions,
|
||||
type InventoryChartItem,
|
||||
} from './wms-home-inventory-chart-options';
|
||||
|
||||
defineOptions({ name: 'WmsHomeInventoryCharts' });
|
||||
|
||||
const GOODS_SHARE_LIMIT = 5;
|
||||
const WAREHOUSE_DISTRIBUTION_LIMIT = 8;
|
||||
|
||||
const loading = ref(false);
|
||||
const totalQuantity = ref(0);
|
||||
const goodsShareList = ref<InventoryChartItem[]>([]);
|
||||
const warehouseDistributionList = ref<InventoryChartItem[]>([]);
|
||||
const goodsShareChartRef = ref<EchartsUIType>();
|
||||
const warehouseDistributionChartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts: renderGoodsShareEcharts } = useEcharts(goodsShareChartRef);
|
||||
const { renderEcharts: renderWarehouseDistributionEcharts } = useEcharts(
|
||||
warehouseDistributionChartRef,
|
||||
);
|
||||
|
||||
/** 使用最新库存汇总数据渲染首页库存图表 */
|
||||
async function renderCharts() {
|
||||
await nextTick();
|
||||
await Promise.all([
|
||||
renderGoodsShareEcharts(getGoodsShareChartOptions(goodsShareList.value)),
|
||||
renderWarehouseDistributionEcharts(
|
||||
getWarehouseDistributionChartOptions(warehouseDistributionList.value),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/** 加载指定仓库的库存汇总和排行数据 */
|
||||
async function load(warehouseId?: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getInventorySummary({
|
||||
...(warehouseId ? { warehouseId } : {}),
|
||||
goodsLimit: GOODS_SHARE_LIMIT,
|
||||
warehouseLimit: WAREHOUSE_DISTRIBUTION_LIMIT,
|
||||
});
|
||||
totalQuantity.value = Number(data.totalQuantity || 0);
|
||||
goodsShareList.value = buildInventoryChartItemList(data.goodsShareList, '未命名商品');
|
||||
warehouseDistributionList.value = buildInventoryChartItemList(
|
||||
data.warehouseDistributionList,
|
||||
'未指定仓库',
|
||||
);
|
||||
await renderCharts();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
|
||||
<div class="rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3">
|
||||
<div class="font-semibold">货物占比</div>
|
||||
<div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div>
|
||||
</div>
|
||||
<div class="relative min-h-[300px]">
|
||||
<EchartsUI ref="goodsShareChartRef" height="300px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3 flex justify-between">
|
||||
<div>
|
||||
<div class="font-semibold">库存分布</div>
|
||||
<div class="text-sm text-muted-foreground">按仓库库存数量汇总</div>
|
||||
</div>
|
||||
<span class="font-semibold">总库存 {{ formatQuantityText(totalQuantity) }}</span>
|
||||
</div>
|
||||
<div class="relative min-h-[300px]">
|
||||
<EchartsUI ref="warehouseDistributionChartRef" height="300px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
|
||||
import { getOrderSummary } from '#/api/wms/home';
|
||||
import { OrderStatusEnum } from '#/views/wms/utils/constants';
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderSummaryCards' });
|
||||
|
||||
const orderDefinitions = [
|
||||
{ color: '#2f7df6', title: '入库', type: 1 },
|
||||
{ color: '#18a058', title: '出库', type: 2 },
|
||||
{ color: '#f59e0b', title: '移库', type: 3 },
|
||||
{ color: '#7c3aed', title: '盘库', type: 4 },
|
||||
];
|
||||
|
||||
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 loading = ref(false);
|
||||
const summaryList = ref<any[]>(
|
||||
orderDefinitions.map((item) => ({ ...item, statuses: [], total: 0 })),
|
||||
);
|
||||
|
||||
function getStatusCount(item: any, status: number) {
|
||||
return item.statuses?.find((row: any) => row.status === status)?.count || 0;
|
||||
}
|
||||
|
||||
async function load(warehouseId?: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getOrderSummary(warehouseId ? { warehouseId } : {});
|
||||
summaryList.value = orderDefinitions.map((definition) => {
|
||||
const summary = data.find((item) => item.type === definition.type);
|
||||
return {
|
||||
...definition,
|
||||
title:
|
||||
getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, definition.type)?.replace(/单$/, '') ||
|
||||
definition.title,
|
||||
statuses: summary?.statuses || [],
|
||||
total: summary?.total || 0,
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
|
||||
<div
|
||||
v-for="item in summaryList"
|
||||
:key="item.type"
|
||||
class="rounded border bg-card p-4 shadow-sm"
|
||||
: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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
interface OrderDefinition {
|
||||
color: string;
|
||||
title: string;
|
||||
trendField: keyof Pick<
|
||||
WmsHomeStatisticsApi.OrderTrend,
|
||||
'checkCount' | 'movementCount' | 'receiptCount' | 'shipmentCount'
|
||||
>;
|
||||
type: number;
|
||||
}
|
||||
|
||||
const OrderTypeEnum = {
|
||||
CHECK: 4,
|
||||
MOVEMENT: 3,
|
||||
RECEIPT: 1,
|
||||
SHIPMENT: 2,
|
||||
} as const;
|
||||
|
||||
/** 获取 WMS 单据类型标题,用于消除字典里的“单”后缀 */
|
||||
function 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[] = [
|
||||
{
|
||||
color: '#2f7df6',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.RECEIPT, '入库'),
|
||||
trendField: 'receiptCount',
|
||||
type: OrderTypeEnum.RECEIPT,
|
||||
},
|
||||
{
|
||||
color: '#18a058',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.SHIPMENT, '出库'),
|
||||
trendField: 'shipmentCount',
|
||||
type: OrderTypeEnum.SHIPMENT,
|
||||
},
|
||||
{
|
||||
color: '#f59e0b',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.MOVEMENT, '移库'),
|
||||
trendField: 'movementCount',
|
||||
type: OrderTypeEnum.MOVEMENT,
|
||||
},
|
||||
{
|
||||
color: '#7c3aed',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.CHECK, '盘库'),
|
||||
trendField: 'checkCount',
|
||||
type: OrderTypeEnum.CHECK,
|
||||
},
|
||||
];
|
||||
|
||||
/** 格式化趋势接口返回的时间戳为图表横轴日期 */
|
||||
function formatTrendTime(time: number | string) {
|
||||
const date = new Date(time);
|
||||
return Number.isNaN(date.getTime()) ? `${time}` : (formatDate(date, 'MM-DD') as string);
|
||||
}
|
||||
|
||||
/** 单据趋势图表配置 */
|
||||
export function getOrderTrendChartOptions(list: WmsHomeStatisticsApi.OrderTrend[]): any {
|
||||
const labels = list.map((item) => formatTrendTime(item.time));
|
||||
return {
|
||||
color: orderDefinitions.map((item) => item.color),
|
||||
grid: { bottom: 24, containLabel: true, left: 28, right: 24, top: 48 },
|
||||
legend: { itemHeight: 10, itemWidth: 10, top: 6 },
|
||||
series: orderDefinitions.map((item) => ({
|
||||
barMaxWidth: 18,
|
||||
data: list.map((row) => Number(row[item.trendField] || 0)),
|
||||
emphasis: { focus: 'series' },
|
||||
name: item.title,
|
||||
type: 'bar',
|
||||
})),
|
||||
tooltip: { axisPointer: { type: 'shadow' }, trigger: 'axis' },
|
||||
xAxis: {
|
||||
axisLine: { lineStyle: { color: '#dcdfe6' } },
|
||||
axisTick: { show: false },
|
||||
data: labels,
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
minInterval: 1,
|
||||
name: '单据数',
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } },
|
||||
type: 'value',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options';
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderTrendChart' });
|
||||
|
||||
const loading = ref(false);
|
||||
const warehouseId = ref<number>();
|
||||
const trendDays = ref(7);
|
||||
const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]);
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
/** 使用最新趋势数据渲染单据趋势图 */
|
||||
async function renderChart() {
|
||||
await nextTick();
|
||||
await renderEcharts(getOrderTrendChartOptions(trendList.value));
|
||||
}
|
||||
|
||||
/** 加载指定仓库的单据趋势数据 */
|
||||
async function load(selectedWarehouseId?: number) {
|
||||
warehouseId.value = selectedWarehouseId;
|
||||
loading.value = true;
|
||||
try {
|
||||
trendList.value = await getOrderTrend(
|
||||
trendDays.value,
|
||||
selectedWarehouseId ? { warehouseId: selectedWarehouseId } : {},
|
||||
);
|
||||
await renderChart();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换趋势统计时间范围并刷新图表 */
|
||||
async function setTrendDays(days: number) {
|
||||
trendDays.value = days;
|
||||
await load(warehouseId.value);
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4 rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="font-semibold">单据趋势</div>
|
||||
<div class="text-sm text-muted-foreground">入库、出库、移库、盘库单据数量</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 7 }" @click="setTrendDays(7)">近7天</button>
|
||||
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 30 }" @click="setTrendDays(30)">近30天</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative min-h-[330px]">
|
||||
<EchartsUI ref="chartRef" height="330px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import { WmsWarehouseSelect } from '#/views/wms/md/warehouse/components';
|
||||
|
||||
import WmsHomeInventoryCharts from './modules/wms-home-inventory-charts.vue';
|
||||
import WmsHomeOrderSummaryCards from './modules/wms-home-order-summary-cards.vue';
|
||||
import WmsHomeOrderTrendChart from './modules/wms-home-order-trend-chart.vue';
|
||||
|
||||
defineOptions({ name: 'WmsHome' });
|
||||
|
||||
const loading = ref(false);
|
||||
const warehouseId = ref<number>();
|
||||
const statTime = ref(formatDateTime(new Date()));
|
||||
const orderSummaryCardsRef = ref<InstanceType<typeof WmsHomeOrderSummaryCards>>();
|
||||
const orderTrendChartRef = ref<InstanceType<typeof WmsHomeOrderTrendChart>>();
|
||||
const inventoryChartsRef = ref<InstanceType<typeof WmsHomeInventoryCharts>>();
|
||||
|
||||
async function refresh() {
|
||||
loading.value = true;
|
||||
try {
|
||||
await Promise.all([
|
||||
orderSummaryCardsRef.value?.load(warehouseId.value),
|
||||
orderTrendChartRef.value?.load(warehouseId.value),
|
||||
inventoryChartsRef.value?.load(warehouseId.value),
|
||||
]);
|
||||
statTime.value = formatDateTime(new Date());
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<template #doc>
|
||||
<DocAlert title="WMS 手册(功能开启)" url="https://doc.iocoder.cn/wms/build/" />
|
||||
</template>
|
||||
<div>
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-4 rounded border bg-card p-4">
|
||||
<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>
|
||||
<WmsHomeOrderSummaryCards ref="orderSummaryCardsRef" />
|
||||
<WmsHomeOrderTrendChart ref="orderTrendChartRef" />
|
||||
<WmsHomeInventoryCharts ref="inventoryChartsRef" />
|
||||
<div class="mt-3 text-center text-sm text-muted-foreground">
|
||||
统计时间:{{ statTime }}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { formatQuantity } from '#/views/wms/utils/format';
|
||||
|
||||
export interface InventoryChartItem {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
/** 格式化库存数量展示文本 */
|
||||
export function formatQuantityText(value?: number) {
|
||||
return formatQuantity(value || 0) || '0.00';
|
||||
}
|
||||
|
||||
/** 转换库存排行接口数据为 ECharts 可消费的 name/value 数据 */
|
||||
export function buildInventoryChartItemList(
|
||||
list: undefined | WmsHomeStatisticsApi.InventoryRankItem[],
|
||||
emptyName: string,
|
||||
) {
|
||||
return (list || [])
|
||||
.map((item) => ({
|
||||
name: item.name || emptyName,
|
||||
value: Number(item.quantity || 0),
|
||||
}))
|
||||
.filter((item) => item.value > 0);
|
||||
}
|
||||
|
||||
/** 格式化货物占比图例,补充当前商品库存占比 */
|
||||
function formatGoodsLegend(name: string, goodsShareList: InventoryChartItem[]) {
|
||||
const total = goodsShareList.reduce((sum, item) => sum + item.value, 0);
|
||||
const item = goodsShareList.find((goods) => goods.name === name);
|
||||
if (!total || !item) {
|
||||
return name;
|
||||
}
|
||||
return `${name} ${((item.value / total) * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/** 货物占比图表配置 */
|
||||
export function getGoodsShareChartOptions(goodsShareList: InventoryChartItem[]): any {
|
||||
return {
|
||||
color: ['#2f7df6', '#18a058', '#f59e0b', '#7c3aed', '#14b8a6'],
|
||||
legend: {
|
||||
formatter: (name: string) => formatGoodsLegend(name, goodsShareList),
|
||||
itemHeight: 10,
|
||||
itemWidth: 10,
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'middle',
|
||||
type: 'scroll',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
avoidLabelOverlap: true,
|
||||
center: ['34%', '52%'],
|
||||
data: goodsShareList,
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
name: '货物占比',
|
||||
radius: ['48%', '70%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
formatter: '{b}<br/>库存:{c} ({d}%)',
|
||||
trigger: 'item',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 库存分布图表配置 */
|
||||
export function getWarehouseDistributionChartOptions(
|
||||
warehouseDistributionList: InventoryChartItem[],
|
||||
): any {
|
||||
const sortedList = warehouseDistributionList.toReversed();
|
||||
return {
|
||||
color: ['#2f7df6'],
|
||||
grid: { bottom: 16, containLabel: true, left: 24, right: 40, top: 12 },
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 16,
|
||||
data: sortedList.map((item) => item.value),
|
||||
label: {
|
||||
formatter: ({ value }: { value?: number }) => formatQuantityText(Number(value || 0)),
|
||||
position: 'right',
|
||||
show: true,
|
||||
},
|
||||
name: '库存',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
formatter: (params: unknown) => {
|
||||
const item = (Array.isArray(params) ? params[0] : params) as {
|
||||
name?: string;
|
||||
value?: number;
|
||||
};
|
||||
return `${item.name || '-'}<br/>库存:${formatQuantityText(item.value)}`;
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } },
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
data: sortedList.map((item) => item.name),
|
||||
type: 'category',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { getInventorySummary } from '#/api/wms/home';
|
||||
|
||||
import {
|
||||
buildInventoryChartItemList,
|
||||
formatQuantityText,
|
||||
getGoodsShareChartOptions,
|
||||
getWarehouseDistributionChartOptions,
|
||||
type InventoryChartItem,
|
||||
} from './wms-home-inventory-chart-options';
|
||||
|
||||
defineOptions({ name: 'WmsHomeInventoryCharts' });
|
||||
|
||||
const GOODS_SHARE_LIMIT = 5;
|
||||
const WAREHOUSE_DISTRIBUTION_LIMIT = 8;
|
||||
|
||||
const loading = ref(false);
|
||||
const totalQuantity = ref(0);
|
||||
const goodsShareList = ref<InventoryChartItem[]>([]);
|
||||
const warehouseDistributionList = ref<InventoryChartItem[]>([]);
|
||||
const goodsShareChartRef = ref<EchartsUIType>();
|
||||
const warehouseDistributionChartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts: renderGoodsShareEcharts } = useEcharts(goodsShareChartRef);
|
||||
const { renderEcharts: renderWarehouseDistributionEcharts } = useEcharts(
|
||||
warehouseDistributionChartRef,
|
||||
);
|
||||
|
||||
/** 使用最新库存汇总数据渲染首页库存图表 */
|
||||
async function renderCharts() {
|
||||
await nextTick();
|
||||
await Promise.all([
|
||||
renderGoodsShareEcharts(getGoodsShareChartOptions(goodsShareList.value)),
|
||||
renderWarehouseDistributionEcharts(
|
||||
getWarehouseDistributionChartOptions(warehouseDistributionList.value),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/** 加载指定仓库的库存汇总和排行数据 */
|
||||
async function load(warehouseId?: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getInventorySummary({
|
||||
...(warehouseId ? { warehouseId } : {}),
|
||||
goodsLimit: GOODS_SHARE_LIMIT,
|
||||
warehouseLimit: WAREHOUSE_DISTRIBUTION_LIMIT,
|
||||
});
|
||||
totalQuantity.value = Number(data.totalQuantity || 0);
|
||||
goodsShareList.value = buildInventoryChartItemList(data.goodsShareList, '未命名商品');
|
||||
warehouseDistributionList.value = buildInventoryChartItemList(
|
||||
data.warehouseDistributionList,
|
||||
'未指定仓库',
|
||||
);
|
||||
await renderCharts();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
|
||||
<div class="rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3">
|
||||
<div class="font-semibold">货物占比</div>
|
||||
<div class="text-sm text-muted-foreground">按商品库存数量汇总 Top 5</div>
|
||||
</div>
|
||||
<div class="relative min-h-[300px]">
|
||||
<EchartsUI ref="goodsShareChartRef" height="300px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3 flex justify-between">
|
||||
<div>
|
||||
<div class="font-semibold">库存分布</div>
|
||||
<div class="text-sm text-muted-foreground">按仓库库存数量汇总</div>
|
||||
</div>
|
||||
<span class="font-semibold">总库存 {{ formatQuantityText(totalQuantity) }}</span>
|
||||
</div>
|
||||
<div class="relative min-h-[300px]">
|
||||
<EchartsUI ref="warehouseDistributionChartRef" height="300px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
|
||||
import { getOrderSummary } from '#/api/wms/home';
|
||||
import { OrderStatusEnum } from '#/views/wms/utils/constants';
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderSummaryCards' });
|
||||
|
||||
const orderDefinitions = [
|
||||
{ color: '#2f7df6', title: '入库', type: 1 },
|
||||
{ color: '#18a058', title: '出库', type: 2 },
|
||||
{ color: '#f59e0b', title: '移库', type: 3 },
|
||||
{ color: '#7c3aed', title: '盘库', type: 4 },
|
||||
];
|
||||
|
||||
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 loading = ref(false);
|
||||
const summaryList = ref<any[]>(
|
||||
orderDefinitions.map((item) => ({ ...item, statuses: [], total: 0 })),
|
||||
);
|
||||
|
||||
function getStatusCount(item: any, status: number) {
|
||||
return item.statuses?.find((row: any) => row.status === status)?.count || 0;
|
||||
}
|
||||
|
||||
async function load(warehouseId?: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getOrderSummary(warehouseId ? { warehouseId } : {});
|
||||
summaryList.value = orderDefinitions.map((definition) => {
|
||||
const summary = data.find((item) => item.type === definition.type);
|
||||
return {
|
||||
...definition,
|
||||
title:
|
||||
getDictLabel(DICT_TYPE.WMS_ORDER_TYPE, definition.type)?.replace(/单$/, '') ||
|
||||
definition.title,
|
||||
statuses: summary?.statuses || [],
|
||||
total: summary?.total || 0,
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4 grid grid-cols-4 gap-4 max-xl:grid-cols-2 max-sm:grid-cols-1">
|
||||
<div
|
||||
v-for="item in summaryList"
|
||||
:key="item.type"
|
||||
class="rounded border bg-card p-4 shadow-sm"
|
||||
: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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import type { WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
interface OrderDefinition {
|
||||
color: string;
|
||||
title: string;
|
||||
trendField: keyof Pick<
|
||||
WmsHomeStatisticsApi.OrderTrend,
|
||||
'checkCount' | 'movementCount' | 'receiptCount' | 'shipmentCount'
|
||||
>;
|
||||
type: number;
|
||||
}
|
||||
|
||||
const OrderTypeEnum = {
|
||||
CHECK: 4,
|
||||
MOVEMENT: 3,
|
||||
RECEIPT: 1,
|
||||
SHIPMENT: 2,
|
||||
} as const;
|
||||
|
||||
/** 获取 WMS 单据类型标题,用于消除字典里的“单”后缀 */
|
||||
function 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[] = [
|
||||
{
|
||||
color: '#2f7df6',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.RECEIPT, '入库'),
|
||||
trendField: 'receiptCount',
|
||||
type: OrderTypeEnum.RECEIPT,
|
||||
},
|
||||
{
|
||||
color: '#18a058',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.SHIPMENT, '出库'),
|
||||
trendField: 'shipmentCount',
|
||||
type: OrderTypeEnum.SHIPMENT,
|
||||
},
|
||||
{
|
||||
color: '#f59e0b',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.MOVEMENT, '移库'),
|
||||
trendField: 'movementCount',
|
||||
type: OrderTypeEnum.MOVEMENT,
|
||||
},
|
||||
{
|
||||
color: '#7c3aed',
|
||||
title: getOrderTypeTitle(OrderTypeEnum.CHECK, '盘库'),
|
||||
trendField: 'checkCount',
|
||||
type: OrderTypeEnum.CHECK,
|
||||
},
|
||||
];
|
||||
|
||||
/** 格式化趋势接口返回的时间戳为图表横轴日期 */
|
||||
function formatTrendTime(time: number | string) {
|
||||
const date = new Date(time);
|
||||
return Number.isNaN(date.getTime()) ? `${time}` : (formatDate(date, 'MM-DD') as string);
|
||||
}
|
||||
|
||||
/** 单据趋势图表配置 */
|
||||
export function getOrderTrendChartOptions(list: WmsHomeStatisticsApi.OrderTrend[]): any {
|
||||
const labels = list.map((item) => formatTrendTime(item.time));
|
||||
return {
|
||||
color: orderDefinitions.map((item) => item.color),
|
||||
grid: { bottom: 24, containLabel: true, left: 28, right: 24, top: 48 },
|
||||
legend: { itemHeight: 10, itemWidth: 10, top: 6 },
|
||||
series: orderDefinitions.map((item) => ({
|
||||
barMaxWidth: 18,
|
||||
data: list.map((row) => Number(row[item.trendField] || 0)),
|
||||
emphasis: { focus: 'series' },
|
||||
name: item.title,
|
||||
type: 'bar',
|
||||
})),
|
||||
tooltip: { axisPointer: { type: 'shadow' }, trigger: 'axis' },
|
||||
xAxis: {
|
||||
axisLine: { lineStyle: { color: '#dcdfe6' } },
|
||||
axisTick: { show: false },
|
||||
data: labels,
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
minInterval: 1,
|
||||
name: '单据数',
|
||||
splitLine: { lineStyle: { color: '#eef2f7' } },
|
||||
type: 'value',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { getOrderTrend, type WmsHomeStatisticsApi } from '#/api/wms/home';
|
||||
|
||||
import { getOrderTrendChartOptions } from './wms-home-order-trend-chart-options';
|
||||
|
||||
defineOptions({ name: 'WmsHomeOrderTrendChart' });
|
||||
|
||||
const loading = ref(false);
|
||||
const warehouseId = ref<number>();
|
||||
const trendDays = ref(7);
|
||||
const trendList = ref<WmsHomeStatisticsApi.OrderTrend[]>([]);
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
/** 使用最新趋势数据渲染单据趋势图 */
|
||||
async function renderChart() {
|
||||
await nextTick();
|
||||
await renderEcharts(getOrderTrendChartOptions(trendList.value));
|
||||
}
|
||||
|
||||
/** 加载指定仓库的单据趋势数据 */
|
||||
async function load(selectedWarehouseId?: number) {
|
||||
warehouseId.value = selectedWarehouseId;
|
||||
loading.value = true;
|
||||
try {
|
||||
trendList.value = await getOrderTrend(
|
||||
trendDays.value,
|
||||
selectedWarehouseId ? { warehouseId: selectedWarehouseId } : {},
|
||||
);
|
||||
await renderChart();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 切换趋势统计时间范围并刷新图表 */
|
||||
async function setTrendDays(days: number) {
|
||||
trendDays.value = days;
|
||||
await load(warehouseId.value);
|
||||
}
|
||||
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4 rounded border bg-card p-4 shadow-sm">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="font-semibold">单据趋势</div>
|
||||
<div class="text-sm text-muted-foreground">入库、出库、移库、盘库单据数量</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 7 }" @click="setTrendDays(7)">近7天</button>
|
||||
<button class="rounded border px-3 py-1" :class="{ 'bg-primary text-white': trendDays === 30 }" @click="setTrendDays(30)">近30天</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative min-h-[330px]">
|
||||
<EchartsUI ref="chartRef" height="330px" />
|
||||
<div
|
||||
v-if="loading"
|
||||
class="absolute inset-0 flex items-center justify-center bg-card/70 text-sm text-muted-foreground"
|
||||
>
|
||||
加载中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,3 +1,38 @@
|
|||
/** 单据状态枚举 */
|
||||
export const OrderStatusEnum = {
|
||||
PREPARE: 0, // 草稿
|
||||
FINISHED: 4, // 已完成
|
||||
CANCELED: 5, // 已作废
|
||||
} as const;
|
||||
|
||||
/** 可修改的单据状态 */
|
||||
export const OrderUpdateStatusList: number[] = [OrderStatusEnum.PREPARE];
|
||||
|
||||
/** 可删除的单据状态 */
|
||||
export const OrderDeleteStatusList: number[] = [
|
||||
OrderStatusEnum.PREPARE,
|
||||
OrderStatusEnum.CANCELED,
|
||||
];
|
||||
|
||||
/** 往来企业类型枚举 */
|
||||
export const MerchantTypeEnum = {
|
||||
CUSTOMER: 1, // 客户
|
||||
SUPPLIER: 2, // 供应商
|
||||
CUSTOMER_SUPPLIER: 3, // 客户/供应商
|
||||
} as const;
|
||||
|
||||
/** 供应商类型的往来企业 */
|
||||
export const SupplierMerchantTypeList = [
|
||||
MerchantTypeEnum.SUPPLIER,
|
||||
MerchantTypeEnum.CUSTOMER_SUPPLIER,
|
||||
];
|
||||
|
||||
/** 客户类型的往来企业 */
|
||||
export const CustomerMerchantTypeList = [
|
||||
MerchantTypeEnum.CUSTOMER,
|
||||
MerchantTypeEnum.CUSTOMER_SUPPLIER,
|
||||
];
|
||||
|
||||
/**
|
||||
* 生成 WMS 编号 / 条码。
|
||||
*
|
||||
|
|
|
|||
|
|
@ -175,6 +175,15 @@ const IOT_DICT = {
|
|||
IOT_MODBUS_FRAME_FORMAT: 'iot_modbus_frame_format', // IoT Modbus 帧格式
|
||||
} as const;
|
||||
|
||||
/** ========== WMS - 仓储管理模块 ========== */
|
||||
const WMS_DICT = {
|
||||
WMS_MERCHANT_TYPE: 'merchant_type', // WMS 往来企业类型
|
||||
WMS_ORDER_TYPE: 'wms_order_type', // WMS 单据类型
|
||||
WMS_ORDER_STATUS: 'wms_order_status', // WMS 单据状态
|
||||
WMS_RECEIPT_ORDER_TYPE: 'wms_receipt_order_type', // WMS 入库单类型
|
||||
WMS_SHIPMENT_ORDER_TYPE: 'wms_shipment_order_type', // WMS 出库单类型
|
||||
} as const;
|
||||
|
||||
/** 字典类型枚举 - 统一导出 */
|
||||
const DICT_TYPE = {
|
||||
...AI_DICT,
|
||||
|
|
@ -183,6 +192,7 @@ const DICT_TYPE = {
|
|||
...ERP_DICT,
|
||||
...INFRA_DICT,
|
||||
...IOT_DICT,
|
||||
...WMS_DICT,
|
||||
...MEMBER_DICT,
|
||||
...MP_DICT,
|
||||
...PAY_DICT,
|
||||
|
|
|
|||
Loading…
Reference in New Issue