You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
6.8 KiB
Vue

<template>
<ContentWrap class="timeline-panel">
<div class="timeline-panel__header">
<div class="timeline-panel__title">{{ t('DataCollection.RunOverview.timelineTitle') }}</div>
<div class="timeline-panel__actions">
<el-select model-value="" class="!w-140px">
<el-option value="" :label="t('DataCollection.RunOverview.deviceFilterAll')" />
</el-select>
<el-select model-value="expand" class="!w-120px">
<el-option value="expand" :label="t('DataCollection.RunOverview.expandAllText')" />
</el-select>
<el-button class="timeline-panel__icon-btn">
<Icon icon="ep:data-analysis" />
</el-button>
<el-button class="timeline-panel__icon-btn">
<Icon icon="ep:download" />
</el-button>
</div>
</div>
<div class="timeline-legend">
<div v-for="item in legendItems" :key="item.status" class="timeline-legend__item">
<span class="timeline-legend__dot" :style="{ background: item.color }"></span>
<span>{{ item.label }}</span>
</div>
</div>
<div class="timeline-table">
<div class="timeline-table__head">
<div class="timeline-table__meta">
<div class="timeline-table__device">{{ t('DataCollection.RunOverview.tableDeviceNameColumn') }}</div>
<div class="timeline-table__rate">{{ t('DataCollection.RunOverview.tableUtilizationRateColumn') }}</div>
</div>
<div class="timeline-scale">
<div v-for="hour in hourTicks" :key="hour" class="timeline-scale__tick">
{{ hour }}
</div>
</div>
</div>
<div v-for="row in pagedRows" :key="row.id" class="timeline-table__row">
<div class="timeline-table__meta">
<div class="timeline-table__device">{{ row.name }}</div>
<div class="timeline-table__rate">{{ row.utilizationRate.toFixed(2) }}%</div>
</div>
<div class="timeline-track">
<div v-for="hour in 24" :key="hour" class="timeline-track__hour"></div>
<div
v-for="(segment, index) in row.segments"
:key="`${row.id}-${index}`"
class="timeline-segment"
:style="{
left: `${(segment.startHour / 24) * 100}%`,
width: `${((segment.endHour - segment.startHour) / 24) * 100}%`,
background: statusColors[segment.status]
}"
></div>
</div>
</div>
</div>
<div class="timeline-panel__footer">
<div class="timeline-panel__summary">
{{ t('DataCollection.RunOverview.statisticsTimeText', { start: statisticsStart, end: statisticsEnd }) }}
</div>
<div class="timeline-panel__pager">
<span>{{ t('DataCollection.RunOverview.totalDevicesText', { total }) }}</span>
<el-select :model-value="pageSize" class="!w-100px" @update:model-value="emit('update:pageSize', Number($event))">
<el-option :value="10" :label="`10${t('DataCollection.RunOverview.pageUnit')}`" />
<el-option :value="20" :label="`20${t('DataCollection.RunOverview.pageUnit')}`" />
</el-select>
<el-pagination
layout="prev, pager, next"
:page-size="pageSize"
:current-page="pageNo"
:total="total"
small
@current-change="emit('update:pageNo', $event)"
/>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { DeviceTimelineRow, RunStatus } from './types'
const props = defineProps<{
rows: DeviceTimelineRow[]
total: number
pageNo: number
pageSize: number
statisticsStart: string
statisticsEnd: string
}>()
const emit = defineEmits<{
(e: 'update:pageNo', value: number): void
(e: 'update:pageSize', value: number): void
}>()
const { t } = useI18n()
const statusColors: Record<RunStatus, string> = {
running: '#67c35b',
standby: '#f5c243',
fault: '#ff6b6b',
offline: '#d5dae3'
}
const legendItems = computed(() => [
{ status: 'running' as const, color: statusColors.running, label: t('DataCollection.RunOverview.legend.running') },
{ status: 'standby' as const, color: statusColors.standby, label: t('DataCollection.RunOverview.legend.standby') },
{ status: 'fault' as const, color: statusColors.fault, label: t('DataCollection.RunOverview.legend.fault') },
{ status: 'offline' as const, color: statusColors.offline, label: t('DataCollection.RunOverview.legend.offline') }
])
const hourTicks = Array.from({ length: 13 }, (_, index) => `${String(index * 2).padStart(2, '0')}:00`)
const pagedRows = computed(() => {
const start = (props.pageNo - 1) * props.pageSize
return props.rows.slice(start, start + props.pageSize)
})
</script>
<style scoped lang="scss">
.timeline-panel__header,
.timeline-panel__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.timeline-panel__title {
color: #101828;
font-size: 16px;
font-weight: 700;
}
.timeline-panel__actions,
.timeline-panel__pager {
display: flex;
align-items: center;
gap: 10px;
}
.timeline-panel__icon-btn {
width: 36px;
padding: 0;
}
.timeline-legend {
display: flex;
justify-content: center;
gap: 32px;
margin: 10px 0 18px;
}
.timeline-legend__item {
display: flex;
align-items: center;
gap: 8px;
color: #475467;
font-size: 13px;
}
.timeline-legend__dot {
width: 18px;
height: 10px;
border-radius: 999px;
}
.timeline-table {
overflow-x: auto;
}
.timeline-table__head,
.timeline-table__row {
display: grid;
grid-template-columns: 210px minmax(720px, 1fr);
gap: 16px;
}
.timeline-table__head {
margin-bottom: 12px;
}
.timeline-table__row {
align-items: center;
margin-bottom: 10px;
}
.timeline-table__meta {
display: grid;
grid-template-columns: 1fr 70px;
gap: 12px;
align-items: center;
}
.timeline-table__device,
.timeline-table__rate {
color: #344054;
font-size: 13px;
}
.timeline-table__head .timeline-table__device,
.timeline-table__head .timeline-table__rate {
color: #475467;
font-weight: 600;
}
.timeline-scale,
.timeline-track {
position: relative;
display: grid;
grid-template-columns: repeat(24, 1fr);
}
.timeline-scale {
color: #667085;
font-size: 12px;
}
.timeline-scale__tick {
position: relative;
}
.timeline-scale__tick::after {
position: absolute;
top: 18px;
right: 0;
width: 1px;
height: 12px;
background: #e4e7ec;
content: '';
}
.timeline-track {
height: 20px;
background: #f5f7fb;
border-radius: 999px;
overflow: hidden;
}
.timeline-track__hour {
border-right: 1px solid #edf1f7;
}
.timeline-segment {
position: absolute;
top: 0;
bottom: 0;
border-radius: 999px;
}
.timeline-panel__summary {
color: #667085;
font-size: 13px;
}
@media (max-width: 1100px) {
.timeline-panel__header,
.timeline-panel__footer {
flex-direction: column;
align-items: flex-start;
}
}
</style>