IoCent 1.0.0 — Code Wiki
目录
- 1. 项目概述
- 2. 整体架构
- 3. 目录结构
- 4. 技术栈与依赖
- 5. 前端模块详解
- 6. 后端模块详解
- 7. 数据模型
- 8. API 接口文档
- 9. 核心流程
- 10. 部署方案
- 11. 项目运行方式
1. 项目概述
IoCent 是一个基于 Node.js 的 AI 对话应用,提供流畅、美观的智能聊天体验。用户可以与 AI 进行多轮对话,支持流式输出(SSE)、对话历史管理、Markdown 渲染与代码高亮。
| 属性 | 说明 |
|------|------|
| 项目名称 | iocent1.0.0 |
| 版本 | 0.0.0 (初始版本) |
| 模块类型 | ESM ("type": "module") |
| 目标用户 | 需要 AI 辅助对话的开发者、创作者和普通用户 |
| 核心价值 | 开箱即用的本地 AI 对话体验,界面精致,交互流畅 |
2. 整体架构
项目采用 前后端分离 的全栈架构,前端与后端在同一仓库中协同开发:
┌─────────────────────────────────────────────────────────┐
│ 前端层 (Client) │
│ React 18 + TypeScript + Vite + TailwindCSS + Zustand │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Pages │ │Components│ │ Store │ │ Hooks │ │
│ │ (Home) │ │(Chat/Nav)│ │(Zustand) │ │(Theme) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────────┘ │
│ └──────────────┼─────────────┘ │
│ │ fetch / SSE │
└──────────────────────┼───────────────────────────────────┘
│ /api/*
┌──────────────────────┼───────────────────────────────────┐
│ ▼ 后端层 (Server) │
│ Express.js + TypeScript (ESM) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Auth │ │ Chat │ │Conversa- │ │ Models │ │
│ │ Routes │ │ Routes │ │ tions │ │ Routes │ │
│ └──────────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ │
│ │ │ │ │
│ ┌──────┴──────┐ │ │ │
│ │MemoryStore │ │ │ │
│ └──────┬──────┘ │ │ │
│ │ │ │ │
└─────────────────────┼─────────────┼──────────────┼───────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ OpenAI 兼容 API (外部服务) │
│ (支持任意 OpenAI 兼容的 LLM 后端) │
└─────────────────────────────────────────┘
关键架构决策:
- 前端通过 Vite 开发代理 (
/api→localhost:3001) 与后端通信,生产环境通过 Vercel rewrites 实现 - 后端使用 SSE (Server-Sent Events) 实现流式响应,前端通过
ReadableStream逐行解析 - 数据存储采用内存 Map,服务重启后数据丢失(适合演示/开发阶段)
- OpenAI 客户端采用懒初始化单例模式,支持自定义
baseURL,兼容各类 OpenAI API 格式的后端
3. 目录结构
iocent1.0.0/
├── api/ # 后端 API 服务
│ ├── routes/ # API 路由模块
│ │ ├── auth.ts # 认证路由(占位,未实现)
│ │ ├── chat.ts # AI 对话核心路由(SSE 流式)
│ │ ├── conversations.ts # 对话 CRUD 路由
│ │ └── models.ts # 模型列表路由
│ ├── store/
│ │ └── memoryStore.ts # 内存数据存储层
│ ├── app.ts # Express 应用配置
│ ├── index.ts # Vercel Serverless 入口
│ └── server.ts # 本地开发服务器入口
├── src/ # 前端源码
│ ├── assets/ # 静态资源
│ │ └── react.svg
│ ├── components/ # UI 组件
│ │ ├── ChatInput.tsx # 消息输入框
│ │ ├── ChatMessage.tsx # 消息气泡(Markdown 渲染)
│ │ ├── Empty.tsx # 空状态占位组件
│ │ ├── ModelSelector.tsx # 模型选择下拉框
│ │ └── Sidebar.tsx # 侧边栏(对话列表+模型选择)
│ ├── hooks/
│ │ └── useTheme.ts # 主题切换 Hook
│ ├── lib/
│ │ └── utils.ts # 工具函数(cn 类名合并)
│ ├── pages/
│ │ └── Home.tsx # 主页面
│ ├── store/
│ │ └── chatStore.ts # Zustand 全局状态
│ ├── App.tsx # 应用根组件(路由配置)
│ ├── index.css # 全局样式 + Tailwind
│ ├── main.tsx # 前端入口
│ └── vite-env.d.ts # Vite 类型声明
├── public/
│ └── favicon.svg # 网站图标
├── dist/ # 构建产物
├── .env # 环境变量配置
├── .gitignore
├── eslint.config.js # ESLint 配置
├── index.html # HTML 入口
├── nodemon.json # Nodemon 开发配置
├── package.json # 项目依赖与脚本
├── postcss.config.js # PostCSS 配置
├── tailwind.config.js # TailwindCSS 配置
├── tsconfig.json # TypeScript 配置
├── vercel.json # Vercel 部署配置
└── vite.config.ts # Vite 构建配置
4. 技术栈与依赖
4.1 核心技术栈
| 层级 | 技术 | 版本 | 用途 | |------|------|------|------| | 前端框架 | React | ^18.3.1 | UI 构建 | | 前端语言 | TypeScript | ~5.8.3 | 类型安全 | | 构建工具 | Vite | ^6.3.5 | 开发服务器 + 打包 | | CSS 框架 | TailwindCSS | ^3.4.17 | 原子化样式 | | 状态管理 | Zustand | ^5.0.3 | 全局状态 | | 路由 | React Router DOM | ^7.3.0 | 前端路由 | | 后端框架 | Express | ^4.21.2 | HTTP 服务 | | AI SDK | OpenAI | ^6.39.1 | 调用 LLM API |
4.2 运行时依赖
| 包名 | 版本 | 用途 |
|------|------|------|
| clsx | ^2.1.1 | 条件类名拼接 |
| cors | ^2.8.5 | 跨域资源共享中间件 |
| dotenv | ^17.2.1 | 环境变量加载 |
| lucide-react | ^0.511.0 | 图标库 |
| react-markdown | ^10.1.0 | Markdown 渲染 |
| react-syntax-highlighter | ^16.1.1 | 代码语法高亮 |
| remark-gfm | ^4.0.1 | GitHub Flavored Markdown 支持 |
| tailwind-merge | ^3.0.2 | Tailwind 类名智能合并 |
4.3 开发依赖
| 包名 | 版本 | 用途 |
|------|------|------|
| @vercel/node | ^5.3.6 | Vercel Serverless 类型支持 |
| concurrently | ^9.2.0 | 并行运行前后端开发服务器 |
| nodemon | ^3.1.10 | 后端热重载 |
| tsx | ^4.20.3 | TypeScript 直接执行 |
| vite-tsconfig-paths | ^5.1.4 | Vite 路径别名支持 |
| autoprefixer | ^10.4.21 | CSS 自动前缀 |
| postcss | ^8.5.3 | CSS 处理管线 |
4.4 依赖关系图
前端依赖链:
React ← react-dom
← react-router-dom (路由)
← zustand (状态)
← lucide-react (图标)
← react-markdown ← remark-gfm
← react-syntax-highlighter
← clsx + tailwind-merge → cn() 工具函数
后端依赖链:
Express ← cors (跨域)
← dotenv (环境变量)
← openai (AI API 客户端)
5. 前端模块详解
5.1 入口与路由
main.tsx
前端应用入口文件,负责:
- 以
StrictMode渲染<App />根组件 - 导入全局样式
index.css
App.tsx
应用根组件,配置前端路由:
| 路径 | 组件 | 说明 |
|------|------|------|
| / | Home | 主对话页面 |
| /other | 占位组件 | 预留扩展页面 |
使用 BrowserRouter 管理路由,通过 @/pages/Home 路径别名导入。
5.2 页面组件
Home.tsx
主对话页面,是应用的核心页面。
职责:
- 初始化加载对话列表和模型列表(
useEffect) - 管理活跃对话的消息展示
- 提供空状态引导("开始对话"按钮)
- 自动滚动到最新消息
关键状态(来自 chatStore):
| 状态 | 类型 | 说明 |
|------|------|------|
| conversations | Conversation[] | 所有对话列表 |
| activeConversationId | string \| null | 当前活跃对话 ID |
| isStreaming | boolean | 是否正在流式生成 |
布局结构:
┌─────────────────────────────────────┐
│ Sidebar │ Header (对话标题+状态) │
│ │───────────────────────────│
│ 对话列表 │ Messages (消息列表) │
│ │ ┌─ ChatMessage ─┐ │
│ │ └───────────────┘ │
│ │───────────────────────────│
│ 模型选择 │ ChatInput (输入框) │
└─────────────────────────────────────┘
5.3 UI 组件
ChatInput.tsx
消息输入组件,固定在对话区底部。
功能:
- 自适应高度的
<textarea>(最大 200px) Enter发送消息,Shift+Enter换行- 流式生成中显示停止按钮(红色方块图标),点击调用
stopStreaming() - 非流式状态显示发送按钮(箭头图标)
关键交互:
| 操作 | 行为 |
|------|------|
| Enter | 发送消息(清空输入框,调用 sendMessage) |
| Shift+Enter | 换行 |
| 点击发送按钮 | 发送消息 |
| 点击停止按钮 | 中止流式请求(AbortController.abort()) |
ChatMessage.tsx
单条消息气泡组件,支持 Markdown 渲染和代码高亮。
功能:
- 用户消息右对齐(翠绿色边框),AI 消息左对齐(暗灰色背景)
- 使用
react-markdown+remark-gfm渲染 Markdown - 代码块使用
react-syntax-highlighter(Prism + vscDarkPlus 主题)高亮 - 代码块顶部显示语言标签和复制按钮
- 流式生成时显示闪烁光标(
.streaming-cursor) - AI 消息支持一键复制(hover 显示复制按钮)
Props:
| 属性 | 类型 | 说明 |
|------|------|------|
| message | Message | 消息对象 |
| isStreaming | boolean | 是否正在流式生成 |
Sidebar.tsx
侧边栏组件,包含对话列表和模型选择。
功能:
- 显示应用标题 "IoCent"
- 新建对话按钮
- 对话列表(点击切换,hover 显示删除按钮)
- 活跃对话高亮(翠绿指示条 + 背景)
- 侧边栏折叠/展开(移动端隐藏,桌面端可切换)
- 底部嵌入
ModelSelector组件
交互逻辑:
- 流式生成中禁止新建、切换、删除对话
- 删除对话时
stopPropagation防止触发选中 - 删除当前活跃对话后,
activeConversationId置为null
ModelSelector.tsx
模型选择下拉组件,嵌入侧边栏底部。
功能:
- 点击展开/收起下拉列表
- 点击外部区域自动关闭(
mousedown事件监听) - 当前选中模型高亮显示
- 从
chatStore.availableModels读取模型列表
Empty.tsx
空状态占位组件,当前为简单文本展示,预留扩展。
5.4 状态管理
chatStore.ts
使用 Zustand 创建的全局状态 store,是前端的核心数据层。
状态定义:
| 字段 | 类型 | 初始值 | 说明 |
|------|------|--------|------|
| conversations | Conversation[] | [] | 对话列表 |
| activeConversationId | string \| null | null | 当前活跃对话 ID |
| selectedModel | string | '' | 当前选中的模型 ID |
| availableModels | {id, name}[] | [] | 可用模型列表 |
| isStreaming | boolean | false | 是否正在流式生成 |
| sidebarOpen | boolean | true | 侧边栏是否展开 |
| abortController | AbortController \| null | null | 用于中止流式请求 |
方法详解:
| 方法 | 说明 | 关键逻辑 |
|------|------|----------|
| loadConversations() | 从后端加载对话列表 | GET /api/conversations,仅加载元数据不含消息 |
| loadModels() | 从后端加载可用模型 | GET /api/models,自动选中第一个模型 |
| createConversation() | 创建新对话 | 本地生成 UUID,不调用后端,标题默认"新对话" |
| selectConversation(id) | 切换到指定对话 | 若本地无消息则从后端加载 GET /api/conversations/:id |
| deleteConversation(id) | 删除对话 | DELETE /api/conversations/:id,若删除的是当前对话则清空活跃 ID |
| sendMessage(content) | 发送消息并处理流式响应 | 核心方法,详见下方 |
| setModel(model) | 设置当前模型 | 直接更新 selectedModel |
| toggleSidebar() | 切换侧边栏 | 取反 sidebarOpen |
| stopStreaming() | 停止流式生成 | 调用 AbortController.abort() |
sendMessage 流式处理流程:
1. 若无活跃对话,先创建一个
2. 构造 userMessage 和 assistantMessage(空内容)
3. 将两条消息追加到当前对话,设置 isStreaming=true
4. 创建 AbortController
5. POST /api/chat 发送请求(含 messages, model, conversationId)
6. 读取 ReadableStream,逐行解析 SSE 数据:
- type=conversation → 更新对话 ID(服务端可能分配新 ID)
- type=content → 追加内容到 assistantMessage
7. 流结束后设置 isStreaming=false
8. 异常处理:AbortError 静默处理,其他错误显示"请求失败"
导出的类型:
interface Message {
id: string
role: 'user' | 'assistant' | 'system'
content: string
createdAt: number
}
interface Conversation {
id: string
title: string
createdAt: number
updatedAt: number
messages: Message[]
}
5.5 Hooks
useTheme.ts
主题管理 Hook,支持亮/暗模式切换。
| 返回值 | 类型 | 说明 |
|--------|------|------|
| theme | 'light' \| 'dark' | 当前主题 |
| toggleTheme | () => void | 切换主题 |
| isDark | boolean | 是否暗色模式 |
逻辑:
- 初始化时优先读取
localStorage中保存的主题 - 若无保存值,使用
prefers-color-scheme媒体查询检测系统偏好 - 切换主题时同步更新
document.documentElement的 class 和localStorage
注意:当前应用 UI 以暗色为主设计,此 Hook 已实现但未在组件中实际调用。
5.6 工具函数
utils.ts
function cn(...inputs: ClassValue[]): string
合并 Tailwind CSS 类名的工具函数,组合 clsx(条件类名)+ twMerge(去重冲突类名)。在所有组件中广泛使用。
5.7 样式体系
index.css
全局样式文件,包含:
- Google Fonts 导入(JetBrains Mono + Noto Sans SC)
- Tailwind 指令(
@tailwind base、@tailwind components、@tailwind utilities) - CSS 变量与全局样式(暗色背景
#0a0a0f) - 自定义工具类:
.scrollbar-thin— 细滚动条样式.no-scrollbar— 隐藏滚动条.glass— 毛玻璃效果
- 流式光标动画(
@keyframes blink+.streaming-cursor) - 代码块样式覆盖(
pre,code字体与背景)
tailwind.config.js
TailwindCSS 配置,扩展了以下自定义设计 Token:
| Token | 值 | 说明 |
|-------|------|------|
| dark | #0a0a0f | 主背景色(暗夜黑) |
| dark-100 | #1a1a2e | 次级背景色 |
| dark-200 | #2a2a3e | 卡片/悬浮背景 |
| dark-300 | #3a3a4e | 更浅的灰 |
| accent | #00d4aa | 强调色(翠绿) |
| accent-dark | #00b892 | 强调色暗色变体 |
| fontFamily.mono | JetBrains Mono | 代码字体 |
| fontFamily.sans | Noto Sans SC | 正文字体 |
6. 后端模块详解
6.1 应用入口与服务器
app.ts
Express 应用配置文件,定义中间件和路由挂载。
中间件链:
| 顺序 | 中间件 | 说明 |
|------|--------|------|
| 1 | cors() | 允许跨域请求 |
| 2 | express.json({ limit: '10mb' }) | JSON 请求体解析 |
| 3 | express.urlencoded({ extended: true, limit: '10mb' }) | URL 编码请求体解析 |
| 4 | 路由中间件 | 挂载各 API 路由 |
| 5 | 健康检查 | GET /api/health |
| 6 | 错误处理 | 500 错误统一响应 |
| 7 | 404 处理 | 未匹配路由统一响应 |
路由挂载:
| 前缀 | 路由模块 | 说明 |
|------|----------|------|
| /api/auth | authRoutes | 认证路由(占位) |
| /api/chat | chatRoutes | AI 对话路由 |
| /api/conversations | conversationRoutes | 对话管理路由 |
| /api/models | modelRoutes | 模型列表路由 |
server.ts
本地开发服务器入口,监听 PORT 环境变量(默认 3001),处理 SIGTERM/SIGINT 优雅关闭。
index.ts
Vercel Serverless 部署入口,将 Express app 导出为 Vercel Serverless Function handler。
6.2 API 路由
chat.ts — AI 对话核心路由
端点: POST /api/chat
请求体:
interface ChatRequestBody {
messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>
model?: string // 可选,默认使用 OPENAI_MODEL 环境变量
conversationId?: string // 可选,若不存在则自动创建
}
响应格式: SSE (text/event-stream)
SSE 事件类型:
| type | 数据 | 说明 |
|------|------|------|
| conversation | { conversationId } | 返回对话 ID(新建或已有) |
| content | { content } | AI 回复的文本片段 |
| done | — | 流式生成完成 |
| error | { error } | 错误信息 |
核心逻辑:
- 验证
messages参数 - 确定使用的模型(请求参数 → 环境变量 → 默认
gpt-3.5-turbo) - 获取或创建对话,将用户消息存入 MemoryStore
- 设置 SSE 响应头
- 发送
conversation事件(含对话 ID) - 调用 OpenAI API 流式接口
- 逐 chunk 发送
content事件 - 流结束后将完整 AI 回复存入 MemoryStore
- 发送
done事件
OpenAI 客户端初始化:
function getOpenAIClient(): OpenAI // 懒加载单例模式
apiKey:从OPENAI_API_KEY环境变量读取baseURL:从OPENAI_BASE_URL环境变量读取(默认https://api.openai.com/v1)
conversations.ts — 对话管理路由
| 端点 | 方法 | 说明 | 响应 |
|------|------|------|------|
| /api/conversations | GET | 获取所有对话列表 | { success, data: Conversation[] } |
| /api/conversations/:id | GET | 获取对话详情(含消息) | { success, data: ConversationDetail } |
| /api/conversations/:id | DELETE | 删除对话 | { success } 或 404 |
models.ts — 模型列表路由
| 端点 | 方法 | 说明 | 响应 |
|------|------|------|------|
| /api/models | GET | 获取可用模型列表 | { success, data: [{id, name}] } |
模型列表来源:OPENAI_AVAILABLE_MODELS 环境变量(逗号分隔),默认 ['gpt-3.5-turbo', 'gpt-4', 'gpt-4o']。
auth.ts — 认证路由(占位)
| 端点 | 方法 | 说明 |
|------|------|------|
| /api/auth/register | POST | 用户注册(TODO) |
| /api/auth/login | POST | 用户登录(TODO) |
| /api/auth/logout | POST | 用户登出(TODO) |
当前认证路由仅有骨架,未实现具体逻辑,所有 API 无需认证即可访问。
6.3 数据存储层
memoryStore.ts
基于内存的数据存储层,使用两个 Map 管理数据:
const conversations = new Map<string, Conversation>() // 对话存储
const messages = new Map<string, Message[]>() // 消息存储(按对话 ID 分组)
数据模型:
interface Conversation {
id: string
title: string
createdAt: Date
updatedAt: Date
}
interface Message {
id: string
conversationId: string
role: 'user' | 'assistant' | 'system'
content: string
createdAt: Date
}
导出函数:
| 函数 | 签名 | 说明 |
|------|------|------|
| createConversation | (title: string) => Conversation | 创建对话,自动生成 ID |
| getConversations | () => Conversation[] | 获取所有对话(按 updatedAt 降序) |
| getConversation | (id: string) => Conversation \| undefined | 按 ID 获取单个对话 |
| deleteConversation | (id: string) => boolean | 删除对话及其消息,返回是否成功 |
| addMessage | (conversationId, role, content) => Message \| undefined | 向对话添加消息,更新 updatedAt |
| getMessages | (conversationId: string) => Message[] | 获取对话的所有消息 |
ID 生成策略:
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9)
}
基于时间戳(36 进制)+ 随机字符串,保证唯一性。
⚠️ 注意:数据存储在内存中,服务重启后所有数据丢失。生产环境需替换为持久化存储。
7. 数据模型
ER 关系图
Conversation 1 ──── * Message
│ │
├─ id (PK) ├─ id (PK)
├─ title ├─ conversationId (FK)
├─ createdAt ├─ role ('user'|'assistant'|'system')
└─ updatedAt ├─ content
└─ createdAt
前后端模型差异
| 字段 | 前端 (chatStore) | 后端 (memoryStore) |
|------|------------------|---------------------|
| createdAt | number (时间戳) | Date (Date 对象) |
| updatedAt | number (时间戳) | Date (Date 对象) |
| Message.conversationId | 无 | string (FK) |
前端在接收后端数据时进行 new Date(c.createdAt).getTime() 转换。
8. API 接口文档
接口总览
| 方法 | 路径 | 说明 | 认证 |
|------|------|------|------|
| POST | /api/chat | AI 对话(SSE 流式) | 无 |
| GET | /api/conversations | 获取对话列表 | 无 |
| GET | /api/conversations/:id | 获取对话详情 | 无 |
| DELETE | /api/conversations/:id | 删除对话 | 无 |
| GET | /api/models | 获取可用模型 | 无 |
| GET | /api/health | 健康检查 | 无 |
| POST | /api/auth/register | 注册(未实现) | 无 |
| POST | /api/auth/login | 登录(未实现) | 无 |
| POST | /api/auth/logout | 登出(未实现) | 无 |
统一响应格式
// 成功响应
{ success: true, data: T }
// 错误响应
{ success: false, error: string }
9. 核心流程
9.1 用户对话流程
用户打开应用
│
├─ loadConversations() → GET /api/conversations
├─ loadModels() → GET /api/models
│
▼
显示空状态页面(无活跃对话时)
│
点击"开始对话" → createConversation()(本地创建)
│
▼
用户输入消息 → sendMessage(content)
│
├─ 构造 userMessage + assistantMessage(空)
├─ POST /api/chat { messages, model, conversationId }
│
▼
SSE 流式响应
├─ type=conversation → 同步对话 ID
├─ type=content → 逐字追加 AI 回复
├─ type=done → 流式结束
│
▼
消息渲染(Markdown + 代码高亮)
9.2 对话切换流程
用户点击侧边栏对话项
│
├─ 本地已有消息?→ 直接切换 activeConversationId
├─ 本地无消息?→ GET /api/conversations/:id 加载远程消息
│
▼
渲染对话消息列表
9.3 流式中止流程
用户点击停止按钮
│
├─ stopStreaming() → AbortController.abort()
├─ fetch 请求中断,抛出 AbortError
├─ catch 中静默处理 AbortError
├─ finally 中设置 isStreaming=false
│
▼
保留已接收的部分回复
10. 部署方案
Vercel Serverless 部署
项目已配置 Vercel 部署,核心配置在 vercel.json:
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "/api/index" },
{ "source": "/(.*)", "destination": "/index.html" }
]
}
/api/*请求路由到api/index.ts(Serverless Function 入口)- 其他请求路由到
index.html(SPA 前端) - 前端构建产物在
dist/目录
环境变量配置
| 变量名 | 必需 | 默认值 | 说明 |
|--------|------|--------|------|
| OPENAI_API_KEY | 是 | — | OpenAI API 密钥 |
| OPENAI_BASE_URL | 否 | https://api.openai.com/v1 | API 基础 URL(可指向兼容后端) |
| OPENAI_MODEL | 否 | gpt-3.5-turbo | 默认模型 |
| OPENAI_AVAILABLE_MODELS | 否 | gpt-3.5-turbo,gpt-4,gpt-4o | 可选模型列表(逗号分隔) |
| PORT | 否 | 3001 | 本地开发服务器端口 |
11. 项目运行方式
安装依赖
npm install
开发模式
同时启动前端 Vite 开发服务器和后端 Express 服务器:
npm run dev
等价于并行执行:
| 命令 | 说明 |
|------|------|
| npm run client:dev | 启动 Vite 开发服务器(默认 5173 端口) |
| npm run server:dev | 启动 Nodemon 监听 api/ 目录变更,运行 tsx api/server.ts |
Vite 开发服务器配置了代理,将 /api 请求转发到 http://localhost:3001。
单独启动
# 仅启动前端
npm run client:dev
# 仅启动后端
npm run server:dev
构建生产版本
npm run build
执行 tsc -b 类型检查 + vite build 打包,产物输出到 dist/ 目录。
其他命令
| 命令 | 说明 |
|------|------|
| npm run preview | 预览生产构建产物 |
| npm run lint | 运行 ESLint 代码检查 |
| npm run check | 运行 TypeScript 类型检查(tsc --noEmit) |
路径别名
项目配置了 @/* → ./src/* 的路径别名,在 tsconfig.json 和 vite.config.ts(通过 vite-tsconfig-paths 插件)中同步生效。
微信扫一扫