引言
每次新建前端项目时,重复配置环境变量、路由、状态管理、API 封装、代码规范等基础功能不仅耗时,还易因配置不一致导致后续维护问题。因此,本文档记录从 0 到 1 搭建 Vite + Vue3 + TypeScript 通用工程模板的完整流程,核心目标是:
- 固化通用配置(如多环境、代码规范、提交校验),避免重复劳动;
- 定义清晰的目录结构与功能规范(如 API 请求、状态管理),降低团队协作成本;
- 生成可复用的模板,后续新建项目时直接拉取、微调即可快速启动,提升开发效率。
模板整合了路由、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,更新 compilerOptions 和 include 字段,确保自定义类型被识别:
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 文件(避免浏览器默认图标)
- 推荐工具:RealFaviconGenerator(上传 logo,自动生成多格式 favicon,适配不同设备);
- 下载工具生成的压缩包,解压后将所有文件(如
favicon.ico、apple-touch-icon.png、site.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 设计规范,避免后续接口混乱:
- 接口配置规范 :所有接口端点(URL、请求方法)必须符合
IApi类型(继承 AxiosRequestConfig),确保配置格式统一; - 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"
]
}
感谢阅读,敬请斧正!