码桶
发现社区成员的开源项目
main.js25.5 KB
// 打字机效果状态
let typingInterval = null;
let currentModelIndex = 0;
let isLocked = false;
// 自动飞出展示相关
let flyoutInterval = null;
let currentFlyoutItem = null;
let isFlyingOut = false;
let lastFlyoutSide = null; // 记录上次飞出的方向
// 初始化粒子效果
function initParticles() {
const container = document.getElementById('particles');
const particleCount = 40;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.cssText = `
left: ${Math.random() * 100}%;
top: ${Math.random() * 100}%;
width: ${Math.random() * 4 + 1}px;
height: ${Math.random() * 4 + 1}px;
animation-delay: ${Math.random() * 20}s;
animation-duration: ${Math.random() * 10 + 15}s;
`;
container.appendChild(particle);
}
// 初始化左右两侧浮动光点
initFloatingDots();
}
// 初始化左右两侧浮动光点
function initFloatingDots() {
const leftContainer = document.getElementById('floatingDotsLeft');
const rightContainer = document.getElementById('floatingDotsRight');
const createDots = (container, count) => {
for (let i = 0; i < count; i++) {
const dot = document.createElement('div');
dot.className = 'floating-dot';
dot.style.cssText = `
left: ${Math.random() * 100}%;
top: ${Math.random() * 100}%;
animation-delay: ${Math.random() * 8}s;
animation-duration: ${6 + Math.random() * 4}s;
`;
container.appendChild(dot);
}
};
createDots(leftContainer, 15);
createDots(rightContainer, 15);
}
// 打字机效果
function typeWriter(text, element, speed = 100) {
return new Promise((resolve) => {
// 1. 【重要】如果元素没找到,直接 resolve,防止后续报错
if (!element) {
//console.warn("TypeWriter Error: Element not found");
resolve();
return;
}
let i = 0;
// 此时 element 肯定存在,可以直接赋值
element.textContent = "";
if (typingInterval) {
clearInterval(typingInterval);
}
typingInterval = setInterval(() => {
if (i < text.length) {
// 2. 这里直接使用 element 是安全的,因为上面已经检查过了
element.textContent += text.charAt(i);
i++;
} else {
clearInterval(typingInterval);
resolve();
}
}, speed);
});
}
// 显示模型信息到中心
async function showModelInCenter(model, lock = false) {
if (lock) {
isLocked = true;
}
const centerIcon = document.getElementById('centerIcon');
const centerTitle = document.getElementById('centerTitle');
const centerDesc = document.getElementById('centerDesc');
const centerBtn = document.getElementById('centerBtn');
const typingText = centerTitle.querySelector('.typing-text');
// 显示图标
centerIcon.src = model.icon;
centerIcon.style.display = 'block';
// 打字机效果显示名称
await typeWriter(model.name, typingText, 80);
// 显示描述和按钮
centerDesc.textContent = model.desc;
centerBtn.href = model.url;
centerBtn.classList.add('visible');
}
// 恢复默认状态
function resetCenter() {
isLocked = false;
const centerIcon = document.getElementById('centerIcon');
const centerTitle = document.getElementById('centerTitle');
const centerDesc = document.getElementById('centerDesc');
const centerBtn = document.getElementById('centerBtn');
const typingText = centerTitle.querySelector('.typing-text');
// 隐藏图标和按钮
centerIcon.style.display = 'none';
centerBtn.classList.remove('visible');
// 清除打字机
if (typingInterval) {
clearInterval(typingInterval);
}
// 恢复默认文字
typingText.textContent = '';
centerDesc.textContent = '';
// 开始循环打字
startTypingLoop();
}
// 循环打字效果
async function startTypingLoop() {
if (isLocked) return;
const centerTitle = document.getElementById('centerTitle');
const centerDesc = document.getElementById('centerDesc');
const typingText = centerTitle.querySelector('.typing-text');
const defaultTexts = [
{ title: '让 AI 为你', desc: '探索人工智能的无限可能' },
{ title: '生成未来', desc: '聚合 500+ 全球顶尖大模型' },
{ title: '智能对话', desc: 'GPT-4 / Claude / Gemini / 文心一言' },
{ title: '图像创作', desc: 'Midjourney / DALL-E / Stable Diffusion' },
{ title: '视频生成', desc: 'Sora / 可灵 / Runway / Pika' }
];
while (!isLocked) {
const text = defaultTexts[currentModelIndex % defaultTexts.length];
// 打字标题
await typeWriter(text.title, typingText, 120);
centerDesc.textContent = text.desc;
// 等待2秒
await new Promise(r => setTimeout(r, 2000));
// 删除文字
if (!isLocked) {
await deleteText(typingText);
centerDesc.textContent = '';
}
currentModelIndex++;
}
}
// 删除文字效果
function deleteText(element) {
return new Promise((resolve) => {
let text = element.textContent;
const deleteInterval = setInterval(() => {
if (text.length > 0 && !isLocked) {
text = text.slice(0, -1);
element.textContent = text;
} else {
clearInterval(deleteInterval);
resolve();
}
}, 50);
});
}
// 初始化轨道 - 从HTML读取模型数据并定位
function initOrbit() {
const orbitOuter = document.getElementById('orbitOuter');
const orbitInner = document.getElementById('orbitInner');
const centerContent = document.getElementById('centerContent');
// 1. 【核心修复】检查关键容器是否存在
if (!orbitOuter) {
console.error("initOrbit Error: #orbitOuter 元素未找到");
return; // 直接退出函数,防止报错
}
if (!orbitInner) {
console.error("initOrbit Error: #orbitInner 元素未找到");
return;
}
if (!centerContent) {
console.error("initOrbit Error: #centerContent 元素未找到");
return;
}
// --- 以下代码保持不变 ---
// 获取外环模型并定位
const outerItems = orbitOuter.querySelectorAll('.orbit-item');
const outerRadius = 370; // 外环半径减去偏移
outerItems.forEach((item, index) => {
const angle = (index / outerItems.length) * 2 * Math.PI - Math.PI / 2;
const x = Math.cos(angle) * outerRadius;
const y = Math.sin(angle) * outerRadius;
// 保存原始位置供飞出后返回
item.dataset.originLeft = `calc(50% + ${x}px - 16px)`;
item.dataset.originTop = `calc(50% + ${y}px - 16px)`;
item.dataset.ring = 'outer';
item.dataset.index = index;
item.style.left = item.dataset.originLeft;
item.style.top = item.dataset.originTop;
// 获取模型数据
const model = {
name: item.dataset.name||'',
desc: item.dataset.desc||'',
url: item.dataset.url||'',
icon: item.querySelector('.orbit-icon')?.src || '/static/images/default-avatar.png'
};
// 绑定事件
item.addEventListener('mouseenter', () => {
if (!isLocked && !isFlyingOut) {
showModelInCenter(model, false);
}
});
item.addEventListener('mouseleave', () => {
if (!isLocked && !isFlyingOut) {
resetCenter();
}
});
item.addEventListener('click', (e) => {
e.stopPropagation();
showModelInCenter(model, true);
});
});
// 获取内环模型并定位
const innerItems = orbitInner.querySelectorAll('.orbit-item');
const innerRadius = 245; // 内环半径减去偏移
innerItems.forEach((item, index) => {
const angle = (index / innerItems.length) * 2 * Math.PI - Math.PI / 2;
const x = Math.cos(angle) * innerRadius;
const y = Math.sin(angle) * innerRadius;
// 保存原始位置供飞出后返回
item.dataset.originLeft = `calc(50% + ${x}px - 16px)`;
item.dataset.originTop = `calc(50% + ${y}px - 16px)`;
item.dataset.ring = 'inner';
item.dataset.index = index;
item.style.left = item.dataset.originLeft;
item.style.top = item.dataset.originTop;
// 获取模型数据
const model = {
name: item.dataset.name||'CC',
desc: item.dataset.desc||'DD',
url: item.dataset.url||'SS',
icon: item.querySelector('.orbit-icon')?.src || '/static/images/default-avatar.png'
};
// 绑定事件
item.addEventListener('mouseenter', () => {
if (!isLocked && !isFlyingOut) {
showModelInCenter(model, false);
}
});
item.addEventListener('mouseleave', () => {
if (!isLocked && !isFlyingOut) {
resetCenter();
}
});
item.addEventListener('click', (e) => {
e.stopPropagation();
showModelInCenter(model, true);
});
});
// 鼠标悬停时暂停轨道动画
orbitOuter.addEventListener('mouseenter', () => {
if (!isLocked) orbitOuter.style.animationPlayState = 'paused';
});
orbitOuter.addEventListener('mouseleave', () => {
orbitOuter.style.animationPlayState = 'running';
});
orbitInner.addEventListener('mouseenter', () => {
if (!isLocked) orbitInner.style.animationPlayState = 'paused';
});
orbitInner.addEventListener('mouseleave', () => {
orbitInner.style.animationPlayState = 'running';
});
// 点击中心内容区域不恢复默认
centerContent.addEventListener('click', (e) => {
e.stopPropagation();
});
// 点击其他地方恢复默认
document.addEventListener('click', (e) => {
if (isLocked && !e.target.closest('.orbit-item') && !e.target.closest('.center-content')) {
resetCenter();
}
});
}
// 页面加载动画
function initLoadAnimation() {
document.body.style.opacity = '0';
setTimeout(() => {
document.body.style.transition = 'opacity 1s ease';
document.body.style.opacity = '1';
// 开始打字循环
setTimeout(startTypingLoop, 500);
}, 100);
}
// 键盘快捷键
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
const orbitOuter = document.getElementById('orbitOuter');
const orbitInner = document.getElementById('orbitInner');
switch(e.code) {
case 'Space':
e.preventDefault();
const isPaused = orbitOuter.style.animationPlayState === 'paused';
orbitOuter.style.animationPlayState = isPaused ? 'running' : 'paused';
orbitInner.style.animationPlayState = isPaused ? 'running' : 'paused';
break;
case 'Escape':
if (isLocked) {
resetCenter();
}
break;
}
});
}
// 初始化
function init() {
initParticles();
initOrbit();
initLoadAnimation();
initKeyboardShortcuts();
initFullscreen();
startAutoFlyout();
}
// 全屏功能
function initFullscreen() {
const btn = document.getElementById('fullscreenBtn');
if (!btn) return;
btn.addEventListener('click', () => {
if (!document.fullscreenElement) {
// 进入全屏
document.documentElement.requestFullscreen().catch(err => {
console.log('全屏失败:', err);
});
} else {
// 退出全屏
document.exitFullscreen();
}
});
// 监听全屏状态变化,更新按钮图标
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
btn.innerHTML = `
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
</svg>
`;
btn.title = '退出全屏';
} else {
btn.innerHTML = `
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>
`;
btn.title = '全屏模式';
}
});
}
// DOM加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// ========== 自动飞出展示功能 ==========
// 随机选择一个图标飞出到左右两侧展示
function startAutoFlyout() {
// 每 4 秒执行一次飞出
flyoutInterval = setInterval(() => {
if (!isLocked && !isFlyingOut) {
autoFlyoutRandom();
}
}, 4000);
}
function autoFlyoutRandom() {
const orbitOuter = document.getElementById('orbitOuter');
const orbitInner = document.getElementById('orbitInner');
// 合并所有图标
const allItems = [
...orbitOuter.querySelectorAll('.orbit-item'),
...orbitInner.querySelectorAll('.orbit-item')
];
if (allItems.length === 0) return;
// 随机选择一个
const randomIndex = Math.floor(Math.random() * allItems.length);
const item = allItems[randomIndex];
// 左右交替飞出:如果上次是左边,这次就右边,反之亦然
const flyToRight = lastFlyoutSide === 'left';
lastFlyoutSide = flyToRight ? 'right' : 'left';
// 执行飞出
flyoutItem(item, flyToRight);
}
function flyoutItem(item, toRight) {
if (isFlyingOut || isLocked) return;
isFlyingOut = true;
currentFlyoutItem = item;
// 不暂停轨道旋转,让它继续转
// 获取模型信息
const model = {
name: item.dataset.name||'',
desc: item.dataset.desc||'',
url: item.dataset.url||window.location.origin,
icon: item.querySelector('.orbit-icon')?.src||'/static/images/default-avatar.png'
};
// 计算飞出目标位置
const targetX = toRight ?
window.innerWidth - 180 : // 右侧
100; // 左侧
const targetY = 200 + Math.random() * 250; // 随机高度
// 添加飞出动画类
item.classList.add('flyout-active');
// 设置飞出位置
item.style.left = `${targetX}px`;
item.style.top = `${targetY}px`;
item.style.zIndex = '999999';
// 显示展开的内容卡片
showFlyoutCard(item, model, toRight, targetX, targetY);
}
function showFlyoutCard(item, model, toRight, x, y) {
// 创建飞出卡片
const card = document.createElement('div');
card.className = 'flyout-card';
card.innerHTML = `
<div class="flyout-card-inner">
<img class="flyout-card-icon" src="${model.icon}" alt="${model.name}">
<div class="flyout-card-content">
<h3 class="flyout-card-title">${model.name}</h3>
<p class="flyout-card-desc">${model.desc}</p>
<a class="flyout-card-btn" href="${model.url}" target="_blank">
立即体验
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</a>
</div>
</div>
`;
// 定位卡片
card.style.left = `${x + (toRight ? -280 : 60)}px`;
card.style.top = `${y - 40}px`;
document.body.appendChild(card);
// 触发动画
requestAnimationFrame(() => {
card.classList.add('visible');
});
// 3秒后收回
setTimeout(() => {
returnItem(item, card);
}, 3500);
}
function returnItem(item, card) {
// 移除卡片
card.classList.remove('visible');
setTimeout(() => card.remove(), 500);
// 返回原位
item.style.left = item.dataset.originLeft;
item.style.top = item.dataset.originTop;
item.style.zIndex = '';
setTimeout(() => {
item.classList.remove('flyout-active');
isFlyingOut = false;
currentFlyoutItem = null;
// 不恢复轨道旋转,因为它一直在转
}, 600);
}
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('authModal');
const loginBtn = document.getElementById('loginBtn');
const registerBtn = document.getElementById('registerBtn');
const closeModal = document.getElementById('closeModal');
const tabs = document.querySelectorAll('.modal-tab');
const forms = document.querySelectorAll('.auth-form');
const toRegister = document.getElementById('toRegister');
const toLogin = document.getElementById('toLogin');
// 底部登录注册按钮
const footerLogin = document.getElementById('footerLogin');
const footerRegister = document.getElementById('footerRegister');
// 打开登录
loginBtn.addEventListener('click', () => {
modal.style.display = 'flex';
switchTab('login');
});
// 打开注册
registerBtn.addEventListener('click', () => {
modal.style.display = 'flex';
switchTab('register');
});
// 底部打开登录
footerLogin.addEventListener('click', () => {
modal.style.display = 'flex';
switchTab('login');
});
// 底部打开注册
footerRegister.addEventListener('click', () => {
modal.style.display = 'flex';
switchTab('register');
});
// 关闭弹窗
closeModal.addEventListener('click', () => {
modal.style.display = 'none';
});
// 点击遮罩关闭
modal.addEventListener('click', (e) => {
if(e.target === modal) modal.style.display = 'none';
});
// 切换标签
tabs.forEach(tab => {
tab.addEventListener('click', () => {
switchTab(tab.dataset.tab);
});
});
// 文字切换
toRegister.addEventListener('click', () => switchTab('register'));
toLogin.addEventListener('click', () => switchTab('login'));
// 切换方法
function switchTab(tabName) {
tabs.forEach(t => t.classList.remove('active'));
forms.forEach(f => f.classList.remove('active'));
document.querySelector(`.modal-tab[data-tab="${tabName}"]`).classList.add('active');
document.getElementById(`${tabName}Form`).classList.add('active');
}
// 表单提交(可对接后端)
document.getElementById('loginForm').addEventListener('submit', (e) => {
e.preventDefault();
//alert('登录功能已触发,可对接后端接口');
//modal.style.display = 'none';
});
document.getElementById('registerForm').addEventListener('submit', (e) => {
e.preventDefault();
//alert('注册功能已触发,可对接后端接口');
//modal.style.display = 'none';
});
});
document.addEventListener('DOMContentLoaded', function() {
const logoutBtn = document.getElementById('logoutBtn');
const logoutBtns = document.getElementById('logoutBtns');
if (logoutBtn) { // 只有元素存在时才绑定事件
logoutBtn.addEventListener('click', function () {
layer.confirm('确定要退出登录吗?', {
icon: 3,
title: '系统提示'
}, function () {
$.ajax({
url: '/logout',
type: 'POST',
dataType: 'json',
success: function (res) {
if (res.code === 1) {
layer.msg('退出登录成功', { icon: 1 });
setTimeout(() => {
location.href = '/';
}, 1000);
} else {
layer.msg('退出失败', { icon: 2 });
}
},
error: function() {
layer.msg('请求失败,请重试', { icon: 2 });
}
});
});
});
}
if (logoutBtns) { // 只有元素存在时才绑定事件
logoutBtns.addEventListener('click', function () {
layer.confirm('确定要退出登录吗?', {
icon: 3,
title: '系统提示'
}, function () {
$.ajax({
url: '/logout',
type: 'POST',
dataType: 'json',
success: function (res) {
if (res.code === 1) {
layer.msg('退出登录成功', { icon: 1 });
setTimeout(() => {
location.href = '/';
}, 1000);
} else {
layer.msg('退出失败', { icon: 2 });
}
},
error: function() {
layer.msg('请求失败,请重试', { icon: 2 });
}
});
});
});
}
});
$(function () {
// ======================================
// 登录提交
// ======================================
$("#loginForm").submit(function (e) {
e.preventDefault(); // 阻止默认刷新
let btn = $(this).find(".auth-btn");
let oldText = btn.text();
btn.text("登录中...").prop("disabled", true);
$.ajax({
url: "/login",
type: "POST",
data: $(this).serialize(),
dataType: "json",
success: function (res) {
if (res.code === 1) {
layer.msg("登录成功", {
icon: 1,
skin: 'custom-dark-msg', // 自定义样式类
time: 2000,
offset: 'auto'
});
setTimeout(() => {
location.reload();
}, 1000);
} else {
layer.msg(res.msg || "登录失败", {
icon: 2,
skin: 'custom-dark-msg', // 自定义样式类
time: 2000,
offset: 'auto'
});
// 刷新验证码
$(".captcha-img").click();
}
},
error: function () {
layer.alert("服务器异常,请稍后重试");
},
complete: function () {
btn.text(oldText).prop("disabled", false);
}
});
});
// ======================================
// 注册提交
// ======================================
$("#registerForm").submit(function (e) {
e.preventDefault();
let btn = $(this).find(".auth-btn");
let oldText = btn.text();
btn.text("注册中...").prop("disabled", true);
let password = $(this).find("input[name=password]").val();
let repassword = $(this).find("input[name=repassword]").val();
// 前端验证两次密码一致
if (password !== repassword) {
layer.msg("两次密码输入不一致", {
icon: 2,
skin: 'custom-dark-msg', // 自定义样式类
time: 2000,
offset: 'auto'
});
btn.text(oldText).prop("disabled", false);
return;
}
$.ajax({
url: "/register",
type: "POST",
data: $(this).serialize(),
dataType: "json",
success: function (res) {
if (res.code === 1) {
layer.msg("注册成功,请登录", {
icon: 1,
skin: 'custom-dark-msg', // 自定义样式类
time: 2000,
offset: 'auto'
});
// 切换到登录
switchTab('login');
$(".modal-tab[data-tab=login]").click();
// 清空表单
$("#registerForm")[0].reset();
} else {
layer.msg(res.msg || "注册失败", {
icon: 2,
skin: 'custom-dark-msg', // 自定义样式类
time: 2000,
offset: 'auto'
});
$(".captcha-img").click();
}
},
error: function () {
layer.alert("服务器异常,请稍后重试");
},
complete: function () {
btn.text(oldText).prop("disabled", false);
}
});
});
function switchTab(tabName) {
const tabs = document.querySelectorAll('.modal-tab');
const forms = document.querySelectorAll('.auth-form');
tabs.forEach(t => t.classList.remove('active'));
forms.forEach(f => f.classList.remove('active'));
document.querySelector(`.modal-tab[data-tab="${tabName}"]`).classList.add('active');
document.getElementById(`${tabName}Form`).classList.add('active');
}
});