741 lines
28 KiB
HTML
741 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>物理知识测验 - 万有引力定律</title>
|
||
<style>
|
||
/* 全局样式 */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||
background-color: #f5f7fa;
|
||
color: #333;
|
||
line-height: 1.6;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||
padding: 30px;
|
||
}
|
||
|
||
header {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 2px solid #eaeaea;
|
||
}
|
||
|
||
h1 {
|
||
color: #2c3e50;
|
||
font-size: 28px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.difficulty-indicator {
|
||
display: inline-block;
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
font-weight: bold;
|
||
margin-bottom: 15px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.difficulty-medium {
|
||
background-color: #3498db;
|
||
color: white;
|
||
}
|
||
|
||
.difficulty-easy {
|
||
background-color: #2ecc71;
|
||
color: white;
|
||
}
|
||
|
||
.difficulty-hard {
|
||
background-color: #e74c3c;
|
||
color: white;
|
||
}
|
||
|
||
.quiz-info {
|
||
color: #7f8c8d;
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* 题目样式 */
|
||
.question {
|
||
margin-bottom: 35px;
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
background-color: #f9f9f9;
|
||
border-left: 4px solid #3498db;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.question:hover {
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.question-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.question-number {
|
||
font-weight: bold;
|
||
font-size: 18px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.question-points {
|
||
background-color: #3498db;
|
||
color: white;
|
||
padding: 4px 10px;
|
||
border-radius: 15px;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.question-text {
|
||
font-size: 18px;
|
||
margin-bottom: 20px;
|
||
color: #34495e;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.options {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.option {
|
||
margin-bottom: 12px;
|
||
padding: 12px 15px;
|
||
border-radius: 8px;
|
||
border: 2px solid #e0e0e0;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
background-color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.option input[type="radio"] {
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.option label {
|
||
cursor: pointer;
|
||
display: inline;
|
||
flex-grow: 1;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.option:hover {
|
||
border-color: #3498db;
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.option input[type="radio"] {
|
||
margin-right: 10px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.option label {
|
||
cursor: pointer;
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
|
||
.question-explanation {
|
||
display: none;
|
||
margin-top: 15px;
|
||
padding: 15px;
|
||
background-color: #e8f4fd;
|
||
border-radius: 8px;
|
||
border-left: 4px solid #3498db;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
|
||
/* 导航按钮样式 */
|
||
.navigation-section {
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
padding: 25px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 10px;
|
||
display: none;
|
||
}
|
||
|
||
#next-btn {
|
||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 30px;
|
||
border-radius: 30px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
#next-btn:before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||
transition: all 0.5s ease;
|
||
}
|
||
|
||
#next-btn:hover:before {
|
||
left: 100%;
|
||
}
|
||
|
||
#next-btn:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
#next-btn:active {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.navigation-message {
|
||
font-size: 16px;
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.submit-section {
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
#submit-btn {
|
||
background-color: #2980b9;
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 30px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
#submit-btn:hover {
|
||
background-color: #1e6fa5;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
#result {
|
||
margin-top: 30px;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
display: none;
|
||
}
|
||
|
||
.result-header {
|
||
font-size: 22px;
|
||
font-weight: bold;
|
||
margin-bottom: 15px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.score {
|
||
font-size: 18px;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.correct-answers {
|
||
background-color: #e8f5e9;
|
||
color: #2e7d32;
|
||
padding: 10px 15px;
|
||
border-radius: 6px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.incorrect-answers {
|
||
background-color: #ffebee;
|
||
color: #c62828;
|
||
padding: 10px 15px;
|
||
border-radius: 6px;
|
||
}
|
||
/* 学伴模型选择器样式 */
|
||
.model-selector {
|
||
position: fixed; top: 40px; left: 20px; z-index: 1000;
|
||
padding: 10px; background-color: rgba(255, 255, 255, 0.8);
|
||
border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
display: flex; flex-direction: column; gap: 15px;
|
||
}
|
||
select { padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: white; font-size: 14px; }
|
||
|
||
/* 录音控制样式 */
|
||
.recording-controls {
|
||
position: static; display: flex; flex-direction: column; gap: 10px; margin: 0;
|
||
}
|
||
.record-button {
|
||
width: 70px; height: 70px; border-radius: 50%;
|
||
background-color: #dc3545; border: none; color: white;
|
||
font-size: 16px; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
display: flex; align-items: center; justify-content: center;
|
||
opacity: 1 !important; visibility: visible !important;
|
||
}
|
||
.record-button:hover { background-color: #c82333; }
|
||
.stop-button {
|
||
width: 70px; height: 70px; border-radius: 50%;
|
||
background-color: #6c757d; border: none; color: white;
|
||
font-size: 16px; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
display: flex; align-items: center; justify-content: center; display: none;
|
||
opacity: 1 !important; visibility: visible !important;
|
||
}
|
||
|
||
/* 录音指示器样式 */
|
||
.recording-indicator {
|
||
position: fixed; bottom: 20px; left: 20px; z-index: 1000;
|
||
padding: 10px 15px; background-color: rgba(220, 53, 69, 0.9);
|
||
color: white; border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||
display: none; align-items: center;
|
||
}
|
||
.recording-dot {
|
||
width: 10px; height: 10px; background-color: white;
|
||
border-radius: 50%; margin-right: 8px; animation: pulse 1.5s infinite;
|
||
}
|
||
|
||
/* 思考中动画样式 */
|
||
.thinking-indicator {
|
||
position: fixed; bottom: 20px; right: 120px; z-index: 1000;
|
||
padding: 10px 15px; background-color: rgba(0, 123, 255, 0.9);
|
||
color: white; border-radius: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||
display: none; align-items: center;
|
||
}
|
||
.thinking-dots { display: flex; gap: 4px; margin-right: 8px; }
|
||
.thinking-dot {
|
||
width: 8px; height: 8px; background-color: white; border-radius: 50%;
|
||
}
|
||
.thinking-dot:nth-child(1) { animation: pulse 1.5s infinite 0s; }
|
||
.thinking-dot:nth-child(2) { animation: pulse 1.5s infinite 0.3s; }
|
||
.thinking-dot:nth-child(3) { animation: pulse 1.5s infinite 0.6s; }
|
||
|
||
/* 结果显示容器样式 */
|
||
.result-container {
|
||
position: fixed; bottom: 80px; left: 20px; z-index: 1000;
|
||
padding: 15px; background-color: rgba(255, 255, 255, 0.95);
|
||
border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
||
max-width: 400px; width: 90%;
|
||
display: none; flex-direction: column; gap: 10px;
|
||
}
|
||
.result-header {
|
||
font-weight: bold; color: #495057; margin-bottom: 5px;
|
||
display: flex; align-items: center; gap: 5px;
|
||
}
|
||
.result-header.asr { color: #007bff; }
|
||
.result-header.feedback { color: #28a745; }
|
||
.result-text {
|
||
color: #333; line-height: 1.5; max-height: 200px; overflow-y: auto;
|
||
padding-right: 5px; word-break: break-all;
|
||
}
|
||
|
||
/* 音频播放器样式 */
|
||
.audio-player-container {
|
||
margin-top: 10px; display: flex; align-items: center; gap: 10px;
|
||
}
|
||
.play-button {
|
||
background-color: #28a745; color: white; border: none;
|
||
border-radius: 50%; width: 40px; height: 40px;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.play-button:hover { background-color: #218838; }
|
||
.audio-progress {
|
||
flex-grow: 1; height: 6px; background-color: #e9ecef; border-radius: 3px;
|
||
overflow: hidden; cursor: pointer;
|
||
}
|
||
.progress-bar {
|
||
height: 100%; background-color: #28a745; width: 0%;
|
||
}
|
||
.audio-time { font-size: 12px; color: #6c757d; }
|
||
|
||
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1>物理知识测验 - 万有引力定律</h1>
|
||
<div class="difficulty-indicator difficulty-medium">当前难度:中等</div>
|
||
<div class="quiz-info">
|
||
共15道题,分三个难度级别。完成当前难度后将根据您的表现推荐下一难度。
|
||
</div>
|
||
</header>
|
||
|
||
<div class="question-section">
|
||
<!-- 试题将通过JavaScript动态渲染 -->
|
||
</div>
|
||
|
||
<div class="navigation-section" id="navigation">
|
||
<h3 id="navigation-message"></h3>
|
||
<button type="button" id="next-btn">开始答题</button>
|
||
</div>
|
||
|
||
<div class="submit-section">
|
||
<button type="button" id="submit-btn">提交答案</button>
|
||
<div id="result">
|
||
<div class="result-header">测验结果</div>
|
||
<div class="score">您的得分:<span id="score-value">0</span>/100分</div>
|
||
<div class="correct-answers">正确题数:<span id="correct-count">0</span>道</div>
|
||
<div class="incorrect-answers">错误题数:<span id="incorrect-count">0</span>道</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 引入试题数据和渲染逻辑 -->
|
||
<script src="physics_quiz.js"></script>
|
||
|
||
<!-- 学伴功能区域 -->
|
||
<div class="model-selector">
|
||
<label for="model-select">选择学伴:</label>
|
||
<select id="model-select">
|
||
<option value="shizuku">小智</option>
|
||
<option value="koharu">小荷</option>
|
||
<option value="wanko">汪喵</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="recording-indicator" id="recordingIndicator">
|
||
<div class="recording-dot"></div>
|
||
<span>正在录音...</span>
|
||
</div>
|
||
|
||
<div class="thinking-indicator" id="thinkingIndicator">
|
||
<div class="thinking-dots">
|
||
<div class="thinking-dot"></div>
|
||
<div class="thinking-dot"></div>
|
||
<div class="thinking-dot"></div>
|
||
</div>
|
||
<span>学伴正在思考中...</span>
|
||
</div>
|
||
|
||
<div class="recording-controls" style="position: fixed; right:37px; bottom: 230px; z-index: 998;">
|
||
<button class="record-button" id="startRecordBtn" style="font-size: 14px;">帮我讲题</button>
|
||
<button class="stop-button" id="stopRecordBtn" style="font-size: 14px;">停止讲话</button>
|
||
</div>
|
||
|
||
<div class="result-container" id="resultContainer">
|
||
<div>
|
||
<div class="result-header asr">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M12 1H4C2.89 1 2 1.9 2 3V13C2 14.1 2.89 15 4 15H12C13.11 15 14 14.1 14 13V3C14 1.9 13.11 1 12 1ZM12 13H4V3H12V13Z" fill="#007bff"/>
|
||
<path d="M8 4C6.34 4 5 5.34 5 7C5 8.66 6.34 10 8 10C9.66 10 11 8.66 11 7C11 5.34 9.66 4 8 4ZM8 8.5C7.17 8.5 6.5 7.83 6.5 7C6.5 6.17 7.17 5.5 8 5.5C8.83 5.5 9.5 6.17 9.5 7C9.5 7.83 8.83 8.5 8 8.5Z" fill="#007bff"/>
|
||
<path d="M8 11C5.79 11 4 12.79 4 15V16H12V15C12 12.79 10.21 11 8 11ZM8 13C9.1 13 10 13.9 10 15H6C6 13.9 6.9 13 8 13Z" fill="#007bff"/>
|
||
</svg>
|
||
你讲的话
|
||
</div>
|
||
<div class="result-text" id="asrResultText"></div>
|
||
</div>
|
||
<div>
|
||
<div class="result-header feedback">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M1 4C1 2.9 1.9 2 3 2H13C14.1 2 15 2.9 15 4V11C15 12.1 14.1 13 13 13H9L5 16V13H3C1.9 13 1 12.1 1 11V4Z" fill="#28a745"/>
|
||
<path d="M8 8H6V5H8V8ZM11 8H9V5H11V8ZM11 11H6V9H11V11Z" fill="#28a745"/>
|
||
</svg>
|
||
学伴回复
|
||
</div>
|
||
<div class="result-text" id="feedbackResultText"></div>
|
||
</div>
|
||
<div class="audio-player-container">
|
||
<button class="play-button" id="playAudioBtn">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||
</svg>
|
||
</button>
|
||
<div class="audio-progress" id="audioProgress">
|
||
<div class="progress-bar" id="progressBar"></div>
|
||
</div>
|
||
<div class="audio-time" id="audioTime">00:00 / 00:00</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
有权限问题的话,请点击<a href="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/11%E3%80%81%E5%AD%A6%E4%BC%B4Chrome%E5%BD%95%E9%9F%B3%E9%85%8D%E7%BD%AE%E4%BF%AE%E6%94%B9.pdf">这里</a>
|
||
</div>
|
||
<script src="https://l2dwidget.js.org/lib/L2Dwidget.min.js"></script>
|
||
<script>
|
||
// 录音相关变量
|
||
let mediaRecorder; let audioChunks = []; let isRecording = false;
|
||
// 音频播放相关变量
|
||
let audioElement = null; let isPlaying = false;
|
||
|
||
// 开始录音
|
||
function startRecording() {
|
||
if (isRecording) return;
|
||
|
||
console.log("尝试开始录音");
|
||
navigator.mediaDevices.getUserMedia({ audio: true })
|
||
.then(stream => {
|
||
mediaRecorder = new MediaRecorder(stream);
|
||
audioChunks = [];
|
||
|
||
mediaRecorder.ondataavailable = event => {
|
||
if (event.data.size > 0) audioChunks.push(event.data);
|
||
};
|
||
|
||
mediaRecorder.onstop = () => {
|
||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||
console.log("录音完成,音频数据大小:", audioBlob.size);
|
||
uploadAudioToServer(audioBlob);
|
||
};
|
||
|
||
mediaRecorder.start();
|
||
isRecording = true;
|
||
document.getElementById('recordingIndicator').style.display = 'flex';
|
||
document.getElementById('startRecordBtn').style.display = 'none';
|
||
document.getElementById('stopRecordBtn').style.display = 'flex';
|
||
console.log("开始录音成功");
|
||
|
||
// 设置最长录音时间为60秒
|
||
setTimeout(stopRecording, 60000);
|
||
})
|
||
.catch(error => {
|
||
console.error("获取麦克风权限失败:", error);
|
||
alert("请授权麦克风权限以使用录音功能");
|
||
});
|
||
}
|
||
|
||
// 停止录音
|
||
function stopRecording() {
|
||
if (!isRecording || !mediaRecorder) return;
|
||
|
||
mediaRecorder.stop();
|
||
isRecording = false;
|
||
document.getElementById('recordingIndicator').style.display = 'none';
|
||
document.getElementById('startRecordBtn').style.display = 'flex';
|
||
document.getElementById('stopRecordBtn').style.display = 'none';
|
||
console.log("停止录音");
|
||
|
||
if (mediaRecorder.stream) {
|
||
mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||
}
|
||
}
|
||
|
||
// 上传音频到服务器
|
||
function uploadAudioToServer(audioBlob) {
|
||
console.log("开始上传音频到服务器");
|
||
document.getElementById('thinkingIndicator').style.display = 'flex';
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', audioBlob, 'recording.wav');
|
||
|
||
fetch('/api/xueban/upload-audio', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) throw new Error('服务器响应错误');
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
console.log("处理结果:", data);
|
||
document.getElementById('thinkingIndicator').style.display = 'none';
|
||
|
||
if (data.success) {
|
||
showResults(data.data);
|
||
} else {
|
||
alert('音频处理失败: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error("上传音频失败:", error);
|
||
document.getElementById('thinkingIndicator').style.display = 'none';
|
||
alert('上传音频失败: ' + error.message);
|
||
});
|
||
}
|
||
|
||
// 显示ASR识别结果和反馈
|
||
function showResults(data) {
|
||
const resultContainer = document.getElementById('resultContainer');
|
||
resultContainer.style.display = 'flex';
|
||
document.getElementById('asrResultText').textContent = data.asr_text || '未识别到内容';
|
||
document.getElementById('feedbackResultText').textContent = data.feedback_text || '无反馈内容';
|
||
|
||
if (data.audio_url) {
|
||
if (audioElement) audioElement.pause();
|
||
audioElement = new Audio(data.audio_url);
|
||
audioElement.onloadedmetadata = function() {
|
||
updateAudioTimeDisplay();
|
||
try { audioElement.play(); isPlaying = true; updatePlayButton(); }
|
||
catch (e) { console.error("自动播放失败:", e); }
|
||
document.getElementById('playAudioBtn').style.display = 'flex';
|
||
};
|
||
audioElement.ontimeupdate = function() { updateAudioProgress(); updateAudioTimeDisplay(); };
|
||
audioElement.onended = function() { isPlaying = false; updatePlayButton(); };
|
||
document.getElementById('playAudioBtn').onclick = togglePlayAudio;
|
||
document.getElementById('audioProgress').onclick = function(e) {
|
||
if (!audioElement) return;
|
||
const rect = this.getBoundingClientRect();
|
||
const clickPosition = (e.clientX - rect.left) / rect.width;
|
||
audioElement.currentTime = clickPosition * audioElement.duration;
|
||
};
|
||
}
|
||
}
|
||
|
||
// 音频播放控制函数
|
||
function togglePlayAudio() { if (!audioElement) return; isPlaying ? audioElement.pause() : audioElement.play(); isPlaying = !isPlaying; updatePlayButton(); }
|
||
function updatePlayButton() {
|
||
const btn = document.getElementById('playAudioBtn');
|
||
btn.innerHTML = isPlaying ? `
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M6 19H10V5H6V19ZM14 19H18V5H14V19Z" fill="white"/>
|
||
</svg>` : `
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M8 5V19L19 12L8 5Z" fill="white"/>
|
||
</svg>`;
|
||
}
|
||
function updateAudioProgress() { if (audioElement) document.getElementById('progressBar').style.width = `${(audioElement.currentTime/audioElement.duration)*100}%`; }
|
||
function updateAudioTimeDisplay() {
|
||
if (!audioElement) return;
|
||
const format = s => `${Math.floor(s/60).toString().padStart(2,'0')}:${Math.floor(s%60).toString().padStart(2,'0')}`;
|
||
document.getElementById('audioTime').textContent = `${format(audioElement.currentTime)} / ${format(audioElement.duration)}`;
|
||
}
|
||
|
||
// 模型配置映射
|
||
const modelConfig = {
|
||
shizuku: {
|
||
jsonPath: "https://unpkg.com/live2d-widget-model-shizuku@1.0.5/assets/shizuku.model.json"
|
||
},
|
||
koharu: {
|
||
jsonPath: "https://unpkg.com/live2d-widget-model-koharu@1.0.5/assets/koharu.model.json"
|
||
},
|
||
wanko: {
|
||
jsonPath: "https://unpkg.com/live2d-widget-model-wanko@1.0.5/assets/wanko.model.json"
|
||
}
|
||
};
|
||
|
||
let currentL2DWidget = null;
|
||
let modelElement = null;
|
||
|
||
// 获取URL参数
|
||
function getUrlParam(name) {
|
||
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
|
||
const r = window.location.search.substr(1).match(reg);
|
||
return r ? unescape(r[2]) : null;
|
||
}
|
||
|
||
// 初始化看板娘
|
||
function initL2DWidget(modelName) {
|
||
// 如果没有传入modelName,则从URL参数获取模型名称,默认使用shizuku
|
||
if (!modelName) {
|
||
modelName = getUrlParam('model') || 'shizuku';
|
||
}
|
||
console.log("切换模型: ", modelName);
|
||
const config = modelConfig[modelName];
|
||
if (!config) {
|
||
console.error("模型配置不存在: ", modelName);
|
||
return;
|
||
}
|
||
console.log("模型JSON路径: ", config.jsonPath);
|
||
|
||
// 彻底清理现有实例
|
||
if (currentL2DWidget) {
|
||
try {
|
||
currentL2DWidget.destroy();
|
||
console.log("旧模型实例已销毁");
|
||
} catch (e) {
|
||
console.error("销毁实例失败: ", e);
|
||
}
|
||
// 修复:强制移除所有canvas元素
|
||
const oldCanvases = document.querySelectorAll('canvas#l2dcanvas');
|
||
oldCanvases.forEach(canvas => canvas.remove());
|
||
currentL2DWidget = null;
|
||
modelElement = null;
|
||
}
|
||
|
||
// 创建新实例
|
||
try {
|
||
currentL2DWidget = L2Dwidget.init({
|
||
model: {
|
||
jsonPath: config.jsonPath,
|
||
scale: 1
|
||
},
|
||
display: {
|
||
position: "right",
|
||
width: 150,
|
||
height: 300,
|
||
hOffset: 0,
|
||
vOffset: -20
|
||
},
|
||
mobile: {
|
||
show: true,
|
||
scale: 0.5
|
||
},
|
||
react: {
|
||
opacityDefault: 0.7,
|
||
opacityOnHover: 0.8
|
||
}
|
||
});
|
||
// 保存模型DOM元素引用
|
||
// 修复:使用setTimeout确保DOM更新完成后再获取元素
|
||
setTimeout(() => {
|
||
modelElement = document.querySelector('canvas#l2dcanvas');
|
||
if (modelElement) {
|
||
console.log("新模型DOM元素已获取");
|
||
} else {
|
||
console.warn("未找到新模型canvas元素");
|
||
}
|
||
console.log("新模型初始化成功");
|
||
}, 100);
|
||
} catch (e) {
|
||
console.error("模型初始化失败: ", e);
|
||
}
|
||
}
|
||
|
||
// 监听模型选择变化
|
||
const modelSelect = document.getElementById('model-select');
|
||
if (modelSelect) {
|
||
// 设置当前选中项
|
||
const currentModel = getUrlParam('model') || 'shizuku';
|
||
modelSelect.value = currentModel;
|
||
|
||
modelSelect.addEventListener('change', function() {
|
||
console.log("选择模型: ", this.value);
|
||
// 通过URL参数刷新页面实现模型切换
|
||
window.location.search = '?model=' + this.value;
|
||
});
|
||
console.log("模型选择监听器已绑定");
|
||
} else {
|
||
console.error("未找到模型选择器元素");
|
||
}
|
||
|
||
// 初始加载默认模型
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initL2DWidget();
|
||
// 添加录音按钮事件绑定
|
||
document.getElementById('startRecordBtn').addEventListener('click', startRecording);
|
||
document.getElementById('stopRecordBtn').addEventListener('click', stopRecording);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |