Files
control/control-ui/src/views/ControlCaseChartView.vue
T
2026-07-13 11:17:57 +05:00

127 lines
3.5 KiB
Vue

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { RouterLink, useRoute } from 'vue-router'
import * as echarts from 'echarts'
import { controlCaseApi, type CsvChartResponse } from '@/api.ts'
import { formatApiError } from '@/api_client.ts'
const route = useRoute()
const containerRef = ref<HTMLDivElement | null>(null)
const loading = ref(false)
const error = ref('')
const data = ref<CsvChartResponse | null>(null)
let chart: echarts.ECharts | null = null
const id = computed(() => Number(route.params.id))
function buildOption(payload: CsvChartResponse): echarts.EChartsOption {
const [xKey, ...seriesKeys] = payload.columns
const rows = payload.rows
const xValues = rows.map((row) => row[0] ?? '')
const series = seriesKeys.map((name, index) => ({
name,
type: 'line' as const,
showSymbol: false,
smooth: true,
data: rows.map((row) => row[index + 1] ?? null),
}))
return {
tooltip: { trigger: 'axis' },
legend: { top: 0 },
grid: { left: 48, right: 24, top: 48, bottom: 48, containLabel: true },
dataZoom: [
{ type: 'inside', xAxisIndex: 0 },
{ type: 'slider', xAxisIndex: 0, height: 18, bottom: 10 },
],
xAxis: {
type: 'category',
name: xKey ?? 'x',
data: xValues,
},
yAxis: { type: 'value' },
series,
}
}
function renderChart() {
if (!chart || !data.value) return
chart.setOption(buildOption(data.value), true)
}
async function loadData() {
if (!Number.isFinite(id.value)) {
error.value = 'Некорректный ID'
data.value = null
return
}
loading.value = true
error.value = ''
try {
data.value = await controlCaseApi.chartData(id.value)
renderChart()
} catch (err) {
error.value = formatApiError(err, 'Не удалось загрузить данные графика.')
data.value = null
} finally {
loading.value = false
}
}
function handleResize() {
chart?.resize()
}
onMounted(async () => {
if (containerRef.value) {
chart = echarts.init(containerRef.value)
window.addEventListener('resize', handleResize)
}
await loadData()
})
watch(id, loadData)
watch(data, renderChart)
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
chart?.dispose()
chart = null
})
</script>
<template>
<section class="d-flex flex-column gap-3">
<div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
<div>
<RouterLink class="link-secondary text-decoration-none" :to="{ name: 'control-case-detail', params: { id } }">
Назад к кейсу
</RouterLink>
<h1 class="h3 mt-2 mb-0">График control case {{ id }}</h1>
</div>
<button class="btn btn-outline-secondary" type="button" @click="loadData" :disabled="loading">
Обновить
</button>
</div>
<div v-if="error" class="alert alert-danger mb-0" role="alert">{{ error }}</div>
<div class="card shadow-sm">
<div class="card-body">
<div class="position-relative" style="min-height: 560px;">
<div ref="containerRef" style="height: 560px; width: 100%;"></div>
<div
v-if="loading"
class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-white bg-opacity-75"
>
<div class="text-muted">Загрузка...</div>
</div>
</div>
</div>
</div>
</section>
</template>