refactor: remove README and REFACTOR_PROGRESS files; enhance MainLayout with sidebar icons and improved navigation item display

This commit is contained in:
Supra4E8C
2025-12-09 01:46:58 +08:00
parent 2ca662e971
commit 5d5334afb1
4 changed files with 123 additions and 212 deletions

180
README.md
View File

@@ -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 重构版本,与原版功能保持一致。

View File

@@ -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 用法以消除警告

View File

@@ -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<SVGSVGElement> = {
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<string, ReactNode> = {
settings: (
<svg {...iconProps}>
<path d="M4 6.5h12" />
<circle cx="9" cy="6.5" r="2" />
<path d="M4 10h12" />
<circle cx="7" cy="10" r="2" />
<path d="M4 13.5h12" />
<circle cx="12" cy="13.5" r="2" />
</svg>
),
apiKeys: (
<svg {...iconProps}>
<circle cx="7.2" cy="10" r="2.4" />
<path d="M9.6 10h6" />
<path d="M12.8 10v2.4" />
<path d="M14.8 10v1.4" />
</svg>
),
aiProviders: (
<svg {...iconProps}>
<circle cx="10" cy="5.2" r="2.2" />
<circle cx="6" cy="13.2" r="2" />
<circle cx="14" cy="13.2" r="2" />
<path d="M8.6 6.8 6.8 10.8" />
<path d="M11.4 6.8 13.2 10.8" />
<path d="M7.8 13.2h4.4" />
</svg>
),
authFiles: (
<svg {...iconProps}>
<path d="M7 3.5h4.8L15 6.8V16H7Z" />
<path d="M11.8 3.5V7h3.2" />
<path d="m8.9 11.8 1.7 1.6 3.4-3.5" />
</svg>
),
oauth: (
<svg {...iconProps}>
<path d="M10 3.5 15.2 5.6v3.6c0 3-2 5.8-5.2 7-3.2-1.2-5.2-4-5.2-7V5.6Z" />
<path d="M8.2 9.6h3.6" />
<path d="m9.6 8.2-1.4 1.4 1.4 1.4" />
<path d="m11.8 8.2 1.4 1.4-1.4 1.4" />
</svg>
),
usage: (
<svg {...iconProps}>
<path d="M4 14.5h12" />
<path d="m6.2 11.3 3-3 2.4 2 2.9-3.7" />
</svg>
),
config: (
<svg {...iconProps}>
<path d="M5.2 8 10 5.8l4.8 2.2L10 10.2Z" />
<path d="M5.2 12 10 9.8l4.8 2.2L10 14.2Z" />
<path d="M10 10.2v3.6" />
</svg>
),
logs: (
<svg {...iconProps}>
<path d="M6.4 6h9" />
<path d="M6.4 10h9" />
<path d="M6.4 14h9" />
<circle cx="4.2" cy="6" r="0.9" />
<circle cx="4.2" cy="10" r="0.9" />
<circle cx="4.2" cy="14" r="0.9" />
</svg>
),
system: (
<svg {...iconProps}>
<circle cx="10" cy="10" r="6.2" />
<path d="M10 8.8v3.6" />
<circle cx="10" cy="6.2" r="0.8" fill="currentColor" stroke="none" />
</svg>
)
};
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}
<span className="nav-icon">{item.icon}</span>
{!sidebarCollapsed && <span className="nav-label">{item.label}</span>}
</NavLink>
))}
</div>

View File

@@ -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);
}