feat(mes): 迁移 home 首页
parent
37b9db148f
commit
b325db0450
|
|
@ -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 } },
|
||||
);
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
@ -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 });
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
@ -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' },
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 } },
|
||||
);
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
@ -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 });
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
Loading…
Reference in New Issue