feat(mes): 迁移 home 首页

pull/350/head
YunaiV 2026-05-30 13:21:55 +08:00
parent 37b9db148f
commit b325db0450
15 changed files with 1112 additions and 0 deletions

View File

@ -0,0 +1,55 @@
import { requestClient } from '#/api/request';
export namespace MesHomeApi {
/** MES 首页汇总统计 */
export interface Summary {
workOrderActiveCount: number; // 进行中工单数
workOrderPrepareCount: number; // 待排产工单数
workOrderFinishedCount: number; // 已完成工单数
todayOutput: number; // 今日产量
yesterdayOutput: number; // 昨日产量
todayQualifiedQuantity: number; // 今日合格品数
todayUnqualifiedQuantity: number; // 今日不良品数
machineryTotal: number; // 设备总数
machineryProducing: number; // 生产中设备数
machineryStop: number; // 停机设备数
machineryMaintenance: number; // 维护中设备数
andonActiveCount: number; // 未处置安灯呼叫数
repairActiveCount: number; // 待处理维修工单数
}
/** MES 工单状态分布 */
export interface WorkOrderStatus {
status: number; // 工单状态
statusName: string; // 工单状态名称
count: number; // 数量
}
/** MES 生产趋势 */
export interface ProductionTrend {
date: string; // 日期
quantity: number; // 产量
qualifiedQuantity: number; // 合格品数
unqualifiedQuantity: number; // 不良品数
}
}
/** 获得首页汇总统计 */
export function getHomeSummary() {
return requestClient.get<MesHomeApi.Summary>('/mes/home-statistics/summary');
}
/** 获得工单状态分布 */
export function getWorkOrderStatusDistribution() {
return requestClient.get<MesHomeApi.WorkOrderStatus[]>(
'/mes/home-statistics/work-order-status',
);
}
/** 获得生产趋势 */
export function getProductionTrend(days?: number) {
return requestClient.get<MesHomeApi.ProductionTrend[]>(
'/mes/home-statistics/production-trend',
{ params: { days } },
);
}

View File

@ -0,0 +1,59 @@
import { requestClient } from '#/api/request';
export namespace MesWmTransferDetailApi {
/** MES 调拨明细 */
export interface TransferDetail {
id?: number; // 编号
lineId?: number; // 转移单行编号
transferId?: number; // 转移单编号
itemId?: number; // 物料产品编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 单位名称
quantity?: number; // 数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
toWarehouseId?: number; // 移入仓库编号
toWarehouseName?: string; // 移入仓库名称
toLocationId?: number; // 移入库区编号
toLocationName?: string; // 移入库区名称
toAreaId?: number; // 移入库位编号
toAreaName?: string; // 移入库位名称
remark?: string; // 备注
}
}
/** 查询调拨明细列表(按行编号) */
export function getTransferDetailListByLineId(lineId: number) {
return requestClient.get<MesWmTransferDetailApi.TransferDetail[]>(
'/mes/wm/transfer-detail/list-by-line',
{ params: { lineId } },
);
}
/** 查询调拨明细详情 */
export function getTransferDetail(id: number) {
return requestClient.get<MesWmTransferDetailApi.TransferDetail>(
`/mes/wm/transfer-detail/get?id=${id}`,
);
}
/** 新增调拨明细 */
export function createTransferDetail(
data: MesWmTransferDetailApi.TransferDetail,
) {
return requestClient.post('/mes/wm/transfer-detail/create', data);
}
/** 修改调拨明细 */
export function updateTransferDetail(
data: MesWmTransferDetailApi.TransferDetail,
) {
return requestClient.put('/mes/wm/transfer-detail/update', data);
}
/** 删除调拨明细 */
export function deleteTransferDetail(id: number) {
return requestClient.delete(`/mes/wm/transfer-detail/delete?id=${id}`);
}

View File

@ -0,0 +1,92 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmTransferApi {
/** MES 转移单 */
export interface Transfer {
id?: number; // 编号
code?: string; // 转移单编号
name?: string; // 转移单名称
type?: number; // 转移单类型
deliveryFlag?: boolean; // 是否配送
recipientName?: string; // 收货人
recipientTelephone?: string; // 联系电话
destinationAddress?: string; // 目的地
carrier?: string; // 承运商
shippingNumber?: string; // 运输单号
confirmFlag?: boolean; // 是否确认
transferDate?: string; // 转移日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: number; // 创建时间
}
/** MES 转移单分页查询参数 */
export interface PageParams extends PageParam {
code?: string;
name?: string;
type?: number;
status?: number;
}
}
/** 查询转移单分页 */
export function getTransferPage(params: MesWmTransferApi.PageParams) {
return requestClient.get<PageResult<MesWmTransferApi.Transfer>>(
'/mes/wm/transfer/page',
{ params },
);
}
/** 查询转移单详情 */
export function getTransfer(id: number) {
return requestClient.get<MesWmTransferApi.Transfer>(
`/mes/wm/transfer/get?id=${id}`,
);
}
/** 新增转移单 */
export function createTransfer(data: MesWmTransferApi.Transfer) {
return requestClient.post<number>('/mes/wm/transfer/create', data);
}
/** 修改转移单 */
export function updateTransfer(data: MesWmTransferApi.Transfer) {
return requestClient.put('/mes/wm/transfer/update', data);
}
/** 删除转移单 */
export function deleteTransfer(id: number) {
return requestClient.delete(`/mes/wm/transfer/delete?id=${id}`);
}
/** 提交转移单 */
export function submitTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/submit?id=${id}`);
}
/** 到货确认 */
export function confirmTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/confirm?id=${id}`);
}
/** 执行上架 */
export function stockTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/stock?id=${id}`);
}
/** 完成转移 */
export function finishTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/finish?id=${id}`);
}
/** 取消转移单 */
export function cancelTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/cancel?id=${id}`);
}
/** 导出转移单 */
export function exportTransfer(params: any) {
return requestClient.download('/mes/wm/transfer/export-excel', { params });
}

View File

@ -0,0 +1,55 @@
import { requestClient } from '#/api/request';
export namespace MesWmTransferLineApi {
/** MES 转移单行 */
export interface TransferLine {
id?: number; // 编号
transferId?: number; // 转移单编号
materialStockId?: number; // 库存台账编号
itemId?: number; // 物料产品编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 单位名称
quantity?: number; // 转移数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
fromWarehouseId?: number; // 移出仓库编号
fromWarehouseName?: string; // 移出仓库名称
fromLocationId?: number; // 移出库区编号
fromLocationName?: string; // 移出库区名称
fromAreaId?: number; // 移出库位编号
fromAreaName?: string; // 移出库位名称
remark?: string; // 备注
}
}
/** 查询转移单行列表 */
export function getTransferLineList(transferId: number) {
return requestClient.get<MesWmTransferLineApi.TransferLine[]>(
'/mes/wm/transfer-line/list',
{ params: { transferId } },
);
}
/** 查询转移单行详情 */
export function getTransferLine(id: number) {
return requestClient.get<MesWmTransferLineApi.TransferLine>(
`/mes/wm/transfer-line/get?id=${id}`,
);
}
/** 新增转移单行 */
export function createTransferLine(data: MesWmTransferLineApi.TransferLine) {
return requestClient.post('/mes/wm/transfer-line/create', data);
}
/** 修改转移单行 */
export function updateTransferLine(data: MesWmTransferLineApi.TransferLine) {
return requestClient.put('/mes/wm/transfer-line/update', data);
}
/** 删除转移单行 */
export function deleteTransferLine(id: number) {
return requestClient.delete(`/mes/wm/transfer-line/delete?id=${id}`);
}

View File

@ -0,0 +1,89 @@
import type { MesHomeApi } from '#/api/mes/home';
import { MesProWorkOrderStatusEnum } from '#/views/mes/utils/constants';
/** 首页汇总统计默认值 */
export const defaultSummary: MesHomeApi.Summary = {
andonActiveCount: 0,
machineryMaintenance: 0,
machineryProducing: 0,
machineryStop: 0,
machineryTotal: 0,
repairActiveCount: 0,
todayOutput: 0,
todayQualifiedQuantity: 0,
todayUnqualifiedQuantity: 0,
workOrderActiveCount: 0,
workOrderFinishedCount: 0,
workOrderPrepareCount: 0,
yesterdayOutput: 0,
};
/** 工单状态对应的颜色映射 */
export const WORK_ORDER_STATUS_COLOR_MAP: Record<number, string> = {
[MesProWorkOrderStatusEnum.PREPARE]: '#909399', // 草稿
[MesProWorkOrderStatusEnum.CONFIRMED]: '#409EFF', // 已确认
[MesProWorkOrderStatusEnum.FINISHED]: '#67C23A', // 已完成
[MesProWorkOrderStatusEnum.CANCELED]: '#F56C6C', // 已取消
};
/** 生产趋势折线图配置 */
export function getProductionTrendChartOptions(
dates: string[],
quantities: number[],
qualified: number[],
unqualified: number[],
): any {
return {
grid: { bottom: 40, left: 50, right: 20, top: 20 },
legend: { bottom: 0, data: ['产量', '合格品', '不良品'] },
series: [
{
areaStyle: { color: 'rgba(64,158,255,0.15)' },
data: quantities,
itemStyle: { color: '#409EFF' },
name: '产量',
smooth: true,
type: 'line',
},
{
data: qualified,
itemStyle: { color: '#67C23A' },
name: '合格品',
smooth: true,
type: 'line',
},
{
data: unqualified,
itemStyle: { color: '#F56C6C' },
name: '不良品',
smooth: true,
type: 'line',
},
],
tooltip: { axisPointer: { type: 'cross' }, trigger: 'axis' },
xAxis: { boundaryGap: false, data: dates, type: 'category' },
yAxis: { minInterval: 1, type: 'value' },
};
}
/** 工单状态分布饼图配置 */
export function getWorkOrderStatusChartOptions(
data: Array<{ itemStyle: { color: string }; name: string; value: number }>,
): any {
return {
legend: { bottom: 0, type: 'scroll' },
series: [
{
avoidLabelOverlap: true,
data,
emphasis: { label: { fontSize: 14, fontWeight: 'bold', show: true } },
itemStyle: { borderColor: '#fff', borderRadius: 6, borderWidth: 2 },
label: { formatter: '{b}\n{c}', show: true },
radius: ['40%', '70%'],
type: 'pie',
},
],
tooltip: { formatter: '{b}: {c} ({d}%)', trigger: 'item' },
};
}

View File

@ -0,0 +1,70 @@
<script lang="ts" setup>
import type { MesHomeApi } from '#/api/mes/home';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { DocAlert, Page } from '@vben/common-ui';
import { Col, Row } from 'ant-design-vue';
import { getHomeSummary } from '#/api/mes/home';
import { defaultSummary } from './data';
import AlertPanel from './modules/alert-panel.vue';
import KpiCards from './modules/kpi-cards.vue';
import ProductionTrend from './modules/production-trend.vue';
import Shortcuts from './modules/shortcuts.vue';
import WorkOrderChart from './modules/work-order-chart.vue';
const router = useRouter();
const summary = ref<MesHomeApi.Summary>(defaultSummary); //
/** 跳转到目标页面(按路由 name */
function handleNavigate(name: string) {
router.push({ name });
}
/** 加载首页汇总统计 */
async function loadSummary() {
summary.value = await getHomeSummary();
}
onMounted(() => {
loadSummary();
});
</script>
<template>
<Page>
<template #doc>
<DocAlert
title="MES 手册(功能开启)"
url="https://doc.iocoder.cn/mes/build/"
/>
</template>
<!-- 第一行核心 KPI 汇总卡片 -->
<KpiCards :summary="summary" class="mb-4" @navigate="handleNavigate" />
<!-- 第二行生产趋势 + 待办异常 -->
<Row :gutter="16" class="mb-4">
<Col :lg="16" :md="24" :sm="24" :xl="16" :xs="24" class="mb-4">
<ProductionTrend />
</Col>
<Col :lg="8" :md="24" :sm="24" :xl="8" :xs="24" class="mb-4">
<AlertPanel :summary="summary" @navigate="handleNavigate" />
</Col>
</Row>
<!-- 第三行工单分布 + 快捷入口 -->
<Row :gutter="16">
<Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24" class="mb-4">
<WorkOrderChart />
</Col>
<Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24" class="mb-4">
<Shortcuts @navigate="handleNavigate" />
</Col>
</Row>
</Page>
</template>

View File

@ -0,0 +1,72 @@
<script lang="ts" setup>
import type { MesHomeApi } from '#/api/mes/home';
import { computed } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Badge, Card } from 'ant-design-vue';
defineOptions({ name: 'MesHomeAlertPanel' });
const props = defineProps<{
summary: MesHomeApi.Summary;
}>();
const emit = defineEmits<{
navigate: [name: string];
}>();
/** 待办提醒列表:标签、描述、图标、目标路由名称、数量 */
const alertItems = computed(() => [
{
count: props.summary.andonActiveCount,
desc: '未处置的安灯呼叫',
icon: 'lucide:bell-ring',
iconClass: 'bg-red-50 text-red-500',
label: '安灯报警',
routeName: 'MesProAndonRecord',
},
{
count: props.summary.repairActiveCount,
desc: '待处理的维修工单',
icon: 'lucide:wrench',
iconClass: 'bg-orange-50 text-orange-500',
label: '设备维修',
routeName: 'MesDvRepair',
},
{
count: props.summary.workOrderPrepareCount,
desc: '草稿状态的生产工单',
icon: 'lucide:clipboard-list',
iconClass: 'bg-blue-50 text-blue-500',
label: '待排产工单',
routeName: 'MesProWorkOrder',
},
]);
</script>
<template>
<Card title="待办与异常" class="h-full">
<div class="flex flex-col">
<div
v-for="item in alertItems"
:key="item.label"
class="hover:bg-accent flex cursor-pointer items-center gap-3 border-b px-2 py-4 transition-colors last:border-b-0"
@click="emit('navigate', item.routeName)"
>
<div
class="flex size-10 flex-shrink-0 items-center justify-center rounded-lg"
:class="item.iconClass"
>
<IconifyIcon class="size-5" :icon="item.icon" />
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium">{{ item.label }}</span>
<span class="text-muted-foreground text-xs">{{ item.desc }}</span>
</div>
<Badge :count="item.count" :show-zero="false" />
</div>
</div>
</Card>
</template>

View File

@ -0,0 +1,174 @@
<script lang="ts" setup>
import type { MesHomeApi } from '#/api/mes/home';
import { computed } from 'vue';
import { CountTo } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Card, Col, Divider, Row } from 'ant-design-vue';
defineOptions({ name: 'MesHomeKpiCards' });
const props = defineProps<{
summary: MesHomeApi.Summary;
}>();
const emit = defineEmits<{
navigate: [name: string];
}>();
/** 是否有质量数据(合格品 + 不良品 > 0 */
const hasQualityData = computed(
() =>
props.summary.todayQualifiedQuantity +
props.summary.todayUnqualifiedQuantity >
0,
);
/** 质量合格率 = 合格品 / (合格品 + 不良品) * 100无数据时为 0 */
const qualityRate = computed(() => {
const total =
props.summary.todayQualifiedQuantity +
props.summary.todayUnqualifiedQuantity;
if (total === 0) {
return 0;
}
return (props.summary.todayQualifiedQuantity / total) * 100;
});
</script>
<template>
<Row :gutter="16">
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
<Card
class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
@click="emit('navigate', 'MesProWorkOrder')"
>
<div class="flex items-center gap-4">
<div
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
style="background: linear-gradient(135deg, #409eff, #66b1ff)"
>
<IconifyIcon class="size-7" icon="lucide:file-text" />
</div>
<div>
<div class="text-muted-foreground mb-1 text-sm">生产工单</div>
<div class="flex items-baseline gap-1">
<CountTo
class="text-2xl font-bold leading-tight text-[#409eff]"
:end-val="summary.workOrderActiveCount"
:duration="1500"
/>
<span class="text-muted-foreground text-xs">进行中</span>
</div>
<div class="text-muted-foreground mt-1 text-xs">
<span>待排产 {{ summary.workOrderPrepareCount }}</span>
<Divider type="vertical" />
<span>已完成 {{ summary.workOrderFinishedCount }}</span>
</div>
</div>
</div>
</Card>
</Col>
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
<Card
class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
@click="emit('navigate', 'MesProFeedback')"
>
<div class="flex items-center gap-4">
<div
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
style="background: linear-gradient(135deg, #67c23a, #85ce61)"
>
<IconifyIcon class="size-7" icon="lucide:bar-chart-3" />
</div>
<div>
<div class="text-muted-foreground mb-1 text-sm">今日产量</div>
<div class="flex items-baseline gap-1">
<CountTo
class="text-2xl font-bold leading-tight text-[#67c23a]"
:end-val="summary.todayOutput"
:duration="1500"
/>
<span class="text-muted-foreground text-xs"></span>
</div>
<div class="text-muted-foreground mt-1 text-xs">
<span>昨日 {{ summary.yesterdayOutput }} </span>
</div>
</div>
</div>
</Card>
</Col>
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
<Card
class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
@click="emit('navigate', 'MesProFeedback')"
>
<div class="flex items-center gap-4">
<div
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
style="background: linear-gradient(135deg, #e6a23c, #ebb563)"
>
<IconifyIcon class="size-7" icon="lucide:circle-check" />
</div>
<div>
<div class="text-muted-foreground mb-1 text-sm">质量合格率</div>
<div class="flex items-baseline gap-1">
<CountTo
class="text-2xl font-bold leading-tight text-[#e6a23c]"
:decimals="1"
:end-val="qualityRate"
:duration="1500"
/>
<span class="text-muted-foreground text-xs">%</span>
</div>
<div class="text-muted-foreground mt-1 text-xs">
<template v-if="hasQualityData">
<span>合格 {{ summary.todayQualifiedQuantity }}</span>
<Divider type="vertical" />
<span>不良 {{ summary.todayUnqualifiedQuantity }}</span>
</template>
<span v-else></span>
</div>
</div>
</div>
</Card>
</Col>
<Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
<Card
class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
@click="emit('navigate', 'MesDvMachinery')"
>
<div class="flex items-center gap-4">
<div
class="flex size-14 flex-shrink-0 items-center justify-center rounded-xl text-white"
style="background: linear-gradient(135deg, #7c3aed, #9461f5)"
>
<IconifyIcon class="size-7" icon="lucide:cpu" />
</div>
<div>
<div class="text-muted-foreground mb-1 text-sm">设备状态</div>
<div class="flex items-baseline gap-1">
<CountTo
class="text-2xl font-bold leading-tight text-[#7c3aed]"
:end-val="summary.machineryProducing"
:duration="1500"
/>
<span class="text-muted-foreground text-xs">
/ {{ summary.machineryTotal }}
</span>
</div>
<div class="text-muted-foreground mt-1 text-xs">
<span class="text-red-400">停机 {{ summary.machineryStop }}</span>
<Divider type="vertical" />
<span class="text-orange-400">
维护 {{ summary.machineryMaintenance }}
</span>
</div>
</div>
</div>
</Card>
</Col>
</Row>
</template>

View File

@ -0,0 +1,56 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card, RadioButton, RadioGroup } from 'ant-design-vue';
import { getProductionTrend } from '#/api/mes/home';
import { getProductionTrendChartOptions } from '../data';
defineOptions({ name: 'MesHomeProductionTrend' });
const trendDays = ref(7); //
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
/** 加载生产趋势数据并渲染图表 */
async function loadData() {
const data = await getProductionTrend(trendDays.value);
const dates = data.map((d) => d.date.slice(5));
const quantities = data.map((d) => d.quantity);
const qualified = data.map((d) => d.qualifiedQuantity);
const unqualified = data.map((d) => d.unqualifiedQuantity);
await renderEcharts(
getProductionTrendChartOptions(dates, quantities, qualified, unqualified),
);
}
/** 切换天数范围 */
function handleDaysChange() {
loadData();
}
onMounted(() => {
loadData();
});
</script>
<template>
<Card title="生产趋势" class="h-full">
<template #extra>
<RadioGroup
v-model:value="trendDays"
size="small"
@change="handleDaysChange"
>
<RadioButton :value="7"> 7 </RadioButton>
<RadioButton :value="30"> 30 </RadioButton>
</RadioGroup>
</template>
<EchartsUI ref="chartRef" class="h-[320px] w-full" />
</Card>
</template>

View File

@ -0,0 +1,90 @@
<script lang="ts" setup>
import { IconifyIcon } from '@vben/icons';
import { Card, Col, Row } from 'ant-design-vue';
defineOptions({ name: 'MesHomeShortcuts' });
const emit = defineEmits<{
navigate: [name: string];
}>();
/** 快捷入口列表3×3 网格布局与工单状态分布面板等高 */
const shortcuts = [
{
bgColor: '#409EFF',
icon: 'lucide:file-text',
name: '生产工单',
routeName: 'MesProWorkOrder',
},
{
bgColor: '#67C23A',
icon: 'lucide:edit',
name: '生产报工',
routeName: 'MesProFeedback',
},
{
bgColor: '#E6A23C',
icon: 'lucide:search',
name: '质量检验',
routeName: 'MesQcIqc',
},
{
bgColor: '#F56C6C',
icon: 'lucide:box',
name: '库存查询',
routeName: 'MesWmMaterialStock',
},
{
bgColor: '#7c3aed',
icon: 'lucide:cpu',
name: '设备管理',
routeName: 'MesDvMachinery',
},
{
bgColor: '#0ea5e9',
icon: 'lucide:list',
name: '生产任务',
routeName: 'MesProTask',
},
{
bgColor: '#14b8a6',
icon: 'lucide:truck',
name: '到货通知',
routeName: 'MesWmArrivalNotice',
},
{
bgColor: '#f59e0b',
icon: 'lucide:settings-2',
name: '设备维修',
routeName: 'MesDvRepair',
},
{
bgColor: '#ec4899',
icon: 'lucide:tickets',
name: '流转卡',
routeName: 'MesProCard',
},
];
</script>
<template>
<Card title="快捷入口" class="h-full">
<Row :gutter="16">
<Col v-for="item in shortcuts" :key="item.name" :span="8" class="mb-4">
<div
class="hover:bg-accent flex cursor-pointer flex-col items-center gap-2 rounded-lg py-3 transition-all hover:-translate-y-0.5"
@click="emit('navigate', item.routeName)"
>
<div
class="flex size-12 items-center justify-center rounded-xl text-white"
:style="{ background: item.bgColor }"
>
<IconifyIcon class="size-6" :icon="item.icon" />
</div>
<span class="text-sm">{{ item.name }}</span>
</div>
</Col>
</Row>
</Card>
</template>

View File

@ -0,0 +1,39 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card } from 'ant-design-vue';
import { getWorkOrderStatusDistribution } from '#/api/mes/home';
import { getWorkOrderStatusChartOptions, WORK_ORDER_STATUS_COLOR_MAP } from '../data';
defineOptions({ name: 'MesHomeWorkOrderChart' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
/** 加载工单状态分布数据并渲染饼图 */
async function loadData() {
const data = await getWorkOrderStatusDistribution();
const chartData = data.map((d) => ({
itemStyle: { color: WORK_ORDER_STATUS_COLOR_MAP[d.status] || '#409EFF' },
name: d.statusName,
value: d.count,
}));
await renderEcharts(getWorkOrderStatusChartOptions(chartData));
}
onMounted(() => {
loadData();
});
</script>
<template>
<Card title="工单状态分布" class="h-full">
<EchartsUI ref="chartRef" class="h-[280px] w-full" />
</Card>
</template>

View File

@ -0,0 +1,55 @@
import { requestClient } from '#/api/request';
export namespace MesHomeApi {
/** MES 首页汇总统计 */
export interface Summary {
workOrderActiveCount: number; // 进行中工单数
workOrderPrepareCount: number; // 待排产工单数
workOrderFinishedCount: number; // 已完成工单数
todayOutput: number; // 今日产量
yesterdayOutput: number; // 昨日产量
todayQualifiedQuantity: number; // 今日合格品数
todayUnqualifiedQuantity: number; // 今日不良品数
machineryTotal: number; // 设备总数
machineryProducing: number; // 生产中设备数
machineryStop: number; // 停机设备数
machineryMaintenance: number; // 维护中设备数
andonActiveCount: number; // 未处置安灯呼叫数
repairActiveCount: number; // 待处理维修工单数
}
/** MES 工单状态分布 */
export interface WorkOrderStatus {
status: number; // 工单状态
statusName: string; // 工单状态名称
count: number; // 数量
}
/** MES 生产趋势 */
export interface ProductionTrend {
date: string; // 日期
quantity: number; // 产量
qualifiedQuantity: number; // 合格品数
unqualifiedQuantity: number; // 不良品数
}
}
/** 获得首页汇总统计 */
export function getHomeSummary() {
return requestClient.get<MesHomeApi.Summary>('/mes/home-statistics/summary');
}
/** 获得工单状态分布 */
export function getWorkOrderStatusDistribution() {
return requestClient.get<MesHomeApi.WorkOrderStatus[]>(
'/mes/home-statistics/work-order-status',
);
}
/** 获得生产趋势 */
export function getProductionTrend(days?: number) {
return requestClient.get<MesHomeApi.ProductionTrend[]>(
'/mes/home-statistics/production-trend',
{ params: { days } },
);
}

View File

@ -0,0 +1,59 @@
import { requestClient } from '#/api/request';
export namespace MesWmTransferDetailApi {
/** MES 调拨明细 */
export interface TransferDetail {
id?: number; // 编号
lineId?: number; // 转移单行编号
transferId?: number; // 转移单编号
itemId?: number; // 物料产品编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 单位名称
quantity?: number; // 数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
toWarehouseId?: number; // 移入仓库编号
toWarehouseName?: string; // 移入仓库名称
toLocationId?: number; // 移入库区编号
toLocationName?: string; // 移入库区名称
toAreaId?: number; // 移入库位编号
toAreaName?: string; // 移入库位名称
remark?: string; // 备注
}
}
/** 查询调拨明细列表(按行编号) */
export function getTransferDetailListByLineId(lineId: number) {
return requestClient.get<MesWmTransferDetailApi.TransferDetail[]>(
'/mes/wm/transfer-detail/list-by-line',
{ params: { lineId } },
);
}
/** 查询调拨明细详情 */
export function getTransferDetail(id: number) {
return requestClient.get<MesWmTransferDetailApi.TransferDetail>(
`/mes/wm/transfer-detail/get?id=${id}`,
);
}
/** 新增调拨明细 */
export function createTransferDetail(
data: MesWmTransferDetailApi.TransferDetail,
) {
return requestClient.post('/mes/wm/transfer-detail/create', data);
}
/** 修改调拨明细 */
export function updateTransferDetail(
data: MesWmTransferDetailApi.TransferDetail,
) {
return requestClient.put('/mes/wm/transfer-detail/update', data);
}
/** 删除调拨明细 */
export function deleteTransferDetail(id: number) {
return requestClient.delete(`/mes/wm/transfer-detail/delete?id=${id}`);
}

View File

@ -0,0 +1,92 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmTransferApi {
/** MES 转移单 */
export interface Transfer {
id?: number; // 编号
code?: string; // 转移单编号
name?: string; // 转移单名称
type?: number; // 转移单类型
deliveryFlag?: boolean; // 是否配送
recipientName?: string; // 收货人
recipientTelephone?: string; // 联系电话
destinationAddress?: string; // 目的地
carrier?: string; // 承运商
shippingNumber?: string; // 运输单号
confirmFlag?: boolean; // 是否确认
transferDate?: string; // 转移日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: number; // 创建时间
}
/** MES 转移单分页查询参数 */
export interface PageParams extends PageParam {
code?: string;
name?: string;
type?: number;
status?: number;
}
}
/** 查询转移单分页 */
export function getTransferPage(params: MesWmTransferApi.PageParams) {
return requestClient.get<PageResult<MesWmTransferApi.Transfer>>(
'/mes/wm/transfer/page',
{ params },
);
}
/** 查询转移单详情 */
export function getTransfer(id: number) {
return requestClient.get<MesWmTransferApi.Transfer>(
`/mes/wm/transfer/get?id=${id}`,
);
}
/** 新增转移单 */
export function createTransfer(data: MesWmTransferApi.Transfer) {
return requestClient.post<number>('/mes/wm/transfer/create', data);
}
/** 修改转移单 */
export function updateTransfer(data: MesWmTransferApi.Transfer) {
return requestClient.put('/mes/wm/transfer/update', data);
}
/** 删除转移单 */
export function deleteTransfer(id: number) {
return requestClient.delete(`/mes/wm/transfer/delete?id=${id}`);
}
/** 提交转移单 */
export function submitTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/submit?id=${id}`);
}
/** 到货确认 */
export function confirmTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/confirm?id=${id}`);
}
/** 执行上架 */
export function stockTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/stock?id=${id}`);
}
/** 完成转移 */
export function finishTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/finish?id=${id}`);
}
/** 取消转移单 */
export function cancelTransfer(id: number) {
return requestClient.put(`/mes/wm/transfer/cancel?id=${id}`);
}
/** 导出转移单 */
export function exportTransfer(params: any) {
return requestClient.download('/mes/wm/transfer/export-excel', { params });
}

View File

@ -0,0 +1,55 @@
import { requestClient } from '#/api/request';
export namespace MesWmTransferLineApi {
/** MES 转移单行 */
export interface TransferLine {
id?: number; // 编号
transferId?: number; // 转移单编号
materialStockId?: number; // 库存台账编号
itemId?: number; // 物料产品编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 单位名称
quantity?: number; // 转移数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
fromWarehouseId?: number; // 移出仓库编号
fromWarehouseName?: string; // 移出仓库名称
fromLocationId?: number; // 移出库区编号
fromLocationName?: string; // 移出库区名称
fromAreaId?: number; // 移出库位编号
fromAreaName?: string; // 移出库位名称
remark?: string; // 备注
}
}
/** 查询转移单行列表 */
export function getTransferLineList(transferId: number) {
return requestClient.get<MesWmTransferLineApi.TransferLine[]>(
'/mes/wm/transfer-line/list',
{ params: { transferId } },
);
}
/** 查询转移单行详情 */
export function getTransferLine(id: number) {
return requestClient.get<MesWmTransferLineApi.TransferLine>(
`/mes/wm/transfer-line/get?id=${id}`,
);
}
/** 新增转移单行 */
export function createTransferLine(data: MesWmTransferLineApi.TransferLine) {
return requestClient.post('/mes/wm/transfer-line/create', data);
}
/** 修改转移单行 */
export function updateTransferLine(data: MesWmTransferLineApi.TransferLine) {
return requestClient.put('/mes/wm/transfer-line/update', data);
}
/** 删除转移单行 */
export function deleteTransferLine(id: number) {
return requestClient.delete(`/mes/wm/transfer-line/delete?id=${id}`);
}