335 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * 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)`;
 | 
						||
    });
 | 
						||
});
 |