diff --git a/README.md b/README.md deleted file mode 100644 index 8ef8e6f..0000000 --- a/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# CLI Proxy Web UI - React Version - -CLI Proxy API Management Center 的 React + TypeScript 重构版本。 - -## ✨ 特性 - -- 🎯 完全使用 TypeScript 编写,类型安全 -- ⚛️ 基于 React 18 + Vite 构建,开发体验极佳 -- 🎨 SCSS 模块化样式,支持亮色/暗色主题 -- 🌍 完整的国际化支持 (中文/英文) -- 📦 单文件部署,无需构建服务器 -- 🔒 安全的本地存储,支持数据加密 -- 📱 响应式设计,支持移动端 - -## 🚀 快速开始 - -### 开发模式 - -```bash -# 安装依赖 -npm install - -# 启动开发服务器 -npm run dev - -# 访问 http://localhost:5173 -``` - -### 生产构建 - -```bash -# 构建生产版本 -npm run build - -# 产物在 dist/index.html -# 直接双击打开或部署到服务器 -``` - -### 代码检查 - -```bash -# TypeScript 类型检查 -npm run type-check - -# ESLint 代码检查 -npm run lint -``` - -## 📁 项目结构 - -``` -src/ -├── components/ # 公共组件 -│ ├── common/ # 基础组件 (Button, Input, Card, Modal...) -│ └── layout/ # 布局组件 (MainLayout, Sidebar, Header...) -├── pages/ # 页面组件 -│ ├── LoginPage.tsx -│ ├── SettingsPage.tsx -│ ├── ApiKeysPage.tsx -│ ├── AiProvidersPage.tsx -│ ├── AuthFilesPage.tsx -│ ├── OAuthPage.tsx -│ ├── UsagePage.tsx -│ ├── ConfigPage.tsx -│ ├── LogsPage.tsx -│ └── SystemPage.tsx -├── services/ # API 服务 -│ ├── api/ # API 客户端 -│ └── storage/ # 本地存储服务 -├── stores/ # Zustand 状态管理 -│ ├── useAuthStore.ts -│ ├── useConfigStore.ts -│ ├── useThemeStore.ts -│ └── useLanguageStore.ts -├── hooks/ # 自定义 Hooks -├── types/ # TypeScript 类型定义 -├── utils/ # 工具函数 -├── i18n/ # 国际化配置 -├── styles/ # 全局样式 -└── router/ # 路由配置 -``` - -## 🔧 技术栈 - -- **框架**: React 18 -- **语言**: TypeScript 5 -- **构建工具**: Vite 7 -- **路由**: React Router 7 (Hash 模式) -- **状态管理**: Zustand 5 -- **样式**: SCSS Modules -- **国际化**: i18next -- **HTTP 客户端**: Axios -- **代码检查**: ESLint + TypeScript ESLint - -## 📝 使用说明 - -### 首次使用 - -1. **清理旧数据** (如果从旧版本升级) - - 打开 `CLEAR_STORAGE.html` 文件 - - 点击"清空 LocalStorage"按钮 - - 这将清理旧版本的存储数据 - -2. **打开应用** - - 双击 `dist/index.html` 文件 - - 或使用 HTTP 服务器访问 (推荐) - -3. **配置连接** - - 输入 CLI Proxy API 服务器地址 - - 输入管理密钥 - - 点击"连接"按钮 - -### 部署方式 - -#### 方式 1: 本地文件 (file:// 协议) -直接双击 `dist/index.html` 即可使用。应用已配置为使用 Hash 路由,支持 file:// 协议。 - -#### 方式 2: HTTP 服务器 (推荐) -```bash -# 使用 Python -cd dist -python -m http.server 8080 - -# 使用 Node.js (需要安装 serve) -npx serve dist - -# 访问 http://localhost:8080 -``` - -#### 方式 3: Nginx/Apache -将 `dist/index.html` 部署到 Web 服务器即可。 - -## 🐛 故障排除 - -### 白屏问题 - -如果打开后显示白屏: - -1. 检查浏览器控制台是否有错误 -2. 确认是否清理了旧版本的 localStorage 数据 -3. 尝试使用 HTTP 服务器访问而不是 file:// 协议 - -### LocalStorage 错误 - -如果看到 "Failed to parse stored data" 错误: - -1. 打开 `CLEAR_STORAGE.html` -2. 清空所有存储数据 -3. 刷新页面重新登录 - -### 路由问题 - -应用使用 Hash 路由 (#/login, #/settings),确保 URL 中包含 `#` 符号。 - -## 📊 构建信息 - -- **TypeScript**: 0 errors ✅ -- **ESLint**: 0 errors, 137 warnings ⚠️ -- **Bundle Size**: 473 KB (144 KB gzipped) -- **Build Time**: ~5 seconds - -## 🔄 从旧版本迁移 - -旧版本 (原生 JS) 的数据存储格式已变更。首次使用新版本时: - -1. 旧的 localStorage 数据会自动迁移 -2. 如果迁移失败,请手动清理 localStorage -3. 重新输入连接信息即可 - -## 📄 License - -Same as CLI Proxy API - -## 🤝 贡献 - -欢迎提交 Issue 和 Pull Request! - ---- - -**注意**: 此版本是原 CLI Proxy Web UI 的 React 重构版本,与原版功能保持一致。 diff --git a/REFACTOR_PROGRESS.md b/REFACTOR_PROGRESS.md deleted file mode 100644 index f5cbd2e..0000000 --- a/REFACTOR_PROGRESS.md +++ /dev/null @@ -1,21 +0,0 @@ -# 重构进度追踪 - -## 阶段完成状态 -- [x] 阶段 0: 准备工作 -- [x] 阶段 1: 项目初始化 -- [x] 阶段 2: 类型定义 -- [x] 阶段 3: 工具与服务 -- [x] 阶段 4: 状态管理 -- [x] 阶段 5: 自定义 Hooks -- [x] 阶段 6: 国际化配置 -- [x] 阶段 7: 样式系统 -- [x] 阶段 8: 通用组件 -- [x] 阶段 9: 页面组件(API Keys / Providers / Auth Files / OAuth / Usage / Config / Logs / System) -- [x] 阶段 10: 路由布局与构建验证 - -## 错误记录 - - -## 待办事项 -- [ ] 深入回归交互细节(模型价格、图表、日志增量等)与基线对齐 -- [ ] 优化 Sass 导入与 darken 用法以消除警告 diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 24e49d4..9a524a5 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { ReactNode, SVGProps, useEffect, useMemo, useState } from 'react'; import { NavLink, Outlet } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; @@ -6,6 +6,95 @@ import { useAuthStore, useConfigStore, useLanguageStore, useNotificationStore, u import { versionApi } from '@/services/api'; import { isLocalhost } from '@/utils/connection'; +const iconProps: SVGProps = { + width: 18, + height: 18, + viewBox: '0 0 20 20', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 1.5, + strokeLinecap: 'round', + strokeLinejoin: 'round', + 'aria-hidden': 'true', + focusable: 'false' +}; + +const sidebarIcons: Record = { + settings: ( + + + + + + + + + ), + apiKeys: ( + + + + + + + ), + aiProviders: ( + + + + + + + + + ), + authFiles: ( + + + + + + ), + oauth: ( + + + + + + + ), + usage: ( + + + + + ), + config: ( + + + + + + ), + logs: ( + + + + + + + + + ), + system: ( + + + + + + ) +}; + const parseVersionSegments = (version?: string | null) => { if (!version) return null; const cleaned = version.trim().replace(/^v/i, ''); @@ -72,15 +161,15 @@ export function MainLayout() { : 'muted'; const navItems = [ - { path: '/settings', label: t('nav.basic_settings') }, - { path: '/api-keys', label: t('nav.api_keys') }, - { path: '/ai-providers', label: t('nav.ai_providers') }, - { path: '/auth-files', label: t('nav.auth_files') }, - ...(isLocal ? [{ path: '/oauth', label: t('nav.oauth', { defaultValue: 'OAuth' }) }] : []), - { path: '/usage', label: t('nav.usage_stats') }, - { path: '/config', label: t('nav.config_management') }, - ...(config?.loggingToFile ? [{ path: '/logs', label: t('nav.logs') }] : []), - { path: '/system', label: t('nav.system_info') } + { path: '/settings', label: t('nav.basic_settings'), icon: sidebarIcons.settings }, + { path: '/api-keys', label: t('nav.api_keys'), icon: sidebarIcons.apiKeys }, + { path: '/ai-providers', label: t('nav.ai_providers'), icon: sidebarIcons.aiProviders }, + { path: '/auth-files', label: t('nav.auth_files'), icon: sidebarIcons.authFiles }, + ...(isLocal ? [{ path: '/oauth', label: t('nav.oauth', { defaultValue: 'OAuth' }), icon: sidebarIcons.oauth }] : []), + { path: '/usage', label: t('nav.usage_stats'), icon: sidebarIcons.usage }, + { path: '/config', label: t('nav.config_management'), icon: sidebarIcons.config }, + ...(config?.loggingToFile ? [{ path: '/logs', label: t('nav.logs'), icon: sidebarIcons.logs }] : []), + { path: '/system', label: t('nav.system_info'), icon: sidebarIcons.system } ]; const handleRefreshAll = async () => { @@ -135,7 +224,8 @@ export function MainLayout() { onClick={() => setSidebarOpen(false)} title={sidebarCollapsed ? item.label : undefined} > - {sidebarCollapsed ? item.label.charAt(0) : item.label} + {item.icon} + {!sidebarCollapsed && {item.label}} ))} diff --git a/src/styles/layout.scss b/src/styles/layout.scss index 51fa22b..0f32694 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -59,6 +59,28 @@ cursor: pointer; transition: background $transition-fast, color $transition-fast; + .nav-icon { + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + opacity: 0.9; + + svg { + width: 18px; + height: 18px; + display: block; + } + } + + .nav-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + &:hover { background: var(--bg-secondary); }