refactor(dashboard): replace diamond bg with sci-fi cylinder panel

This commit is contained in:
LIghtJUNction
2026-03-29 16:25:54 +08:00
parent f97aca8501
commit 7c0e8414a6
6 changed files with 82 additions and 69 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div class="star-bg">
<div class="bg">
<canvas ref="canvas"></canvas>
</div>
</template>
@@ -10,62 +10,83 @@ import { onMounted, onUnmounted, ref } from "vue";
const canvas = ref<HTMLCanvasElement | null>(null);
let animId: number | null = null;
interface Star {
interface Cylinder {
x: number;
y: number;
s: number;
phase: number;
spd: number;
}
const createGrid = (w: number, h: number): Star[] => {
const stars: Star[] = [];
const s = 28;
const hStep = s * 0.866;
const cols = Math.ceil(w / s) + 2;
const rows = Math.ceil(h / hStep) + 2;
const createGrid = (w: number, h: number): Cylinder[] => {
const grid: Cylinder[] = [];
const sx = 44, sy = 38;
const cols = Math.ceil(w / sx) + 2;
const rows = Math.ceil(h / sy) + 2;
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
stars.push({
x: i * s + (j % 2) * (s / 2),
y: j * hStep,
s: 5 + Math.random() * 3,
phase: Math.random() * Math.PI * 2,
spd: 0.3 + Math.random() * 0.4,
grid.push({
x: i * sx + (j % 2) * (sx / 2),
y: j * sy,
phase: (i + j * 0.5) * 0.3,
});
}
}
return stars;
return grid;
};
const draw = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
s: number,
a: number,
up: boolean
) => {
const h = s * 0.5;
const d = up ? 0 : h * 0.4;
const draw = (ctx: CanvasRenderingContext2D, w: number, h: number, t: number) => {
ctx.fillStyle = "#0c0e12";
ctx.fillRect(0, 0, w, h);
ctx.beginPath();
ctx.moveTo(x, y - h + d);
ctx.lineTo(x + s * 0.5 + d, y);
ctx.lineTo(x, y + h - d);
ctx.lineTo(x - s * 0.5 - d, y);
ctx.closePath();
const sx = 44, sy = 38;
const cols = Math.ceil(w / sx) + 2;
const rows = Math.ceil(h / sy) + 2;
const g = ctx.createLinearGradient(x, y - h, x, y + h);
g.addColorStop(0, `rgba(180, 210, 255, ${a})`);
g.addColorStop(1, `rgba(80, 140, 220, ${a * 0.6})`);
ctx.fillStyle = g;
ctx.fill();
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const x = i * sx + (j % 2) * (sx / 2);
const y = j * sy;
ctx.strokeStyle = `rgba(220, 240, 255, ${a * 0.4})`;
ctx.lineWidth = 0.5;
ctx.stroke();
// Outer rim
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.fillStyle = "#1a1c22";
ctx.fill();
// Recessed cylinder body
ctx.beginPath();
ctx.arc(x, y, 7, 0, Math.PI * 2);
const bodyGrad = ctx.createRadialGradient(x, y - 2, 0, x, y, 7);
bodyGrad.addColorStop(0, "#14161c");
bodyGrad.addColorStop(1, "#0a0b0e");
ctx.fillStyle = bodyGrad;
ctx.fill();
// Sharp outer rim highlight
ctx.beginPath();
ctx.arc(x, y, 8, 0, Math.PI * 2);
ctx.strokeStyle = "#2a2c34";
ctx.lineWidth = 0.5;
ctx.stroke();
// Deep indigo glow from depth
const depthPulse = 0.15 + 0.1 * Math.sin(t * 0.2);
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
const glowGrad = ctx.createRadialGradient(x, y, 0, x, y, 4);
glowGrad.addColorStop(0, `rgba(60, 40, 120, ${depthPulse})`);
glowGrad.addColorStop(1, "transparent");
ctx.fillStyle = glowGrad;
ctx.fill();
// Starlight pinpoint at center
const starPulse = 0.6 + 0.4 * Math.sin(t * 0.3 + (i + j) * 0.2);
ctx.beginPath();
ctx.arc(x, y, 1, 0, Math.PI * 2);
ctx.fillStyle = `rgba(220, 230, 255, ${starPulse})`;
ctx.fill();
}
}
};
onMounted(() => {
@@ -73,28 +94,18 @@ onMounted(() => {
const ctx = canvas.value.getContext("2d");
if (!ctx) return;
let stars: Star[] = [];
let t = 0;
const resize = () => {
if (!canvas.value) return;
canvas.value.width = window.innerWidth;
canvas.value.height = window.innerHeight;
stars = createGrid(canvas.value.width, canvas.value.height);
};
const loop = () => {
if (!canvas.value || !ctx) return;
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
t += 0.012;
stars.forEach((st) => {
const fy = Math.sin(t * st.spd + st.phase) * 3;
const up = Math.sin(t * st.spd + st.phase) > 0;
const a = 0.2 + Math.abs(Math.sin(t * st.spd + st.phase)) * 0.5;
draw(ctx, st.x, st.y + fy, st.s, a, up);
});
draw(ctx, canvas.value.width, canvas.value.height, t);
t += 0.015;
animId = requestAnimationFrame(loop);
};
@@ -109,11 +120,11 @@ onUnmounted(() => {
</script>
<style scoped>
.star-bg {
.bg {
position: fixed;
inset: 0;
z-index: 0;
background: #0a0e1a;
background: #0c0e12;
}
canvas {

View File

@@ -6,7 +6,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import { useI18n } from "@/i18n/composables";
const { locale } = useI18n();
@@ -84,13 +84,14 @@ const getQuoteOfTheDay = (): Quote => {
return langQuotes[index];
};
onMounted(() => {
currentQuote.value = getQuoteOfTheDay();
});
// Update quote when locale changes
locale.value; // track dependency
currentQuote.value = getQuoteOfTheDay();
watch(
locale,
() => {
currentQuote.value = getQuoteOfTheDay();
},
{ immediate: true }
);
</script>
<style scoped>

View File

@@ -2,7 +2,7 @@
"login": "Login",
"username": "Username",
"password": "Password",
"defaultHint": "Default username: astrbot. Run `astrbot conf admin` before the first login.",
"defaultHint": "Run `astrbot conf admin` to set up the administrator account first.",
"shareLink": "Share Link",
"linkCopied": "Link copied to clipboard",
"linkCopyFailed": "Failed to copy link",

View File

@@ -2,9 +2,9 @@
"login": "Вход",
"username": "Имя пользователя",
"password": "Пароль",
"defaultHint": "Имя пользователя по умолчанию: astrbot. Перед первым входом выполните `astrbot conf admin`.",
"defaultHint": "Сначала выполните `astrbot conf admin` для настройки учётной записи администратора.",
"logo": {
"title": "AstrBot Starlight Panel"
"title": "AstrBot Звёздная Панель"
},
"theme": {
"switchToDark": "Перейти на темную тему",

View File

@@ -2,12 +2,12 @@
"login": "登录",
"username": "用户名",
"password": "密码",
"defaultHint": "默认账户为astrbot请先行 `astrbot conf admin` 设置密码",
"defaultHint": "请先行 `astrbot conf admin` 设置管理员账号",
"shareLink": "分享链接",
"linkCopied": "链接已复制到剪贴板",
"linkCopyFailed": "复制链接失败",
"logo": {
"title": "AstrBot Starlight Panel"
"title": "AstrBot 星光面板"
},
"theme": {
"switchToDark": "切换到深色主题",

View File

@@ -8,7 +8,7 @@ import { useAuthStore } from "@/stores/auth";
import { useApiStore } from "@/stores/api";
import { useRouter } from "vue-router";
import { useCustomizerStore } from "@/stores/customizer";
import { useModuleI18n } from "@/i18n/composables";
import { useI18n, useModuleI18n } from "@/i18n/composables";
import { useToast } from "@/utils/toast";
import { getApiBaseUrlValidationError } from "@/utils/request";
@@ -17,6 +17,7 @@ const router = useRouter();
const authStore = useAuthStore();
const apiStore = useApiStore();
const customizer = useCustomizerStore();
const { locale } = useI18n();
const { tm: t } = useModuleI18n("features/auth");
const toast = useToast();
@@ -116,7 +117,7 @@ onMounted(() => {
<div class="login-page-container">
<DiamondBg />
<v-card class="login-card" elevation="1">
<v-card-title>
<v-card-title :key="locale">
<div class="d-flex justify-space-between align-center w-100">
<img
width="80"