Create 2026 admin theme interactions

This commit is contained in:
2026-06-05 12:17:46 +08:00
parent 17dc587555
commit f37f321d64
10 changed files with 240 additions and 101 deletions
+8 -5
View File
@@ -7,13 +7,16 @@
@click="handleBackdropClick"
>
<!-- 背景遮罩 -->
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"></div>
<div
class="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
@click.stop="handleBackdropClick"
></div>
<!-- 模态框容器 -->
<div class="flex min-h-full items-center justify-center p-4">
<div
ref="modalRef"
class="relative transform overflow-hidden rounded-lg shadow-xl transition-all"
class="relative transform overflow-hidden rounded-[30px] shadow-xl transition-all"
:class="[
sizeClasses,
isDarkMode ? 'bg-gray-800' : 'bg-white'
@@ -100,8 +103,8 @@ const sizeClasses = computed(() => {
return sizes[props.size]
})
const handleBackdropClick = (event: MouseEvent) => {
if (props.closeOnBackdrop && event.target === event.currentTarget) {
const handleBackdropClick = () => {
if (props.closeOnBackdrop) {
emit('close')
}
}
@@ -127,4 +130,4 @@ const handleBackdropClick = (event: MouseEvent) => {
.modal-leave-to .relative {
transform: scale(0.95) translateY(-20px);
}
</style>
</style>
+61 -22
View File
@@ -1,5 +1,5 @@
<template>
<div class="relative overflow-hidden rounded-[30px] border" :class="panelClass">
<div class="relative overflow-visible rounded-[30px] border" :class="panelClass">
<canvas
ref="canvasRef"
class="block h-80 min-h-80 w-full touch-none select-none md:h-72 md:min-h-72"
@@ -13,12 +13,12 @@
/>
<div
v-if="tooltip"
class="pointer-events-none absolute z-10 min-w-32 rounded-lg border px-3 py-2 text-xs shadow-lg"
class="pointer-events-none absolute z-30 w-max min-w-36 max-w-80 whitespace-nowrap rounded-xl border px-3 py-2 text-xs shadow-xl"
:class="tooltipClass"
:style="{
left: `${tooltip.left}px`,
top: `${tooltip.top}px`,
transform: 'translate(-50%, -112%)'
transform: tooltip.transform
}"
>
<p class="font-semibold">{{ tooltip.date }}</p>
@@ -56,6 +56,7 @@ type TooltipState = {
uploads: number
downloadTraffic: string
uploadTraffic: string
transform: string
}
type ChartPoint = {
@@ -74,12 +75,14 @@ const rafId = ref<number | null>(null)
const tooltip = ref<TooltipState | null>(null)
const panelClass = computed(() =>
props.isDarkMode ? 'border-gray-700 bg-gray-900/30' : 'border-gray-100 bg-gray-50'
props.isDarkMode
? 'border-[#40545c] bg-[linear-gradient(135deg,rgba(34,49,56,0.96),rgba(24,35,42,0.92))] shadow-md shadow-black/25'
: 'border-[#d4e0e3] bg-[linear-gradient(135deg,#f8fbfb,#edf5f6)] shadow-md shadow-[#a9bdc2]/35'
)
const tooltipClass = computed(() =>
props.isDarkMode
? 'border-gray-700 bg-gray-800 text-gray-100'
: 'border-gray-200 bg-white text-gray-900'
? 'border-[#40545c] bg-[#203039] text-[#eef5f5]'
: 'border-[#c9d9dd] bg-[#fbfdfd] text-[#263f48]'
)
const formatBytes = (value: number | string | undefined) => {
@@ -90,6 +93,10 @@ const formatBytes = (value: number | string | undefined) => {
return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 1)} ${units[index]}`
}
const lerp = (start: number, end: number, amount: number) => start + (end - start) * amount
const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value))
const getDefaultWindow = () => {
const width = canvasRef.value?.getBoundingClientRect().width || window.innerWidth
return Math.max(2, Math.min(props.rows.length || 2, width < 640 ? 14 : 60))
@@ -145,8 +152,8 @@ const draw = () => {
)
const muted = props.isDarkMode ? '#9ca3af' : '#6b7280'
const border = props.isDarkMode ? '#374151' : '#e5e7eb'
const downloadColor = '#4f46e5'
const uploadColor = '#10b981'
const downloadColor = props.isDarkMode ? '#8fb6c5' : '#5f8fa3'
const uploadColor = props.isDarkMode ? '#a9c7b8' : '#759f8e'
ctx.strokeStyle = border
ctx.lineWidth = 1
@@ -209,7 +216,7 @@ const draw = () => {
const points = downloadPoints
const drawHoverPoint = (point: ChartPoint, color: string) => {
const drawHoverPoint = (point: Pick<ChartPoint, 'x' | 'y'>, color: string) => {
ctx.fillStyle = props.isDarkMode ? '#111827' : '#fff'
ctx.strokeStyle = color
ctx.lineWidth = 3
@@ -239,22 +246,54 @@ const draw = () => {
: '无数据'
)
if (hoverX.value !== null && points.length) {
const nearest = points.reduce((current, point) =>
Math.abs(point.x - hoverX.value!) < Math.abs(current.x - hoverX.value!) ? point : current
if (hoverX.value !== null && points.length && total) {
const hoverRatio = clamp((hoverX.value - padLeft) / width, 0, 1)
const sourceIndex = clamp(offset.value + hoverRatio * denominator, 0, total - 1)
const leftIndex = Math.floor(sourceIndex)
const rightIndex = Math.min(total - 1, Math.ceil(sourceIndex))
const mix = sourceIndex - leftIndex
const leftRow = props.rows[leftIndex]
const rightRow = props.rows[rightIndex] || leftRow
const interpolatedX = padLeft + ((sourceIndex - offset.value) / denominator) * width
const downloads = lerp(Number(leftRow.downloads || 0), Number(rightRow.downloads || 0), mix)
const uploads = lerp(Number(leftRow.uploads || 0), Number(rightRow.uploads || 0), mix)
const downloadTraffic = lerp(
Number(leftRow.downloadTraffic || 0),
Number(rightRow.downloadTraffic || 0),
mix
)
const uploadPoint = uploadPoints.find((point) => point.row.date === nearest.row.date)
const uploadTraffic = lerp(
Number(leftRow.uploadTraffic || 0),
Number(rightRow.uploadTraffic || 0),
mix
)
const downloadY = padTop + height - (downloads / max) * height
const uploadY = padTop + height - (uploads / max) * height
const anchorY = Math.min(downloadY, uploadY)
const placeBelow = anchorY < 104
const tooltipInset = Math.min(mobile ? 116 : 132, Math.max(48, rect.width / 2 - 8))
const dateText =
leftRow.date === rightRow.date ? leftRow.date : `${leftRow.date} - ${rightRow.date}`
ctx.strokeStyle = props.isDarkMode ? 'rgba(148, 163, 184, 0.35)' : 'rgba(91, 121, 132, 0.28)'
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(interpolatedX, padTop)
ctx.lineTo(interpolatedX, padTop + height)
ctx.stroke()
tooltip.value = {
left: Math.min(rect.width - 80, Math.max(80, nearest.x)),
top: Math.max(46, Math.min(nearest.y, uploadPoint?.y ?? nearest.y)),
date: nearest.row.date,
downloads: Number(nearest.row.downloads || 0),
uploads: Number(nearest.row.uploads || 0),
downloadTraffic: formatBytes(nearest.row.downloadTraffic),
uploadTraffic: formatBytes(nearest.row.uploadTraffic)
left: clamp(interpolatedX, tooltipInset, rect.width - tooltipInset),
top: placeBelow ? anchorY + 16 : anchorY - 12,
date: dateText,
downloads: Math.round(downloads),
uploads: Math.round(uploads),
downloadTraffic: formatBytes(downloadTraffic),
uploadTraffic: formatBytes(uploadTraffic),
transform: placeBelow ? 'translate(-50%, 0)' : 'translate(-50%, -100%)'
}
drawHoverPoint(nearest, downloadColor)
if (uploadPoint) drawHoverPoint(uploadPoint, uploadColor)
drawHoverPoint({ x: interpolatedX, y: downloadY }, downloadColor)
drawHoverPoint({ x: interpolatedX, y: uploadY }, uploadColor)
} else {
tooltip.value = null
}
+3 -3
View File
@@ -1,10 +1,10 @@
<template>
<div
class="group rounded-[30px] p-6 shadow-md transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
class="theme-2026-card theme-2026-card-hover group rounded-[30px] p-6 shadow-md transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
:class="[
isDarkMode
? 'bg-gray-800/70 hover:bg-gray-800 hover:shadow-black/20'
: 'bg-white hover:shadow-gray-200/80'
? 'hover:bg-gray-800 hover:shadow-black/20'
: 'hover:shadow-gray-200/80'
]"
>
<div class="flex items-center justify-between">