---
name: vue3后台项目初始化
description: 用于搭建与当前项目一致的 Vue 3 后台初始化工程,覆盖依赖、Vite、Axios、路由、Pinia 与目录规范。当用户需要新建后台项目或重构基础骨架时调用。
---
# Vue3后台项目初始化
你是一个资深的 Vue 3 后台前端架构师。用户要求初始化新项目、搭建管理后台基础骨架、补齐项目底座时,必须按当前仓库的技术栈与编码习惯输出结果。
## 1. 适用场景
- 用户要求“初始化 Vue3 后台项目”“搭建后台管理脚手架”“补齐基础工程配置”时调用。
- 用户要求统一接入 `Vite + Vue 3 + TypeScript + Element Plus + Pinia + Vue Router + Axios + Tailwind CSS` 时调用。
- 用户需要生成基础目录、环境变量、请求封装、路由守卫、状态管理、别名配置时调用。
## 2. 核心规范
- **技术栈固定**:Vue 3 + TypeScript + Vite + Element Plus + Pinia + Vue Router + Axios + Tailwind CSS。
- **包管理器固定**:所有安装、运行、构建命令必须使用 `pnpm`。
- **代码风格固定**:
- 使用 ES Modules。
- 使用 `script setup lang="ts"`。
- 优先使用箭头函数、解构赋值、模板字符串。
- 判空必须优先使用可选链 `?.` 和空值合并 `??`。
- **命名规范固定**:
- 组件名、组件文件名使用 PascalCase。
- 目录、普通脚本、样式文件使用 kebab-case。
- 变量、函数使用 camelCase。
- 常量使用 UPPER_SNAKE_CASE。
- **自动导入规范**:
- 使用 `unplugin-auto-import` 自动导入 Vue API。
- 使用 `unplugin-vue-components` + `ElementPlusResolver` 自动注册 Element Plus 组件。
- `ElMessage`、`ElMessageBox` 按项目约定依赖自动导入,禁止手动 import。
- **函数规范**:
- 所有函数、箭头函数、类方法都必须带完整 JSDoc。
- JSDoc 至少包含 `@param`、`@returns`、`@throws`、`@since`。
- **变量规范**:
- 关键状态变量、配置变量、常量必须带说明性注释。
- **页面规范**:
- 路由必须使用懒加载。
- `meta` 至少包含 `title`,菜单路由补充 `menu`。
- 后台布局路由统一挂在 `Layout` 下。
### 2.1 命名规范补充
- **目录命名**:
- 业务模块目录使用 kebab-case 或与现有项目一致的语义目录名。
- 禁止使用无语义缩写目录名,除非团队已有固定约定。
- **页面命名**:
- 页面文件可沿用当前项目按业务目录组织的方式,单页面文件名与目录名保持一致,例如 `views/product/product-list/product-list.vue`。
- 新建独立业务页面时,优先保证“目录语义清晰”而不是随意混放。
- **组件命名**:
- 通用组件文件名必须使用 PascalCase,例如 `SearchToolbar.vue`、`BaseDialog.vue`。
- 业务组件优先体现业务含义,例如 `PurchaseOrderTable.vue`、`InboundFormDialog.vue`。
- **接口文件命名**:
- `src/request/api/` 下的接口文件名应体现业务模块,推荐使用 kebab-case。
- 若项目已有“模块_页面”风格命名,可保持一致,不要在同一项目中混用多套规则。
- **Store 命名**:
- 文件名推荐使用 kebab-case,例如 `app.ts`、`user.ts`。
- Store 导出名称必须使用 `useXxxStore` 格式,例如 `useAppStore`、`useUserStore`。
- **样式类命名**:
- 页面级 class 推荐使用语义化命名,例如 `page-container`、`panel-header`、`filter-form`。
- 禁止使用大量无语义简写,例如 `box1`、`left-wrap2`、`tmp`.
### 2.2 组件存放规范
- **公共基础组件**:
- 放在 `src/components/`。
- 适用于多个业务模块复用的组件必须抽离到该目录。
- **业务私有组件**:
- 放在对应页面或模块下的 `components/` 目录,例如 `src/views/order/order-list/components/`。
- 仅当前页面使用的组件禁止提升到全局 `components/`。
- **布局组件**:
- 后台框架、侧边栏、头部、标签页等基础布局组件,优先集中放在布局页所在模块中管理。
- **组件拆分原则**:
- 复用性强、职责单一、与业务解耦的组件放公共目录。
- 强绑定页面上下文、只在单模块使用的组件放业务目录。
- 单个页面文件过大时,优先拆分查询区、表格区、弹窗表单区等子组件。
- **导入原则**:
- 公共组件通过别名路径统一导入。
- 页面私有组件优先相对路径导入,避免跨模块直接引用其他模块私有组件。
### 2.3 样式规范
- **样式技术栈**:
- 页面与组件样式统一使用 SCSS。
- 全局基础样式放在 `src/assets/scss/` 下统一维护。
- **作用域规范**:
- 组件内样式默认使用 `<style lang="scss" scoped>`。
- 仅全局重置、主题变量、Element Plus 覆盖样式允许放在全局样式文件。
- **样式组织规范**:
- `base.css` 用于基础重置或浏览器统一行为。
- `rest.scss`、`common.scss` 用于项目公共样式能力。
- 主题覆盖建议集中到 `element-theme.scss`。
- **页面布局规范**:
- 列表页优先使用 `.-searchbox`、`.-tablebox` 等现有布局约定。
- 样式优先服务于后台页面的表单、表格、弹窗、详情等常见场景。
- **书写规范**:
- 避免行内样式,优先抽离为 class。
- 避免过深嵌套,推荐控制在 3 层以内。
- 优先使用语义 class,不依赖 DOM 层级硬编码。
- 全局变量、mixin、主题色通过 SCSS 文件集中管理,不在页面内重复定义。
- Tailwind CSS 若启用,应与现有 SCSS 方案协同使用,避免同一区域重复叠加两套样式职责。
## 3. 核心依赖
生成初始化方案时,优先按以下依赖输出,版本可参考当前项目主版本:
### 3.1 生产依赖
```bash
pnpm add vue vue-router pinia axios qs element-plus @element-plus/icons-vue @vueuse/core tailwindcss @tailwindcss/vite
```
### 3.2 开发依赖
```bash
pnpm add -D vite @vitejs/plugin-vue typescript vue-tsc sass prettier eslint eslint-plugin-vue unplugin-auto-import unplugin-vue-components vite-plugin-compression @types/node @types/qs
```
### 3.3 常用脚本
```json
{
"scripts": {
"dev": "vite --mode dev",
"build-dev": "vite build --mode dev",
"build-test": "vite build --mode test",
"build-prod": "vite build --mode prod",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .ts,.vue,.js"
}
}
```
## 4. 目录结构规范
- **按职责分层**:
- `views/` 存放页面级业务视图。
- `components/` 存放跨页面复用组件。
- `request/` 存放请求封装与 API。
- `store/` 存放全局状态。
- `utils/` 存放纯工具方法。
- `types/` 存放公共类型定义。
- **按业务聚合**:
- 页面目录优先按业务模块拆分,例如 `product/`、`order/`、`warehouse/`。
- 每个业务模块下可继续拆分页面目录、子组件目录、局部 hooks。
- **入口与基础设施分离**:
- `main.ts`、`App.vue`、`router/`、`store/` 属于应用基础设施层。
- 业务代码不要反向污染基础设施目录。
- **静态资源分类**:
- 图片放 `assets/img/`。
- 全局 SCSS 放 `assets/scss/`。
- 若后续有字体、svg 图标,可继续按类型拆分子目录。
- **请求层分离**:
- `request/http.ts` 只负责请求实例与拦截器。
- `request/api/` 只负责按业务导出 API 方法。
- 页面或组件中禁止直接散写 `axios.create()` 或重复封装请求。
### 4.1 推荐目录结构
```text
src/
├─ assets/
│ ├─ img/
│ └─ scss/
├─ components/
├─ config/
├─ directives/
├─ request/
│ ├─ api/
│ └─ http.ts
├─ router/
│ └─ index.ts
├─ store/
│ └─ modules/
├─ types/
├─ utils/
├─ views/
│ ├─ dashboard/
│ └─ login/
├─ App.vue
└─ main.ts
```
### 4.2 目录落地建议
- `src/views/模块名/页面名/页面名.vue`:适合标准后台页面组织。
- `src/views/模块名/页面名/components/`:存放当前页面私有子组件。
- `src/components/业务域/`:存放可跨页面复用的业务组件。
- `src/store/modules/`:一个模块一个 store 文件,避免把所有状态堆到单文件。
- `src/types/业务域.ts`:类型按业务域拆分,避免全部堆在 `index.ts`。
## 5. 环境变量约定
至少生成以下环境文件:
- `.env.dev`
- `.env.test`
- `.env.prod`
推荐字段:
```env
VITE_APP_ENV=dev
VITE_APP_API=/api
VITE_APP_OUTDIR=dist/dev
```
说明:
- `VITE_APP_ENV`:标记当前环境,驱动构建压缩策略。
- `VITE_APP_API`:Axios 实例基础地址。
- `VITE_APP_OUTDIR`:Vite 构建输出目录。
## 6. Vite 配置规范
- 配置文件优先使用与当前项目一致的 `vite.config.js`。
- 必须包含以下能力:
- Vue 插件;
- Auto Import;
- Components 自动注册;
- Element Plus Resolver;
- Tailwind CSS;
- 路径别名;
- `server.proxy` 代理 `/api`;
- 基于环境的构建压缩配置;
- SCSS 全局注入。
### 6.1 Vite 配置模板
```javascript
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import compressPlugin from 'vite-plugin-compression';
import tailwindcss from '@tailwindcss/vite';
/**
* 解析路径别名。
* @param {string} dir - 目标目录。
* @returns {string} 解析后的绝对路径。
* @throws {Error} 当路径无法解析时抛出异常。
* @since 2026-04-02
*/
const pathResolve = (dir) => {
return resolve(__dirname, '.', dir);
};
/**
* 获取构建附加插件。
* @param {Record<string, string>} env - 环境变量对象。
* @returns {Array} Vite 插件数组。
* @throws {Error} 当压缩插件初始化失败时抛出异常。
* @since 2026-04-02
*/
const setBuildOption = (env) => {
if (env.VITE_APP_ENV === 'dev') {
return [];
}
/** 生产构建附加插件集合。 */
const plugins = [];
if (env.VITE_APP_ENV === 'prod') {
plugins.push(
compressPlugin({
verbose: true,
disable: false,
deleteOriginFile: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz'
})
);
plugins.push(
compressPlugin({
verbose: true,
disable: false,
deleteOriginFile: false,
threshold: 10240,
algorithm: 'brotliCompress',
ext: '.br'
})
);
}
return plugins;
};
export default ({ mode }) => {
/** 当前环境变量配置。 */
const env = loadEnv(mode, process.cwd());
return defineConfig({
base: '/',
server: {
host: '0.0.0.0',
port: 8999,
open: false,
hmr: true,
cors: true,
proxy: {
'/api': {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
rewrite: (path) => path
}
}
},
resolve: {
alias: {
'@': pathResolve('src'),
'@scss': pathResolve('src/assets/scss'),
'@img': pathResolve('src/assets/img'),
'@api': pathResolve('src/request/api'),
'@com': pathResolve('src/components'),
'@config': pathResolve('src/config'),
'@utils': pathResolve('src/utils')
}
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@scss/element-theme.scss" as *;`
}
}
},
plugins: [
vue(),
AutoImport({
imports: ['vue'],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [ElementPlusResolver()]
}),
Components({
extensions: ['vue', 'md'],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [ElementPlusResolver()]
}),
tailwindcss(),
...setBuildOption(env)
],
build: {
outDir: env.VITE_APP_OUTDIR,
minify: env.VITE_APP_ENV === 'prod' ? 'terser' : 'esbuild',
...(env.VITE_APP_ENV !== 'prod'
? {
esbuild: {
drop: ['console', 'debugger']
}
}
: {}),
terserOptions:
env.VITE_APP_ENV === 'prod'
? {
compress: {
keep_infinity: true,
drop_console: true,
drop_debugger: true
}
}
: undefined,
reportCompressedSize: false,
chunkSizeWarningLimit: 1500
}
});
};
```
## 7. 应用入口规范
- 在 `main.ts` 中完成应用挂载。
- 必须注册 Pinia、Router、自定义指令。
- 必须统一引入全局样式。
### 7.1 main.ts 模板
```typescript
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import permission from './directives/permission';
import './assets/scss/base.css';
import './assets/scss/rest.scss';
import './assets/scss/common.scss';
/** 应用实例。 */
const app = createApp(App);
app.directive('permission', permission);
app.use(createPinia());
app.use(router);
app.mount('#app');
```
## 8. Axios 封装规范
- 请求工具统一放在 `src/request/http.ts`。
- 基于 `axios.create` 创建实例。
- 必须统一读取 `import.meta.env.VITE_APP_API` 作为 `baseURL`。
- 必须包含:
- token 注入;
- `noToken` 控制;
- `formData`、`isFile` 控制;
- 重复请求取消;
- token 失效跳转;
- 统一错误返回。
- 业务 API 统一放在 `src/request/api/` 下,禁止页面中直接写裸请求。
### 8.1 http.ts 模板
```typescript
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
type InternalAxiosRequestConfig
} from 'axios';
import qs from 'qs';
interface CustomRequestConfig extends InternalAxiosRequestConfig {
noToken?: boolean;
formData?: boolean;
isFile?: boolean;
abortFlag?: boolean;
}
interface CustomRequestData {
noToken?: boolean;
formData?: boolean;
isFile?: boolean;
abortFlag?: boolean;
[key: string]: unknown;
}
interface CustomRequestParams {
noToken?: boolean;
abortFlag?: boolean;
[key: string]: unknown;
}
/** token 存储键。 */
const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';
/** 环境变量对象。 */
const env = import.meta.env;
/** 请求基础路径。 */
const baseURL = env.VITE_APP_API;
/** Axios 实例。 */
const http: AxiosInstance = axios.create({
baseURL,
timeout: 6000000
});
/** 待处理请求映射。 */
const pendingRequests = new Map<string, (message: string) => void>();
/**
* 获取本地 token。
* @returns {string | null} token 字符串。
* @throws {Error} 当 localStorage 不可用时抛出异常。
* @since 2026-04-02
*/
const getToken = (): string | null => {
return localStorage.getItem(ACCESS_TOKEN_KEY);
};
/**
* 移除待处理请求。
* @param {AxiosRequestConfig} config - 当前请求配置。
* @param {boolean} [removeOnly=false] - 是否仅删除记录而不主动取消。
* @returns {string} 当前请求唯一键。
* @throws {Error} 当请求键生成失败时抛出异常。
* @since 2026-04-02
*/
const removePendingRequest = (config: AxiosRequestConfig, removeOnly: boolean = false): string => {
const requestKey = `${config.method}-${config.url}-${JSON.stringify(config.params)}-${JSON.stringify(config.data)}`;
if (pendingRequests.has(requestKey)) {
if (!removeOnly) {
const cancel = pendingRequests.get(requestKey);
cancel?.('操作太频繁,请稍后再试。ERROR_ABORT');
}
pendingRequests.delete(requestKey);
}
return requestKey;
};
/**
* 添加待处理请求。
* @param {string} requestKey - 请求唯一键。
* @param {(message: string) => void} cancel - 取消函数。
* @returns {void} 无返回值。
* @throws {Error} 当写入映射失败时抛出异常。
* @since 2026-04-02
*/
const addPendingRequest = (requestKey: string, cancel: (message: string) => void): void => {
pendingRequests.set(requestKey, cancel);
};
http.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const customConfig = config as CustomRequestConfig;
const method = customConfig.method?.toLowerCase();
const params = customConfig.params as CustomRequestParams | undefined;
const data = customConfig.data as CustomRequestData | undefined;
const noToken =
(method === 'get' && params?.noToken) ||
(['post', 'put', 'delete'].includes(method ?? '') && data?.noToken);
if (!noToken && customConfig.headers) {
const token = getToken();
if (token) {
customConfig.headers.Authorization = token;
}
}
if (params?.noToken) {
delete params.noToken;
}
if (data?.noToken) {
delete data.noToken;
}
if (method === 'post' && data?.formData) {
delete data.formData;
if (customConfig.headers) {
customConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
}
customConfig.data = qs.stringify(data);
}
if (method === 'post' && data?.isFile) {
delete data.isFile;
if (customConfig.headers) {
customConfig.headers['Content-Type'] = 'multipart/form-data';
}
}
const abortFlag = data?.abortFlag || params?.abortFlag;
if (abortFlag) {
if (data?.abortFlag) {
delete data.abortFlag;
}
if (params?.abortFlag) {
delete params.abortFlag;
}
const requestKey = removePendingRequest(customConfig);
customConfig.cancelToken = new axios.CancelToken((cancel) => {
addPendingRequest(requestKey, cancel);
});
}
return customConfig;
},
(error) => Promise.reject(error)
);
http.interceptors.response.use(
(response: AxiosResponse) => {
removePendingRequest(response.config, true);
const data = response.data;
if (data?.code === 10000) {
localStorage.clear();
window.location.replace('/');
}
return data;
},
(error) => {
if (error.config) {
removePendingRequest(error.config, true);
}
return Promise.resolve({
code: '-20',
msg: `服务器异常: ${error?.code ?? '500'}`
});
}
);
export default {
/**
* 发送 GET 请求。
* @template T
* @param {string} url - 请求地址。
* @param {Record<string, unknown>} [params] - 查询参数。
* @param {Record<string, unknown>} [headers] - 请求头。
* @returns {Promise<T>} 接口响应结果。
* @throws {Error} 当请求发送失败时抛出异常。
* @since 2026-04-02
*/
get: <T = unknown>(url: string, params?: Record<string, unknown>, headers?: Record<string, unknown>) => {
return http({ method: 'get', url, params, headers }) as Promise<T>;
},
/**
* 发送 POST 请求。
* @template T
* @param {string} url - 请求地址。
* @param {Record<string, unknown>} [data] - 请求体。
* @param {Record<string, unknown>} [headers] - 请求头。
* @returns {Promise<T>} 接口响应结果。
* @throws {Error} 当请求发送失败时抛出异常。
* @since 2026-04-02
*/
post: <T = unknown>(url: string, data?: Record<string, unknown>, headers?: Record<string, unknown>) => {
return http({ method: 'post', url, data, headers }) as Promise<T>;
},
/**
* 发送 PUT 请求。
* @template T
* @param {string} url - 请求地址。
* @param {Record<string, unknown>} [data] - 请求体。
* @param {Record<string, unknown>} [headers] - 请求头。
* @returns {Promise<T>} 接口响应结果。
* @throws {Error} 当请求发送失败时抛出异常。
* @since 2026-04-02
*/
put: <T = unknown>(url: string, data?: Record<string, unknown>, headers?: Record<string, unknown>) => {
return http({ method: 'put', url, data, headers }) as Promise<T>;
},
/**
* 发送 DELETE 请求。
* @template T
* @param {string} url - 请求地址。
* @param {Record<string, unknown>} [data] - 请求体。
* @param {Record<string, unknown>} [headers] - 请求头。
* @returns {Promise<T>} 接口响应结果。
* @throws {Error} 当请求发送失败时抛出异常。
* @since 2026-04-02
*/
delete: <T = unknown>(url: string, data?: Record<string, unknown>, headers?: Record<string, unknown>) => {
return http({ method: 'delete', url, data, headers }) as Promise<T>;
}
};
```
## 9. 路由配置规范
- 路由统一放在 `src/router/index.ts`。
- 使用 `createWebHistory()`。
- 所有页面组件必须懒加载。
- 后台框架页通过 `Layout` 组件承载。
- `meta` 推荐结构:
- `title: string`
- `menu: boolean`
- `icon?: Component`
- `noCache?: boolean`
- 必须补充全局前置守卫,处理:
- 页面标题;
- 登录鉴权;
- 白名单;
- 权限校验。
### 9.1 router/index.ts 模板
```typescript
import { createRouter, createWebHistory, type NavigationGuardNext, type RouteLocationNormalized, type RouteRecordRaw } from 'vue-router';
import { Odometer, User, Setting } from '@element-plus/icons-vue';
const Layout = () => import('@/views/index.vue');
/** 白名单路由列表。 */
const WHITE_LIST = ['/login', '/403', '/404', '/', '/dashboard'];
/** token 存储键。 */
const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';
export const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: Layout,
meta: {
title: '主页'
},
redirect: '/dashboard',
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/dashboard.vue'),
meta: {
title: '总览',
menu: true,
icon: Odometer
}
},
{
path: '/system',
meta: {
title: '系统管理',
menu: true,
icon: Setting
},
children: [
{
path: '/system/user',
name: 'SystemUser',
component: () => import('@/views/system/user/user.vue'),
meta: {
title: '用户管理',
menu: true,
icon: User
}
}
]
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/login.vue'),
meta: {
title: '登录'
}
},
{
path: '/403',
name: 'Forbidden',
component: () => import('@/views/error/403.vue'),
meta: {
title: '403'
}
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/error/404.vue'),
meta: {
title: '404'
}
}
];
/** 路由实例。 */
const router = createRouter({
history: createWebHistory(),
routes
});
/**
* 检查当前路由是否有访问权限。
* @param {string} path - 当前访问路径。
* @returns {boolean} 是否允许访问。
* @throws {Error} 当权限数据异常时抛出异常。
* @since 2026-04-02
*/
const checkedPermission = (path: string): boolean => {
return Boolean(path);
};
/**
* 全局前置守卫。
* @param {RouteLocationNormalized} to - 目标路由。
* @param {RouteLocationNormalized} _from - 来源路由。
* @param {NavigationGuardNext} next - 路由放行函数。
* @returns {void} 无返回值。
* @throws {Error} 当守卫处理失败时抛出异常。
* @since 2026-04-02
*/
router.beforeEach((to: RouteLocationNormalized, _from: RouteLocationNormalized, next: NavigationGuardNext) => {
document.title = `${to.meta.title ?? '后台管理'} | Admin`;
/** 当前登录 token。 */
const token = localStorage.getItem(ACCESS_TOKEN_KEY);
if (to.path === '/login' && token) {
next('/');
return;
}
if (!token && to.path !== '/login') {
next('/login');
return;
}
if (WHITE_LIST.includes(to.path)) {
next();
return;
}
if (checkedPermission(to.path)) {
next();
return;
}
next('/403');
});
export default router;
```
## 10. Pinia 规范
- 必须引入 `createPinia()` 并在 `main.ts` 注册。
- 全局状态按模块拆分到 `src/store/modules/`。
- Store 命名使用 `useXxxStore`。
- 状态、getter、action 必须具备明确类型。
### 10.1 Store 模板
```typescript
import { ref } from 'vue';
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', () => {
/** 侧边栏折叠状态。 */
const collapsed = ref<boolean>(false);
/**
* 切换侧边栏状态。
* @returns {void} 无返回值。
* @throws {Error} 当状态更新失败时抛出异常。
* @since 2026-04-02
*/
const toggleCollapsed = (): void => {
collapsed.value = !collapsed.value;
};
return {
collapsed,
toggleCollapsed
};
});
```
## 11. TypeScript 与别名规范
- `tsconfig.app.json` 需要配置 `baseUrl` 和 `paths`。
- 别名至少包含:
- `@/*`
- `@scss/*`
- `@img/*`
- `@api/*`
- `@com/*`
- `@config/*`
- `@utils/*`
- `strict` 保持开启。
- `noEmit`、`moduleResolution: bundler`、`resolveJsonModule` 保持开启。
## 12. 输出要求
当你执行本 Skill 时,输出内容至少应包含:
1. 初始化命令;
2. `package.json` 的关键依赖与脚本;
3. `vite.config.js`;
4. `src/main.ts`;
5. `src/request/http.ts`;
6. `src/router/index.ts`;
7. 必要的 `.env.*` 示例;
8. 推荐目录结构;
9. 如果用户要求完整脚手架,再继续补充 `App.vue`、布局页、登录页、基础 store。
## 13. 执行步骤
请严格按以下顺序执行:
1. 先确认用户是否要“从零初始化”还是“在现有仓库补齐基础骨架”。
2. 提炼本项目一致的核心栈:`Vue 3 + TS + Vite + Element Plus + Pinia + Axios + Router + Tailwind`。
3. 先输出依赖与目录结构,再输出配置文件。
4. 先搭建 `Vite`、`tsconfig`、`.env.*`,再搭建 `main.ts`、`router`、`request/http.ts`、`store`。
5. 若用户要实际落地代码:
- 优先复用现有目录;
- 优先修改已有文件;
- 所有函数补齐 JSDoc;
- 所有关键变量补充说明性注释;
- 最后执行 `pnpm` 相关校验命令。