Initial commit

This commit is contained in:
fawney19
2025-12-10 20:52:44 +08:00
commit f784106826
485 changed files with 110993 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
<template>
<div class="w-full h-full">
<canvas ref="chartRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
BarController,
Title,
Tooltip,
Legend,
type ChartData,
type ChartOptions
} from 'chart.js'
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
BarController,
Title,
Tooltip,
Legend
)
interface Props {
data: ChartData<'bar'>
options?: ChartOptions<'bar'>
height?: number
stacked?: boolean
}
const props = withDefaults(defineProps<Props>(), {
height: 300,
stacked: true
})
const chartRef = ref<HTMLCanvasElement>()
let chart: ChartJS<'bar'> | null = null
const defaultOptions: ChartOptions<'bar'> = {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
stacked: true,
grid: {
color: 'rgba(156, 163, 175, 0.1)'
},
ticks: {
color: 'rgb(107, 114, 128)'
}
},
y: {
stacked: true,
grid: {
color: 'rgba(156, 163, 175, 0.1)'
},
ticks: {
color: 'rgb(107, 114, 128)'
}
}
},
plugins: {
legend: {
position: 'top',
labels: {
color: 'rgb(107, 114, 128)',
usePointStyle: true,
padding: 16
}
},
tooltip: {
backgroundColor: 'rgb(31, 41, 55)',
titleColor: 'rgb(243, 244, 246)',
bodyColor: 'rgb(243, 244, 246)',
borderColor: 'rgb(75, 85, 99)',
borderWidth: 1
}
}
}
function createChart() {
if (!chartRef.value) return
const stackedOptions = props.stacked ? {
scales: {
x: { ...defaultOptions.scales?.x, stacked: true },
y: { ...defaultOptions.scales?.y, stacked: true }
}
} : {
scales: {
x: { ...defaultOptions.scales?.x, stacked: false },
y: { ...defaultOptions.scales?.y, stacked: false }
}
}
chart = new ChartJS(chartRef.value, {
type: 'bar',
data: props.data,
options: {
...defaultOptions,
...stackedOptions,
...props.options
}
})
}
function updateChart() {
if (chart) {
chart.data = props.data
chart.update('none')
}
}
onMounted(async () => {
await nextTick()
createChart()
})
onUnmounted(() => {
if (chart) {
chart.destroy()
chart = null
}
})
watch(() => props.data, updateChart, { deep: true })
watch(() => props.options, () => {
if (chart) {
chart.options = {
...defaultOptions,
...props.options
}
chart.update()
}
}, { deep: true })
</script>

View File

@@ -0,0 +1,128 @@
<template>
<div class="w-full h-full">
<canvas ref="chartRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
LineController,
Title,
Tooltip,
Legend,
type ChartData,
type ChartOptions
} from 'chart.js'
// 注册 Chart.js 组件
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
LineController,
Title,
Tooltip,
Legend
)
interface Props {
data: ChartData<'line'>
options?: ChartOptions<'line'>
height?: number
}
const props = withDefaults(defineProps<Props>(), {
height: 300
})
const chartRef = ref<HTMLCanvasElement>()
let chart: ChartJS<'line'> | null = null
const defaultOptions: ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: {
color: 'rgba(156, 163, 175, 0.1)' // gray-400 with opacity
},
ticks: {
color: 'rgb(107, 114, 128)' // gray-500
}
},
y: {
grid: {
color: 'rgba(156, 163, 175, 0.1)' // gray-400 with opacity
},
ticks: {
color: 'rgb(107, 114, 128)' // gray-500
}
}
},
plugins: {
legend: {
labels: {
color: 'rgb(107, 114, 128)' // gray-500
}
},
tooltip: {
backgroundColor: 'rgb(31, 41, 55)', // gray-800
titleColor: 'rgb(243, 244, 246)', // gray-100
bodyColor: 'rgb(243, 244, 246)', // gray-100
borderColor: 'rgb(75, 85, 99)', // gray-600
borderWidth: 1
}
}
}
function createChart() {
if (!chartRef.value) return
chart = new ChartJS(chartRef.value, {
type: 'line',
data: props.data,
options: {
...defaultOptions,
...props.options
}
})
}
function updateChart() {
if (chart) {
chart.data = props.data
chart.update('none') // 禁用动画以提高性能
}
}
onMounted(async () => {
await nextTick()
createChart()
})
onUnmounted(() => {
if (chart) {
chart.destroy()
chart = null
}
})
// 监听数据变化
watch(() => props.data, updateChart, { deep: true })
watch(() => props.options, () => {
if (chart) {
chart.options = {
...defaultOptions,
...props.options
}
chart.update()
}
}, { deep: true })
</script>