fix: opt page style
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||||
import { resetAuthCache } from '@/router'
|
import { resetAuthCache } from '@/router'
|
||||||
import { LayoutDashboard, ListTodo, FileCode, Settings, LogOut, ScrollText, Terminal, Variable, KeyRound, Package, Menu, X, Server } from 'lucide-vue-next'
|
import { LayoutDashboard, ListTodo, FileCode, Settings, LogOut, ScrollText, Terminal, Variable, KeyRound, Package, Menu, X, Server } from 'lucide-vue-next'
|
||||||
@@ -46,6 +46,10 @@ const cachedSentence = loadSentenceFromCache()
|
|||||||
const sentence = ref(cachedSentence || '欢迎使用白虎面板')
|
const sentence = ref(cachedSentence || '欢迎使用白虎面板')
|
||||||
const { siteSettings, loadSettings } = useSiteSettings()
|
const { siteSettings, loadSettings } = useSiteSettings()
|
||||||
const mobileMenuOpen = ref(false)
|
const mobileMenuOpen = ref(false)
|
||||||
|
const sentenceContent = computed(() => {
|
||||||
|
const match = sentence.value.match(/^"(.+)"—— /)
|
||||||
|
return match ? match[1] : sentence.value
|
||||||
|
})
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ to: '/', icon: LayoutDashboard, label: '数据仪表', exact: true },
|
{ to: '/', icon: LayoutDashboard, label: '数据仪表', exact: true },
|
||||||
@@ -140,7 +144,10 @@ onMounted(() => {
|
|||||||
<Button variant="ghost" size="icon" class="h-8 w-8 lg:hidden" @click="mobileMenuOpen = true">
|
<Button variant="ghost" size="icon" class="h-8 w-8 lg:hidden" @click="mobileMenuOpen = true">
|
||||||
<Menu class="h-5 w-5" />
|
<Menu class="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<span class="text-[10px] sm:text-sm text-muted-foreground truncate flex-1 min-w-0 mr-4" :title="sentence">{{ sentence }}</span>
|
<span class="text-sm text-muted-foreground truncate flex-1 min-w-0 mr-4" :title="sentence">
|
||||||
|
<span class="hidden sm:inline">{{ sentence }}</span>
|
||||||
|
<span class="sm:hidden">{{ sentenceContent }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const taskStats = ref<TaskStatsItem[]>([])
|
|||||||
const chartsLoaded = ref(false)
|
const chartsLoaded = ref(false)
|
||||||
const sentence = ref('')
|
const sentence = ref('')
|
||||||
const isMobile = ref(window.innerWidth < 768)
|
const isMobile = ref(window.innerWidth < 768)
|
||||||
|
const sentenceContent = computed(() => {
|
||||||
|
const match = sentence.value.match(/^"(.+)"—— /)
|
||||||
|
return match ? match[1] : sentence.value
|
||||||
|
})
|
||||||
const chartDays = computed(() => isMobile.value ? 15 : 30)
|
const chartDays = computed(() => isMobile.value ? 15 : 30)
|
||||||
|
|
||||||
let lineChart: ApexCharts | null = null
|
let lineChart: ApexCharts | null = null
|
||||||
@@ -38,23 +42,23 @@ function animateNumber(key: keyof Stats, target: number) {
|
|||||||
const start = displayStats.value[key]
|
const start = displayStats.value[key]
|
||||||
const duration = 800 // 动画持续时间
|
const duration = 800 // 动画持续时间
|
||||||
const startTime = performance.now()
|
const startTime = performance.now()
|
||||||
|
|
||||||
function update(currentTime: number) {
|
function update(currentTime: number) {
|
||||||
const elapsed = currentTime - startTime
|
const elapsed = currentTime - startTime
|
||||||
const progress = Math.min(elapsed / duration, 1)
|
const progress = Math.min(elapsed / duration, 1)
|
||||||
|
|
||||||
// 使用 easeOutCubic 缓动函数
|
// 使用 easeOutCubic 缓动函数
|
||||||
const easeProgress = 1 - Math.pow(1 - progress, 3)
|
const easeProgress = 1 - Math.pow(1 - progress, 3)
|
||||||
|
|
||||||
displayStats.value[key] = Math.round(start + (target - start) * easeProgress)
|
displayStats.value[key] = Math.round(start + (target - start) * easeProgress)
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
requestAnimationFrame(update)
|
requestAnimationFrame(update)
|
||||||
} else {
|
} else {
|
||||||
displayStats.value[key] = target
|
displayStats.value[key] = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(update)
|
requestAnimationFrame(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +84,7 @@ function getGridColor() {
|
|||||||
function handleThemeChange() {
|
function handleThemeChange() {
|
||||||
// 初始化期间忽略主题变化
|
// 初始化期间忽略主题变化
|
||||||
if (isInitializing) return
|
if (isInitializing) return
|
||||||
|
|
||||||
const newIsDark = document.documentElement.classList.contains('dark')
|
const newIsDark = document.documentElement.classList.contains('dark')
|
||||||
if (newIsDark !== isDark.value) {
|
if (newIsDark !== isDark.value) {
|
||||||
isDark.value = newIsDark
|
isDark.value = newIsDark
|
||||||
@@ -128,11 +132,11 @@ async function reloadCharts() {
|
|||||||
// 等待 Vue 更新 DOM
|
// 等待 Vue 更新 DOM
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await new Promise(resolve => setTimeout(resolve, 100))
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
|
||||||
// 检查容器是否存在再渲染
|
// 检查容器是否存在再渲染
|
||||||
const statsChart = document.querySelector("#stats-chart")
|
const statsChart = document.querySelector("#stats-chart")
|
||||||
const pieChartEl = document.querySelector("#pie-chart")
|
const pieChartEl = document.querySelector("#pie-chart")
|
||||||
|
|
||||||
if (statsChart && pieChartEl && statsChart.parentElement && pieChartEl.parentElement) {
|
if (statsChart && pieChartEl && statsChart.parentElement && pieChartEl.parentElement) {
|
||||||
renderLineChart()
|
renderLineChart()
|
||||||
renderPieChart()
|
renderPieChart()
|
||||||
@@ -144,15 +148,15 @@ async function reloadCharts() {
|
|||||||
const renderLineChart = () => {
|
const renderLineChart = () => {
|
||||||
const container = document.querySelector("#stats-chart")
|
const container = document.querySelector("#stats-chart")
|
||||||
if (!container || !container.parentElement) return
|
if (!container || !container.parentElement) return
|
||||||
|
|
||||||
if (lineChart) {
|
if (lineChart) {
|
||||||
lineChart.destroy()
|
lineChart.destroy()
|
||||||
lineChart = null
|
lineChart = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空容器
|
// 清空容器
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
|
|
||||||
const textColor = getTextColor()
|
const textColor = getTextColor()
|
||||||
const gridColor = getGridColor()
|
const gridColor = getGridColor()
|
||||||
const options = {
|
const options = {
|
||||||
@@ -283,18 +287,18 @@ const renderLineChart = () => {
|
|||||||
|
|
||||||
const renderPieChart = () => {
|
const renderPieChart = () => {
|
||||||
if (taskStats.value.length === 0) return
|
if (taskStats.value.length === 0) return
|
||||||
|
|
||||||
const container = document.querySelector("#pie-chart")
|
const container = document.querySelector("#pie-chart")
|
||||||
if (!container || !container.parentElement) return
|
if (!container || !container.parentElement) return
|
||||||
|
|
||||||
if (pieChart) {
|
if (pieChart) {
|
||||||
pieChart.destroy()
|
pieChart.destroy()
|
||||||
pieChart = null
|
pieChart = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空容器
|
// 清空容器
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
|
|
||||||
const totalCount = taskStats.value.reduce((sum, item) => sum + item.count, 0)
|
const totalCount = taskStats.value.reduce((sum, item) => sum + item.count, 0)
|
||||||
const textColor = getTextColor()
|
const textColor = getTextColor()
|
||||||
const options = {
|
const options = {
|
||||||
@@ -367,13 +371,13 @@ const renderPieChart = () => {
|
|||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formatter: (val: number) => val.toFixed(1) + '%',
|
formatter: (val: number) => val.toFixed(1) + '%',
|
||||||
style: {
|
style: {
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontFamily: 'Inter, sans-serif',
|
fontFamily: 'Inter, sans-serif',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
colors: ['#ffffff']
|
colors: ['#ffffff']
|
||||||
},
|
},
|
||||||
dropShadow: {
|
dropShadow: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
top: 1,
|
top: 1,
|
||||||
left: 1,
|
left: 1,
|
||||||
@@ -396,7 +400,7 @@ const renderPieChart = () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [statsData, sendStatsData, taskStatsData, sentenceData] = await Promise.all([
|
const [statsData, sendStatsData, taskStatsData, sentenceData] = await Promise.all([
|
||||||
api.dashboard.stats(),
|
api.dashboard.stats(),
|
||||||
@@ -408,13 +412,13 @@ onMounted(async () => {
|
|||||||
sendStats.value = sendStatsData
|
sendStats.value = sendStatsData
|
||||||
taskStats.value = taskStatsData
|
taskStats.value = taskStatsData
|
||||||
sentence.value = sentenceData.sentence
|
sentence.value = sentenceData.sentence
|
||||||
|
|
||||||
// 渲染图表
|
// 渲染图表
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
renderLineChart()
|
renderLineChart()
|
||||||
renderPieChart()
|
renderPieChart()
|
||||||
chartsLoaded.value = true
|
chartsLoaded.value = true
|
||||||
|
|
||||||
// 图表渲染完成后,延迟启动主题监听,避免初始化时的闪烁
|
// 图表渲染完成后,延迟启动主题监听,避免初始化时的闪烁
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isInitializing = false
|
isInitializing = false
|
||||||
@@ -425,7 +429,7 @@ onMounted(async () => {
|
|||||||
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
|
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
|
||||||
}, 500)
|
}, 500)
|
||||||
}, 100)
|
}, 100)
|
||||||
} catch {}
|
} catch { }
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -447,11 +451,15 @@ onUnmounted(() => {
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-bold tracking-tight">数据仪表</h2>
|
<h2 class="text-2xl font-bold tracking-tight">数据仪表</h2>
|
||||||
<p class="text-muted-foreground">{{ sentence || '查看系统运行状态和统计数据' }}</p>
|
<p class="text-muted-foreground truncate" :title="sentence">
|
||||||
|
<span class="hidden sm:inline">{{ sentence || '查看系统运行状态和统计数据' }}</span>
|
||||||
|
<span class="sm:hidden">{{ sentenceContent || '查看系统运行状态和统计数据' }}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-6">
|
<div class="grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-6">
|
||||||
<Card v-for="item in statItems" :key="item.key" class="cursor-pointer hover:bg-accent/50 transition-colors" @click="navigateTo(item.route)">
|
<Card v-for="item in statItems" :key="item.key" class="cursor-pointer hover:bg-accent/50 transition-colors"
|
||||||
|
@click="navigateTo(item.route)">
|
||||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle class="text-xs sm:text-sm font-medium">{{ item.label }}</CardTitle>
|
<CardTitle class="text-xs sm:text-sm font-medium">{{ item.label }}</CardTitle>
|
||||||
<component :is="item.icon" class="h-4 w-4 text-muted-foreground" />
|
<component :is="item.icon" class="h-4 w-4 text-muted-foreground" />
|
||||||
@@ -470,7 +478,8 @@ onUnmounted(() => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="relative h-[300px]">
|
<CardContent class="relative h-[300px]">
|
||||||
<div id="stats-chart" class="w-full h-full"></div>
|
<div id="stats-chart" class="w-full h-full"></div>
|
||||||
<div v-if="!chartsLoaded" class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
<div v-if="!chartsLoaded"
|
||||||
|
class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
||||||
加载中...
|
加载中...
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -483,10 +492,12 @@ onUnmounted(() => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="relative h-[300px]">
|
<CardContent class="relative h-[300px]">
|
||||||
<div id="pie-chart" class="w-full h-full"></div>
|
<div id="pie-chart" class="w-full h-full"></div>
|
||||||
<div v-if="!chartsLoaded" class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
<div v-if="!chartsLoaded"
|
||||||
|
class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
||||||
加载中...
|
加载中...
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="taskStats.length === 0" class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
<div v-else-if="taskStats.length === 0"
|
||||||
|
class="absolute inset-0 flex items-center justify-center text-muted-foreground text-sm bg-card">
|
||||||
暂无数据
|
暂无数据
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user