feat: 娣诲姞缃戦〉鐗堝簲鐢ㄥ拰鑳岃鎺掑簭鍔熻兘
- 鍒涘缓Flask缃戦〉搴旂敤妗嗘灦(web_app.py) - 娣诲姞鑳岃鎺掑簭鍔熻兘锛氱煡璇嗙偣璇嗗埆鍜岄殢鏈烘帓搴?- 瀹炵幇杞洏鎶借儗鍔熻兘(鍩轰簬SVG) - 鍒涘缓鍓嶇椤甸潰锛氶椤靛拰鑳岃鎺掑簭椤甸潰 - 娣诲姞鍝嶅簲寮廋SS鏍峰紡 - 鍒涘缓鍚姩鑴氭湰(start_web.py) - 鏇存柊requirements.txt娣诲姞Flask渚濊禆 - 娣诲姞缃戦〉鐗堜娇鐢ㄨ鏄?README_WEB.md)
This commit is contained in:
285
static/js/recitation.js
Normal file
285
static/js/recitation.js
Normal file
@@ -0,0 +1,285 @@
|
||||
// 背诵排序功能脚本
|
||||
|
||||
let extractedItems = [];
|
||||
let sortedItems = [];
|
||||
let currentSpinIndex = 0;
|
||||
let isSpinning = false;
|
||||
|
||||
// 颜色配置 - 转盘使用不同颜色
|
||||
const colors = [
|
||||
'#667eea', '#764ba2', '#f093fb', '#f5576c',
|
||||
'#4facfe', '#00f2fe', '#43e97b', '#38f9d7',
|
||||
'#fa709a', '#fee140', '#30cfd0', '#330867'
|
||||
];
|
||||
|
||||
// DOM元素
|
||||
const textInput = document.getElementById('textInput');
|
||||
const extractBtn = document.getElementById('extractBtn');
|
||||
const extractedSection = document.getElementById('extractedSection');
|
||||
const itemsList = document.getElementById('itemsList');
|
||||
const itemCount = document.getElementById('itemCount');
|
||||
const sortBtn = document.getElementById('sortBtn');
|
||||
const wheelSection = document.getElementById('wheelSection');
|
||||
const wheel = document.getElementById('wheel');
|
||||
const spinBtn = document.getElementById('spinBtn');
|
||||
const currentItem = document.getElementById('currentItem');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const sortedList = document.getElementById('sortedList');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
|
||||
// 提取知识点
|
||||
extractBtn.addEventListener('click', async () => {
|
||||
const text = textInput.value.trim();
|
||||
|
||||
if (!text) {
|
||||
alert('请输入要处理的文本');
|
||||
return;
|
||||
}
|
||||
|
||||
extractBtn.disabled = true;
|
||||
extractBtn.textContent = '识别中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/extract', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ text })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
extractedItems = data.items;
|
||||
displayExtractedItems(extractedItems);
|
||||
extractedSection.style.display = 'block';
|
||||
textInput.disabled = true;
|
||||
} else {
|
||||
alert(data.message || '提取失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提取失败:', error);
|
||||
alert('提取失败,请检查网络连接');
|
||||
} finally {
|
||||
extractBtn.disabled = false;
|
||||
extractBtn.textContent = '识别知识点';
|
||||
}
|
||||
});
|
||||
|
||||
// 显示提取的项目
|
||||
function displayExtractedItems(items) {
|
||||
itemCount.textContent = items.length;
|
||||
itemsList.innerHTML = '';
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'item-tag';
|
||||
tag.textContent = `${index + 1}. ${item}`;
|
||||
itemsList.appendChild(tag);
|
||||
});
|
||||
}
|
||||
|
||||
// 随机排序
|
||||
sortBtn.addEventListener('click', async () => {
|
||||
if (extractedItems.length === 0) {
|
||||
alert('请先提取知识点');
|
||||
return;
|
||||
}
|
||||
|
||||
sortBtn.disabled = true;
|
||||
sortBtn.textContent = '排序中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/sort', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ items: extractedItems })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
sortedItems = data.items;
|
||||
displaySortedItems(sortedItems);
|
||||
createWheel(sortedItems);
|
||||
wheelSection.style.display = 'block';
|
||||
resultSection.style.display = 'block';
|
||||
currentSpinIndex = 0;
|
||||
} else {
|
||||
alert(data.message || '排序失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('排序失败:', error);
|
||||
alert('排序失败,请检查网络连接');
|
||||
} finally {
|
||||
sortBtn.disabled = false;
|
||||
sortBtn.textContent = '开始随机排序';
|
||||
}
|
||||
});
|
||||
|
||||
// 创建转盘 - 使用SVG实现更真实的转盘效果
|
||||
function createWheel(items) {
|
||||
wheel.innerHTML = '';
|
||||
|
||||
if (items.length === 0) return;
|
||||
|
||||
const anglePerItem = 360 / items.length;
|
||||
const radius = 190; // 转盘半径(考虑边框)
|
||||
const centerX = 200;
|
||||
const centerY = 200;
|
||||
|
||||
// 创建SVG转盘
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('width', '400');
|
||||
svg.setAttribute('height', '400');
|
||||
svg.setAttribute('viewBox', '0 0 400 400');
|
||||
svg.style.position = 'absolute';
|
||||
svg.style.top = '0';
|
||||
svg.style.left = '0';
|
||||
svg.style.width = '100%';
|
||||
svg.style.height = '100%';
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const startAngle = (index * anglePerItem - 90) * Math.PI / 180;
|
||||
const endAngle = ((index + 1) * anglePerItem - 90) * Math.PI / 180;
|
||||
|
||||
const x1 = centerX + radius * Math.cos(startAngle);
|
||||
const y1 = centerY + radius * Math.sin(startAngle);
|
||||
const x2 = centerX + radius * Math.cos(endAngle);
|
||||
const y2 = centerY + radius * Math.sin(endAngle);
|
||||
|
||||
const largeArcFlag = anglePerItem > 180 ? 1 : 0;
|
||||
|
||||
const pathData = [
|
||||
`M ${centerX} ${centerY}`,
|
||||
`L ${x1} ${y1}`,
|
||||
`A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
|
||||
'Z'
|
||||
].join(' ');
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', pathData);
|
||||
const colorIndex = index % colors.length;
|
||||
path.setAttribute('fill', colors[colorIndex]);
|
||||
path.setAttribute('stroke', '#fff');
|
||||
path.setAttribute('stroke-width', '2');
|
||||
|
||||
svg.appendChild(path);
|
||||
|
||||
// 添加文本
|
||||
const midAngle = (startAngle + endAngle) / 2;
|
||||
const textRadius = radius * 0.7;
|
||||
const textX = centerX + textRadius * Math.cos(midAngle);
|
||||
const textY = centerY + textRadius * Math.sin(midAngle);
|
||||
|
||||
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
text.setAttribute('x', textX);
|
||||
text.setAttribute('y', textY);
|
||||
text.setAttribute('text-anchor', 'middle');
|
||||
text.setAttribute('fill', 'white');
|
||||
text.setAttribute('font-size', items.length > 8 ? '12' : '14');
|
||||
text.setAttribute('font-weight', 'bold');
|
||||
text.setAttribute('transform', `rotate(${(midAngle * 180 / Math.PI + 90)}, ${textX}, ${textY})`);
|
||||
|
||||
const displayText = item.length > 12 ? item.substring(0, 12) + '...' : item;
|
||||
text.textContent = displayText;
|
||||
|
||||
svg.appendChild(text);
|
||||
});
|
||||
|
||||
wheel.appendChild(svg);
|
||||
}
|
||||
|
||||
// 转动转盘
|
||||
spinBtn.addEventListener('click', () => {
|
||||
if (isSpinning || sortedItems.length === 0) return;
|
||||
|
||||
isSpinning = true;
|
||||
spinBtn.disabled = true;
|
||||
currentItem.textContent = '转盘中...';
|
||||
|
||||
// 随机选择一个索引(添加多圈旋转效果)
|
||||
const randomIndex = Math.floor(Math.random() * sortedItems.length);
|
||||
const spins = 3; // 转3圈
|
||||
const anglePerItem = 360 / sortedItems.length;
|
||||
// 计算目标角度:多转几圈 + 指向选中项
|
||||
const targetAngle = spins * 360 + (360 - (randomIndex * anglePerItem) - anglePerItem / 2);
|
||||
|
||||
// 获取当前角度
|
||||
const svg = wheel.querySelector('svg');
|
||||
const currentAngle = getCurrentRotation(svg);
|
||||
|
||||
// 计算总旋转角度(考虑当前角度)
|
||||
const totalRotation = currentAngle + targetAngle;
|
||||
|
||||
svg.style.transform = `rotate(${totalRotation}deg)`;
|
||||
|
||||
// 转盘停止后显示结果
|
||||
setTimeout(() => {
|
||||
currentItem.textContent = `${randomIndex + 1}. ${sortedItems[randomIndex]}`;
|
||||
currentSpinIndex = randomIndex;
|
||||
isSpinning = false;
|
||||
spinBtn.disabled = false;
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// 获取当前旋转角度
|
||||
function getCurrentRotation(element) {
|
||||
const style = window.getComputedStyle(element);
|
||||
const transform = style.transform;
|
||||
if (transform === 'none') return 0;
|
||||
|
||||
const matrix = new DOMMatrix(transform);
|
||||
const angle = Math.atan2(matrix.b, matrix.a) * (180 / Math.PI);
|
||||
return angle;
|
||||
}
|
||||
|
||||
// 显示排序结果
|
||||
function displaySortedItems(items) {
|
||||
sortedList.innerHTML = '';
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'sorted-item';
|
||||
|
||||
const numberSpan = document.createElement('span');
|
||||
numberSpan.className = 'sorted-item-number';
|
||||
numberSpan.textContent = index + 1;
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.className = 'sorted-item-text';
|
||||
textSpan.textContent = item;
|
||||
|
||||
itemDiv.appendChild(numberSpan);
|
||||
itemDiv.appendChild(textSpan);
|
||||
sortedList.appendChild(itemDiv);
|
||||
});
|
||||
}
|
||||
|
||||
// 重置
|
||||
resetBtn.addEventListener('click', () => {
|
||||
extractedItems = [];
|
||||
sortedItems = [];
|
||||
currentSpinIndex = 0;
|
||||
|
||||
textInput.value = '';
|
||||
textInput.disabled = false;
|
||||
|
||||
extractedSection.style.display = 'none';
|
||||
wheelSection.style.display = 'none';
|
||||
resultSection.style.display = 'none';
|
||||
|
||||
wheel.innerHTML = '';
|
||||
const svg = wheel.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
currentItem.textContent = '';
|
||||
|
||||
isSpinning = false;
|
||||
spinBtn.disabled = false;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user