feat(mes): 添加用户已上工的错误码检查

pull/871/MERGE
YunaiV 2026-04-06 00:50:13 +08:00
parent 91adaff611
commit 89b38dbdd8
5 changed files with 428 additions and 0 deletions

View File

@ -0,0 +1,79 @@
<!-- TODO @AI补充一些注释 -->
<template>
<el-card shadow="hover" class="h-full alert-card">
<template #header>
<div class="flex justify-between items-center">
<span class="text-16px font-600">待办与异常</span>
</div>
</template>
<div class="flex flex-col">
<div
v-for="item in alertItems"
:key="item.label"
class="flex items-center gap-12px px-20px py-16px cursor-pointer border-b border-b-[var(--el-border-color-lighter)] last:border-b-0 transition-colors hover:bg-[var(--el-fill-color-light)]"
@click="emit('navigate', item.url)"
>
<div
class="w-40px h-40px rounded-10px flex items-center justify-center flex-shrink-0"
:class="item.iconClass"
>
<Icon :icon="item.icon" :size="20" />
</div>
<div class="flex-1 flex flex-col gap-2px">
<span class="text-14px font-500">{{ item.label }}</span>
<span class="text-12px color-[var(--el-text-color-placeholder)]">{{ item.desc }}</span>
</div>
<el-badge :value="item.count" :hidden="!item.count" class="flex-shrink-0" />
</div>
</div>
</el-card>
</template>
<script lang="ts" setup>
import { MesHomeSummaryVO } from '@/api/mes/home'
defineOptions({ name: 'HomeAlertPanel' })
const props = defineProps<{
summary: MesHomeSummaryVO
}>()
const emit = defineEmits<{
navigate: [url: string]
}>()
const alertItems = computed(() => [
{
label: '安灯报警',
desc: '未处置的安灯呼叫',
icon: 'ep:warning-filled',
url: '/mes/pro/andon',
count: props.summary.andonActiveCount,
iconClass: 'bg-[rgba(245,108,108,0.1)] color-[#f56c6c]'
},
{
label: '设备维修',
desc: '待处理的维修工单',
icon: 'ep:set-up',
url: '/mes/dv/repair',
count: props.summary.repairActiveCount,
iconClass: 'bg-[rgba(230,162,60,0.1)] color-[#e6a23c]'
},
{
label: '待排产工单',
desc: '草稿状态的生产工单',
icon: 'ep:document-checked',
url: '/mes/pro/workorder',
count: props.summary.workOrderPrepareCount,
iconClass: 'bg-[rgba(64,158,255,0.1)] color-[#409eff]'
}
])
</script>
<style lang="scss" scoped>
.alert-card {
:deep(.el-card__body) {
padding: 0;
}
}
</style>

View File

@ -0,0 +1,175 @@
<!-- TODO @AI补充一些注释 -->
<template>
<el-row :gutter="16" class="mb-16px">
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-16px">
<el-card
shadow="hover"
class="kpi-card kpi-card--production"
@click="handleNavigate('/mes/pro/workorder')"
>
<div class="flex items-center gap-16px">
<div
class="w-56px h-56px rounded-12px flex items-center justify-center flex-shrink-0 text-white"
style="background: linear-gradient(135deg, #409eff, #66b1ff)"
>
<Icon icon="ep:document" :size="28" />
</div>
<div>
<div class="text-14px color-[var(--el-text-color-secondary)] mb-4px">生产工单</div>
<div class="flex items-baseline gap-4px">
<CountTo
:end-val="summary.workOrderActiveCount"
:duration="1500"
class="text-28px font-700 leading-[1.2] color-[#409eff]"
/>
<span class="text-13px color-[var(--el-text-color-secondary)]">进行中</span>
</div>
<div class="text-12px color-[var(--el-text-color-placeholder)] mt-4px">
<span>待排产 {{ summary.workOrderPrepareCount }}</span>
<el-divider direction="vertical" />
<span>已完成 {{ summary.workOrderFinishedCount }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-16px">
<el-card
shadow="hover"
class="kpi-card kpi-card--output"
@click="handleNavigate('/mes/pro/feedback')"
>
<div class="flex items-center gap-16px">
<div
class="w-56px h-56px rounded-12px flex items-center justify-center flex-shrink-0 text-white"
style="background: linear-gradient(135deg, #67c23a, #85ce61)"
>
<Icon icon="ep:data-analysis" :size="28" />
</div>
<div>
<div class="text-14px color-[var(--el-text-color-secondary)] mb-4px">今日产量</div>
<div class="flex items-baseline gap-4px">
<CountTo
:end-val="summary.todayOutput"
:duration="1500"
class="text-28px font-700 leading-[1.2] color-[#67c23a]"
/>
<span class="text-13px color-[var(--el-text-color-secondary)]"></span>
</div>
<div class="text-12px color-[var(--el-text-color-placeholder)] mt-4px">
<span>昨日 {{ summary.yesterdayOutput }} </span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-16px">
<el-card
shadow="hover"
class="kpi-card kpi-card--quality"
@click="handleNavigate('/mes/qc/iqc')"
>
<div class="flex items-center gap-16px">
<div
class="w-56px h-56px rounded-12px flex items-center justify-center flex-shrink-0 text-white"
style="background: linear-gradient(135deg, #e6a23c, #ebb563)"
>
<Icon icon="ep:circle-check" :size="28" />
</div>
<div>
<div class="text-14px color-[var(--el-text-color-secondary)] mb-4px">质量合格率</div>
<div class="flex items-baseline gap-4px">
<CountTo
:end-val="qualityRate"
:decimals="1"
:duration="1500"
class="text-28px font-700 leading-[1.2] color-[#e6a23c]"
/>
<span class="text-13px color-[var(--el-text-color-secondary)]">%</span>
</div>
<div class="text-12px color-[var(--el-text-color-placeholder)] mt-4px">
<span>合格 {{ summary.todayQualifiedQuantity }}</span>
<el-divider direction="vertical" />
<span>不良 {{ summary.todayUnqualifiedQuantity }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-16px">
<el-card
shadow="hover"
class="kpi-card kpi-card--equipment"
@click="handleNavigate('/mes/dv/machinery')"
>
<div class="flex items-center gap-16px">
<div
class="w-56px h-56px rounded-12px flex items-center justify-center flex-shrink-0 text-white"
style="background: linear-gradient(135deg, #7c3aed, #9461f5)"
>
<Icon icon="ep:cpu" :size="28" />
</div>
<div>
<div class="text-14px color-[var(--el-text-color-secondary)] mb-4px">设备状态</div>
<div class="flex items-baseline gap-4px">
<CountTo
:end-val="summary.machineryProducing"
:duration="1500"
class="text-28px font-700 leading-[1.2] color-[#7c3aed]"
/>
<span class="text-13px color-[var(--el-text-color-secondary)]"
>/ {{ summary.machineryTotal }} </span
>
</div>
<div class="text-12px color-[var(--el-text-color-placeholder)] mt-4px">
<span class="text-red-400">停机 {{ summary.machineryStop }}</span>
<el-divider direction="vertical" />
<span class="text-orange-400">维护 {{ summary.machineryMaintenance }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { MesHomeSummaryVO } from '@/api/mes/home'
defineOptions({ name: 'HomeKpiCards' })
const props = defineProps<{
summary: MesHomeSummaryVO
}>()
const emit = defineEmits<{
navigate: [url: string]
}>()
const qualityRate = computed(() => {
const total = props.summary.todayQualifiedQuantity + props.summary.todayUnqualifiedQuantity
if (total === 0) return 100
return (props.summary.todayQualifiedQuantity / total) * 100
})
const handleNavigate = (url: string) => {
emit('navigate', url)
}
</script>
<style lang="scss" scoped>
.kpi-card {
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid var(--el-border-color);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
:deep(.el-card__body) {
padding: 20px;
}
}
</style>

View File

@ -0,0 +1,75 @@
<!-- TODO @AI补充一些注释 -->
<template>
<el-card shadow="hover">
<template #header>
<div class="flex justify-between items-center">
<span class="text-16px font-600">生产趋势</span>
<el-radio-group v-model="trendDays" size="small" @change="loadData">
<el-radio-button :value="7"> 7 </el-radio-button>
<el-radio-button :value="30"> 30 </el-radio-button>
</el-radio-group>
</div>
</template>
<Echart :options="trendChartOptions" :height="320" />
</el-card>
</template>
<script lang="ts" setup>
import { EChartsOption } from 'echarts'
import { MesHomeStatisticsApi, MesHomeProductionTrendVO } from '@/api/mes/home'
defineOptions({ name: 'HomeProductionTrend' })
const trendDays = ref(7)
const trendChartOptions = reactive<EChartsOption>({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' }
},
legend: { data: ['产量', '合格品', '不良品'], bottom: 0 },
grid: { left: 50, right: 20, top: 20, bottom: 40 },
xAxis: { type: 'category', data: [], boundaryGap: false },
yAxis: { type: 'value', minInterval: 1 },
series: [
{
name: '产量',
type: 'line',
smooth: true,
data: [],
itemStyle: { color: '#409EFF' },
areaStyle: { color: 'rgba(64,158,255,0.15)' }
},
{
name: '合格品',
type: 'line',
smooth: true,
data: [],
itemStyle: { color: '#67C23A' }
},
{
name: '不良品',
type: 'line',
smooth: true,
data: [],
itemStyle: { color: '#F56C6C' }
}
]
}) as EChartsOption
const loadData = async () => {
const data: MesHomeProductionTrendVO[] = await MesHomeStatisticsApi.getProductionTrend(
trendDays.value
)
const dates = data.map((d) => d.date.substring(5))
const quantities = data.map((d) => d.quantity)
const qualified = data.map((d) => d.qualifiedQuantity)
const unqualified = data.map((d) => d.unqualifiedQuantity)
;(trendChartOptions as any).xAxis.data = dates
;(trendChartOptions as any).series[0].data = quantities
;(trendChartOptions as any).series[1].data = qualified
;(trendChartOptions as any).series[2].data = unqualified
}
/** 暴露加载方法供父组件调用 */
defineExpose({ loadData })
</script>

View File

@ -0,0 +1,44 @@
<!-- TODO @AI补充一些注释 -->
<template>
<el-card shadow="hover">
<template #header>
<div class="flex justify-between items-center">
<span class="text-16px font-600">快捷入口</span>
</div>
</template>
<el-row :gutter="16">
<el-col v-for="item in shortcuts" :key="item.name" :span="8" class="mb-16px">
<div
class="flex flex-col items-center gap-8px cursor-pointer py-12px rounded-8px transition-all hover:bg-[var(--el-fill-color-light)] hover:translate-y--2px"
@click="emit('navigate', item.url)"
>
<div
class="w-48px h-48px rounded-12px flex items-center justify-center"
:style="{ background: item.bgColor }"
>
<Icon :icon="item.icon" :size="24" color="#fff" />
</div>
<span class="text-13px color-[var(--el-text-color-regular)]">{{ item.name }}</span>
</div>
</el-col>
</el-row>
</el-card>
</template>
<script lang="ts" setup>
defineOptions({ name: 'HomeShortcuts' })
const emit = defineEmits<{
navigate: [url: string]
}>()
// TODO @AI 9
const shortcuts = [
{ name: '生产工单', icon: 'ep:document', url: '/mes/pro/workorder', bgColor: '#409EFF' },
{ name: '生产报工', icon: 'ep:edit', url: '/mes/pro/feedback', bgColor: '#67C23A' },
{ name: '质量检验', icon: 'ep:search', url: '/mes/qc/iqc', bgColor: '#E6A23C' },
{ name: '库存查询', icon: 'ep:box', url: '/mes/wm/materialstock', bgColor: '#F56C6C' },
{ name: '设备管理', icon: 'ep:cpu', url: '/mes/dv/machinery', bgColor: '#7c3aed' },
{ name: '库存流水', icon: 'ep:tickets', url: '/mes/wm/transaction', bgColor: '#0ea5e9' }
]
</script>

View File

@ -0,0 +1,55 @@
<!-- TODO @AI补充一些注释 -->
<template>
<el-card shadow="hover">
<template #header>
<div class="flex justify-between items-center">
<span class="text-16px font-600">工单状态分布</span>
</div>
</template>
<Echart :options="workOrderChartOptions" :height="280" />
</el-card>
</template>
<script lang="ts" setup>
import { EChartsOption } from 'echarts'
import { MesHomeStatisticsApi, MesHomeWorkOrderStatusVO } from '@/api/mes/home'
defineOptions({ name: 'HomeWorkOrderChart' })
const statusColorMap: Record<number, string> = {
// TODO @AIkey 使
0: '#909399', // 稿
1: '#409EFF', //
2: '#67C23A', //
3: '#F56C6C' //
}
const workOrderChartOptions = reactive<EChartsOption>({
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { bottom: 0, type: 'scroll' },
series: [
{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: true,
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
label: { show: true, formatter: '{b}\n{c}' },
emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
data: []
}
]
}) as EChartsOption
const loadData = async () => {
const data: MesHomeWorkOrderStatusVO[] =
await MesHomeStatisticsApi.getWorkOrderStatusDistribution()
;(workOrderChartOptions as any).series[0].data = data.map((d) => ({
name: d.statusName,
value: d.count,
itemStyle: { color: statusColorMap[d.status] || '#409EFF' }
}))
}
/** 暴露加载方法供父组件调用 */
defineExpose({ loadData })
</script>