✨ feat(mes): 添加用户已上工的错误码检查
parent
91adaff611
commit
89b38dbdd8
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 @AI:key 最好使用枚举;
|
||||
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>
|
||||
Loading…
Reference in New Issue