Happy_language/app/static/js/dashboard.js
2025-09-22 06:06:19 +08:00

335 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Dashboard页面 JavaScript - 增强动效版
*/
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializeDashboard();
loadVoiceStatus();
bindEvents();
});
/**
* 初始化Dashboard - 增强版
*/
function initializeDashboard() {
// 添加页面加载动画类
document.body.classList.add('page-loading');
// 为所有主要元素添加渐入动画类
setTimeout(() => {
document.querySelectorAll('.welcome-card-kid, .scenario-card-kid, .voice-clone-card, .quick-action-card-kid, .achievement-card').forEach((el, index) => {
el.classList.add('fade-in-up');
el.style.animationDelay = `${index * 0.1}s`;
});
}, 100);
// 移除加载状态
setTimeout(() => {
document.body.classList.remove('page-loading');
}, 2000);
// 添加页面加载动画
animateCards();
// 初始化成就数据
animateAchievements();
}
/**
* 绑定事件
*/
function bindEvents() {
// 场景卡片点击事件
document.querySelectorAll('[data-scenario]').forEach(btn => {
btn.addEventListener('click', function() {
const scenarioId = this.getAttribute('data-scenario');
handleScenarioClick(scenarioId);
});
});
// 快速测试按钮
const quickTestBtn = document.getElementById('quick-test-btn');
if (quickTestBtn) {
quickTestBtn.addEventListener('click', handleQuickTest);
}
}
/**
* 卡片加载动画
*/
function animateCards() {
const cards = document.querySelectorAll('.scenario-card-kid, .quick-action-card-kid, .achievement-card');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(30px)';
setTimeout(() => {
card.style.transition = 'all 0.5s ease';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 100);
});
}
/**
* 成就数据动画
*/
function animateAchievements() {
const achievements = [
{ selector: '.time-card .achievement-number', target: 0, suffix: '' },
{ selector: '.conversation-card .achievement-number', target: 0, suffix: '' },
{ selector: '.star-card .achievement-number', target: 0, suffix: '' },
{ selector: '.medal-card .achievement-number', target: 0, suffix: '' }
];
achievements.forEach((achievement, index) => {
setTimeout(() => {
animateNumber(achievement.selector, 0, achievement.target, 1000, achievement.suffix);
}, index * 200);
});
}
/**
* 数字动画
*/
function animateNumber(selector, start, end, duration, suffix = '') {
const element = document.querySelector(selector);
if (!element) return;
const range = end - start;
const minTimer = 50;
const stepTime = Math.abs(Math.floor(duration / range));
const timer = stepTime < minTimer ? minTimer : stepTime;
const startTime = new Date().getTime();
const endTime = startTime + duration;
function run() {
const now = new Date().getTime();
const remaining = Math.max((endTime - now) / duration, 0);
const value = Math.round(end - (remaining * range));
element.textContent = value + suffix;
if (value === end) {
return;
}
setTimeout(run, timer);
}
run();
}
/**
* 加载语音状态
*/
async function loadVoiceStatus() {
try {
const response = await fetch('/voice-clone/api/voice-clone/status');
const result = await response.json();
updateVoiceStatusDisplay(result);
} catch (error) {
console.error('加载语音状态失败:', error);
updateVoiceStatusDisplay({
success: false,
has_sample: false,
message: '无法加载语音状态'
});
}
}
/**
* 更新语音状态显示
*/
function updateVoiceStatusDisplay(result) {
const display = document.getElementById('voice-status-display');
const quickTestBtn = document.getElementById('quick-test-btn');
if (result.has_sample && result.success) {
display.innerHTML = `
<h4 class="fw-bold mb-2">
<i class="fas fa-check-circle text-success me-2"></i>
你的专属声音已就绪!
</h4>
<p class="mb-1">AI已经学会了你的声音${result.recognized_text}」</p>
<small class="opacity-75">录制时间:${result.upload_time}</small>
`;
if (quickTestBtn) {
quickTestBtn.disabled = false;
}
// 激活语音波浪动画
document.querySelectorAll('.voice-wave').forEach((wave, index) => {
wave.style.animationDelay = `${index * 0.2}s`;
wave.style.animationPlayState = 'running';
});
} else {
display.innerHTML = `
<h4 class="fw-bold mb-2">
<i class="fas fa-microphone-slash text-warning me-2"></i>
还没有录制语音样本
</h4>
<p class="mb-0">录制你的专属声音让AI学会模仿你说话</p>
`;
if (quickTestBtn) {
quickTestBtn.disabled = true;
}
}
}
/**
* 处理场景点击
*/
function handleScenarioClick(scenarioId) {
// 显示动画效果
showComingSoonAnimation();
}
/**
* 快速测试语音克隆
*/
async function handleQuickTest() {
const btn = document.getElementById('quick-test-btn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>测试中...';
try {
const response = await fetch('/voice-clone/api/voice-clone/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: '你好,这是我的专属声音测试!'
})
});
const result = await response.json();
if (result.success) {
showSuccess('快速测试成功!播放你的专属声音');
// 播放生成的音频
const audio = new Audio();
const filename = result.audio_url.split('/').pop();
audio.src = `/voice-test/download-audio/${filename}`;
audio.play().catch(e => console.error('播放失败:', e));
} else {
showError(result.message || '测试失败');
}
} catch (error) {
showError('测试失败,请检查网络连接');
console.error('快速测试失败:', error);
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
}
}
/**
* 显示即将上线动画
*/
function showComingSoonAnimation() {
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.innerHTML = `
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0" style="border-radius: 20px;">
<div class="modal-body text-center py-5">
<div class="mb-4">
<i class="fas fa-rocket" style="font-size: 4rem; color: #4ECDC4; animation: bounce 1s ease-in-out infinite;"></i>
</div>
<h3 class="fw-bold text-primary mb-3">敬请期待!</h3>
<p class="text-muted mb-4">这个超棒的功能正在努力开发中<br>很快就能和小朋友们见面啦!</p>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" style="border-radius: 25px; padding: 0.8rem 2rem;">
<i class="fas fa-heart me-2"></i>好期待哦
</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
const bsModal = new bootstrap.Modal(modal);
bsModal.show();
// 自动清理
modal.addEventListener('hidden.bs.modal', () => {
document.body.removeChild(modal);
});
}
/**
* 显示成功消息
*/
function showSuccess(message) {
showToast(message, 'success');
}
/**
* 显示错误消息
*/
function showError(message) {
showToast(message, 'danger');
}
/**
* 显示Toast消息
*/
function showToast(message, type = 'info') {
const colors = {
'success': 'bg-success',
'danger': 'bg-danger',
'warning': 'bg-warning',
'info': 'bg-info'
};
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white ${colors[type]} border-0 position-fixed top-0 end-0 m-3`;
toast.style.zIndex = '9999';
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
<i class="fas fa-check-circle me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
document.body.appendChild(toast);
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
// 添加鼠标交互效果
document.addEventListener('mousemove', function(e) {
// 鼠标跟随效果(轻微)
const shapes = document.querySelectorAll('.shape');
shapes.forEach((shape, index) => {
const speed = (index + 1) * 0.01;
const x = e.clientX * speed;
const y = e.clientY * speed;
shape.style.transform += ` translate(${x}px, ${y}px)`;
});
});