diff --git a/frontend/design/开发规范.md b/frontend/design/开发规范.md index a10f2d5..e01bfcf 100644 --- a/frontend/design/开发规范.md +++ b/frontend/design/开发规范.md @@ -25,16 +25,7 @@ - **反馈及时**:操作有明确状态反馈 - **容错友好**:预防错误,提示明确 ---- -## 颜色系统 - -| 类型 | 用途 | 颜色值 | -|------|------|-------| -| 主色调 | 关键按钮、重要信息、链接 | `#b8178d` | -| 辅助色 | 信息提示 | `#1677ff` | -| 功能色 | Success / Warning / Error / Info | 按语义使用 | -| 中性色 | 文本、背景、边框 | - | **使用规范**: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f4b8d4e..e05b6a6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,8 +11,11 @@ "@ant-design/icons": "^6.1.0", "antd": "^5.13.2", "axios": "^1.6.7", + "i18next": "^25.8.6", + "i18next-browser-languagedetector": "^8.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^16.5.4", "react-router-dom": "^6.22.3", "zustand": "^4.5.2" }, @@ -2051,6 +2054,55 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.8.6", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.8.6.tgz", + "integrity": "sha512-HsS6p2yr/Vo5EPljWuBJ9OxKVFok2Q/Oa6PvFTpv2bMcDt2sQMOnKDQ7FTDDdME+3d1YULQjKj7aVSZP1bCouQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/is-mobile": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", @@ -2856,6 +2908,33 @@ "react": "^18.3.1" } }, + "node_modules/react-i18next": { + "version": "16.5.4", + "resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-16.5.4.tgz", + "integrity": "sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.6.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", @@ -3024,7 +3103,7 @@ "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3134,6 +3213,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8cb4008..71d0ee0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,8 +12,11 @@ "@ant-design/icons": "^6.1.0", "antd": "^5.13.2", "axios": "^1.6.7", + "i18next": "^25.8.6", + "i18next-browser-languagedetector": "^8.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^16.5.4", "react-router-dom": "^6.22.3", "zustand": "^4.5.2" }, diff --git a/frontend/src/layouts/AppLayout.tsx b/frontend/src/layouts/AppLayout.tsx index c2b59c3..469c3ea 100644 --- a/frontend/src/layouts/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout.tsx @@ -1,6 +1,7 @@ import { Layout, Menu, Button, Space, Avatar, Dropdown, message, type MenuProps } from "antd"; import { useEffect, useState } from "react"; import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { DashboardOutlined, VideoCameraOutlined, @@ -12,7 +13,8 @@ import { MenuUnfoldOutlined, MenuFoldOutlined, BellOutlined, - SettingOutlined + SettingOutlined, + GlobalOutlined } from "@ant-design/icons"; import { useAuth } from "../hooks/useAuth"; import { usePermission } from "../hooks/usePermission"; @@ -31,6 +33,7 @@ const iconMap: Record = { }; export default function AppLayout() { + const { t, i18n } = useTranslation(); const [collapsed, setCollapsed] = useState(false); const [menus, setMenus] = useState([]); const location = useLocation(); @@ -50,7 +53,7 @@ export default function AppLayout() { .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); setMenus(filtered); } catch (e) { - message.error("获取菜单失败"); + message.error(t('common.error')); } }; @@ -63,6 +66,11 @@ export default function AppLayout() { navigate("/login"); }; + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng); + message.success(lng === 'zh-CN' ? '已切换至中文' : 'Switched to English'); + }; + const buildMenuTree = (list: SysPermission[]) => { const map = new Map(); const roots: (SysPermission & { children?: SysPermission[] })[] = []; @@ -104,10 +112,15 @@ export default function AppLayout() { const menuItems = toMenuItems(buildMenuTree(menus)); const userMenuItems: MenuProps["items"] = [ - { key: 'profile', label: '个人信息', icon: }, - { key: 'settings', label: '系统设置', icon: }, + { key: 'profile', label: t('layout.profile'), icon: }, + { key: 'settings', label: t('layout.settings'), icon: }, { type: 'divider' }, - { key: 'logout', label: '退出登录', icon: , onClick: handleLogout }, + { key: 'logout', label: t('layout.logout'), icon: , onClick: handleLogout }, + ]; + + const langMenuItems: MenuProps["items"] = [ + { key: 'zh-CN', label: '简体中文', onClick: () => changeLanguage('zh-CN') }, + { key: 'en-US', label: 'English', onClick: () => changeLanguage('en-US') }, ]; return ( @@ -167,6 +180,9 @@ export default function AppLayout() { style={{ fontSize: '16px', width: 64, height: 64 }} /> + + + diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e227cd3..856674a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import "antd/dist/reset.css"; import "./index.css"; +import "./i18n"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index a952c49..bafab2e 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -8,72 +8,75 @@ import { SyncOutlined, ArrowRightOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; import StatCard from "../components/shared/StatCard/StatCard"; const { Title, Text } = Typography; -const recentMeetings = [ - { key: '1', name: '产品周会', type: '转录中', time: '2024-02-10 14:00', duration: '45min', status: 'processing' }, - { key: '2', name: '技术分享', type: '已完成', time: '2024-02-10 10:00', duration: '60min', status: 'success' }, - { key: '3', name: '部门早会', type: '已完成', time: '2024-02-10 09:00', duration: '15min', status: 'success' }, - { key: '4', name: '客户会议', type: '待开始', time: '2024-02-10 16:30', duration: '30min', status: 'default' }, -]; - -const columns = [ - { - title: '会议名称', - dataIndex: 'name', - key: 'name', - render: (text: string) => {text} - }, - { - title: '开始时间', - dataIndex: 'time', - key: 'time', - className: 'tabular-nums', - render: (text: string) => {text} - }, - { - title: '时长', - dataIndex: 'duration', - key: 'duration', - width: 100, - className: 'tabular-nums' - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - width: 120, - render: (status: string) => { - if (status === 'processing') return ; - if (status === 'success') return ; - return 待开始; - } - }, - { - title: '操作', - key: 'action', - width: 80, - render: () => +