feat: 娣诲姞缃戦〉鐗堝簲鐢ㄥ拰鑳岃鎺掑簭鍔熻兘

- 鍒涘缓Flask缃戦〉搴旂敤妗嗘灦(web_app.py)
- 娣诲姞鑳岃鎺掑簭鍔熻兘锛氱煡璇嗙偣璇嗗埆鍜岄殢鏈烘帓搴?- 瀹炵幇杞洏鎶借儗鍔熻兘(鍩轰簬SVG)
- 鍒涘缓鍓嶇椤甸潰锛氶椤靛拰鑳岃鎺掑簭椤甸潰
- 娣诲姞鍝嶅簲寮廋SS鏍峰紡
- 鍒涘缓鍚姩鑴氭湰(start_web.py)
- 鏇存柊requirements.txt娣诲姞Flask渚濊禆
- 娣诲姞缃戦〉鐗堜娇鐢ㄨ鏄?README_WEB.md)
This commit is contained in:
赵杰 Jie Zhao (雄狮汽车科技)
2025-11-02 20:44:19 +08:00
parent 14521369b9
commit 8b5063a092
9 changed files with 1258 additions and 1 deletions

212
static/css/recitation.css Normal file
View File

@@ -0,0 +1,212 @@
.recitation-container {
max-width: 900px;
margin: 0 auto;
}
.input-section,
.extracted-section,
.wheel-section,
.result-section {
margin-bottom: 40px;
padding: 30px;
background: #f8f9fa;
border-radius: 15px;
}
.input-section h2,
.extracted-section h2,
.wheel-section h2,
.result-section h2 {
font-size: 1.8em;
margin-bottom: 15px;
color: #333;
}
.hint,
.info {
color: #666;
margin-bottom: 20px;
line-height: 1.6;
}
.info span {
color: #667eea;
font-weight: bold;
}
.text-input {
width: 100%;
padding: 15px;
border: 2px solid #e9ecef;
border-radius: 10px;
font-size: 1em;
font-family: inherit;
resize: vertical;
margin-bottom: 20px;
transition: border-color 0.3s;
}
.text-input:focus {
outline: none;
border-color: #667eea;
}
.items-list {
max-height: 400px;
overflow-y: auto;
padding: 15px;
background: white;
border-radius: 10px;
margin-bottom: 20px;
}
.item-tag {
display: inline-block;
padding: 8px 15px;
margin: 5px;
background: #667eea;
color: white;
border-radius: 20px;
font-size: 0.9em;
}
/* 转盘样式 */
.wheel-container {
position: relative;
width: 400px;
height: 400px;
margin: 40px auto;
}
.wheel {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
border: 8px solid #667eea;
background: white;
overflow: hidden;
transition: transform 3s cubic-bezier(0.17, 0.67, 0.12, 0.99);
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}
.wheel svg {
transition: transform 3s cubic-bezier(0.17, 0.67, 0.12, 0.99);
}
.wheel-pointer {
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 15px solid transparent;
border-right: 15px solid transparent;
border-top: 30px solid #667eea;
z-index: 10;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}
.btn-spin {
display: block;
margin: 30px auto;
padding: 15px 50px;
font-size: 1.2em;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s;
}
.btn-spin:hover {
transform: scale(1.05);
box-shadow: 0 5px 20px rgba(245, 87, 108, 0.4);
}
.btn-spin:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.current-item {
text-align: center;
padding: 20px;
background: white;
border-radius: 10px;
margin-top: 20px;
font-size: 1.3em;
font-weight: bold;
color: #667eea;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
/* 排序结果列表 */
.sorted-list {
background: white;
border-radius: 10px;
padding: 20px;
max-height: 500px;
overflow-y: auto;
}
.sorted-item {
padding: 15px;
margin: 10px 0;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
display: flex;
align-items: center;
transition: all 0.3s;
}
.sorted-item:hover {
background: #e9ecef;
transform: translateX(5px);
}
.sorted-item-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 35px;
height: 35px;
background: #667eea;
color: white;
border-radius: 50%;
font-weight: bold;
margin-right: 15px;
flex-shrink: 0;
}
.sorted-item-text {
flex: 1;
color: #333;
}
/* 响应式设计 */
@media (max-width: 768px) {
.wheel-container {
width: 300px;
height: 300px;
}
.wheel-item {
font-size: 0.7em;
}
.input-section,
.extracted-section,
.wheel-section,
.result-section {
padding: 20px;
}
}

179
static/css/style.css Normal file
View File

@@ -0,0 +1,179 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.1em;
opacity: 0.9;
}
.nav {
display: flex;
background: #f8f9fa;
padding: 0;
border-bottom: 2px solid #e9ecef;
}
.nav-item {
flex: 1;
padding: 15px 20px;
text-align: center;
text-decoration: none;
color: #666;
transition: all 0.3s;
border-bottom: 3px solid transparent;
}
.nav-item:hover {
background: #e9ecef;
color: #667eea;
}
.nav-item.active {
color: #667eea;
border-bottom-color: #667eea;
font-weight: bold;
}
.main {
padding: 40px 30px;
min-height: 500px;
}
.welcome-section {
text-align: center;
}
.welcome-section h2 {
font-size: 2em;
margin-bottom: 20px;
color: #333;
}
.welcome-section p {
font-size: 1.1em;
color: #666;
margin-bottom: 40px;
}
.feature-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-top: 40px;
}
.card {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
text-align: center;
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.card-icon {
font-size: 3em;
margin-bottom: 20px;
}
.card h3 {
font-size: 1.5em;
margin-bottom: 15px;
color: #333;
}
.card p {
color: #666;
margin-bottom: 20px;
line-height: 1.6;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 25px;
font-size: 1em;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
.footer {
background: #f8f9fa;
padding: 20px;
text-align: center;
color: #666;
border-top: 1px solid #e9ecef;
}
/* ÏìӦʽÉè¼Æ */
@media (max-width: 768px) {
.header h1 {
font-size: 2em;
}
.main {
padding: 20px 15px;
}
.feature-cards {
grid-template-columns: 1fr;
}
}

285
static/js/recitation.js Normal file
View 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;
});