dashboard.html 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>AI智能辅助教学系统</title>
  7. <!-- 引入 ECharts -->
  8. <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
  9. <style>
  10. :root {
  11. --primary-color: #4a90e2;
  12. --secondary-color: #f5f7fa;
  13. --text-color: #333;
  14. --sidebar-width: 250px;
  15. --header-height: 60px;
  16. }
  17. * {
  18. margin: 0;
  19. padding: 0;
  20. box-sizing: border-box;
  21. }
  22. body {
  23. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  24. background-color: var(--secondary-color);
  25. color: var(--text-color);
  26. display: flex;
  27. height: 100vh;
  28. overflow: hidden;
  29. }
  30. /* 侧边栏样式 */
  31. .sidebar {
  32. width: var(--sidebar-width);
  33. background-color: #2c3e50;
  34. color: #ecf0f1;
  35. display: flex;
  36. flex-direction: column;
  37. transition: all 0.3s;
  38. }
  39. .sidebar-header {
  40. height: var(--header-height);
  41. display: flex;
  42. align-items: center;
  43. justify-content: center;
  44. font-size: 20px;
  45. font-weight: bold;
  46. border-bottom: 1px solid #34495e;
  47. background-color: #1a252f;
  48. }
  49. .nav-links {
  50. list-style: none;
  51. padding-top: 20px;
  52. flex: 1;
  53. }
  54. .nav-links li {
  55. padding: 15px 20px;
  56. cursor: pointer;
  57. transition: background 0.3s;
  58. display: flex;
  59. align-items: center;
  60. }
  61. .nav-links li a {
  62. color: inherit;
  63. text-decoration: none;
  64. display: flex;
  65. align-items: center;
  66. }
  67. .nav-links li:hover,
  68. .nav-links li a:hover {
  69. background-color: #34495e;
  70. }
  71. .nav-links li.active {
  72. background-color: var(--primary-color);
  73. border-left: 4px solid #fff;
  74. }
  75. .nav-links li.active a { color: #fff; }
  76. .nav-links li i {
  77. margin-right: 10px;
  78. }
  79. /* 主体内容区域 */
  80. .main-content {
  81. flex: 1;
  82. display: flex;
  83. flex-direction: column;
  84. height: 100vh;
  85. }
  86. /* 顶部导航栏 */
  87. header {
  88. height: var(--header-height);
  89. background-color: #fff;
  90. box-shadow: 0 2px 5px rgba(0,0,0,0.05);
  91. display: flex;
  92. justify-content: space-between;
  93. align-items: center;
  94. padding: 0 20px;
  95. }
  96. .user-info {
  97. display: flex;
  98. align-items: center;
  99. cursor: pointer;
  100. position: relative;
  101. }
  102. .user-avatar {
  103. width: 35px;
  104. height: 35px;
  105. border-radius: 50%;
  106. background-color: var(--primary-color);
  107. color: white;
  108. display: flex;
  109. align-items: center;
  110. justify-content: center;
  111. margin-right: 10px;
  112. font-weight: bold;
  113. }
  114. .dropdown-menu {
  115. position: absolute;
  116. top: 50px;
  117. right: 0;
  118. background-color: #fff;
  119. border-radius: 4px;
  120. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  121. width: 120px;
  122. display: none;
  123. z-index: 1000;
  124. }
  125. .dropdown-menu.show {
  126. display: block;
  127. }
  128. .dropdown-menu a {
  129. display: block;
  130. padding: 10px 15px;
  131. text-decoration: none;
  132. color: #333;
  133. transition: background 0.2s;
  134. }
  135. .dropdown-menu a:hover {
  136. background-color: #f5f7fa;
  137. color: var(--primary-color);
  138. }
  139. /* 内容展示区 */
  140. .content-area {
  141. flex: 1;
  142. padding: 20px;
  143. overflow-y: auto;
  144. }
  145. /* 页面切换逻辑 */
  146. .page-section {
  147. display: none;
  148. animation: fadeIn 0.3s;
  149. }
  150. .page-section.active {
  151. display: block;
  152. }
  153. @keyframes fadeIn {
  154. from { opacity: 0; transform: translateY(10px); }
  155. to { opacity: 1; transform: translateY(0); }
  156. }
  157. /* 首页样式 */
  158. .welcome-card {
  159. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  160. color: white;
  161. padding: 40px;
  162. border-radius: 10px;
  163. text-align: center;
  164. margin-bottom: 20px;
  165. }
  166. .stats-grid {
  167. display: grid;
  168. grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  169. gap: 20px;
  170. margin-top: 20px;
  171. }
  172. .stat-card {
  173. background: white;
  174. padding: 20px;
  175. border-radius: 8px;
  176. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  177. text-align: center;
  178. }
  179. .stat-number {
  180. font-size: 32px;
  181. font-weight: bold;
  182. color: var(--primary-color);
  183. margin: 10px 0;
  184. }
  185. /* 每日训练样式 */
  186. .question-card {
  187. background: white;
  188. padding: 20px;
  189. border-radius: 8px;
  190. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  191. margin-bottom: 20px;
  192. }
  193. .question-title {
  194. font-size: 18px;
  195. margin-bottom: 15px;
  196. color: #2c3e50;
  197. }
  198. .options {
  199. list-style-type: none;
  200. }
  201. .options li {
  202. padding: 10px;
  203. border: 1px solid #eee;
  204. margin-bottom: 10px;
  205. border-radius: 4px;
  206. cursor: pointer;
  207. transition: all 0.2s;
  208. }
  209. .options li:hover {
  210. background-color: #f0f7ff;
  211. border-color: var(--primary-color);
  212. }
  213. .options li.selected {
  214. background-color: #e6f7ff;
  215. border-color: var(--primary-color);
  216. color: var(--primary-color);
  217. }
  218. /* 学情分析样式 */
  219. .chart-container {
  220. background: white;
  221. padding: 20px;
  222. border-radius: 8px;
  223. box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  224. height: 400px;
  225. }
  226. </style>
  227. </head>
  228. <body>
  229. <!-- 侧边栏 -->
  230. <div class="sidebar">
  231. <div class="sidebar-header">
  232. AI 教学系统
  233. </div>
  234. <ul class="nav-links">
  235. <li id="nav-home"><a href="/dashboard"><i>🏠</i> 首页</a></li>
  236. <li id="nav-ai"><a href="/ai_assistant"><i>🤖</i> AI 助手</a></li>
  237. <li id="nav-knowledge_qa"><a href="/knowledge_qa"><i>💬</i> 知识点询问</a></li>
  238. <li id="nav-knowledge_graph"><a href="/knowledge_graph"><i>🔗</i> 知识图谱</a></li>
  239. <li id="nav-training"><a href="/daily_training"><i>📚</i> 每日训练</a></li>
  240. <li id="nav-wrong_questions"><a href="/wrong_questions"><i>📝</i> 错题集</a></li>
  241. <li id="nav-analysis"><a href="/analysis"><i>📊</i> 学情分析</a></li>
  242. </ul>
  243. </div>
  244. <!-- 主体内容 -->
  245. <div class="main-content">
  246. <!-- 顶部导航 -->
  247. <header>
  248. <div class="page-title">
  249. <h2 id="current-page-title">首页</h2>
  250. </div>
  251. <div class="user-info" onclick="toggleDropdown()">
  252. <div class="user-avatar">{{ session['username'][0] }}</div>
  253. <span>{{ session['username'] }}</span>
  254. <div class="dropdown-menu" id="user-dropdown">
  255. <a href="/logout">退出登录</a>
  256. </div>
  257. </div>
  258. </header>
  259. <!-- 内容区域 -->
  260. <div class="content-area">
  261. <!-- 1. 首页内容 -->
  262. <div id="page-home" class="page-section active">
  263. <div class="welcome-card">
  264. <h1>欢迎回来,{{ session['username'] }}!</h1>
  265. <p>今天也是充满进步的一天,准备好开始学习了吗?</p>
  266. </div>
  267. <div class="stats-grid">
  268. <div class="stat-card">
  269. <h3>今日学习时长</h3>
  270. <div class="stat-number">45 分钟</div>
  271. <p>比昨日增加 12%</p>
  272. </div>
  273. <div class="stat-card">
  274. <h3>已完成习题</h3>
  275. <div class="stat-number">12 道</div>
  276. <p>正确率 85%</p>
  277. </div>
  278. <div class="stat-card">
  279. <h3>知识点掌握</h3>
  280. <div class="stat-number">72%</div>
  281. <p>进步显著</p>
  282. </div>
  283. </div>
  284. </div>
  285. <!-- 2. 每日训练内容(动态题库) -->
  286. <div id="page-training" class="page-section">
  287. <h3 style="margin-bottom: 16px;">习题训练 · 个性化推荐</h3>
  288. {% if training_result %}
  289. <div class="question-card" style="border-left: 4px solid var(--primary-color);">
  290. <div class="question-title">本次训练结果</div>
  291. <p>共 {{ training_result.total }} 题,答对 {{ training_result.correct }} 题,正确率 {{ training_result.accuracy }}%。</p>
  292. </div>
  293. {% endif %}
  294. {% if questions and questions|length > 0 and active_page == 'training' %}
  295. <form method="post" action="{{ url_for('daily_training') }}">
  296. {% for q in questions %}
  297. <div class="question-card">
  298. <div class="question-title">{{ loop.index }}. {{ q.content }}</div>
  299. <ul class="options">
  300. <li>
  301. <label>
  302. <input type="radio" name="q_{{ q.id }}" value="A" required>
  303. A. {{ q.option_a }}
  304. </label>
  305. </li>
  306. <li>
  307. <label>
  308. <input type="radio" name="q_{{ q.id }}" value="B">
  309. B. {{ q.option_b }}
  310. </label>
  311. </li>
  312. <li>
  313. <label>
  314. <input type="radio" name="q_{{ q.id }}" value="C">
  315. C. {{ q.option_c }}
  316. </label>
  317. </li>
  318. <li>
  319. <label>
  320. <input type="radio" name="q_{{ q.id }}" value="D">
  321. D. {{ q.option_d }}
  322. </label>
  323. </li>
  324. </ul>
  325. </div>
  326. <input type="hidden" name="question_ids" value="{{ q.id }}">
  327. {% endfor %}
  328. <div style="margin-top: 15px; text-align: right;">
  329. <button type="submit" style="padding: 8px 16px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer;">
  330. 提交答案
  331. </button>
  332. </div>
  333. </form>
  334. {% elif active_page == 'training' %}
  335. <p>题库暂无试题,请联系教师或管理员添加。</p>
  336. {% endif %}
  337. </div>
  338. <!-- 2b. 错题集内容 -->
  339. <div id="page-wrong_questions" class="page-section">
  340. <h3 style="margin-bottom: 16px;">错题集 · 重做巩固</h3>
  341. {% if questions and questions|length > 0 and active_page == 'wrong_questions' %}
  342. <form method="post" action="{{ url_for('daily_training') }}">
  343. {% for q in questions %}
  344. <div class="question-card">
  345. <div class="question-title">{{ loop.index }}. {{ q.content }}</div>
  346. <ul class="options">
  347. <li><label><input type="radio" name="q_{{ q.id }}" value="A" required> A. {{ q.option_a }}</label></li>
  348. <li><label><input type="radio" name="q_{{ q.id }}" value="B"> B. {{ q.option_b }}</label></li>
  349. <li><label><input type="radio" name="q_{{ q.id }}" value="C"> C. {{ q.option_c }}</label></li>
  350. <li><label><input type="radio" name="q_{{ q.id }}" value="D"> D. {{ q.option_d }}</label></li>
  351. </ul>
  352. </div>
  353. <input type="hidden" name="question_ids" value="{{ q.id }}">
  354. {% endfor %}
  355. <div style="margin-top: 15px; text-align: right;">
  356. <button type="submit" style="padding: 8px 16px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer;">提交答案</button>
  357. </div>
  358. </form>
  359. {% else %}
  360. {% if active_page == 'wrong_questions' %}
  361. <p>暂无错题,保持练习,加油!</p>
  362. <a href="/daily_training" style="color: var(--primary-color);">去每日训练</a>
  363. {% endif %}
  364. {% endif %}
  365. </div>
  366. <!-- 3. 学情分析内容 -->
  367. <div id="page-analysis" class="page-section">
  368. <h3 style="margin-bottom: 16px;">学情分析 · 知识点掌握情况</h3>
  369. <div class="chart-container" id="knowledge-chart"></div>
  370. <div style="margin-top: 20px; text-align: center; color: #666;">
  371. <p>基于您的答题记录生成 · 建议针对薄弱知识点加强练习</p>
  372. </div>
  373. </div>
  374. </div>
  375. </div>
  376. <script>
  377. // 页面切换逻辑
  378. function switchPage(pageId) {
  379. // 更新导航栏状态
  380. document.querySelectorAll('.nav-links li').forEach(li => {
  381. li.classList.remove('active');
  382. });
  383. document.getElementById('nav-' + pageId).classList.add('active');
  384. // 更新内容显示
  385. document.querySelectorAll('.page-section').forEach(section => {
  386. section.classList.remove('active');
  387. });
  388. const targetSection = document.getElementById('page-' + pageId);
  389. if (targetSection) targetSection.classList.add('active');
  390. // 更新标题
  391. const titles = {
  392. 'home': '首页',
  393. 'training': '每日训练',
  394. 'wrong_questions': '错题集',
  395. 'analysis': '学情分析'
  396. };
  397. document.getElementById('current-page-title').innerText = titles[pageId];
  398. // 如果切换到分析页,渲染图表
  399. if (pageId === 'analysis') {
  400. renderChart();
  401. }
  402. }
  403. // 用户下拉菜单切换
  404. function toggleDropdown() {
  405. const dropdown = document.getElementById('user-dropdown');
  406. dropdown.classList.toggle('show');
  407. }
  408. // 点击页面其他地方关闭下拉菜单
  409. window.onclick = function(event) {
  410. if (!event.target.matches('.user-info') && !event.target.matches('.user-info *')) {
  411. const dropdowns = document.getElementsByClassName("dropdown-menu");
  412. for (let i = 0; i < dropdowns.length; i++) {
  413. const openDropdown = dropdowns[i];
  414. if (openDropdown.classList.contains('show')) {
  415. openDropdown.classList.remove('show');
  416. }
  417. }
  418. }
  419. }
  420. // 习题选项选择逻辑
  421. function selectOption(element) {
  422. // 找到当前题目下的所有选项
  423. const parent = element.parentElement;
  424. const options = parent.getElementsByTagName('li');
  425. // 移除其他选项的选中状态
  426. for (let i = 0; i < options.length; i++) {
  427. options[i].classList.remove('selected');
  428. }
  429. // 添加当前选项的选中状态
  430. element.classList.add('selected');
  431. }
  432. // 渲染 ECharts 图表(使用后端传入的真实数据)
  433. function renderChart() {
  434. const chartDom = document.getElementById('knowledge-chart');
  435. const myChart = echarts.init(chartDom);
  436. const chartData = {{ chart_data | tojson | safe if chart_data is defined else '[]' }};
  437. const hasData = chartData && chartData.length > 0;
  438. const option = {
  439. title: {
  440. text: hasData ? '知识点掌握情况(正确率 %)' : '暂无答题数据',
  441. left: 'center'
  442. },
  443. tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
  444. legend: { orient: 'vertical', left: 'left' },
  445. color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452'],
  446. series: [{
  447. name: '掌握程度',
  448. type: 'pie',
  449. radius: '50%',
  450. data: hasData ? chartData.map(d => ({ value: d.value, name: d.name })) : [],
  451. emphasis: {
  452. itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' }
  453. }
  454. }]
  455. };
  456. myChart.setOption(option);
  457. window.addEventListener('resize', function() { myChart.resize(); });
  458. }
  459. // 初始化:根据后端传入的 active_page 决定显示哪个页面
  460. document.addEventListener('DOMContentLoaded', function() {
  461. const activePage = "{{ active_page }}";
  462. if (activePage) {
  463. switchPage(activePage);
  464. }
  465. });
  466. </script>
  467. </body>
  468. </html>