import Home from './components/Home';
import StudyMode from './components/StudyMode';
import TestMode from './components/TestMode';
+import GroupView from './components/GroupView';
function App() {
return (
<Route path="/" element={<Home />} />
<Route path="/study/:groupId" element={<StudyMode />} />
<Route path="/test/:groupId" element={<TestMode />} />
+ <Route path="/group/:groupId" element={<GroupView />} />
</Routes>
</Router>
);
--- /dev/null
+/* GroupView.css - 组别只读预览(上图下表) */
+
+.group-view-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ background-color: #f0f7ff;
+ min-height: 100vh;
+}
+
+.group-view-card {
+ display: flex;
+ flex-direction: column;
+ background: white;
+ border-radius: 20px;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+ width: 95%;
+ max-width: 1100px;
+ overflow: hidden;
+}
+
+.group-view-header {
+ text-align: center;
+ padding: 24px 20px 12px;
+}
+
+.group-view-header h1 {
+ font-size: 22px;
+ color: #1e40af;
+ margin: 0 0 6px;
+}
+
+.group-view-count {
+ color: #475569;
+ font-size: 14px;
+ margin: 0;
+}
+
+/* 思维导图(上方) */
+.group-image-section {
+ background: #fafafa;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 16px;
+ border-bottom: 1px solid #eee;
+}
+
+.group-image-section img {
+ max-width: 100%;
+ max-height: 520px;
+ object-fit: contain;
+}
+
+/* 词汇列表(下方,可横向滚动) */
+.group-table-wrap {
+ overflow-x: auto;
+ padding: 12px 16px 20px;
+}
+
+.group-word-table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+ min-width: 760px;
+}
+
+.group-word-table th {
+ background-color: #eff6ff;
+ color: #1e40af;
+ font-size: 14px;
+ padding: 12px 10px;
+ text-align: left;
+ border-bottom: 2px solid #dbeafe;
+ white-space: nowrap;
+}
+
+.group-word-table td {
+ padding: 12px 10px;
+ font-size: 14px;
+ color: #1f2937;
+ border-bottom: 1px solid #f1f5f9;
+ vertical-align: top;
+ word-break: break-word;
+}
+
+.group-word-table tr:hover td {
+ background-color: #f8fafc;
+}
+
+/* 列宽分配 */
+.group-word-table th:nth-child(1),
+.group-word-table td:nth-child(1) { width: 15%; font-weight: 600; color: #1d4ed8; }
+.group-word-table th:nth-child(2),
+.group-word-table td:nth-child(2) { width: 8%; }
+.group-word-table th:nth-child(3),
+.group-word-table td:nth-child(3) { width: 22%; }
+.group-word-table th:nth-child(4),
+.group-word-table td:nth-child(4) { width: 10%; color: #64748b; }
+.group-word-table th:nth-child(5),
+.group-word-table td:nth-child(5) { width: 20%; color: #64748b; font-size: 13px; }
+.group-word-table th:nth-child(6),
+.group-word-table td:nth-child(6) { width: 25%; color: #475569; font-size: 13px; }
+
+/* 返回首页按钮 */
+.back-home {
+ margin-top: 20px;
+ background: none;
+ border: none;
+ color: #64748b;
+ text-decoration: underline;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+/* 响应式 */
+@media (max-width: 768px) {
+ .group-view-card {
+ width: 98%;
+ }
+ .group-view-header h1 {
+ font-size: 20px;
+ }
+ .group-image-section img {
+ max-height: 360px;
+ }
+ .group-word-table th,
+ .group-word-table td {
+ padding: 10px 8px;
+ font-size: 13px;
+ }
+}
--- /dev/null
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import axios from 'axios';
+import './GroupView.css';
+
+const API_BASE = "http://124.222.157.124:1060";
+
+function GroupView() {
+ const { groupId } = useParams();
+ const navigate = useNavigate();
+ const [words, setWords] = useState([]);
+
+ useEffect(() => {
+ axios.get(`${API_BASE}/api/words/${groupId}`)
+ .then(res => setWords(res.data))
+ .catch(err => console.error("获取词汇失败:", err));
+ }, [groupId]);
+
+ // 组内所有单词共享同一张思维导图,取首词的 picture_id(兼容 picture_id==groupid 约定)
+ const pictureId = words.length > 0 ? words[0].picture_id : null;
+
+ if (words.length === 0) {
+ return (
+ <div className="group-view-container">
+ <div className="group-view-card">
+ <p className="loading">加载中...</p>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className="group-view-container">
+ <div className="group-view-card">
+ <div className="group-view-header">
+ <h1>Group {groupId} · 词汇预览</h1>
+ <p className="group-view-count">共 {words.length} 个单词(只读预览,不记录进度)</p>
+ </div>
+
+ {pictureId && (
+ <div className="group-image-section">
+ <img src={`${API_BASE}/api/picture/${pictureId}`} alt="mindmap" />
+ </div>
+ )}
+
+ <div className="group-table-wrap">
+ <table className="group-word-table">
+ <thead>
+ <tr>
+ <th>单词</th>
+ <th>词性</th>
+ <th>中文释义</th>
+ <th>词根</th>
+ <th>词缀</th>
+ <th>例句</th>
+ </tr>
+ </thead>
+ <tbody>
+ {words.map(w => (
+ <tr key={w.id}>
+ <td className="gw-word">{w.word}</td>
+ <td className="gw-pos">{w.part_of_speech}</td>
+ <td className="gw-cn">{w.chinese_meaning}</td>
+ <td className="gw-root">{w.word_root}</td>
+ <td className="gw-affix">{w.affix}</td>
+ <td className="gw-ex">{w.example_sentence}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <button className="back-home" onClick={() => navigate('/')}>返回首页</button>
+ </div>
+ );
+}
+
+export default GroupView;
const handleCellClick = (item, type) => {
const groupId = item.group.replace('Group ', '');
+ if (type === 'group') {
+ navigate(`/group/${groupId}`);
+ return;
+ }
if (type === 'study' && item.study && item.study.includes('请开始学习')) {
navigate(`/study/${groupId}`);
}
<tbody>
{filteredData.map((item, index) => (
<tr key={index}>
- <td className="group-name">{item.group}</td>
+ <td
+ className="group-name"
+ onClick={() => handleCellClick(item, 'group')}
+ style={{ cursor: 'pointer' }}
+ >
+ {item.group}
+ </td>
<td
className={getStatusClass(item.study)}
onClick={() => handleCellClick(item, 'study')}