diff --git a/.DS_Store b/.DS_Store index 04054d9..53fdf9b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/dist.zip b/dist.zip index 3197a8f..d5163d7 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/index.html b/index.html index 1c9e6d8..596e08c 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - iMeeting + iMeeting - 智能会议助手
diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 0000000..c176d00 Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..cc9275d --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + AI + + diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/ContentViewer.jsx b/src/components/ContentViewer.jsx index bf81e75..d7fcbc8 100644 --- a/src/components/ContentViewer.jsx +++ b/src/components/ContentViewer.jsx @@ -1,11 +1,8 @@ import React from 'react'; import { Tabs } from 'antd'; import { FileText, Brain } from 'lucide-react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import rehypeRaw from 'rehype-raw'; -import rehypeSanitize from 'rehype-sanitize'; import MindMap from './MindMap'; +import MarkdownRenderer from './MarkdownRenderer'; import './ContentViewer.css'; const { TabPane } = Tabs; @@ -48,16 +45,11 @@ const ContentViewer = ({ {summaryActions &&
{summaryActions}
}
- {content ? ( - - {content} - - ) : ( -
{emptyMessage}
- )} +
diff --git a/src/components/MarkdownEditor.jsx b/src/components/MarkdownEditor.jsx index 644b941..ff7b795 100644 --- a/src/components/MarkdownEditor.jsx +++ b/src/components/MarkdownEditor.jsx @@ -2,10 +2,7 @@ import React, { useState, useRef, useMemo } from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { EditorView } from '@codemirror/view'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import rehypeRaw from 'rehype-raw'; -import rehypeSanitize from 'rehype-sanitize'; +import MarkdownRenderer from './MarkdownRenderer'; import './MarkdownEditor.css'; const MarkdownEditor = ({ @@ -193,14 +190,11 @@ const MarkdownEditor = ({ {showPreview ? ( -
- - {value || '*暂无内容*'} - -
+ ) : ( { + if (!content || content.trim() === '') { + return
{emptyMessage}
; + } + + return ( +
+ + {content} + +
+ ); +}; + +export default MarkdownRenderer; diff --git a/src/components/MeetingTimeline.jsx b/src/components/MeetingTimeline.jsx index ceff9ea..b3fd4dc 100644 --- a/src/components/MeetingTimeline.jsx +++ b/src/components/MeetingTimeline.jsx @@ -1,13 +1,10 @@ import React, { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { Clock, Users, FileText, User, Edit, Calendar , Trash2, MoreVertical } from 'lucide-react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import rehypeRaw from 'rehype-raw'; -import rehypeSanitize from 'rehype-sanitize'; import TagDisplay from './TagDisplay'; import ConfirmDialog from './ConfirmDialog'; import Dropdown from './Dropdown'; +import MarkdownRenderer from './MarkdownRenderer'; import tools from '../utils/tools'; import './MeetingTimeline.css'; @@ -151,14 +148,10 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore 会议摘要
-
- - {tools.truncateSummary(meeting.summary)} - -
+ {shouldShowMoreButton(meeting.summary) && (
点击查看完整摘要 diff --git a/src/config/api.js b/src/config/api.js index b13d19a..becdf26 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -38,6 +38,7 @@ const API_CONFIG = { SYSTEM_CONFIG: '/api/admin/system-config', DASHBOARD_STATS: '/api/admin/dashboard/stats', ONLINE_USERS: '/api/admin/online-users', + USER_STATS: '/api/admin/user-stats', KICK_USER: (userId) => `/api/admin/kick-user/${userId}`, TASKS_MONITOR: '/api/admin/tasks/monitor', SYSTEM_RESOURCES: '/api/admin/system/resources' diff --git a/src/pages/AdminDashboard.css b/src/pages/AdminDashboard.css index cacc4b7..bf269d4 100644 --- a/src/pages/AdminDashboard.css +++ b/src/pages/AdminDashboard.css @@ -412,6 +412,46 @@ background: #94a3b8; } +/* 全宽面板 */ +.admin-panel.full-width { + margin-bottom: 2rem; +} + +/* 用户列表表格 */ +.users-table { + width: 100%; + border-collapse: collapse; +} + +.users-table thead { + background: #f8fafc; + border-bottom: 2px solid #e2e8f0; +} + +.users-table th { + padding: 1rem; + text-align: left; + font-size: 0.875rem; + font-weight: 600; + color: #475569; + white-space: nowrap; +} + +.users-table tbody tr { + border-bottom: 1px solid #e2e8f0; + transition: background-color 0.2s; +} + +.users-table tbody tr:hover { + background: #f8fafc; +} + +.users-table td { + padding: 1rem; + font-size: 0.875rem; + color: #1e293b; +} + /* 响应式设计 */ @media (max-width: 768px) { .stats-grid { diff --git a/src/pages/AdminDashboard.jsx b/src/pages/AdminDashboard.jsx index 25d7314..1576a7b 100644 --- a/src/pages/AdminDashboard.jsx +++ b/src/pages/AdminDashboard.jsx @@ -43,6 +43,7 @@ const AdminDashboard = ({ user, onLogout }) => { // 统计数据 const [stats, setStats] = useState(null); const [onlineUsers, setOnlineUsers] = useState([]); + const [usersList, setUsersList] = useState([]); const [tasks, setTasks] = useState([]); const [resources, setResources] = useState(null); const [loading, setLoading] = useState(true); @@ -127,6 +128,7 @@ const AdminDashboard = ({ user, onLogout }) => { await Promise.all([ fetchStats(), fetchOnlineUsers(), + fetchUsersList(), fetchTasks(), fetchResources() ]); @@ -161,6 +163,17 @@ const AdminDashboard = ({ user, onLogout }) => { } }; + const fetchUsersList = async () => { + try { + const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.USER_STATS)); + if (response.code === '200') { + setUsersList(response.data.users || []); + } + } catch (err) { + console.error('获取用户列表失败:', err); + } + }; + const fetchTasks = async () => { try { const params = new URLSearchParams(); @@ -395,6 +408,45 @@ const AdminDashboard = ({ user, onLogout }) => {
)} + {/* 用户列表 */} +
+
+

用户列表 ({usersList.length})

+
+
+ {usersList.length === 0 ? ( +
暂无用户数据
+ ) : ( + + + + + + + + + + + + + + {usersList.map(u => ( + + + + + + + + + + ))} + +
ID用户名姓名注册时间最新登录时间会议数量会议时长
{u.user_id}{u.username}{u.caption}{u.created_at ? new Date(u.created_at).toLocaleString('zh-CN') : '-'}{u.last_login_time ? new Date(u.last_login_time).toLocaleString('zh-CN') : '-'}{u.meeting_count || 0}{u.total_duration_formatted || '-'}
+ )} +
+
+
{/* 在线用户列表 */}
diff --git a/src/pages/HomePage.css b/src/pages/HomePage.css index e9bb7b0..0b9ee87 100644 --- a/src/pages/HomePage.css +++ b/src/pages/HomePage.css @@ -304,6 +304,10 @@ .form-group input[type="text"]:focus, .form-group input[type="password"]:focus { outline: none; border-color: #8a63d2; box-shadow: 0 0 0 4px rgba(111, 66, 193, 0.1); } .form-group.password-group { position: relative; margin-bottom: 0.5rem; } +.password-input-wrapper { position: relative; display: flex; align-items: center; } +.password-input-wrapper input { width: 100%; padding-right: 45px; } +.password-toggle-btn { position: absolute; right: 12px; background: none; border: none; color: #888; cursor: pointer; padding: 8px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: all 0.2s ease; } +.password-toggle-btn:hover { background: #f1f3f5; color: #333; } .remember-me-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; font-weight: 400; color: #555; font-size: 0.9rem; margin-top: 0.5rem; align-self: flex-end; } .remember-me-label input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--accent-color); } .remember-me-label span { user-select: none; } diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 3134479..d0a5fba 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Brain, Users, Calendar, TrendingUp, X, User, Lock, Library, Download, LogIn } from 'lucide-react'; +import { Brain, Users, Calendar, TrendingUp, X, User, Lock, Library, Download, LogIn, Eye, EyeOff } from 'lucide-react'; import { Link } from 'react-router-dom'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; @@ -11,6 +11,7 @@ const HomePage = ({ onLogin }) => { const [loginError, setLoginError] = useState(''); const [isLoading, setIsLoading] = useState(false); const [rememberMe, setRememberMe] = useState(false); + const [showPassword, setShowPassword] = useState(false); // 组件挂载时,从localStorage读取保存的用户名 useEffect(() => { @@ -181,15 +182,25 @@ const HomePage = ({ onLogin }) => { 密码 - +
+ + +