文件:src/util/axios-instance.ts
操作:
localStorage 读取 token 并设置 Authorization header 的逻辑withCredentials: true(携带 Session Cookie)/login 逻辑保留实例名保持 customAxiosInstance(与 orval.config.ts 的 mutator 配置一致)
// 修改后的核心代码
const axiosInstance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 30000,
withCredentials: true, // 携带 Session Cookie
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器 — 移除 token 逻辑,仅保留必要处理
axiosInstance.interceptors.request.use(
(config) => {
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器 — 根据后端 Result 格式调整
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
const { code, message } = response.data;
if (code === 200) {
return response.data;
}
return Promise.reject(new Error(message || '请求失败'));
},
(error) => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);
文件:src/store/user.ts
操作:
useLoginUserStore → useUserStore状态结构从嵌套改为扁平:
// 新状态结构
const state = reactive({
userId: null as number | null,
nickname: '',
avatarUrl: '',
role: '',
authStatus: '',
isLoggedIn: false,
});
Actions 重命名:
loginSuccess(user) → login(user):登录成功后设置用户信息logoutUser() → logout():清空状态updateUser(user) → updateProfile(profile):更新资料移除 localStorage.removeItem('token') 调用
Getters:isAuthenticated(authStatus === 'AUTHENTICATED')、isAdmin(role === 'ADMIN')
更新 import 引用:store/index.ts、layout/UserLayout.vue、layout/AdminLayout.vue、router/index.ts、Login.vue
文件:src/store/index.ts
export { useMetaStore } from "./meta.ts";
export { useUserStore } from "./user.ts"; // 改名
文件:src/type/index.ts
操作:移除 LoginUser 接口中的 token?: string 字段(后续阶段六统一使用 Orval 生成类型,这里仅做最低限度清理)
export interface User {
id: string,
name: string,
avatar?: string
role: 'admin' | 'user' | 'guest'
}
export interface LoginUser {
isLogin: boolean,
// token?: string ← 移除
user?: User
}
文件:src/router/index.ts
操作:
重写路由表,按详细设计定义所有路径:
const routes = [
// 公开页面
{ path: '/login', name: 'login', component: () => import('@/view/Login.vue'), meta: { noAuth: true } },
{ path: '/', redirect: '/home' },
{ path: '/home', name: 'home', component: () => import('@/view/Home.vue') },
{ path: '/item/:id', name: 'itemDetail', component: () => import('@/view/ItemDetail.vue') },
{ path: '/search', name: 'search', component: () => import('@/view/Search.vue') },
// C端 — 需登录
{ path: '/publish', name: 'publish', component: () => import('@/view/Publish.vue'), meta: { requiresAuth: true } },
{ path: '/my/items', name: 'myItems', component: () => import('@/view/MyItems.vue'), meta: { requiresAuth: true } },
{ path: '/my/orders', name: 'myOrders', component: () => import('@/view/MyOrders.vue'), meta: { requiresAuth: true } },
{ path: '/order/:id', name: 'orderDetail', component: () => import('@/view/OrderDetail.vue'), meta: { requiresAuth: true } },
{ path: '/messages', name: 'messages', component: () => import('@/view/Messages.vue'), meta: { requiresAuth: true } },
{ path: '/messages/:userId', name: 'chat', component: () => import('@/view/Chat.vue'), meta: { requiresAuth: true } },
{ path: '/profile', name: 'profile', component: () => import('@/view/Profile.vue'), meta: { requiresAuth: true } },
{ path: '/profile/password', name: 'changePassword', component: () => import('@/view/ChangePassword.vue'), meta: { requiresAuth: true } },
// B端 — 需管理员权限
{ path: '/admin/login', name: 'adminLogin', component: () => import('@/view/admin/Login.vue'), meta: { noAuth: true } },
{ path: '/admin', redirect: '/admin/review/user' },
{ path: '/admin/review/user', name: 'reviewUser', component: () => import('@/view/admin/ReviewUser.vue'), meta: { requiresAuth: true, requiresAdmin: true } },
{ path: '/admin/review/item', name: 'reviewItem', component: () => import('@/view/admin/ReviewItem.vue'), meta: { requiresAuth: true, requiresAdmin: true } },
{ path: '/admin/users', name: 'userManage', component: () => import('@/view/admin/UserManage.vue'), meta: { requiresAuth: true, requiresAdmin: true } },
{ path: '/admin/categories', name: 'categoryManage', component: () => import('@/view/admin/CategoryManage.vue'), meta: { requiresAuth: true, requiresAdmin: true } },
];
路由守卫改用 useUserStore:
router.beforeEach((to, _from, next) => {
const userStore = useUserStore();
if (to.meta.requiresAdmin) {
if (!userStore.isLoggedIn) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else if (!userStore.isAdmin) {
next('/');
} else {
next();
}
} else if (to.meta.requiresAuth) {
if (!userStore.isLoggedIn) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
} else {
next();
}
});
文件:src/layout/UserLayout.vue
操作:
useLoginUserStore 引用改为 useUserStoreloginUserStore.loginUser.isLogin → userStore.isLoggedInuserStore.logout(),移除 localStorage.removeItem('token')文件:src/layout/AdminLayout.vue
操作:
/admin/review/user)/admin/review/item)/admin/users)/admin/categories)| 原文件名 | 新文件名 | 操作 |
|---|---|---|
view/LoginView.vue |
view/Login.vue |
重命名 + 更新 router import |
view/HomeView.vue |
view/Home.vue |
重命名 + 更新 router import |
view/AboutView.vue |
— | 删除(设计文档无此页面) |
view/UserView.vue |
view/admin/UserManage.vue |
移动并重命名,后续适配新 API |
文件:src/view/Home.vue
功能:
/search)getItems()、getCategories() API文件:src/view/ItemDetail.vue
功能:
文件:src/view/Search.vue
功能:
文件:src/view/Publish.vue
功能:
publishItem()(multipart/form-data)文件:src/view/MyItems.vue
功能:
文件:src/view/MyOrders.vue
功能:
文件:src/view/OrderDetail.vue
功能:
文件:src/view/Messages.vue
功能:
/messages/{userId})getConversations() API文件:src/view/Chat.vue
功能:
getMessageHistory()、sendMessage() API文件:src/view/Profile.vue、src/view/ChangePassword.vue
Profile.vue:
updateProfile()(multipart/form-data)ChangePassword.vue:
changePassword()文件:src/view/admin/ReviewUser.vue
功能:
文件:src/view/admin/ReviewItem.vue
功能:
文件:src/view/admin/CategoryManage.vue
功能:
文件:src/view/admin/UserManage.vue
操作:
UserView.vue 迁移至此getUserManageController())文件:src/components/ImageUploader.vue
功能:
限制格式和大小
<template>
<el-upload
:action="uploadUrl"
:on-success="handleSuccess"
:on-remove="handleRemove"
:file-list="fileList"
list-type="picture-card"
:multiple="multiple"
:limit="limit"
:before-upload="beforeUpload"
>
<el-icon><Plus /></el-icon>
</el-upload>
</template>
文件:src/components/EmptyState.vue
功能:
cd server && mvn spring-boot:run
确认 http://localhost:8080/api/v3/api-docs 可访问。
cd ui && npm run api:gen
生成产出:
src/api/auth-controller.ts(认证模块)src/api/user-controller.ts(用户模块)src/api/item-controller.ts(物品模块)src/api/order-controller.ts(订单模块)src/api/message-controller.ts(消息模块)src/api/review-controller.ts(审核模块)src/api/user-manage-controller.ts(用户管理)src/api/category-controller.ts(分类管理)src/api/models/(全部 TypeScript 类型)操作:
src/type/index.ts 中的手动接口import type { UserVO, ItemVO, OrderVO } from '@/api/models'文件:src/style.css
操作:
#app 居中布局、a 标签颜色等)替换为:
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body, #app { height: 100%; }
HTML 文件添加 viewport 设置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
确保每个页面:
使用 Chrome DevTools 验证:
| 阶段 | 新建文件 | 修改文件 | 删除文件 |
|---|---|---|---|
| 一 | 0 | axios-instance.ts, store/user.ts, store/index.ts, type/index.ts |
0 |
| 二 | 0 | router/index.ts, UserLayout.vue, AdminLayout.vue |
AboutView.vue |
| 三 | Home.vue, ItemDetail.vue, Search.vue, Publish.vue, MyItems.vue, MyOrders.vue, OrderDetail.vue, Messages.vue, Chat.vue, Profile.vue, ChangePassword.vue |
0 | 0 |
| 四 | ReviewUser.vue, ReviewItem.vue, CategoryManage.vue |
UserManage.vue(迁移) |
UserView.vue |
| 五 | ImageUploader.vue, EmptyState.vue |
0 | 0 |
| 六 | 0 | Orval 自动覆盖生成 | type/index.ts(部分) |
| 七 | 0 | style.css, index.html |
0 |
| 合计 | 16 | 11 | 2 |