- 确保异步提词操作结束后可靠恢复按钮状态 - 改进批量文件解析、清空操作与来源列表同步 - 防止复习提前完成,并拦截无效的会话跳转 - 校验 AI 翻译数据并保留 JSON 最外层结构 - 仅使用测验记录计算正确率和易错单词排名 - 限制自动播放设置范围,并在词表变化时停止播放 - 避免云端语音被中断后触发重复的降级朗读 - 改进 Service Worker 对临时网络故障的缓存回退 - 增加 AI 响应解析和测验统计相关测试
135 lines
5.7 KiB
HTML
135 lines
5.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN" data-theme="light">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<meta name="theme-color" content="#6366f1">
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<title>AI 英语助教</title>
|
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%236366f1'/%3E%3Ctext x='32' y='42' text-anchor='middle' font-size='34' font-family='Arial, sans-serif' fill='white'%3EAI%3C/text%3E%3C/svg%3E">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
|
integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g"
|
|
crossorigin="anonymous" referrerpolicy="no-referrer">
|
|
<script>
|
|
const ASSET_VERSION = '20260720-1';
|
|
window.APP_VERSION = ASSET_VERSION;
|
|
const stylesheets = [
|
|
'styles/variables.css',
|
|
'styles/base.css',
|
|
'styles/layout.css',
|
|
'styles/components.css',
|
|
'styles/pages/home.css',
|
|
'styles/pages/words.css',
|
|
'styles/pages/learn.css',
|
|
'styles/pages/quiz.css',
|
|
'styles/pages/review.css',
|
|
'styles/pages/stats.css',
|
|
'styles/pages/settings.css',
|
|
'styles/responsive.css',
|
|
'styles/pages/extract.css',
|
|
'styles/pages/mail-learn.css',
|
|
'styles/pages/reader.css'
|
|
];
|
|
document.write(stylesheets
|
|
.map(path => `<link rel="stylesheet" href="${path}?v=${ASSET_VERSION}">`)
|
|
.join(''));
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"
|
|
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC"
|
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<header id="mobile-header">
|
|
<button id="menu-btn" aria-label="菜单">
|
|
<i class="fas fa-bars"></i>
|
|
</button>
|
|
<h1>AI 英语助教</h1>
|
|
<button id="theme-toggle-mobile" aria-label="切换主题"><i class="fas fa-moon"></i></button>
|
|
</header>
|
|
|
|
<aside id="sidebar">
|
|
<div class="sidebar-header">
|
|
<span class="logo"><i class="fas fa-graduation-cap"></i></span>
|
|
<h2>AI 英语助教</h2>
|
|
</div>
|
|
<nav class="sidebar-nav">
|
|
<a href="#home" class="nav-item active" data-page="home">
|
|
<span class="nav-icon"><i class="fas fa-chart-pie"></i></span><span class="nav-text">仪表盘</span>
|
|
</a>
|
|
<a href="#words" class="nav-item" data-page="words">
|
|
<span class="nav-icon"><i class="fas fa-book"></i></span><span class="nav-text">单词库</span>
|
|
</a>
|
|
<a href="#quiz" class="nav-item" data-page="quiz">
|
|
<span class="nav-icon"><i class="fas fa-robot"></i></span><span class="nav-text">AI 测试</span>
|
|
</a>
|
|
<a href="#review" class="nav-item" data-page="review">
|
|
<span class="nav-icon"><i class="fas fa-redo-alt"></i></span><span class="nav-text">错题复习</span>
|
|
</a>
|
|
<a href="#extract" class="nav-item" data-page="extract">
|
|
<span class="nav-icon"><i class="fas fa-envelope"></i></span><span class="nav-text">邮件提词</span>
|
|
</a>
|
|
<a href="#mail-learn" class="nav-item" data-page="mail-learn">
|
|
<span class="nav-icon"><i class="fas fa-graduation-cap"></i></span><span class="nav-text">邮件学习</span>
|
|
</a>
|
|
<a href="#stats" class="nav-item" data-page="stats">
|
|
<span class="nav-icon"><i class="fas fa-chart-line"></i></span><span class="nav-text">统计</span>
|
|
</a>
|
|
<a href="#settings" class="nav-item" data-page="settings">
|
|
<span class="nav-icon"><i class="fas fa-cog"></i></span><span class="nav-text">设置</span>
|
|
</a>
|
|
</nav>
|
|
<div class="sidebar-footer">
|
|
<button id="theme-toggle">
|
|
<span id="theme-icon"><i class="fas fa-moon"></i></span>
|
|
<span>切换主题</span>
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<div id="overlay"></div>
|
|
|
|
<main id="main-content">
|
|
<div id="page-content"></div>
|
|
</main>
|
|
|
|
<div id="toast-container"></div>
|
|
</div>
|
|
|
|
<div id="modal-root"></div>
|
|
|
|
<script>
|
|
document.write(`<script type="module" src="js/main.js?v=${ASSET_VERSION}"><\/script>`);
|
|
</script>
|
|
<script>
|
|
// 注册 Service Worker:由它统一保证模块图不陈旧并提供离线回退。
|
|
// 注册失败(如旧版 iOS 不支持)不影响使用,上方 ?v= 版本号仍作兜底。
|
|
if ('serviceWorker' in navigator) {
|
|
const needsFirstControlledReload = !navigator.serviceWorker.controller;
|
|
let reloadingForServiceWorker = false;
|
|
|
|
if (needsFirstControlledReload) {
|
|
// 首次取得页面控制权后刷新一次,让首轮已加载的子资源也经过 SW 并写入离线缓存。
|
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
if (reloadingForServiceWorker) return;
|
|
reloadingForServiceWorker = true;
|
|
window.location.reload();
|
|
});
|
|
}
|
|
|
|
window.addEventListener('load', () => {
|
|
// updateViaCache: 'none' 让 sw.js 本身始终走网络校验,避免被 HTTP 缓存拖住策略更新。
|
|
navigator.serviceWorker.register('sw.js', { updateViaCache: 'none' }).catch(error => {
|
|
console.warn('Service Worker 注册失败,应用仍可正常使用:', error);
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|