1. 创建 Vue 3 项目
进入 ai-chat/ 目录,使用 Vite 脚手架创建前端项目:
npm create vite@latest ai-chat-frontend -- --template vue-ts构建完成后此时目录结构为:
ai-chat/
├── ai-chat-backend/ # 后端(前面创建)
└── ai-chat-frontend/ # 前端(本次创建)
├── public/
├── src/
├── index.html
├── package.json
├── vite.config.ts
├── tsconfig.json
├── tsconfig.app.json
└── tsconfig.node.json2. 清理脚手架默认内容
Vite 脚手架会生成示例代码和资源文件,我们需要先清理掉,从干净的状态开始。
删除不需要的文件:
cd ai-chat-frontend
# 删除示例组件
rm -rf src/components/HelloWorld.vue
# 删除示例资源图片
rm -rf src/assets/hero.png
rm -rf src/assets/vite.svg
rm -rf src/assets/vue.svg清空 src/style.css(后续由 Tailwind 接管)
重写 src/App.vue(删除对 HelloWorld 的引用)
<template>
<div>
<h1>AI Chat</h1>
</div>
</template>src/main.ts 保持不动,脚手架生成的已经是最简形式:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')添加 .vue 类型声明
新版 Vite 脚手架使用 TypeScript Project References 模式(tsconfig.json -> tsconfig.app.json),部分 IDE 在这种模式下无法正确识别 .vue 文件的类型,导致 import XxxView from '../views/XxxView.vue' 报 TS2307: Cannot find module 错误。
创建 src/shims-vue.d.ts,显式声明 .vue 模块类型:
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}此时运行 npm run dev 应该能看到一个干净的页面,只显示 "AI Chat"。
3. 引入 DaisyUI
DaisyUI 是基于 Tailwind CSS 的组件库,提供了开箱即用的 UI 组件(按钮、输入框、卡片、模态框等),省去手写样式的时间。
安装 Tailwind CSS v4 和 DaisyUI 命令如下:
npm install tailwindcss @tailwindcss/vite daisyui配置 Vite 插件,编辑 vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
vue(),
tailwindcss(),
],
})在 src/style.css 中引入 Tailwind 和 DaisyUI:
@import "tailwindcss";
@plugin "daisyui";4. 安装常用工具
# HTTP 请求库
npm install axios
# 路由
npm install vue-router
# 状态管理
npm install pinia简要说明:
- Axios:HTTP 请求库,后续会配置拦截器自动携带 JWT Token
- Vue Router:前端路由,管理页面导航和路由守卫
- Pinia:Vue 3 官方推荐的状态管理库,用于管理全局数据
5. 规划项目结构
在 src/ 下创建以下目录:
mkdir -p src/{api,views,stores,composables,router}src/
├── api/ # 后端 API 请求模块
├── composables/ # 可复用的组合式函数
├── router/ # 路由配置
├── stores/ # Pinia 状态管理
├── views/ # 页面组件
├── App.vue
├── main.ts
└── style.css每个目录的职责:
api/:按后端模块拆分请求函数,如auth.ts、topic.ts、chat.tsviews/:页面级组件,如LoginView.vue、ChatView.vuestores/:全局状态,如用户认证状态composables/:可复用逻辑,如 SSE 流式请求router/:路由表和导航守卫
6. Axios 封装
前面后端定义了统一响应格式 ApiResponse<T>,所有接口返回的 JSON 结构为 { code, message, data }。前端需要对应地做自动拆包,成功时直接取出 data,失败时抛出错误。
创建 src/api/index.ts:
import axios from 'axios'
// 后端统一响应格式
interface ApiResponse<T = any> {
code: number
message: string
data: T
}
const api = axios.create({
baseURL: '/api',
timeout: 30000,
})
// 请求拦截器:自动携带 JWT Token(后续实现登录后生效)
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:自动拆包 ApiResponse
api.interceptors.response.use(
(response) => {
const apiResponse = response.data as ApiResponse
if (apiResponse.code === 200) {
// 成功:将 response.data 替换为实际数据,调用方直接拿到业务数据
response.data = apiResponse.data
return response
}
// 业务错误(code !== 200 但 HTTP 状态码为 200)
return Promise.reject(new Error(apiResponse.message))
},
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
// 尝试从 ApiResponse 中提取错误信息
const message = error.response?.data?.message || error.message
return Promise.reject(new Error(message))
}
)
export default api7. 配置路由
创建 src/router/index.ts:
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import LoginView from '../views/LoginView.vue'
import ChatView from '../views/ChatView.vue'
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: LoginView,
},
{
path: '/',
name: 'Chat',
component: ChatView,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router8. 配置 Pinia
创建 src/stores/index.ts:
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia9. 更新入口文件
现在把 Router 和 Pinia 注册到应用中,编辑 src/main.ts:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import pinia from './stores'
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')同时更新 src/App.vue,用 <RouterView /> 替换之前的占位内容:
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>10. 创建占位页面
先创建两个最简页面,后续章节再填充具体内容。
src/views/LoginView.vue
<template>
<div class="min-h-screen flex items-center justify-center">
<div class="card bg-base-200 w-96 shadow-xl">
<div class="card-body items-center text-center">
<h2 class="card-title">AI Chat</h2>
<p>登录页面(待实现)</p>
</div>
</div>
</div>
</template>src/views/ChatView.vue
<template>
<div class="min-h-screen flex items-center justify-center">
<h1 class="text-2xl">聊天页面(待实现)</h1>
</div>
</template>11. 配置 Vite 代理
开发时前端运行在 5173 端口,后端在 8080 端口。配置代理后,前端发往 /api 的请求会自动转发到后端,避免跨域问题。
编辑 vite.config.ts(在之前的基础上追加 server 配置):
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
vue(),
tailwindcss(),
],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})12. 验证前后端通信
确保后端正在运行,然后启动前端:
npm run dev打开浏览器访问 http://localhost:5173,应该能看到 DaisyUI 风格的登录占位卡片。
打开浏览器的开发者工具,在 Console 中执行:
fetch('/api/ping').then(r => r.json()).then(console.log)
// 应输出:{code: 200, message: "操作成功", data: "pong"}如果看到正确的响应,说明前端请求成功代理到后端,前后端通信没问题。
13. 小结
从零搭建了 Vue 3 前端项目,完成了开发环境配置和前后端通信验证。
| 知识点 | 说明 |
|---|---|
| 项目创建 | Vite 创建 Vue 3 + TypeScript 项目,清理脚手架默认内容 |
| UI 框架 | 引入 DaisyUI(基于 Tailwind CSS 的组件库) |
| 核心依赖 | Axios(HTTP 请求)、Vue Router(路由)、Pinia(状态管理) |
| 目录结构 | 按功能划分 api / views / stores / composables 等目录 |
| Axios 拦截器 | 自动拆包后端 ApiResponse 统一响应格式 |
| Vite 代理 | /api 请求代理到后端 8080 端口,解决跨域问题 |
接下来实现用户认证功能,包括登录页面、JWT Token 存储和自动携带、路由守卫等。