blob: ffd685da8d328628e37a4c0c51cd52870502f582 [file]
<template>
<div class="chart-preview">
<div class="preview-header">
<h3>{{ $t('preview.chartPreview') }}</h3>
</div>
<div class="charts-grid">
<div
v-for="(config, index) in displayedCharts"
:key="config.type + index"
class="chart-item"
>
<div
:ref="el => setChartDomRef(el, index)"
class="chart-container"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, watch, computed, markRaw, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getChartConfigs } from '../utils/chartConfigs'
import { useThemeStore } from '../stores/theme'
import type { ECharts } from 'echarts'
import { useI18n } from 'vue-i18n'
// Debounce function to limit how often a function can be called
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null
return function(this: any, ...args: Parameters<T>) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
// Initialize i18n
const { locale } = useI18n()
const themeStore = useThemeStore()
const chartInstances: ECharts[] = []
const chartDoms: (HTMLElement | null)[] = []
// Dynamically generate charts based on seriesCnt
const displayedCharts = computed(() => getChartConfigs(themeStore.theme.seriesCnt))
// Set chart DOM ref
function setChartDomRef(el: any, index: number) {
chartDoms[index] = el as HTMLElement
}
// Original update charts implementation
function _updateChartsImpl() {
// Dispose existing charts
chartInstances.forEach(chart => chart.dispose())
chartInstances.length = 0
// re-register theme with the same name
const currentTheme = themeStore.getEChartsTheme()
const themeId = 'customized'
echarts.registerTheme(themeId, currentTheme)
const chartLocale = locale.value === 'zh' ? 'ZH' : 'EN'
// Recreate charts with new theme
nextTick(() => {
displayedCharts.value.forEach((config, index) => {
const container = chartDoms[index]
const chart = markRaw(echarts.init(container, themeId, {
locale: chartLocale
}))
chart.setOption(config.option)
chartInstances[index] = chart
})
})
}
const updateCharts = debounce(_updateChartsImpl, 100)
// Expose updateCharts method for external calling
defineExpose({
updateCharts
})
// Watch for theme or locale changes and automatically update charts
watch(() => [themeStore.theme, locale.value], updateCharts, { deep: true })
// Resize charts when window resizes
function handleResize() {
chartInstances.forEach(chart => chart.resize())
}
const debouncedHandleResize = debounce(handleResize, 100)
onMounted(() => {
updateCharts()
window.addEventListener('resize', debouncedHandleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', debouncedHandleResize)
chartInstances.forEach(chart => chart.dispose())
chartDoms.length = chartInstances.length = 0
})
</script>
<style scoped>
.chart-preview {
height: 100%;
display: flex;
flex-direction: column;
}
.preview-header {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 20px;
}
.preview-header h3 {
margin: 0;
color: #333;
font-size: 24px;
}
.charts-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 12px;
overflow-y: auto;
padding-right: 8px;
}
.chart-item {
background: #fff;
border: 1px solid #e9ecef;
border-radius: 8px;
}
.chart-container {
width: 100%;
height: 320px;
border-radius: 4px;
}
/* Responsive adjustments */
@media (max-width: 1400px) {
.charts-grid {
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
}
}
@media (max-width: 1200px) {
.charts-grid {
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
}
@media (max-width: 768px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
</style>