257 lines
5.4 KiB
Vue
257 lines
5.4 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<!-- 1. 新增:星空 Canvas 背景 -->
|
||
<canvas ref="starCanvas" class="star-bg"></canvas>
|
||
<!-- 原有内容包裹层 (确保内容在背景之上) -->
|
||
<div class="content-wrapper">
|
||
<slot></slot>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted } from 'vue';
|
||
|
||
// 模拟原有数据
|
||
const links = [{ name: 'Home' }, { name: 'Showcase' }, { name: 'Docs' }];
|
||
|
||
// --- 星空背景逻辑开始 ---
|
||
const starCanvas = ref(null);
|
||
let ctx = null;
|
||
let animationFrameId = null;
|
||
let stars = [];
|
||
|
||
// 配置参数
|
||
const config = {
|
||
starCount: 150, // 星星数量
|
||
speedBase: 0.2, // 基础流速
|
||
starColor: 'rgba(255, 255, 255, 0.8)',
|
||
};
|
||
|
||
class Star {
|
||
constructor(width, height) {
|
||
this.width = width;
|
||
this.height = height;
|
||
this.init(true);
|
||
}
|
||
|
||
init(randomY = false) {
|
||
this.x = Math.random() * this.width;
|
||
// 如果是初始化,Y轴随机;如果是重置,从底部出现
|
||
this.y = randomY ? Math.random() * this.height : this.height;
|
||
this.size = Math.random() * 2; // 星星大小
|
||
this.speed = Math.random() * 0.5 + config.speedBase; // 移动速度
|
||
this.opacity = Math.random();
|
||
this.fadeDir = Math.random() > 0.5 ? 0.01 : -0.01; // 闪烁方向
|
||
}
|
||
|
||
update() {
|
||
// 向上流动效果 (y 减小)
|
||
this.y -= this.speed;
|
||
|
||
// 闪烁效果
|
||
this.opacity += this.fadeDir;
|
||
if (this.opacity >= 1 || this.opacity <= 0.2) {
|
||
this.fadeDir = -this.fadeDir;
|
||
}
|
||
|
||
// 边界检查:如果飞出顶部,重置到底部
|
||
if (this.y < 0) {
|
||
this.init(false);
|
||
}
|
||
}
|
||
|
||
draw(context) {
|
||
context.beginPath();
|
||
context.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||
context.fillStyle = `rgba(255, 255, 255, ${this.opacity})`;
|
||
context.fill();
|
||
}
|
||
}
|
||
|
||
const initStars = () => {
|
||
const canvas = starCanvas.value;
|
||
if (!canvas) return;
|
||
|
||
ctx = canvas.getContext('2d');
|
||
|
||
const resize = () => {
|
||
canvas.width = window.innerWidth;
|
||
canvas.height = window.innerHeight;
|
||
// 重置星星
|
||
stars = [];
|
||
for (let i = 0; i < config.starCount; i++) {
|
||
stars.push(new Star(canvas.width, canvas.height));
|
||
}
|
||
};
|
||
|
||
window.addEventListener('resize', resize);
|
||
resize(); // 初始化尺寸
|
||
|
||
const animate = () => {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
stars.forEach(star => {
|
||
star.update();
|
||
star.draw(ctx);
|
||
});
|
||
|
||
animationFrameId = requestAnimationFrame(animate);
|
||
};
|
||
|
||
animate();
|
||
};
|
||
|
||
onMounted(() => {
|
||
initStars();
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
cancelAnimationFrame(animationFrameId);
|
||
window.removeEventListener('resize', () => {});
|
||
});
|
||
// --- 星空背景逻辑结束 ---
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 1. 核心背景样式 */
|
||
.page-container {
|
||
position: relative;
|
||
min-height: 100vh;
|
||
/* 深空渐变背景 */
|
||
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
|
||
color: #ffffff; /* 确保文字在深色背景上可见 */
|
||
}
|
||
|
||
.star-bg {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0; /* 放在最底层 */
|
||
pointer-events: none; /* 让鼠标事件穿透 Canvas */
|
||
}
|
||
|
||
/* 2. 内容层样式 (确保内容浮在星空上) */
|
||
.content-wrapper {
|
||
position: relative;
|
||
z-index: 1; /* 放在 Canvas 之上 */
|
||
}
|
||
|
||
/* --- 以下是页面基础排版样式,按需调整 --- */
|
||
.navbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px 40px;
|
||
}
|
||
.nav-links a {
|
||
color: #ccc;
|
||
margin: 0 15px;
|
||
text-decoration: none;
|
||
}
|
||
.nav-links a:hover { color: #fff; }
|
||
|
||
.hero {
|
||
text-align: center;
|
||
padding: 80px 20px;
|
||
}
|
||
.hero h1 {
|
||
font-size: 3.5rem;
|
||
margin-bottom: 10px;
|
||
background: linear-gradient(to right, #fff, #a5b4fc);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.subtitle {
|
||
font-size: 1.2rem;
|
||
color: #94a3b8;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.section {
|
||
max-width: 1000px;
|
||
margin: 60px auto;
|
||
text-align: center;
|
||
background: rgba(255, 255, 255, 0.03); /* 轻微的磨砂玻璃背景 */
|
||
backdrop-filter: blur(10px);
|
||
padding: 40px;
|
||
border-radius: 16px;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #6366f1;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 24px;
|
||
border-radius: 30px;
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
transition: transform 0.2s;
|
||
}
|
||
.btn-primary:hover { background: #4f46e5; transform: scale(1.05); }
|
||
|
||
.btn-outline {
|
||
background: transparent;
|
||
color: white;
|
||
border: 1px solid rgba(255,255,255,0.3);
|
||
padding: 10px 24px;
|
||
border-radius: 30px;
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 流程图样式 */
|
||
.demo-flow {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
align-items: center;
|
||
margin-top: 30px;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
.flow-item {
|
||
background: rgba(0,0,0,0.3);
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
border: 1px solid #333;
|
||
}
|
||
.flow-item.active {
|
||
border-color: #6366f1;
|
||
box-shadow: 0 0 15px rgba(99, 102, 241, 0.3);
|
||
}
|
||
|
||
/* 卡片网格 */
|
||
.card-grid {
|
||
display: flex;
|
||
gap: 20px;
|
||
justify-content: center;
|
||
margin-top: 30px;
|
||
}
|
||
.card {
|
||
width: 200px;
|
||
background: rgba(255,255,255,0.05);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
.card-img-placeholder {
|
||
height: 120px;
|
||
background: #2d3748;
|
||
}
|
||
.card-info {
|
||
padding: 10px;
|
||
text-align: left;
|
||
}
|
||
|
||
.footer {
|
||
margin-top: 100px;
|
||
border-top: 1px solid rgba(255,255,255,0.1);
|
||
padding: 40px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
color: #64748b;
|
||
}
|
||
</style> |