文章

Vue 路由、状态管理与接口分层实战

Vue 路由、状态管理与接口分层实战

后端同学在 Vue 项目里最应该先打通的是三条线:

  • 页面路由怎么走。
  • 状态数据放哪里。
  • 接口请求如何统一治理。

打通这三条线,绝大多数中后台需求都能接住。

一、路由设计:按业务模块拆

router/modules/user.ts 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import type { RouteRecordRaw } from "vue-router";

const userRoutes: RouteRecordRaw[] = [
  {
    path: "/users",
    name: "UserList",
    component: () => import("@/views/user/UserList.vue"),
    meta: {
      title: "用户管理",
      permission: "user:list"
    }
  }
];

export default userRoutes;

meta.permission 可以和后端权限点做映射,便于统一权限控制。

二、路由守卫:统一处理登录态和权限

router/guard.ts 示例:

1
2
3
4
5
6
7
8
9
10
11
router.beforeEach((to, _from, next) => {
  const token = localStorage.getItem("token");

  if (!token && to.path !== "/login") {
    next("/login");
    return;
  }

  // 这里可扩展权限点判断
  next();
});

注意:权限判断最好放守卫或专门的权限工具里,不要散落在每个页面里。

三、状态管理:只存“共享状态”

stores/user.ts 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { defineStore } from "pinia";

interface UserState {
  token: string;
  nickname: string;
}

export const useUserStore = defineStore("user", {
  state: (): UserState => ({
    token: "",
    nickname: ""
  }),
  actions: {
    setToken(token: string) {
      this.token = token;
    }
  }
});

不要把页面私有表单状态也放进 store,否则状态边界会越来越模糊。

四、接口层分层:页面不直接碰 axios

推荐分层:

  • utils/request.ts:统一 axios 实例与拦截器。
  • api/modules/*.ts:具体业务接口。
  • views/*.vue:只调用 api,不关心底层细节。

utils/request.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import axios from "axios";

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
});

request.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

request.interceptors.response.use(
  (response) => response.data,
  (error) => {
    // 这里可统一错误码处理
    return Promise.reject(error);
  }
);

export default request;

五、类型定义:接口契约前置

types/user.ts

1
2
3
4
5
6
7
8
9
10
export interface UserItem {
  id: number;
  username: string;
  status: "ENABLED" | "DISABLED";
}

export interface PageResult<T> {
  list: T[];
  total: number;
}

api/modules/user.ts

1
2
3
4
5
6
import request from "@/utils/request";
import type { PageResult, UserItem } from "@/types/user";

export function queryUsers(params: { pageNo: number; pageSize: number }) {
  return request.get<PageResult<UserItem>>("/users", { params });
}

六、错误处理建议

  • 网络错误:统一提示“网络异常,请稍后重试”。
  • 401:跳转登录并清理本地登录态。
  • 403:提示无权限并上报埋点。
  • 500:展示可追踪的错误编号,方便后端排查。

七、后端同学的加分动作

  • 主动定义分页返回结构统一规范(list/total)。
  • 统一时间字段格式(UTC / 时区 / 展示格式)。
  • 统一空值语义(null""[] 的业务含义)。

八、实战检查清单

  • 新路由是否按模块独立维护。
  • 是否只有 api 层直接依赖 axios。
  • store 是否只存跨页面共享状态。
  • 页面是否避免重复错误处理代码。

这套分层做好后,你的前端改动会像后端分层一样可维护、可演进。

本文由作者按照 CC BY 4.0 进行授权