style:Dashboard8数据大屏页面中英文适配

main
黄伟杰 4 days ago
parent 50f112c28a
commit cf117ea396

@ -37,6 +37,7 @@ export default {
toolDes: 'Used to set up custom systems',
query: 'Query',
reset: 'Reset',
noData: 'No data',
shrink: 'Put away',
expand: 'Expand',
confirmTitle: 'System Hint',
@ -59,6 +60,134 @@ export default {
copyError: 'Copy Error',
code:'Auto-generate on Save'
},
ReportDashboard: {
DashboardList: {
searchNameLabel: 'Name',
searchNamePlaceholder: 'Please enter name',
searchRemarkLabel: 'Remark',
searchRemarkPlaceholder: 'Please enter remark',
searchStateLabel: 'Status',
searchStatePlaceholder: 'Please select status',
coverAlt: 'Cover image',
stateEnabled: 'Enabled',
stateDisabled: 'Disabled',
noRemark: 'No description',
dialogCreateTitle: 'Create Dashboard',
dialogEditTitle: 'Edit Dashboard',
dialogNameLabel: 'Name',
dialogNamePlaceholder: 'Please enter name',
dialogTypeLabel: 'Type',
dialogTypePlaceholder: 'Please select type',
dialogOrgLabel: 'Line',
dialogOrgPlaceholder: 'Please select line',
dialogDeviceLabel: 'Device',
dialogDevicePlaceholder: 'Please select device',
dialogPointPlaceholder: 'Please select points',
dialogAddDeviceButton: 'Add device',
dialogContentLabel: 'Content',
dialogContentPlaceholder: 'Please enter content',
dialogRemarkLabel: 'Remark',
dialogRemarkPlaceholder: 'Please enter remark',
dialogStateLabel: 'Status',
validatorNameRequired: 'Name is required',
validatorTypeRequired: 'Dashboard type is required',
validatorOrgRequired: 'Line is required',
messageRouteMissing: 'Preview route is not configured',
messageDevicePointRequired: 'Please configure at least one device and point group',
messageMissingId: 'Missing record ID, unable to edit'
}
},
Dashboard8: {
Header: {
title: 'Smart Manufacturing Line Task Overview',
subTitle: 'INTELLIGENT MANUFACTURING REAL-TIME DASHBOARD'
},
TaskBoard: {
title: 'Line Task Board',
tag: 'Auto refresh · Scrolling',
statusCompleted: 'Completed',
statusLowProgress: 'Behind',
columns: {
lineName: 'Line',
planNo: 'Plan No.',
productName: 'Product',
planQty: 'Planned',
doneQty: 'Completed',
passRate: 'Pass Rate'
}
},
DayCapacity: {
title: 'Daily Capacity',
tag: 'Today',
metrics: {
orders: 'Orders',
scheduled: 'Scheduled',
produced: 'Produced',
rate: 'Quality Rate'
},
chart: {
scheduled: 'Scheduled',
produced: 'Produced'
}
},
MonthCapacity: {
title: 'Monthly Capacity',
tag: 'This month',
metrics: {
orders: 'Orders',
scheduled: 'Scheduled',
produced: 'Produced',
rate: 'Quality Rate'
},
chart: {
scheduled: 'Scheduled',
produced: 'Produced'
}
},
WeekTrend: {
title: 'Weekly Output Trend',
legendOutput: 'Output',
legendPlan: 'Plan',
seriesPlanOutput: 'Planned Output',
seriesActualOutput: 'Actual Output'
},
OpsTrend: {
title: 'Completed Output',
defaultLine: 'All lines',
yAxisName: 'Completed Output',
seriesName: 'Completed Output',
summary: '{line} · Avg {value}'
},
TodayOps: {
title: 'Today Power/Utilization',
empty: 'No data',
powerOnRate: 'Power-on Rate',
utilizationRate: 'Utilization'
},
QualityTrend: {
title: 'Product Quality Trend',
tag: 'Daily (All lines)',
seriesName: 'Pass Rate',
markLineAverage: 'Avg {value}%',
markLineAverageName: 'Average'
},
RealAlarm: {
title: 'Real-time Alarms',
tag: 'Scrolling',
levelSevere: 'Severe',
levelWarn: 'Warning',
levelInfo: 'Info'
},
EnergyTrend: {
title: 'Weekly Energy Trend',
selectPlaceholder: 'Please select',
seriesActualEnergy: 'Actual Energy (kWh)'
}
},
ErpStock: {
Warehouse: {
name: 'Warehouse Name',

@ -37,6 +37,7 @@ export default {
toolDes: '用于设置定制系统',
query: '查询',
reset: '重置',
noData: '暂无数据',
shrink: '收起',
expand: '展开',
confirmTitle: '系统提示',
@ -59,6 +60,134 @@ export default {
copyError: '复制失败',
code: '编码保存后自动生成'
},
ReportDashboard: {
DashboardList: {
searchNameLabel: '名称',
searchNamePlaceholder: '请输入名称',
searchRemarkLabel: '备注',
searchRemarkPlaceholder: '请输入备注',
searchStateLabel: '启用状态',
searchStatePlaceholder: '请选择启用状态',
coverAlt: '封面图',
stateEnabled: '启用',
stateDisabled: '禁用',
noRemark: '暂无描述',
dialogCreateTitle: '新增数据大屏',
dialogEditTitle: '编辑数据大屏',
dialogNameLabel: '名称',
dialogNamePlaceholder: '请输入名称',
dialogTypeLabel: '大屏类型',
dialogTypePlaceholder: '请选择大屏类型',
dialogOrgLabel: '产线',
dialogOrgPlaceholder: '请选择产线',
dialogDeviceLabel: '设备',
dialogDevicePlaceholder: '请选择设备',
dialogPointPlaceholder: '请选择点位',
dialogAddDeviceButton: '添加设备',
dialogContentLabel: '内容',
dialogContentPlaceholder: '请输入内容',
dialogRemarkLabel: '备注',
dialogRemarkPlaceholder: '请输入备注',
dialogStateLabel: '启用状态',
validatorNameRequired: '名称不能为空',
validatorTypeRequired: '大屏类型不能为空',
validatorOrgRequired: '产线不能为空',
messageRouteMissing: '未配置预览路由',
messageDevicePointRequired: '请至少配置一组设备和点位',
messageMissingId: '缺少数据编号,无法编辑'
}
},
Dashboard8: {
Header: {
title: '智能制造产线任务总览',
subTitle: '智能制造实时看板'
},
TaskBoard: {
title: '产线任务看板',
tag: '实时刷新 · 滚动展示',
statusCompleted: '已完成',
statusLowProgress: '进度偏低',
columns: {
lineName: '产线名称',
planNo: '计划单',
productName: '产品名称',
planQty: '计划数量',
doneQty: '完工数量',
passRate: '合格率'
}
},
DayCapacity: {
title: '日产能达成情况',
tag: '当日维度',
metrics: {
orders: '排产单数量',
scheduled: '已排产数量',
produced: '已生产数量',
rate: '产能合格率'
},
chart: {
scheduled: '已排产',
produced: '已生产'
}
},
MonthCapacity: {
title: '月产能达成情况',
tag: '当月累计',
metrics: {
orders: '排产单数量',
scheduled: '已排产数量',
produced: '已生产数量',
rate: '产能合格率'
},
chart: {
scheduled: '已排产',
produced: '已生产'
}
},
WeekTrend: {
title: '周生产趋势',
legendOutput: '产量',
legendPlan: '计划',
seriesPlanOutput: '计划产量',
seriesActualOutput: '实际产量'
},
OpsTrend: {
title: '产能完成数',
defaultLine: '全部产线',
yAxisName: '产能完成数',
seriesName: '产能完成数',
summary: '{line} · 日均完成 {value}'
},
TodayOps: {
title: '今日开机率/稼动率',
empty: '暂无数据',
powerOnRate: '开机率',
utilizationRate: '稼动率'
},
QualityTrend: {
title: '成品检合格率趋势图',
tag: '按天统计(全产线)',
seriesName: '合格率',
markLineAverage: '平均 {value}%',
markLineAverageName: '平均值'
},
RealAlarm: {
title: '实时报警信息',
tag: '滚动展示',
levelSevere: '严重',
levelWarn: '警告',
levelInfo: '提示'
},
EnergyTrend: {
title: '能耗周趋势',
selectPlaceholder: '请选择',
seriesActualEnergy: '实际能耗(kWh)'
}
},
ErpStock: {
Warehouse: {
name: '仓库名称',

@ -48,7 +48,7 @@
</el-tag>
</div>
<div class="header-right">
<span class="device-id">ID: {{ item.deviceId }}</span>
<span class="device-id">{{ t('ReportDashboard.Dashboard1.deviceIdPrefix') }}{{ item.deviceId }}</span>
</div>
</div>
<div class="device-body">
@ -80,7 +80,7 @@
</el-tag>
</div>
<div class="header-right">
<span class="device-id">ID: {{ item.deviceId }}</span>
<span class="device-id">{{ t('ReportDashboard.Dashboard1.deviceIdPrefix') }}{{ item.deviceId }}</span>
</div>
</div>
<div class="device-body">
@ -128,6 +128,7 @@ import ProductionTrend from './components/ProductionTrend.vue'
const route = useRoute()
const goviewId = route.query.goviewId as string
const orgId = route.query.orgId
const { t } = useI18n()
interface DeviceAttribute {
attributeName: string
@ -164,7 +165,7 @@ const loadDeviceAttributes = async () => {
const cards: DeviceCardData[] = list.map((d: any) => {
return {
deviceName: d.deviceName || 'Device',
deviceName: d.deviceName || t('ReportDashboard.Dashboard1.defaultDeviceName'),
deviceId: d.deviceId,
operatingStatus: d.operatingStatus,
attributes: (d.attributes || []).map((attr: any) => ({
@ -193,7 +194,7 @@ const getDeviceStatusType = (status?: string) => {
}
const getDeviceStatusText = (status?: string) => {
if (!status) return '离线'
if (!status) return t('ReportDashboard.Dashboard1.statusOffline')
return status
}

@ -6,8 +6,8 @@
<Icon icon="fa-solid:microchip" class="title-mark-icon" />
</div>
<div>
<div class="title">智能制造产线任务总览</div>
<div class="sub-title">INTELLIGENT MANUFACTURING REAL-TIME DASHBOARD</div>
<div class="title">{{ t('Header.title') }}</div>
<div class="sub-title">{{ t('Header.subTitle') }}</div>
</div>
</div>
<div class="header-right">
@ -31,8 +31,10 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
const router = useRouter()
const { t } = useI18n('Dashboard8')
const timeStr = ref('')
let timer: number | undefined

@ -5,9 +5,9 @@
<span class="card-title-icon">
<Icon icon="fa-solid:sun" />
</span>
<span>日产能达成情况</span>
<span>{{ t('DayCapacity.title') }}</span>
</div>
<span class="tag">当日维度</span>
<span class="tag">{{ t('DayCapacity.tag') }}</span>
</div>
<div class="card-body">
<div class="card-body-row">
@ -17,19 +17,19 @@
<div class="card-body-col">
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">排产单数量</div>
<div class="metric-label">{{ t('DayCapacity.metrics.orders') }}</div>
<div class="metric-value" ref="dayOrderEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">已排产数量</div>
<div class="metric-label">{{ t('DayCapacity.metrics.scheduled') }}</div>
<div class="metric-value" ref="dayPlanEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">已生产数量</div>
<div class="metric-label">{{ t('DayCapacity.metrics.produced') }}</div>
<div class="metric-value ok" ref="dayPendingEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">产能合格率</div>
<div class="metric-label">{{ t('DayCapacity.metrics.rate') }}</div>
<div class="metric-value accent" ref="dayRateEl"></div>
</div>
</div>
@ -40,14 +40,18 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { colors, animateCount, style } from '../utils'
import { PlanApi } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
type CapacityData = {
orders: number
@ -71,6 +75,27 @@ const dayRateEl = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
const buildOption = () => {
return {
backgroundColor: 'transparent',
tooltip: { trigger: 'item' },
legend: { bottom: '0%', left: 'center', textStyle: style.legendText },
series: [
{
type: 'pie',
radius: ['60%', '82%'],
center: ['50%', '55%'],
label: { show: false },
emphasis: { scale: false },
data: [
{ value: day.scheduled, name: t('DayCapacity.chart.scheduled'), itemStyle: { color: colors.cyan } },
{ value: day.produced, name: t('DayCapacity.chart.produced'), itemStyle: { color: colors.green } }
]
}
]
}
}
const mapCapacity = (raw: any): CapacityData => {
const orderCount = Number(raw?.orders ?? raw?.order ?? 0)
const scheduled = Number(raw?.plan ?? 0)
@ -94,24 +119,7 @@ const loadDayCapacity = async () => {
const initChart = () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
chart.setOption({
backgroundColor: 'transparent',
tooltip: { trigger: 'item' },
legend: { bottom: '0%', left: 'center', textStyle: style.legendText },
series: [
{
type: 'pie',
radius: ['60%', '82%'],
center: ['50%', '55%'],
label: { show: false },
emphasis: { scale: false },
data: [
{ value: day.scheduled, name: '已排产', itemStyle: { color: colors.cyan } },
{ value: day.produced, name: '已生产', itemStyle: { color: colors.green } }
]
}
]
})
chart.setOption(buildOption())
}
const resizeHandler = () => {
@ -121,6 +129,7 @@ const resizeHandler = () => {
onMounted(async () => {
await loadDayCapacity()
initChart()
chart?.setOption(buildOption(), true)
animateCount(dayOrderEl.value, day.orders, '', 900)
animateCount(dayPlanEl.value, day.scheduled, '', 900)
animateCount(dayPendingEl.value, day.produced, '', 900)
@ -128,6 +137,13 @@ onMounted(async () => {
window.addEventListener('resize', resizeHandler)
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
chart?.setOption(buildOption(), true)
}
)
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
chart?.dispose()

@ -5,11 +5,11 @@
<span class="card-title-icon">
<Icon icon="fa-solid:bolt" />
</span>
<span>能耗周趋势</span>
<span>{{ t('EnergyTrend.title') }}</span>
</div>
<div class="card-toolbar">
<el-select
v-model="selectedEnergyTypeId" placeholder="请选择" class="energy-type-select" size="small"
v-model="selectedEnergyTypeId" :placeholder="t('EnergyTrend.selectPlaceholder')" class="energy-type-select" size="small"
@change="handleEnergyTypeChange">
<el-option v-for="item in energyTypes" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@ -22,15 +22,19 @@ v-model="selectedEnergyTypeId" placeholder="请选择" class="energy-type-select
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { colors, style } from '../utils'
import { EnergyTypeApi, EnergyTypeVO } from '@/api/mes/energytype'
import { EnergyDeviceApi } from '@/api/mes/energydevice'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
const energyTypes = ref<EnergyTypeVO[]>([])
const selectedEnergyTypeId = ref<number | undefined>(undefined)
@ -68,24 +72,8 @@ const handleEnergyTypeChange = () => {
getChartData()
}
const render = (data: any = []) => {
if (!chart) return
const list = (data as any).data || (Array.isArray(data) ? data : [])
const x: string[] = []
const actual: number[] = []
const baseline: number[] = []
if (list && list.length > 0) {
list.forEach((item: any) => {
const label = item.hour || item.day || ''
x.push(label)
const v = Number(item.value ?? item.energy ?? item.consumption ?? 0)
actual.push(Number.isNaN(v) ? 0 : v)
})
}
chart.setOption({
const buildOption = (x: string[], actual: number[]) => {
return {
backgroundColor: 'transparent',
tooltip: { trigger: 'axis' },
legend: { top: 0, right: 0, textStyle: style.legendText },
@ -99,7 +87,7 @@ const render = (data: any = []) => {
yAxis: { type: 'value', axisLine: { show: false }, axisLabel: style.axisLabel, splitLine: style.splitLine },
series: [
{
name: '实际能耗(kWh)',
name: t('EnergyTrend.seriesActualEnergy'),
type: 'bar',
barWidth: 16,
itemStyle: {
@ -112,7 +100,26 @@ const render = (data: any = []) => {
data: actual
}
]
})
}
}
const render = (data: any = []) => {
if (!chart) return
const list = (data as any).data || (Array.isArray(data) ? data : [])
const x: string[] = []
const actual: number[] = []
if (list && list.length > 0) {
list.forEach((item: any) => {
const label = item.hour || item.day || ''
x.push(label)
const v = Number(item.value ?? item.energy ?? item.consumption ?? 0)
actual.push(Number.isNaN(v) ? 0 : v)
})
}
chart.setOption(buildOption(x, actual))
}
const resize = () => {
@ -122,11 +129,18 @@ const resize = () => {
onMounted(async () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
render([])
chart.setOption(buildOption([], []))
await getEnergyTypes()
window.addEventListener('resize', resize)
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
getChartData()
}
)
onUnmounted(() => {
window.removeEventListener('resize', resize)
chart?.dispose()

@ -5,9 +5,9 @@
<span class="card-title-icon">
<Icon icon="fa-solid:moon" />
</span>
<span>月产能达成情况</span>
<span>{{ t('MonthCapacity.title') }}</span>
</div>
<span class="tag">当月累计</span>
<span class="tag">{{ t('MonthCapacity.tag') }}</span>
</div>
<div class="card-body">
<div class="card-body-row">
@ -17,19 +17,19 @@
<div class="card-body-col">
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">排产单数量</div>
<div class="metric-label">{{ t('MonthCapacity.metrics.orders') }}</div>
<div class="metric-value" ref="monthOrderEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">已排产数量</div>
<div class="metric-label">{{ t('MonthCapacity.metrics.scheduled') }}</div>
<div class="metric-value" ref="monthPlanEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">已生产数量</div>
<div class="metric-label">{{ t('MonthCapacity.metrics.produced') }}</div>
<div class="metric-value ok" ref="monthPendingEl"></div>
</div>
<div class="metric-card">
<div class="metric-label">产能合格率</div>
<div class="metric-label">{{ t('MonthCapacity.metrics.rate') }}</div>
<div class="metric-value accent" ref="monthRateEl"></div>
</div>
</div>
@ -40,14 +40,18 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { colors, animateCount, style } from '../utils'
import { PlanApi } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
type CapacityData = {
orders: number
@ -71,6 +75,27 @@ const monthRateEl = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
const buildOption = () => {
return {
backgroundColor: 'transparent',
tooltip: { trigger: 'item' },
legend: { bottom: '0%', left: 'center', textStyle: style.legendText },
series: [
{
type: 'pie',
radius: ['60%', '82%'],
center: ['50%', '55%'],
label: { show: false },
emphasis: { scale: false },
data: [
{ value: month.scheduled, name: t('MonthCapacity.chart.scheduled'), itemStyle: { color: colors.cyan } },
{ value: month.produced, name: t('MonthCapacity.chart.produced'), itemStyle: { color: colors.green } }
]
}
]
}
}
const mapCapacity = (raw: any): CapacityData => {
const orderCount = Number(raw?.orders ?? raw?.order ?? 0)
const scheduled = Number(raw?.plan ?? 0)
@ -94,24 +119,7 @@ const loadMonthCapacity = async () => {
const initChart = () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
chart.setOption({
backgroundColor: 'transparent',
tooltip: { trigger: 'item' },
legend: { bottom: '0%', left: 'center', textStyle: style.legendText },
series: [
{
type: 'pie',
radius: ['60%', '82%'],
center: ['50%', '55%'],
label: { show: false },
emphasis: { scale: false },
data: [
{ value: month.scheduled, name: '已排产', itemStyle: { color: colors.cyan } },
{ value: month.produced, name: '已生产', itemStyle: { color: colors.green } }
]
}
]
})
chart.setOption(buildOption())
}
const resizeHandler = () => {
@ -121,6 +129,7 @@ const resizeHandler = () => {
onMounted(async () => {
await loadMonthCapacity()
initChart()
chart?.setOption(buildOption(), true)
animateCount(monthOrderEl.value, month.orders, '', 900)
animateCount(monthPlanEl.value, month.scheduled, '', 900)
animateCount(monthPendingEl.value, month.produced, '', 900)
@ -128,6 +137,13 @@ onMounted(async () => {
window.addEventListener('resize', resizeHandler)
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
chart?.setOption(buildOption(), true)
}
)
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
chart?.dispose()

@ -5,7 +5,7 @@
<span class="card-title-icon">
<Icon icon="fa-solid:wave-square" />
</span>
<span>产能完成数</span>
<span>{{ t('OpsTrend.title') }}</span>
</div>
<div class="ops-header-right">
<span class="tag">{{ summary }}</span>
@ -18,17 +18,21 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { colors, style } from '../utils'
import { PlanApi } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
const opsDays: string[] = []
const opsLines: { name: string; value: number[] }[] = [
{ name: '全部产线', value: [] }
{ name: t('OpsTrend.defaultLine'), value: [] }
]
const currentLine = ref(opsLines[0].name)
@ -43,7 +47,7 @@ const avg = (arr: number[]) => {
const updateOps = (lineName: string) => {
currentLine.value = lineName
const item = opsLines.find((x) => x.name === lineName) || opsLines[0]
summary.value = `${item.name} · 日均完成 ${avg(item.value)}`
summary.value = t('OpsTrend.summary', { line: item.name, value: avg(item.value) })
if (!chart) return
chart.setOption({
@ -74,7 +78,7 @@ const loadOpsData = async () => {
opsDays.length = 0
opsDays.push(...days)
opsLines.length = 0
opsLines.push({ name: '全部产线', value: values })
opsLines.push({ name: t('OpsTrend.defaultLine'), value: values })
if (opsLines[0]) {
currentLine.value = opsLines[0].name
updateOps(currentLine.value)
@ -95,14 +99,14 @@ const initChart = async () => {
xAxis: { type: 'category', data: opsDays, axisLine: style.axisLine, axisLabel: style.axisLabel },
yAxis: {
type: 'value',
name: '产能完成数',
name: t('OpsTrend.yAxisName'),
nameTextStyle: { color: '#a8b7d8', fontSize: 12 },
axisLabel: style.axisLabel,
splitLine: style.splitLine
},
series: [
{
name: '产能完成数',
name: t('OpsTrend.seriesName'),
type: 'line',
smooth: true,
showSymbol: false,
@ -124,6 +128,20 @@ onMounted(() => {
window.addEventListener('resize', resizeHandler)
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
chart?.setOption(
{
yAxis: { name: t('OpsTrend.yAxisName') },
series: [{ name: t('OpsTrend.seriesName') }]
},
false
)
updateOps(currentLine.value)
}
)
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
chart?.dispose()

@ -5,9 +5,9 @@
<span class="card-title-icon">
<Icon icon="fa-solid:check-double" />
</span>
<span>成品检合格率趋势图</span>
<span>{{ t('QualityTrend.title') }}</span>
</div>
<span class="tag">按天统计全产线</span>
<span class="tag">{{ t('QualityTrend.tag') }}</span>
</div>
<div class="card-body">
<div id="chart-quality" class="chart"></div>
@ -16,16 +16,20 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { useChart, colors, style } from '../utils'
import { PlanApi } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
const { init } = useChart('chart-quality')
const { init, instance } = useChart('chart-quality')
const last7Days: string[] = []
for (let i = 6; i >= 0; i--) {
@ -35,6 +39,8 @@ for (let i = 6; i >= 0; i--) {
}
const passRate = [98.5, 99.2, 97.8, 98.9, 99.0, 98.2, 99.5]
const xAxisDataRef = ref<string[]>(last7Days.slice())
const seriesDataRef = ref<number[]>(passRate.slice())
type QualityPoint = {
label: string
@ -71,15 +77,12 @@ const normalizeQualityData = (raw: any): QualityPoint[] => {
}
onMounted(async () => {
let xAxisData = last7Days.slice()
let seriesData = passRate.slice()
try {
const data = await PlanApi.getLastDaysRate({ orgId })
const list = normalizeQualityData(data)
if (list.length > 0) {
xAxisData = list.map((item) => item.label)
seriesData = list.map((item) => item.rate)
xAxisDataRef.value = list.map((item) => item.label)
seriesDataRef.value = list.map((item) => item.rate)
}
} catch {
}
@ -91,10 +94,10 @@ onMounted(async () => {
backgroundColor: 'transparent',
tooltip: { trigger: 'axis' },
grid: { top: '18%', left: '6%', right: '6%', bottom: '10%', containLabel: true },
xAxis: { type: 'category', data: xAxisData, axisLine: style.axisLine, axisLabel: style.axisLabel },
xAxis: { type: 'category', data: xAxisDataRef.value, axisLine: style.axisLine, axisLabel: style.axisLabel },
yAxis: { type: 'value', min: 0, max: 100, axisLine: { show: false }, axisLabel: style.axisLabel, splitLine: style.splitLine },
series: [{
name: '合格率',
name: t('QualityTrend.seriesName'),
type: 'bar',
barWidth: '45%',
itemStyle: {
@ -104,16 +107,52 @@ onMounted(async () => {
{ offset: 1, color: 'rgba(139,92,246,0.10)' }
])
},
data: seriesData,
data: seriesDataRef.value,
markLine: {
symbol: ['none', 'none'],
label: { show: true, color: '#fff', fontSize: 12, formatter: '平均 {c}%' },
label: {
show: true,
color: '#fff',
fontSize: 12,
formatter: (p: any) => t('QualityTrend.markLineAverage', { value: p?.value ?? 0 })
},
lineStyle: { color: colors.cyan, width: 2, type: 'dashed' },
data: [{ type: 'average', name: '平均值' }]
data: [{ type: 'average', name: t('QualityTrend.markLineAverageName') }]
}
}]
})
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
const chart = instance()
if (!chart) return
chart.setOption(
{
xAxis: { data: xAxisDataRef.value },
series: [
{
name: t('QualityTrend.seriesName'),
data: seriesDataRef.value,
markLine: {
symbol: ['none', 'none'],
label: {
show: true,
color: '#fff',
fontSize: 12,
formatter: (p: any) => t('QualityTrend.markLineAverage', { value: p?.value ?? 0 })
},
lineStyle: { color: colors.cyan, width: 2, type: 'dashed' },
data: [{ type: 'average', name: t('QualityTrend.markLineAverageName') }]
}
}
]
},
true
)
}
)
</script>
<style scoped>

@ -5,9 +5,9 @@
<span class="card-title-icon">
<Icon icon="fa-solid:bell" />
</span>
<span>实时报警信息</span>
<span>{{ t('RealAlarm.title') }}</span>
</div>
<span class="tag">滚动展示</span>
<span class="tag">{{ t('RealAlarm.tag') }}</span>
</div>
<div class="card-body">
<ul ref="listRef" class="alarm-list">
@ -28,22 +28,39 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { DeviceWarningRecordApi, DeviceWarningRecordVO } from '@/api/iot/deviceWarningRecord'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
const alarms = ref<any[]>([])
const isAnimating = ref(false)
let timer: ReturnType<typeof setInterval> | null = null
const mapAlarmLevel = (alarmLevel: unknown) => {
const raw = String(alarmLevel ?? '').trim()
if (raw === '2' || raw === '严重' || raw.toLowerCase() === 'severe') {
return { type: 'danger', levelText: t('RealAlarm.levelSevere') }
}
if (raw === '1' || raw === '警告' || raw.toLowerCase() === 'warning' || raw.toLowerCase() === 'warn') {
return { type: 'warn', levelText: t('RealAlarm.levelWarn') }
}
if (raw === '0' || raw === '提示' || raw.toLowerCase() === 'info' || raw.toLowerCase() === 'notice') {
return { type: 'safe', levelText: t('RealAlarm.levelInfo') }
}
return { type: 'safe', levelText: raw || t('RealAlarm.levelInfo') }
}
const getAlarms = async () => {
try {
const data = await DeviceWarningRecordApi.getList({ orgId }) || []
console.log('data',data)
alarms.value = data.map((item: DeviceWarningRecordVO) => {
//
@ -54,29 +71,12 @@ const getAlarms = async () => {
}
//
let type = 'safe'
let levelText = item.alarmLevel
if (item.alarmLevel === '2') {
type = 'danger'
levelText = '严重'
} else if (item.alarmLevel === '1') {
type = 'warn'
levelText = '警告'
} else if (item.alarmLevel === '0') {
type = 'safe'
levelText = '提示'
} else {
//
if (item.alarmLevel === '严重') type = 'danger'
else if (item.alarmLevel === '警告') type = 'warn'
else if (item.alarmLevel === '提示') type = 'safe'
}
const mapped = mapAlarmLevel(item.alarmLevel)
return {
time: timeStr,
level: levelText,
type: type,
level: mapped.levelText,
type: mapped.type,
msg: `${item.deviceName}-${item.ruleName}`
}
})
@ -107,6 +107,13 @@ onMounted(() => {
getAlarms()
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
getAlarms()
}
)
onUnmounted(() => {
if (timer) clearInterval(timer)
})

@ -5,12 +5,12 @@
<span class="card-title-icon">
<Icon icon="fa-solid:table-list" />
</span>
<span>产线任务看板</span>
<span>{{ t('TaskBoard.title') }}</span>
</div>
<div class="legend-inline">
<span class="tag">实时刷新 · 滚动展示</span>
<span class="dot" style="background: var(--green);"></span> 已完成
<span class="dot" style="background: var(--warn);"></span> 进度偏低
<span class="tag">{{ t('TaskBoard.tag') }}</span>
<span class="dot" style="background: var(--green);"></span> {{ t('TaskBoard.statusCompleted') }}
<span class="dot" style="background: var(--warn);"></span> {{ t('TaskBoard.statusLowProgress') }}
</div>
</div>
<div class="card-body">
@ -18,12 +18,12 @@
<table class="task-table">
<thead>
<tr>
<th>产线名称</th>
<th>计划单</th>
<th>产品名称</th>
<th>计划数量</th>
<th>完工数量</th>
<th>合格率</th>
<th>{{ t('TaskBoard.columns.lineName') }}</th>
<th>{{ t('TaskBoard.columns.planNo') }}</th>
<th>{{ t('TaskBoard.columns.productName') }}</th>
<th>{{ t('TaskBoard.columns.planQty') }}</th>
<th>{{ t('TaskBoard.columns.doneQty') }}</th>
<th>{{ t('TaskBoard.columns.passRate') }}</th>
</tr>
</thead>
<tbody ref="tbodyRef">
@ -53,9 +53,11 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { PlanApi, PlanVO } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const colors = {
blue: '#1e90ff',

@ -5,7 +5,7 @@
<span class="card-title-icon">
<Icon icon="fa-solid:gauge-high" />
</span>
<span>今日开机率/稼动率</span>
<span>{{ t('TodayOps.title') }}</span>
</div>
<span class="chip">{{ currentLineName }}</span>
</div>
@ -16,11 +16,15 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useChart, colors } from '../utils'
import { DeviceOperationRecordApi, type DeviceOperationRecordVO } from '@/api/iot/deviceOperationRecord'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const { init, instance } = useChart('chart-today')
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
type LineMetric = { name: string; run: number; avail: number }
const lineMetrics = ref<LineMetric[]>([])
@ -94,7 +98,7 @@ const gaugeOption = (run: number, avail: number) => {
progress: { show: true, width: 8, itemStyle: { color: colors.blue } },
detail: { formatter: '{value}%', color: '#fff', fontSize: 26, offsetCenter: ['0%', '-5%'] },
title: { show: true, color: '#a8b7d8', fontSize: 12, offsetCenter: ['0%', '-25%'] },
data: [{ value: run, name: '开机率' }]
data: [{ value: run, name: t('TodayOps.powerOnRate') }]
},
{
type: 'gauge',
@ -112,7 +116,7 @@ const gaugeOption = (run: number, avail: number) => {
progress: { show: true, width: 8, itemStyle: { color: colors.green } },
detail: { formatter: '{value}%', color: '#fff', fontSize: 20, offsetCenter: ['0%', '45%'] },
title: { show: true, color: '#a8b7d8', fontSize: 12, offsetCenter: ['0%', '25%'] },
data: [{ value: avail, name: '稼动率' }]
data: [{ value: avail, name: t('TodayOps.utilizationRate') }]
}
]
}
@ -121,7 +125,7 @@ const gaugeOption = (run: number, avail: number) => {
const renderCurrent = () => {
const list = lineMetrics.value
if (!list.length) {
currentLineName.value = '暂无数据'
currentLineName.value = t('TodayOps.empty')
const c = instance()
if (c) c.setOption(gaugeOption(0, 0))
return
@ -170,6 +174,13 @@ onMounted(async () => {
startRotate()
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
renderCurrent()
}
)
onUnmounted(() => {
if (timer) clearInterval(timer)
})

@ -5,11 +5,11 @@
<span class="card-title-icon">
<Icon icon="fa-solid:chart-line" />
</span>
<span>周生产趋势</span>
<span>{{ t('WeekTrend.title') }}</span>
</div>
<div class="legend-inline">
<span class="dot" style="background: var(--accent);"></span> 产量
<span class="dot" style="background: var(--purple);"></span> 计划
<span class="dot" style="background: var(--accent);"></span> {{ t('WeekTrend.legendOutput') }}
<span class="dot" style="background: var(--purple);"></span> {{ t('WeekTrend.legendPlan') }}
</div>
</div>
<div class="card-body">
@ -19,14 +19,18 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { colors, style } from '../utils'
import { PlanApi } from '@/api/mes/plan'
import { PlanApi } from '@/api/mes/plan'
import { useI18n } from '@/hooks/web/useI18n'
import { useLocaleStore } from '@/store/modules/locale'
const route = useRoute()
const orgId = route.query.orgId
const { t } = useI18n('Dashboard8')
const localeStore = useLocaleStore()
const weekDays = ref()
const weekPlan = ref()
@ -35,10 +39,8 @@ const weekReal = ref()
const chartRef = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
const initChart = () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
chart.setOption({
const buildOption = () => {
return {
backgroundColor: 'transparent',
tooltip: { trigger: 'axis' },
legend: { top: 0, right: 0, textStyle: style.legendText },
@ -47,7 +49,7 @@ const initChart = () => {
yAxis: { type: 'value', axisLine: style.axisLine, axisLabel: style.axisLabel, splitLine: style.splitLine },
series: [
{
name: '计划产量',
name: t('WeekTrend.seriesPlanOutput'),
type: 'line',
smooth: true,
showSymbol: false,
@ -55,7 +57,7 @@ const initChart = () => {
data: weekPlan.value
},
{
name: '实际产量',
name: t('WeekTrend.seriesActualOutput'),
type: 'line',
smooth: true,
showSymbol: true,
@ -71,7 +73,13 @@ const initChart = () => {
data: weekReal.value
}
]
})
}
}
const initChart = () => {
if (!chartRef.value) return
chart = echarts.init(chartRef.value, 'dark', { renderer: 'canvas' })
chart.setOption(buildOption())
}
const resizeHandler = () => {
@ -85,9 +93,15 @@ onMounted( async() => {
weekPlan.value = data.weekPlan
weekReal.value = data.weekReal
initChart()
// console.log(data)
})
watch(
() => localeStore.getCurrentLocale.lang,
() => {
chart?.setOption(buildOption(), true)
}
)
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
chart?.dispose()

Loading…
Cancel
Save