diff --git a/.DS_Store b/.DS_Store index d4c681b..e9df439 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Dockerfile b/Dockerfile index 6e389bb..5eddf73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # 生产环境服务器 - PM2版本 -FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/node:18-alpine +FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/node:18-slim # 设置工作目录 WORKDIR /app diff --git a/deploy-prod-nobuild.sh b/deploy-prod-nobuild.sh new file mode 100755 index 0000000..e2d4340 --- /dev/null +++ b/deploy-prod-nobuild.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +echo "🚀 开始部署iMeeting前端服务(PM2模式)..." + +# 手动构建dist目录 +if [ ! -d "dist" ]; then + echo "❌ 构建失败!dist目录未生成" + exit 1 +fi + +echo "✅ 前端构建完成,开始Docker部署..." + +# 创建日志目录 +mkdir -p logs + +# 停止并删除现有容器 +echo "📦 停止现有容器..." +docker-compose -f docker-compose.prod.yml down + +# 构建新镜像 +echo "🔨 构建Docker镜像..." +docker-compose -f docker-compose.prod.yml build --no-cache + +# 启动服务 +echo "▶️ 启动PM2服务..." +docker-compose -f docker-compose.prod.yml up -d + +# 检查服务状态 +echo "🔍 检查服务状态..." +sleep 15 +docker-compose -f docker-compose.prod.yml ps + +# 检查PM2进程状态 +echo "🔄 检查PM2进程状态..." +docker exec imeeting-frontend pm2 list + +# 检查健康状态 +echo "🏥 检查健康状态..." +curl -f http://localhost:3001/health && echo "✅ 前端服务健康检查通过" || echo "❌ 前端服务健康检查失败" + +echo "" +echo "🎉 部署完成!" +echo "📱 前端服务访问地址: http://localhost:3001" +echo "📊 查看日志: docker-compose -f docker-compose.prod.yml logs -f" +echo "📈 查看PM2状态: docker exec imeeting-frontend pm2 monit" +echo "📋 查看PM2进程: docker exec imeeting-frontend pm2 list" +echo "🛑 停止服务: docker-compose -f docker-compose.prod.yml down" +echo "" +echo "💡 提示:PM2模式特性:" +echo " ✅ 集群模式(2个实例)" +echo " ✅ 自动重启和故障恢复" +echo " ✅ 内存限制保护(1GB)" +echo " ✅ 详细日志管理" +echo " ✅ 进程监控和健康检查" \ No newline at end of file diff --git a/dist.zip b/dist.zip new file mode 100644 index 0000000..e6fa54c Binary files /dev/null and b/dist.zip differ diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f3bc752..32bce59 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,7 +3,6 @@ services: build: context: . dockerfile: Dockerfile - container_name: imeeting-frontend ports: - "3001:3001" environment: @@ -14,6 +13,9 @@ services: # 挂载日志目录到宿主机 - ./logs:/app/logs restart: unless-stopped + container_name: imeeting-frontend + extra_hosts: + - "host.docker.internal:host-gateway" healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/health", "||", "exit", "1"] interval: 30s diff --git a/server.js b/server.js index ff45212..92e3db2 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ const app = express(); const PORT = process.env.PORT; const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:8000"; + // 健康检查 app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); @@ -39,6 +40,7 @@ app.use(express.static(path.join(__dirname, "dist"), { // SPA路由处理 - 必须放在最后 app.get("*", (req, res) => { + console.log("Proxying request to:", BACKEND_URL); res.sendFile(path.join(__dirname, "dist", "index.html")); }); diff --git a/src/App.jsx b/src/App.jsx index cce8f5a..88ffb2c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import Dashboard from './pages/Dashboard'; import MeetingDetails from './pages/MeetingDetails'; import CreateMeeting from './pages/CreateMeeting'; import EditMeeting from './pages/EditMeeting'; +import AdminManagement from './pages/AdminManagement'; import './App.css'; function App() { @@ -77,6 +78,9 @@ function App() { : } /> + : + } /> diff --git a/src/components/admin/SystemConfiguration.jsx b/src/components/admin/SystemConfiguration.jsx new file mode 100644 index 0000000..85e3253 --- /dev/null +++ b/src/components/admin/SystemConfiguration.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const SystemConfiguration = () => { + return ( +
+

系统配置

+

系统相关配置项。

+
+ ); +}; + +export default SystemConfiguration; diff --git a/src/components/admin/UserManagement.css b/src/components/admin/UserManagement.css new file mode 100644 index 0000000..5aa8f13 --- /dev/null +++ b/src/components/admin/UserManagement.css @@ -0,0 +1,205 @@ +/* UserManagement.css */ +.user-management .toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.users-table { + width: 100%; + border-collapse: collapse; + background: #fff; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.users-table th, .users-table td { + border-bottom: 1px solid #e2e8f0; + padding: 1rem; + text-align: left; +} + +.users-table th { + background: #f8fafc; + font-weight: 600; + color: #475569; +} + +.users-table .action-btn { + padding: 0.25rem; + border-radius: 4px; + border: none; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + font-size: 0.875rem; + transition: all 0.3s ease; + color: #64748b; +} + +.users-table .action-btn:last-child { + margin-right: 0; +} + +.users-table .action-btn:hover { + transform: translateY(-1px); + background: #f1f5f9; + border-radius: 4px; +} + +.users-table .btn-danger { + color: #ef4444; +} + +.users-table .btn-danger:hover { + background: #fef2f2; + color: #dc2626; +} + +.users-table .btn-warning { + color: #f59e0b; +} + +.users-table .btn-warning:hover { + background: #fffbeb; + color: #d97706; +} + +.action-cell { + white-space: nowrap; + width: 80px; + text-align: center; +} + +.pagination { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1.5rem; +} + +.pagination button { + padding: 0.5rem 1rem; + border-radius: 8px; + border: 1px solid #e2e8f0; + background: white; + cursor: pointer; + transition: all 0.3s ease; +} + +.pagination button:hover:not(:disabled) { + background: #f8fafc; + transform: translateY(-1px); +} + +.pagination button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.pagination span { + margin: 0 1rem; + color: #64748b; +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: white; + padding: 2rem; + border-radius: 12px; + width: 90%; + max-width: 500px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); +} + +.modal-content h2 { + margin-top: 0; + margin-bottom: 1.5rem; + color: #1e293b; + font-size: 1.5rem; + font-weight: 600; +} + +.modal-content .form-group { + margin-bottom: 1rem; +} + +.modal-content .form-group label { + display: block; + margin-bottom: 0.5rem; + color: #374151; + font-weight: 500; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 1rem; + margin-top: 2rem; +} + +.error-message { + color: #ef4444; + margin-bottom: 1rem; + padding: 0.75rem; + background: #fef2f2; + border: 1px solid #fecaca; + border-radius: 8px; + font-size: 0.9rem; +} + +.info-note { + background: #f0f9ff; + border: 1px solid #bae6fd; + border-radius: 8px; + padding: 0.75rem; + margin-bottom: 1rem; +} + +.info-note p { + margin: 0; + color: #0369a1; + font-size: 0.9rem; +} + +/* Button styles for confirmations */ +.btn-danger { + background: #ef4444; + color: white; + border: none; +} + +.btn-danger:hover { + background: #dc2626; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); +} + +.btn-warning { + background: #f59e0b; + color: white; + border: none; +} + +.btn-warning:hover { + background: #d97706; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); +} diff --git a/src/components/admin/UserManagement.jsx b/src/components/admin/UserManagement.jsx new file mode 100644 index 0000000..f634b2c --- /dev/null +++ b/src/components/admin/UserManagement.jsx @@ -0,0 +1,266 @@ +import React, { useState, useEffect } from 'react'; +import apiClient from '../../utils/apiClient'; +import { buildApiUrl, API_ENDPOINTS } from '../../config/api'; +import { Plus, Edit, Trash2, KeyRound } from 'lucide-react'; +import './UserManagement.css'; + +const UserManagement = () => { + const [users, setUsers] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [showAddUserModal, setShowAddUserModal] = useState(false); + const [showEditUserModal, setShowEditUserModal] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [showResetConfirm, setShowResetConfirm] = useState(false); + const [processingUser, setProcessingUser] = useState(null); + const [newUser, setNewUser] = useState({ username: '', caption: '', email: '', role_id: 2 }); + const [editingUser, setEditingUser] = useState(null); + + useEffect(() => { + fetchUsers(); + }, [page, pageSize]); + + const fetchUsers = async () => { + setLoading(true); + try { + const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.USERS.LIST}?page=${page}&size=${pageSize}`)); + setUsers(response.data.users); + setTotal(response.data.total); + } catch (err) { + setError('无法加载用户列表'); + } finally { + setLoading(false); + } + }; + + const handleAddUser = async (e) => { + e.preventDefault(); + try { + await apiClient.post(buildApiUrl(API_ENDPOINTS.USERS.CREATE), newUser); + setShowAddUserModal(false); + setNewUser({ username: '', caption: '', email: '', role_id: 2 }); + setError(''); // Clear any previous errors + fetchUsers(); // Refresh user list + } catch (err) { + console.error('Error adding user:', err); + setError(err.response?.data?.detail || '新增用户失败'); + } + }; + + const handleUpdateUser = async (e) => { + e.preventDefault(); + try { + // 只发送模型期望的字段 + const updateData = { + caption: editingUser.caption, + email: editingUser.email, + role_id: editingUser.role_id + }; + + // 只有当用户名被修改时才发送 + if (editingUser.username && editingUser.username.trim()) { + updateData.username = editingUser.username; + } + + console.log('Sending update data:', updateData); // 调试用 + + await apiClient.put(buildApiUrl(API_ENDPOINTS.USERS.UPDATE(editingUser.user_id)), updateData); + setShowEditUserModal(false); + setError(''); // Clear any previous errors + fetchUsers(); // Refresh user list + } catch (err) { + console.error('Error updating user:', err); + console.error('Error response:', err.response?.data); // 调试用 + setError(err.response?.data?.detail || '修改用户失败'); + } + }; + + const handleDeleteUser = async () => { + try { + await apiClient.delete(buildApiUrl(API_ENDPOINTS.USERS.DELETE(processingUser.user_id))); + setShowDeleteConfirm(false); + fetchUsers(); // Refresh user list + } catch (err) { + console.error('Error deleting user:', err); + } + }; + + const handleResetPassword = async () => { + try { + await apiClient.post(buildApiUrl(API_ENDPOINTS.USERS.RESET_PASSWORD(processingUser.user_id))); + setShowResetConfirm(false); + } catch (err) { + console.error('Error resetting password:', err); + } + }; + + const openEditModal = (user) => { + setEditingUser({ ...user }); + setShowEditUserModal(true); + }; + + const openDeleteConfirm = (user) => { + setProcessingUser(user); + setShowDeleteConfirm(true); + }; + + const openResetConfirm = (user) => { + setProcessingUser(user); + setShowResetConfirm(true); + }; + + return ( +
+
+

用户列表

+ +
+ {loading &&

加载中...

} + {error &&

{error}

} + {!loading && !error && ( + <> + + + + + + + + + + + + + + {users.map(user => ( + + + + + + + + + + ))} + +
ID用户名姓名邮箱角色创建时间操作
{user.user_id}{user.username}{user.caption}{user.email}{user.role_id === 1 ? '管理员' : '普通用户'}{new Date(user.created_at).toLocaleString()} + + + +
+
+ 总计 {total} 条 +
+ + 第 {page} 页 / {Math.ceil(total / pageSize)} + +
+
+ + )} + + {showAddUserModal && ( +
+
+
+

新增用户

+ {error &&
{error}
} +
+ + setNewUser({...newUser, username: e.target.value})} required /> +
+
+ + setNewUser({...newUser, caption: e.target.value})} required /> +
+
+ + setNewUser({...newUser, email: e.target.value})} required /> +
+
+ + +
+
+

注:新用户的默认密码为系统配置的默认密码,用户可登录后自行修改

+
+
+ + +
+
+
+
+ )} + + {showEditUserModal && editingUser && ( +
+
+
+

修改用户

+ {error &&
{error}
} +
+ + setEditingUser({...editingUser, username: e.target.value})} required /> +
+
+ + setEditingUser({...editingUser, caption: e.target.value})} required /> +
+
+ + setEditingUser({...editingUser, email: e.target.value})} required /> +
+
+ + +
+
+ + +
+
+
+
+ )} + + {showDeleteConfirm && processingUser && ( +
+
+

确认删除

+

您确定要删除用户 {processingUser.caption} 吗?此操作无法撤销。

+
+ + +
+
+
+ )} + + {showResetConfirm && processingUser && ( +
+
+

确认重置密码

+

您确定要重置用户 {processingUser.caption} 的密码吗?

+
+ + +
+
+
+ )} +
+ ); +}; + +export default UserManagement; diff --git a/src/config/api.js b/src/config/api.js index eeb337d..69d0c3b 100644 --- a/src/config/api.js +++ b/src/config/api.js @@ -11,7 +11,12 @@ const API_CONFIG = { }, USERS: { LIST: '/api/users', - DETAIL: (userId) => `/api/users/${userId}` + CREATE: '/api/users', + UPDATE: (userId) => `/api/users/${userId}`, + DELETE: (userId) => `/api/users/${userId}`, + RESET_PASSWORD: (userId) => `/api/users/${userId}/reset-password`, + DETAIL: (userId) => `/api/users/${userId}`, + UPDATE_PASSWORD: (userId) => `/api/users/${userId}/password` }, MEETINGS: { LIST: '/api/meetings', diff --git a/src/pages/AdminManagement.css b/src/pages/AdminManagement.css new file mode 100644 index 0000000..a11ce56 --- /dev/null +++ b/src/pages/AdminManagement.css @@ -0,0 +1,129 @@ +/* AdminManagement.css */ +.admin-management-page { + min-height: 100vh; + background: #f8fafc; +} + +/* Header */ +.admin-header { + background: white; + border-bottom: 1px solid #e2e8f0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + margin-bottom: 0; +} + +.admin-header .header-content { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.admin-header .logo { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.5rem; + font-weight: bold; + color: #667eea; +} + +.admin-header .logo-icon { + width: 32px; + height: 32px; +} + +.admin-header h1 { + margin: 0; + font-size: 1.5rem; + color: #1e293b; + font-weight: 600; +} + +/* Content */ +.admin-content { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 2rem; +} + +.admin-wrapper { + background: white; + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + overflow: hidden; +} + +/* Tabs */ +.tabs { + display: flex; + border-bottom: 1px solid #e2e8f0; + background: transparent; +} + +.tab-btn { + padding: 1rem 2rem; + border: none; + background: transparent; + cursor: pointer; + font-size: 1rem; + color: #64748b; + position: relative; + border-bottom: 3px solid transparent; + transition: all 0.3s ease; + font-weight: 500; + outline: none; +} + +.tab-btn:hover { + color: #475569; + background: transparent; +} + +.tab-btn:active { + color: #667eea; + border-bottom-color: #667eea; + background: transparent; + outline: none; +} + +.tab-btn.active { + color: #667eea; + border-bottom-color: #667eea; + background: transparent; +} + +.tab-btn:focus { + outline: none; + background: transparent; +} + +.tab-content { + padding: 2rem; + min-height: 60vh; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .admin-content { + padding: 1rem; + } + + .admin-header .header-content { + padding: 1rem; + } + + .tab-btn { + padding: 0.75rem 1rem; + font-size: 0.9rem; + } + + .tab-content { + padding: 1rem; + } +} diff --git a/src/pages/AdminManagement.jsx b/src/pages/AdminManagement.jsx new file mode 100644 index 0000000..81e6d3b --- /dev/null +++ b/src/pages/AdminManagement.jsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { MessageSquare, Settings, Users } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import UserManagement from '../components/admin/UserManagement'; +import SystemConfiguration from '../components/admin/SystemConfiguration'; +import './AdminManagement.css'; + +const AdminManagement = () => { + const [activeTab, setActiveTab] = useState('userManagement'); + const navigate = useNavigate(); + + const handleLogoClick = () => { + navigate('/dashboard'); + }; + + return ( +
+
+
+
+ + iMeeting +
+

平台管理

+
+
+
+
+
+ + +
+
+ {activeTab === 'userManagement' && } + {activeTab === 'systemConfiguration' && } +
+
+
+
+ ); +}; + +export default AdminManagement; \ No newline at end of file diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css index 0accce9..aaa1266 100644 --- a/src/pages/Dashboard.css +++ b/src/pages/Dashboard.css @@ -301,4 +301,112 @@ .welcome-text { font-size: 0.9rem; } +} + +/* User Menu */ +.user-menu-container { + position: relative; +} + +.user-menu-trigger { + display: flex; + align-items: center; + cursor: pointer; + gap: 0.5rem; +} + +.dropdown-menu { + position: absolute; + top: 100%; + right: 0; + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border: 1px solid #e2e8f0; + padding: 0.5rem; + margin-top: 0.5rem; + width: 160px; + z-index: 10; + display: flex; + flex-direction: column; +} + +.dropdown-menu button, +.dropdown-menu a { + background: none; + border: none; + text-align: left; + padding: 0.75rem 1rem; + width: 100%; + border-radius: 6px; + cursor: pointer; + color: #334155; + text-decoration: none; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.dropdown-menu button:hover, +.dropdown-menu a:hover { + background: #f1f5f9; +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-content { + background: white; + padding: 2rem; + border-radius: 8px; + width: 90%; + max-width: 500px; +} + +.modal-content h2 { + margin-top: 0; +} + +.modal-content .form-group { + margin-bottom: 1rem; +} + +.modal-content .form-group label { + display: block; + margin-bottom: 0.5rem; +} + +.modal-content .form-group input { + width: 100%; + padding: 0.5rem; + border-radius: 4px; + border: 1px solid #ccc; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 1rem; + margin-top: 2rem; +} + +.error-message { + color: red; + margin-bottom: 1rem; +} + +.success-message { + color: green; + margin-bottom: 1rem; } \ No newline at end of file diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index aeb3aa2..ee3dce5 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { LogOut, User, Calendar, Users, TrendingUp, Clock, MessageSquare, Plus } from 'lucide-react'; +import React, { useState, useEffect, useRef } from 'react'; +import { LogOut, User, Calendar, Users, TrendingUp, Clock, MessageSquare, Plus, ChevronDown, KeyRound, Shield } from 'lucide-react'; import apiClient from '../utils/apiClient'; import { Link } from 'react-router-dom'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; @@ -11,11 +11,31 @@ const Dashboard = ({ user, onLogout }) => { const [meetings, setMeetings] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); + const [oldPassword, setOldPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordChangeError, setPasswordChangeError] = useState(''); + const [passwordChangeSuccess, setPasswordChangeSuccess] = useState(''); + const dropdownRef = useRef(null); useEffect(() => { fetchUserData(); }, [user.user_id]); + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [dropdownRef]); + const fetchUserData = async () => { try { setLoading(true); @@ -49,6 +69,39 @@ const Dashboard = ({ user, onLogout }) => { } }; + const handlePasswordChange = async (e) => { + e.preventDefault(); + if (newPassword !== confirmPassword) { + setPasswordChangeError('新密码不匹配'); + return; + } + if (newPassword.length < 6) { + setPasswordChangeError('新密码长度不能少于6位'); + return; + } + + setPasswordChangeError(''); + setPasswordChangeSuccess(''); + + try { + await apiClient.put(buildApiUrl(API_ENDPOINTS.USERS.UPDATE_PASSWORD(user.user_id)), { + old_password: oldPassword, + new_password: newPassword, + }); + setPasswordChangeSuccess('密码修改成功!'); + // 清空输入框并准备关闭模态框 + setOldPassword(''); + setNewPassword(''); + setConfirmPassword(''); + setTimeout(() => { + setShowChangePasswordModal(false); + setPasswordChangeSuccess(''); + }, 2000); + } catch (err) { + setPasswordChangeError(err.response?.data?.detail || '密码修改失败'); + } + }; + const groupMeetingsByDate = (meetings) => { return meetings.reduce((acc, meeting) => { const date = new Date(meeting.meeting_time || meeting.created_at).toISOString().split('T')[0]; @@ -104,11 +157,21 @@ const Dashboard = ({ user, onLogout }) => { iMeeting
- 欢迎,{userInfo?.caption} - +
+
setDropdownOpen(!dropdownOpen)}> + 欢迎,{userInfo?.caption} + +
+ {dropdownOpen && ( +
+ + {user.role_id === 1 && ( + setDropdownOpen(false)}> 平台管理 + )} + +
+ )} +
@@ -186,6 +249,34 @@ const Dashboard = ({ user, onLogout }) => { /> + + {showChangePasswordModal && ( +
+
+
+

修改密码

+ {passwordChangeError &&

{passwordChangeError}

} + {passwordChangeSuccess &&

{passwordChangeSuccess}

} +
+ + setOldPassword(e.target.value)} required /> +
+
+ + setNewPassword(e.target.value)} required /> +
+
+ + setConfirmPassword(e.target.value)} required /> +
+
+ + +
+
+
+
+ )} ); }; diff --git a/src/pages/HomePage.css b/src/pages/HomePage.css index bfe1abb..56c670d 100644 --- a/src/pages/HomePage.css +++ b/src/pages/HomePage.css @@ -36,6 +36,10 @@ color: #ffd700; } +.logo-text-white { + color: #fff; +} + .login-btn { background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.3); @@ -305,4 +309,17 @@ .login-modal { margin: 1rem; } +} + +/* Footer */ +.homepage-footer { + text-align: center; + padding: 2rem; + background: rgba(0, 0, 0, 0.2); + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.7); +} + +.homepage-footer p { + margin: 0.5rem 0; } \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index ebbc143..00f9f87 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -46,7 +46,7 @@ const HomePage = ({ onLogin }) => {
- iMeeting + iMeeting
+ + + {/* Footer */} +
+

© 2025 紫光汇智信息技术有限公司. All rights reserved.

+

备案号:渝ICP备2023007695号-11

+
+ {/* Login Modal */} {showLoginModal && (
@@ -160,8 +168,8 @@ const HomePage = ({ onLogin }) => {
-

开通账号:

-

请联系:18980500203

+

开通业务账号:

+

请联系平台管理员