从 0 到 1 搭建 Vite+Vue3+TS 工程模板:能上手操作的指南

引言

每次新建前端项目时,重复配置环境变量、路由、状态管理、API 封装、代码规范等基础功能不仅耗时,还易因配置不一致导致后续维护问题。因此,本文档记录从 01 搭建 Vite + Vue3 + TypeScript 通用工程模板的完整流程,核心目标是:

  1. 固化通用配置(如多环境、代码规范、提交校验),避免重复劳动;
  2. 定义清晰的目录结构与功能规范(如 API 请求、状态管理),降低团队协作成本;
  3. 生成可复用的模板,后续新建项目时直接拉取、微调即可快速启动,提升开发效率。

模板整合了路由、Pinia 状态管理、API 类型安全封装、国际化、代码规范校验等核心能力,适配大多数前端项目的基础需求。

项目初始化(生成基础目录)

通过 Vite 官方命令快速生成 Vue+TypeScript 基础结构,无需手动配置基础依赖。

bash 复制代码
# 1. 执行初始化命令(若未安装pnpm,先执行 `npm install -g pnpm`)
pnpm create vite

# 2. 按终端交互提示进行选择:

环境配置(区分多环境参数)

创建多环境配置文件,统一管理 "公共参数" 和 "环境专属参数",遵循 Vite 核心规则:
环境变量必须以 VITE_为前缀 (否则无法在代码中通过 import.meta.env 访问)。

创建环境文件(项目根目录下)

新建 3 个文件,分别对应 "公共配置"、"开发环境"、"生产环境":

  • .env(所有环境通用配置)
  • .env.development(本地开发环境,执行 --mode=development 时生效)
  • .env.production(线上生产环境,执行 --mode=production 时生效)

环境文件示例(可按需修改)

  • .env(公共配置):
bash 复制代码
# 页面默认标题(路由未配置title时使用)
VITE_APP_TITLE=LearnDoing-Web
  • .env.development(开发环境):
bash 复制代码
# 开发环境接口地址(本地后端服务地址)
VITE_APP_API_URL=http://localhost:3000/dev-api

# 项目基础路径(部署子路径时使用,如 /demo/)
VITE_APP_BASE_PATH=/demo/
  • .env.production(生产环境):
bash 复制代码
# 生产环境接口地址(线上后端服务地址)
VITE_APP_API_URL=https://api.example.com/api

# 生产环境基础路径(根路径部署)
VITE_APP_BASE_PATH=/

TypeScript 配置(完善类型提示)

创建公共类型目录,扩展 Vite、Vue Router 等工具的默认类型,避免代码中出现 "类型 any" 或 "类型提示缺失" 问题。

创建类型目录与核心文件

在项目根目录新建 types 文件夹,存放 3 个核心类型文件:

  • types/global.d.ts(扩展全局类型,如 Window)
  • types/vite-env.d.ts(定义 VITE 环境变量类型)
  • types/vue-router.d.ts(扩展路由元信息类型)

类型文件内容

  • types/global.d.ts(全局类型扩展):
ts 复制代码
declare global {
    // 可在此扩展 Window 上的自定义属性(如全局工具、埋点对象)
    interface Window {

    }
}

export {};
  • types/vite-env.d.ts(环境变量类型):
ts 复制代码
/// <reference types="vite/client" />

// 定义环境变量的类型(与.env文件中的VITE_变量一一对应)
declare namespace Env {
    interface ImportMetaEnv {
        readonly VITE_APP_API_URL: string;
        readonly VITE_APP_BASE_PATH: string;
        readonly VITE_APP_TITLE: string;
    }
}

// 让import.meta.env自动继承上述类型,获得类型提示
interface ImportMetaEnv extends Env.ImportMetaEnv {
}

interface ImportMeta {
    readonly env: ImportMetaEnv;
}
  • types/vue-router.d.ts(路由元信息扩展):
ts 复制代码
import 'vue-router';

// 扩展路由的meta字段(支持自定义属性,如页面标题)
declare module 'vue-router' {
    interface RouteMeta {
        title?: string; // 页面标题(用于router.beforeEach设置文档标题)
    }
}

修改 tsconfig.app.json(让 TS 识别类型文件)

打开项目根目录的 tsconfig.app.json,更新 compilerOptionsinclude 字段,确保自定义类型被识别:

json5 复制代码
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "types": [
      "vite/client",
      "types/**/*.d.ts"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "types/**/*.d.ts"
  ]
}

Vite 配置优化(基础路径、代理等)

修改 vite.config.ts,配置 路径别名 (@指向 src)、接口代理 (解决开发环境跨域)、基础路径(从环境变量读取),适配项目实际需求。

ts 复制代码
import {defineConfig, loadEnv} from 'vite';
import vue from '@vitejs/plugin-vue';
import {resolve} from 'node:path';

export default defineConfig(configEnv => {
    // 加载当前环境的.env文件(mode:dev/prod,cwd:项目根目录)
    const env = loadEnv(configEnv.mode, process.cwd(), "") as unknown as Env.ImportMetaEnv;
    return {
        // 项目基础路径(部署子路径时生效,从环境变量读取)
        base: env.VITE_APP_BASE_PATH,
        // 路径解析配置(@别名指向src)
        resolve: {
            alias: {"@": resolve(__dirname, "src")}
        },
        // 插件配置(Vue插件必装,支持单文件组件)
        plugins: [vue()],
        // 开发服务器配置(解决跨域)
        server: {
            proxy: {
                // 匹配以/api开头的请求,转发到后端服务
                "/api": {
                    target: env.VITE_APP_API_URL, // 代理目标地址(从环境变量读取)
                    changeOrigin: true, // 开启跨域(模拟请求头的Origin)
                    rewrite: (path) => path.replace(/^/api/, "") // 去掉请求中的/api前缀
                }
            }
        }
    };
});

入口文件与根组件配置(页面基础)

修改 index.html(配置 favicon、视口)和 App.vue(添加路由出口),确保页面基础功能正常。

配置 index.html(项目入口页面)

打开项目根目录的 index.html,替换为以下内容(重点:favicon、禁止缩放视口、动态标题):

html 复制代码
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <!-- Favicon配置(需先准备favicon文件,步骤见6.2) -->
    <link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96"/>
    <link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg"/>
    <link rel="shortcut icon" href="/favicon/favicon.ico"/>
    <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png"/>
    <link rel="manifest" href="/favicon/site.webmanifest"/>
    <!-- 视口配置:禁止缩放(适配移动端,避免手动缩放导致布局错乱) -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
    <!-- 动态标题:从环境变量读取(%VITE_XXX%是Vite的环境变量注入语法) -->
    <title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

准备 Favicon 文件(避免浏览器默认图标)

  1. 推荐工具:RealFaviconGenerator(上传 logo,自动生成多格式 favicon,适配不同设备);
  2. 下载工具生成的压缩包,解压后将所有文件(如 favicon.icoapple-touch-icon.pngsite.webmanifest 等)放入 **项目根目录的 **public/favicon文件夹 (需手动新建 public/favicon)。

修改 App.vue(添加路由出口)

打开 src/App.vue,删除默认内容,仅保留路由出口(后续路由匹配的组件会在此渲染):

ts 复制代码
<script setup lang="ts">
// 定义组件名称(便于调试和Vue DevTools识别)
defineOptions({
   name: 'LearnDoingWeb',
});

</script>

<template>
   <!-- 路由出口:匹配的路由组件将在此处渲染 -->
   <router-view />
</template>

<style scoped></style>

目录结构规划

按 "功能拆分、便于维护、降低耦合" 原则,规划 src 目录结构(提前创建好空文件夹,后续逐步填充内容),核心是 " 按功能模块归类,避免文件杂乱":

bash 复制代码
src/
├── api/                  # 接口请求层(统一管理接口配置、类型、请求逻辑)
│   ├── endpoints/        # 接口端点配置(URL、请求方法,需符合IApi类型)
│   │   ├── index.ts      # 接口端点聚合导出
│   │   └── user.ts       # 用户相关接口端点(示例)
│   ├── dto/              # 接口DTO类型(入参/出参类型定义,遵循固定规范)
│   │   └── user.ts       # 用户相关接口DTO(示例)
│   ├── request.ts        # Axios实例封装(拦截器、超时、路径参数替换)
│   └── index.ts          # API相关能力统一导出(实例、类型、端点)
├── assets/               # 静态资源(需通过import引用,会被Vite处理)
│   ├── images/           # 图片资源(如logo、业务图片)
│   └── styles/           # 全局样式(重置样式、变量、混合器)
│       └── index.css     # 样式入口(导入全局样式,在main.ts中引入)
├── components/           # 公共组件(可复用、非页面级,按用途拆分)
│   ├── common/           # 通用基础组件(按钮、弹窗、输入框等,跨业务复用)
│   └── business/         # 业务公共组件(如表单卡片、数据列表,业务内复用)
├── composables/          # Vue3组合式函数(复用业务逻辑,替代mixins)
│   ├── useApi.ts         # API请求Hooks(类型安全调用接口,集中处理loading/错误)
│   └── index.ts          # 组合函数统一导出(简化组件导入)
├── i18n/                 # 国际化配置(多语言支持,便于后续扩展)
│   ├── locales/          # 多语言文件(按语言拆分)
│   │   └── zh-CN.ts      # 中文语言包(示例)
│   └── index.ts          # i18n实例初始化(配置默认语言、注入全局)
├── router/               # 路由配置(页面跳转、权限控制)
│   ├── routes.ts         # 路由规则(定义路径、组件、元信息)
│   └── index.ts          # 路由实例(创建router、全局守卫)
├── store/                # 状态管理(Pinia,轻量、类型友好,替代Vuex)
│   ├── modules/          # 模块化状态(按业务拆分,避免单文件过大)
│   ├── userInfo.ts       # 用户信息状态(示例:登录状态、token)
│   └── index.ts          # Pinia实例初始化(启用持久化、导出模块)
├── utils/                # 工具函数(通用能力,无业务依赖)
│   ├── index.ts          # 工具函数统一导出(简化组件导入)
│   └── format.ts         # 格式化工具(如时间、金额格式化,示例)
├── views/                # 页面级组件(对应路由,一个路由对应一个页面)
│   └── login/            # 登录页面(示例)
│       └── index.vue     # 登录组件(页面逻辑、模板)
├── App.vue               # 根组件(路由出口、全局布局容器)
└── main.ts               # 应用入口(初始化Vue、挂载插件)

Vue Router 配置(页面跳转)

安装路由依赖,配置基础路由规则(如登录页)和全局守卫(设置页面标题),实现页面间跳转的类型安全与统一控制。

安装依赖

bash 复制代码
pnpm add vue-router

创建登录页面(避免路由匹配时组件缺失)

新建 src/views/login/index.vue(路由会指向此组件),简单实现登录页结构(后续可根据业务完善):

创建路由配置文件

  • src/router/routes.ts(路由规则定义,明确每个路由的路径、组件、元信息):
ts 复制代码
import {type RouteRecordRaw} from 'vue-router';

// 路由规则数组(每个对象对应一个页面)
export const routes: RouteRecordRaw[] = [
    {
        // 根路径(访问http://localhost:5173/时触发)
        path: '/',
        // 重定向到登录页(避免根路径空白)
        redirect: {name: 'LoginView'},
    },
    {
        // 登录页路径(访问http://localhost:5173/login)
        path: '/login',
        name: 'LoginView', // 路由名称(跳转时使用,需唯一)
        // 懒加载组件(打包时拆分代码,减少初始加载体积)
        component: () => import('@/views/login/index.vue'),
        // 路由元信息(与types/vue-router.d.ts扩展的RouteMeta对应)
        meta: {title: '登录'}
    },
];
  • src/router/index.ts(路由实例创建 + 全局守卫,统一控制路由行为):
ts 复制代码
import {createRouter, createWebHistory} from 'vue-router';
import {routes} from './routes';

// 创建路由实例
const router = createRouter({
    // 历史模式:HTML5 History(无#号,需后端配置 fallback)
    history: createWebHistory(import.meta.env.VITE_APP_BASE_PATH),
    routes: routes // 导入路由规则
});

// 全局前置守卫:每次路由跳转前执行(此处用于统一设置页面标题)
router.beforeEach((to, _from, next) => {
    // 优先使用路由meta.title,无则使用环境变量的默认标题
    document.title = to.meta.title ?? import.meta.env.VITE_APP_TITLE;
    // 继续执行路由跳转(必须调用next(),否则路由会阻塞)
    next();
});

export default router;

Pinia 状态管理配置(全局状态)

安装 Pinia 及持久化插件(防止页面刷新后状态丢失),配置用户信息状态(如登录状态、token),实现全局状态的统一管理与类型安全。

安装依赖

bash 复制代码
# 安装Pinia核心 + 持久化插件(实现状态本地存储)
pnpm add pinia pinia-persisted-plugin

创建 Pinia 配置与状态模块

  • src/store/index.ts(Pinia 实例初始化 + 模块导出,集中管理状态模块):
ts 复制代码
import {createPinia} from 'pinia';
import {persistedPlugin} from 'pinia-persisted-plugin';

// 导出业务状态模块(组件中可直接导入useUserInfoStore使用)
export {useUserInfoStore} from './userInfo.ts';

// 创建Pinia实例
const pinia = createPinia();

// 使用持久化插件(默认存储到localStorage,支持配置存储方式)
pinia.use(persistedPlugin);

export default pinia;
  • src/store/userInfo.ts(用户信息状态模块,按业务拆分,便于维护):
ts 复制代码
import {defineStore} from 'pinia';

// 定义用户信息Store(id必须唯一,建议与文件名对应,便于识别)
export const useUserInfoStore = defineStore('userInfo', {
    // 状态:类似组件的data,返回初始状态对象(支持TypeScript类型推导)
    state: () => ({
        username: 'learnDoing', // 用户名(示例值)
        isLoggedIn: false, // 是否登录(初始未登录)
        token: '' // 登录凭证(后续接口请求需携带,初始为空)
    }),

    // 持久化配置(开启后,状态会自动存储到localStorage,刷新不丢失)
    persisted: true
});

API 请求封装(类型安全调用接口)

基于 Axios 封装统一请求工具,严格遵循自定义规范(接口配置符合 IApi 类型、DTO 区分 URLParams/params/data、响应格式统一),通过 Hooks 提供类型安全的接口调用方式,降低接口调用的重复代码与错误率。

核心规范说明(必须遵守,确保一致性)

在封装前明确 API 设计规范,避免后续接口混乱:

  1. 接口配置规范 :所有接口端点(URL、请求方法)必须符合 IApi 类型(继承 AxiosRequestConfig),确保配置格式统一;
  2. DTO 类型规范
  • URLParams:路径参数(如 /users/{userId}/profile 中的 userId),仅用于 RESTful 路径占位符替换;
  • params:查询参数(如 ?username=xxx&page=1),仅用于 GET/DELETE 请求;
  • 平铺字段:请求体(data),用于 POST/PUT/PATCH 请求,直接在 DTO 中平铺定义(无需嵌套在 data 字段内);

2响应处理规范 :需与后端约定统一响应格式(如 { success: boolean; code: number; message: string; data: T } ),便于前端统一处理成功 / 失败逻辑。

安装 Axios 依赖

bash 复制代码
pnpm add axios

创建 API 基础类型文件(定义规范约束)

新建 src/api/types.ts,定义请求 / 响应的通用类型,约束接口配置与调用逻辑:

ts 复制代码
import type {AxiosRequestConfig, InternalAxiosRequestConfig} from 'axios';
import type {UserRequest, UserResponse} from './dto/user.ts';

// 1. 接口配置类型(所有接口端点必须符合此类型,确保格式统一)
export type IApi = Record<string, AxiosRequestConfig>;

export type APIRequest = UserRequest;
export type APIResponse = UserResponse;

// 2. 请求配置类型(扩展AxiosRequestConfig,支持URLParams/params/data)
// D:请求体数据类型,P:查询参数类型,U:路径参数类型
export interface IRequestConfig<D = unknown, P = unknown, U = unknown>
    extends AxiosRequestConfig<D> {
    params?: P; // 查询参数(GET/DELETE 请求用)
    URLParams?: U; // 路径参数(RESTful 路径占位符用)
}

// 3. 内部请求配置类型(扩展InternalAxiosRequestConfig,供拦截器使用)
export interface InternalRequestConfig<D = unknown, P = unknown, U = unknown>
    extends InternalAxiosRequestConfig<D> {
    params?: P;
    URLParams?: U;
}

export type ApiRequestConfig<A> = IRequestConfig<
    // 请求体类型
    A extends keyof APIRequest ? Omit<APIRequest[A], 'URLParams' | 'params'> : unknown,
    // 查询参数类型
    A extends keyof APIRequest
        ? 'params' extends keyof APIRequest[A]
            ? APIRequest[A]['params']
            : undefined
        : undefined,
    // 路径参数类型
    A extends keyof APIRequest
        ? 'URLParams' extends keyof APIRequest[A]
            ? APIRequest[A]['URLParams']
            : undefined
        : undefined
>;

// 4. 响应配置类型([错误, 数据] 元组,二选一有值,便于组件统一处理)
export type ApiResponseConfig<A> = [
        Error | null,
        (A extends keyof APIResponse ? APIResponse[A] : unknown) | null,
];

创建 API 核心文件(实现规范与调用逻辑)

src/api/request.ts(Axios 实例封装 + 拦截器,统一处理请求 / 响应)

ts 复制代码
import axios from 'axios';
import type {AxiosInstance} from 'axios';
import type {InternalRequestConfig} from './types';

// 判断当前环境(开发环境:dev,生产环境:production)
const isDev = import.meta.env.MODE === 'development';

// 创建Axios实例(统一配置基础路径、超时、请求头)
export const instance: AxiosInstance = axios.create({
    // 基础路径:开发环境用/api(配合Vite代理),生产环境用环境变量地址
    baseURL: isDev ? '/api' : import.meta.env.VITE_APP_API_URL,

    // 默认请求头:JSON格式(后端需支持application/json)
    headers: {'Content-Type': 'application/json'},
});

// 路径参数替换工具函数(将 /users/{userId} 替换为 /users/123)

export function replacePlaceholders<T extends object>(template: string, placeholders: T): string {
    const regex = /{([^}]+)}/g;

    return template.replace(regex, (match, key) => {
        if (key in placeholders) {
            return String(placeholders[key as keyof T]);
        }
        return match;
    });
}

// 请求拦截器:发送请求前处理(路径参数替换、添加token等)
instance.interceptors.request.use(
    (config: InternalRequestConfig) => {
        // 1. 处理路径参数(符合RESTful API风格)
        const {URLParams} = config;
        if (URLParams) {
            config.url = replacePlaceholders(config.url!, URLParams);
        }

        // 2. 后续可添加token(从Pinia的userInfoStore中获取)
        // import { useUserInfoStore } from '@/store';
        // const userStore = useUserInfoStore();
        // if (userStore.token) {
        //     config.headers.Authorization = `Bearer ${userStore.token}`;
        // }

        return config;
    },
    (error) => {
        // 请求错误处理(如网络错误、请求被取消)
        return Promise.reject(error);
    }
);

// 响应拦截器:接收响应后处理(统一解析响应、错误提示等)
instance.interceptors.response.use(
    (response) => {
        // 直接返回响应体(简化组件调用:无需每次写 res.data)
        return response.data;
    },
    (error) => {
        // 响应错误处理(如401未登录、500服务器错误)
        return Promise.reject(error);
    }
);

src/api/dto/user.ts(用户接口 DTO 类型,严格遵循规范)

ts 复制代码
// 用户接口请求参数类型(DTO:Data Transfer Object,与后端接口一一对应)
export interface UserRequest {
    // 1. 账号密码登录(POST请求,请求体为平铺字段)
    USER_LOGIN: {
        username: string; // 用户名(必传,无默认值)
        password: string; // 密码(必传,建议加密后传输)
        rememberMe?: boolean; // 是否记住登录(可选,默认false)
    };

    // 2. 获取用户信息(GET请求,路径参数+无请求体)
    USER_GET_PROFILE: {
        URLParams: {
            userId: string; // 路径参数(必传,如 /users/{userId}/profile)
        };
        // 无params(查询参数)、无平铺字段(请求体)
    };

    // 3. 示例:带查询参数的请求(GET请求,params为查询参数)
    USER_LIST: {
        params: {
            page: number; // 页码(必传)
            size: number; // 每页条数(必传)
            keyword?: string; // 搜索关键词(可选)
        };
        // 无URLParams、无请求体
    };
}

// 用户接口响应结果类型(与请求参数一一对应,需与后端约定统一格式)
export interface UserResponse {
    // 1. 登录响应(与USER_LOGIN请求对应)
    USER_LOGIN: {
        success: boolean; // 业务是否成功(建议后端统一返回)
        userId: string; // 用户ID(登录成功后返回)
        token: string; // 登录token(后续接口请求需携带)
        refreshToken: string; // 刷新token(token过期时使用)
        nickname: string; // 用户昵称(用于前端展示)
    };

    // 2. 获取用户信息响应(与USER_GET_PROFILE请求对应)
    USER_GET_PROFILE: {
        success: boolean;
        data: {
            userId: string;
            username: string;
            nickname: string;
            avatar?: string; // 头像(可选,无则用默认图)
            createdAt: string; // 注册时间(ISO格式:2024-05-20T10:00:00Z)
        };
    };
}

src/api/endpoints/user.ts(用户接口端点配置,符合 IApi 类型)

ts 复制代码
import type {IApi} from '@/api/types';

// 用户相关接口端点配置(必须符合IApi类型,确保格式统一)
// key:与UserRequest/UserResponse的key一致,value:Axios请求配置(URL+method)

export default {
    // 登录接口(POST请求,URL:/users/login)
    USER_LOGIN: {
        method: 'post',
        url: '/users/login',
    },

    // 获取用户信息接口(GET请求,URL:/users/{userId}/profile,含路径占位符)
    USER_GET_PROFILE: {
        method: 'get',
        url: '/users/{userId}/profile',
    },

    // 示例:用户列表接口(GET请求,URL:/users)

    USER_LIST: {
        method: 'get',
        url: '/users',
    },

} satisfies IApi; // 使用satisfies确保配置符合IApi类型,不符合会报错

src/api/endpoints/index.ts(接口端点聚合导出,简化组件导入)

ts 复制代码
import type {IApi} from '@/api/types';
import userEndpoints from './user.ts';

// 聚合所有接口端点(后续新增业务模块,在此处导入并扩展)
const endpoints = {
    ...userEndpoints,
    // 示例:新增订单接口端点
    // ...orderEndpoints,
} satisfies IApi;

export default endpoints;

src/api/index.ts(API 相关能力统一导出,组件只需导入此文件)

ts 复制代码
// 导出Axios实例(特殊场景用,如取消请求)
export {default as instance} from './request.ts';
// 导出接口端点配置(供useApi Hooks使用)
export {default as endpoints} from './endpoints/index.ts';
// 导出API通用类型(组件中需定义请求/响应类型时使用)
export * from './types.ts';

src/composables/useApi.ts(API 请求 Hooks,实现类型安全调用)

ts 复制代码
import {instance, endpoints, type ApiResponseConfig, type ApiRequestConfig} from '@/api';

// 接口键名类型(必须是endpoints的key)
type ApiKey = keyof typeof endpoints;

/**
 * 通用API请求Hooks(类型安全,自动推导请求参数与响应类型)
 * @param apiKey 接口键名(如'USER_LOGIN',必须是ApiKey类型)
 * @param config 请求配置(URLParams/params/data,自动推导类型)
 * @returns [错误对象, 响应数据] 元组(二选一有值)
 */

export const useApi = async <K extends ApiKey>(
    apiKey: K,
    config?: ApiRequestConfig<K>
): Promise<ApiResponseConfig<Api>> => {
    try {
        // TODO:后续可添加loading状态(如用ref(true),finally中置为false)
        // 合并接口端点默认配置与用户自定义配置(自定义配置优先级高)
        const requestConfig = Object.assign({}, endpoints[apiKey], config || {});

        // 发送请求(使用封装好的Axios实例)
        const response = await instance(requestConfig);

        // TODO:精细化处理响应(如判断success为false时抛错)
        // if (!response.success) throw new Error(response.message || '接口请求失败');
        return [null, response] as const;

    } catch (error) {
        // 捕获错误(网络错误、接口业务错误等)
        return [error as Error, null] as const;
    } finally {
        // TODO:关闭loading状态
        // loading.value = false;
    }

};
ts 复制代码
  const [loginError, loginData] = await useApi("USER_LOGIN", {
    data: {
        username: "learndoing", // 用户名(必传,无默认值)
        password: "123456", // 密码(必传,建议加密后传输)
        rememberMe: true // 是否记住登录(可选,默认false)
    }
});

if (loginError || !loginData?.success) {
    return;
}
// 处理 loginData

国际化配置(Vue I18n)

安装 Vue I18n,配置中文语言包(后续可扩展英文等),实现多语言支持,便于项目国际化扩展。

安装依赖

bash 复制代码
# Vue3需使用vue-i18n@9+版本(适配Composition API)
pnpm add vue-i18n

创建国际化配置文件

  • src/i18n/locales/zh-CN.ts(中文语言包,按模块拆分,便于维护):
ts 复制代码
// 中文语言包:按业务模块拆分(common=通用,login=登录页)
export default {
    common: {
        confirm: '确认',
        cancel: '取消',
        submit: '提交',
        loading: '加载中...',
        success: '操作成功',
        error: '操作失败'
    },
    login: {
        title: '用户登录',
        username: '用户名',
        password: '密码',
        loginBtn: '登录',
        rememberMe: '记住我',
        forgotPassword: '忘记密码?'
    }
};
  • src/i18n/index.ts(i18n 实例初始化,配置全局注入):
ts 复制代码
import {createI18n} from 'vue-i18n';

// 导入中文语言包(后续扩展英文,需导入en-US.ts)
import zhCN from './locales/zh-CN.ts';

// 语言包对象(key为语言标识,如zh-CN、en-US,符合ISO 639-1标准)
const messages = {
    'zh-CN': zhCN,
    // 后续扩展英文:'en-US': enUS
};

// 创建i18n实例
const i18n = createI18n({
    locale: 'zh-CN', // 默认语言(中文)
    fallbackLocale: 'zh-CN', // 降级语言(当默认语言缺失文案时使用)
    messages: messages, // 导入语言包
    globalInjection: true, // 全局注入$t方法(组件模板中可直接用$t('xxx'))
    legacy: false // 启用Vue3 Composition API模式(必须设为false)
});

export default i18n;

入口文件整合(main.ts)

src/main.ts 中导入路由、Pinia、I18n 等核心模块,挂载到 Vue 实例,完成应用初始化。

ts 复制代码
import {createApp} from 'vue';
import App from './App.vue';
// 导入核心插件
import router from './router'; // 路由
import pinia from './store'; // 状态管理
import i18n from './i18n'; // 国际化

// 导入全局样式(包含重置样式、变量等)
import './assets/styles/index.css';

// 创建Vue实例
const app = createApp(App);

// 挂载插件(顺序不强制,但建议按"路由→状态→国际化"的顺序)
app.use(router);
app.use(pinia);
app.use(i18n);

// 挂载到DOM(对应index.html中的<div id="app"></div>)
app.mount('#app');

代码规范配置(ESLint + Prettier)

整合 ESLint(语法校验 + 代码风格)和 Prettier(格式美化),统一代码规范,避免团队协作时因格式 / 语法不一致导致的冲突。

安装依赖

bash 复制代码
# 安装Prettier及ESLint整合依赖(解决ESLint与Prettier的规则冲突)
pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D

初始化 ESLint 配置(生成基础配置文件)

bash 复制代码
pnpm create @eslint/config@latest

按终端交互提示选择(适配 Vue3+TypeScript 技术栈)

完善 ESLint 配置(eslint.config.ts)

替换生成的 eslint.config.ts 内容,添加 Vue、Prettier 整合规则,确保规范统一:

ts 复制代码
import js from '@eslint/js';
import globals from 'globals';
import tsEslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import prettierEslint from 'eslint-config-prettier';
import pluginPrettier from 'eslint-plugin-prettier';
import {defineConfig} from 'eslint/config';

export default defineConfig([
    // 1. 忽略无需校验的文件(提升校验速度,避免无用报错)
    {
        ignores: [
            'node_modules/**', // 依赖目录
            'dist/**', // 构建产物目录
            'build/**', // 打包配置目录(若有)
            '**/*.d.ts', // TypeScript类型文件
            'public/**' // 静态资源目录(如favicon)
        ],
    },

    // 2. 基础校验规则(JS/TS/Vue通用)
    {
        files: ['**/*.{js,mjs,cjs,ts,mts,cts,vue}'],
        plugins: {js},
        extends: ['js/recommended'],
        languageOptions: {globals: globals.browser}, // 支持浏览器全局变量(如window)
        rules: {
            // 强制语句末尾加分号(统一风格,避免ASI陷阱)
            semi: 'error',
            '@typescript-eslint/semi': 'error',
            // 忽略下划线开头的未使用变量/参数(如_temp、_next)
            '@typescript-eslint/no-unused-vars': [
                'error',
                {
                    argsIgnorePattern: '^_',
                    varsIgnorePattern: '^_',
                },
            ],
        },
    },

    // 3. TypeScript 推荐规则(语法校验 + 代码风格)
    tsEslint.configs.recommended,
    tsEslint.configs.stylistic,

    // 4. Vue 推荐规则(适配Vue3单文件组件)
    pluginVue.configs['flat/recommended'],

    // 5. 为Vue文件指定TS解析器(确保<script setup lang="ts">语法被识别)
    {
        files: ['**/*.vue'],
        languageOptions: {
            parserOptions: {parser: tsEslint.parser}
        }
    },

    // 6. Prettier 整合(禁用ESLint与Prettier冲突的规则,启用Prettier格式校验)
    {
        plugins: {prettier: pluginPrettier},
        extends: [prettierEslint], // 禁用冲突规则
        rules: {
            'prettier/prettier': 'error' // 将Prettier格式问题标记为ESLint错误
        },
    },
]);

配置 Prettier 格式规则(.prettierrc)

在项目根目录新建 .prettierrc 文件,定义统一的格式规则(与 ESLint 规则兼容):

json5 复制代码
{
  "semi": true,
  // 语句末尾加分号
  "singleQuote": true,
  // 使用单引号(替代双引号)
  "trailingComma": "all",
  // 数组/对象末尾添加逗号(便于新增元素)
  "printWidth": 100,
  // 每行最大长度(超过自动换行)
  "tabWidth": 2,
  // 缩进宽度(2个空格)
  "vueIndentScriptAndStyle": true,
  // Vue文件中<script>和<style>缩进与模板一致
  "endOfLine": "lf",
  // 换行符使用LF(Unix格式,避免Windows与Unix换行符冲突)
  "useTabs": false,
  // 不使用Tab缩进(用空格)
  "bracketSpacing": true,
  // 对象括号前后加空格(如{ key: value })
  "jsxBracketSameLine": false,
  // JSX标签闭合括号换行
  "arrowParens": "always"
  // 箭头函数参数必须加括号(如(a) => a,而非a => a)
}

添加格式化脚本(package.json)

打开项目根目录的 package.json,在 scripts 字段中添加自动修复命令,便于快速格式化代码:

json5 复制代码
{
  "scripts": {
    "dev": "vite --mode=development",
    // 启动开发服务器(默认地址:http://localhost:5173)
    "build": "vue-tsc && vite build --mode=production",
    // 构建生产产物(先TS类型校验,再打包)
    "preview": "vite preview",
    // 预览生产产物(本地模拟部署)
    "lint:fix": "eslint --fix"
    // 自动修复ESLint可修复问题(含Prettier格式)
  }
}

执行

pnpm lint:fix

可自动修复项目中所有符合规则的代码问题(如分号、缩进、引号等),减少手动调整成本。

Git 提交校验(Husky + lint-staged)

配置 Git 提交前钩子,仅校验暂存区文件(未修改的文件不校验,提升效率),确保提交到仓库的代码符合规范,避免 "脏代码" 入库。

安装依赖(开发依赖)

bash 复制代码
# 安装Husky(Git钩子工具)+ lint-staged(暂存区文件校验)
pnpm add husky lint-staged -D

初始化 Husky(生成钩子配置目录)

bash 复制代码
pnpm exec husky init

执行后会在项目根目录生成 .husky 文件夹,其中包含 pre-commit 钩子文件(默认内容为 pnpm test,需修改)。

配置 Pre-Commit 钩子(提交前执行 lint-staged)

打开 .husky/pre-commit 文件,替换默认内容(仅保留核心命令):

bash 复制代码
#!/usr/bin/env sh

# 执行lint-staged(仅校验暂存区文件,不影响未修改文件)
npx lint-staged

创建 lint-staged 配置文件(.lintstagedrc)

在项目根目录新建 .lintstagedrc 文件,定义暂存区文件的处理规则(仅处理需要校验的文件类型):

json5 复制代码
{
  // 对暂存区的vue/ts/js文件,执行pnpm lint:fix(自动修复规范问题)
  "**/*.{vue,ts,js}": [
    "pnpm lint:fix"
  ]
}

感谢阅读,敬请斧正!

相关推荐
咖啡の猫8 小时前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
盼哥PyAI实验室8 小时前
纯前端打造个人成长网站:零后端、零部署、零服务器的实践分享
运维·服务器·前端·javascript·echarts·个人开发
nppe68 小时前
NestJs 从入门到实战项目笔记
前端·后端
景彡先生8 小时前
Python Flask详解:从入门到实战,轻量级Web框架的魅力
前端·python·flask
qq_420362038 小时前
AI在前端工作中的应用
前端·人工智能·sse
Lsx_8 小时前
详解ECharts中的convertToPixel和convertFromPixel
前端·javascript·echarts
吃饺子不吃馅8 小时前
Web端PPT应用画布方案:Canvas 还是 DOM?
前端·架构·canvas
濮水大叔8 小时前
VonaJS业务抽象层: 验证码体系
typescript·nodejs·nestjs
晴殇i8 小时前
Web端PDF预览方法详解
前端·javascript·vue.js