Vue3 + TypeScript 项目框架搭建指南
搭建一个vue3+Ts 项目基本框架以及配置, 要求配置好server中proxy代理、有sit,development,production环境,同时 配置好axios,mock 、less、代码压缩
1. 项目初始化
perl
# 使用 Vite 创建项目
npm create vue@latest my-vue3-project
# 选择配置
# ✔ Project name: ... my-vue3-project
# ✔ Add TypeScript? ... Yes
# ✔ Add JSX Support? ... No
# ✔ Add Vue Router for Single Page Application development? ... Yes
# ✔ Add Pinia for state management? ... Yes
# ✔ Add Vitest for Unit testing? ... No
# ✔ Add an End-to-End Testing Solution? › No
# ✔ Add ESLint for code quality? ... Yes
# ✔ Add Prettier for code formatting? ... Yes
cd my-vue3-project
npm install
2. 安装必要依赖
bash
# 安装 axios、mockjs、less 等
npm install axios mockjs
npm install -D less @types/node
3. 项目目录结构
bash
src/
├── api/ # API 接口
├── assets/ # 静态资源
├── components/ # 组件
├── mock/ # Mock 数据
├── router/ # 路由
├── stores/ # 状态管理
├── types/ # TypeScript 类型定义
├── utils/ # 工具函数
├── views/ # 页面组件
├── App.vue
└── main.ts
4. 环境配置
环境变量文件
.env.development
env
ini
VITE_APP_TITLE=Development
VITE_APP_BASE_API=/api
VITE_APP_MOCK=true
.env.sit
env
ini
VITE_APP_TITLE=SIT
VITE_APP_BASE_API=/api
VITE_APP_MOCK=false
.env.production
env
ini
VITE_APP_TITLE=Production
VITE_APP_BASE_API=/api
VITE_APP_MOCK=false
5. Vite 配置
vite.config.ts
javascript
import { fileURLToPath, URL } from 'node:url'
import { defineConfig ,loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig(({mode})=>{
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
console.log("env",env)
return {
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true, // 启用 Less 中的 JavaScript 表达式
additionalData: `@import "@/styles/variables.less";`// 全局注入 Less 变量文件
}
}
},
server: {
// port: 3000, // 端口号
// open: true, // 自动打开浏览器 [在package.json dev --open也可以]
// cors: true, // 允许跨域
// host: '0.0.0.0', // 允许外部访问(可选)
proxy: {
// 代理接口如以"/api 开头
[env.VITE_APP_BASE_API]: {
//获取数据的服务器地址设置
target: 'http://10.241.154.118:90',// 代理人的地址
//需要代理跨域
changeOrigin: true,
//路径重写
rewrite: (path) => path.replace(/^\/api/, '')
}
},
// 热更新配置
hmr: {
overlay: true
}
},
build: {
target: 'es2015',// 构建目标
minify: 'terser',// 压缩工具
terserOptions: {
compress: {
drop_console: mode === 'production', // 生产环境移除console
drop_debugger: mode === 'production' // 生产环境移除debugger
}
},
rollupOptions: { // Rollup打包配置
output: {
chunkFileNames: 'js/[name]-[hash].js', // 代码分割文件命名
entryFileNames: 'js/[name]-[hash].js', // 入口文件命名
assetFileNames: '[ext]/[name]-[hash].[ext]',// 静态资源命名
manualChunks: { // 手动代码分割
vue: ['vue', 'vue-router', 'pinia'] // Vue生态库单独打包
}
}
}
}
}
})
安装 terser 压缩工具
npm install -D terser
Vite 中配置 Less 预处理器
这是 Vite 中配置 Less 预处理器 的选项,让我为你详细解释:
配置作用分析
yaml
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true, // 启用 Less 中的 JavaScript 表达式
additionalData: `@import "@/styles/variables.less";` // 全局注入 Less 变量文件
}
}
}
详细功能说明
1. javascriptEnabled: true
- 作用:允许在 Less 文件中使用 JavaScript 表达式
- 示例:
less
// 可以在 Less 中使用 JS 计算
@color: `Math.random() > 0.5 ? 'red' : 'blue'`;
@width: `100 + 50`;
2. additionalData: '@import "@/styles/variables.less";'
- 作用:在每个 Less 文件开头自动注入指定的变量文件
- 好处:无需在每个文件中手动导入变量
实际使用效果
配置前:
less
// 每个 .vue 文件或 .less 文件都需要手动导入
<style lang="less">
@import "@/styles/variables.less";
.container {
color: @primary-color;
background: @bg-color;
}
</style>
配置后:
less
// 无需手动导入,变量自动可用
<style lang="less">
.container {
color: @primary-color; // 直接使用全局变量
background: @bg-color;
}
</style>
典型项目结构
bash
src/
├── styles/
│ ├── variables.less # 全局变量定义
│ └── mixins.less # 全局混合器
├── components/
│ └── Button.vue # 组件,自动拥有变量访问权
└── App.vue
variables.less 示例内容:
less
// 颜色变量
@primary-color: #1890ff;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #f5222d;
// 尺寸变量
@header-height: 64px;
@sidebar-width: 200px;
// 字体变量
@font-size-base: 14px;
@font-family: 'Arial', sans-serif;
完整配置示例
php
// vite.config.js
export default defineConfig({
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
additionalData: `
@import "@/styles/variables.less";
@import "@/styles/mixins.less";
`,
modifyVars: {
// 修改变量值(可选)
'primary-color': '#1DA57A'
}
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // 确保 @ 别名正确配置
}
}
})
优势总结
- 代码复用:全局变量和混合器
- 维护方便:统一管理样式变量
- 开发效率:无需重复导入
- 主题切换:通过修改变量实现整体主题变更
6. TypeScript 配置
上面初始化项目时候选择ts 默认生成好的 tsconfig.json
json
json
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}
7. Axios 配置
src/utils/request.ts
typescript
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
interface RequestOptions extends AxiosRequestConfig {
loading?: boolean
}
class Request {
private instance: AxiosInstance
constructor(baseURL: string) {
this.instance = axios.create({
baseURL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
this.setupInterceptors()
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
// 添加 token 等
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response
// 根据后端接口规范调整
if (data.code === 200) {
return data
} else {
ElMessage.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
}
},
(error) => {
let message = '请求失败'
if (error.response?.status) {
switch (error.response.status) {
case 401:
message = '未授权'
// 跳转到登录页
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址错误'
break
case 500:
message = '服务器内部错误'
break
default:
message = '网络连接错误'
}
}
ElMessage.error(message)
return Promise.reject(error)
}
)
}
public request<T = any>(config: RequestOptions): Promise<T> {
return this.instance.request(config)
}
public get<T = any>(url: string, config?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'GET', url })
}
public post<T = any>(url: string, data?: any, config?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'POST', url, data })
}
public put<T = any>(url: string, data?: any, config?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'PUT', url, data })
}
public delete<T = any>(url: string, config?: RequestOptions): Promise<T> {
return this.request({ ...config, method: 'DELETE', url })
}
}
// 创建请求实例
const baseURL = import.meta.env.VITE_APP_BASE_API as string
export const http = new Request(baseURL)
8. Mock 配置
src/mock/index.ts
typescript
javascript
import Mock from 'mockjs'
import user from './user'
// 判断是否开启 mock
const isMock = import.meta.env.VITE_APP_MOCK === 'true'
if (isMock) {
Mock.setup({
timeout: '200-600'
})
// 用户相关接口
Mock.mock(//api/user/login/, 'post', user.login)
Mock.mock(//api/user/info/, 'get', user.getUserInfo)
Mock.mock(//api/user/list/, 'get', user.getUserList)
}
src/mock/user.ts
typescript
css
import { MockMethod } from 'vite-plugin-mock'
export default {
login: () => {
return {
code: 200,
message: 'success',
data: {
token: Mock.Random.string(32),
userInfo: {
id: 1,
username: 'admin',
nickname: '管理员',
avatar: Mock.Random.image('100x100')
}
}
}
},
getUserInfo: () => {
return {
code: 200,
message: 'success',
data: {
id: 1,
username: 'admin',
nickname: '管理员',
avatar: Mock.Random.image('100x100'),
roles: ['admin']
}
}
},
getUserList: () => {
return {
code: 200,
message: 'success',
data: {
total: 100,
list: Array.from({ length: 10 }, (_, index) => ({
id: index + 1,
username: Mock.Random.string(5, 10),
nickname: Mock.Random.cname(),
email: Mock.Random.email(),
phone: Mock.Random.string('number', 11),
createTime: Mock.Random.datetime()
}))
}
}
}
} as MockMethod
9. Less 样式配置
src/styles/variables.less
less
less
// 颜色变量
@primary-color: #1890ff;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #f5222d;
// 字体
@font-size-base: 14px;
@font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
// 布局
@layout-header-height: 64px;
@layout-sider-width: 200px;
// 边框
@border-radius-base: 4px;
@border-color-base: #d9d9d9;
src/styles/global.less
less
less
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: @font-family;
font-size: @font-size-base;
}
#app {
height: 100%;
}
// 通用样式
.text-center {
text-align: center;
}
.mt-20 {
margin-top: 20px;
}
.mb-20 {
margin-bottom: 20px;
}
10. API 接口管理
src/api/user.ts
typescript
typescript
import { http } from '@/utils/request'
export interface LoginParams {
username: string
password: string
}
export interface UserInfo {
id: number
username: string
nickname: string
avatar: string
roles: string[]
}
export interface UserListParams {
page: number
size: number
keyword?: string
}
export const userApi = {
// 登录
login: (data: LoginParams) => http.post<{ token: string; userInfo: UserInfo }>('/user/login', data),
// 获取用户信息
getUserInfo: () => http.get<UserInfo>('/user/info'),
// 获取用户列表
getUserList: (params: UserListParams) => http.get<{ total: number; list: UserInfo[] }>('/user/list', { params })
}
11. 类型定义
src/types/api.ts
typescript
typescript
export interface ApiResponse<T = any> {
code: number
message: string
data: T
}
export interface PageParams {
page: number
size: number
}
export interface PageResponse<T = any> {
total: number
list: T[]
}
12. 主入口文件
src/main.ts
typescript
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
// 引入 mock
import './mock'
// 引入全局样式
import './styles/global.less'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')
13. 示例页面组件
src/views/Home.vue
vue
xml
<template>
<div class="home-container">
<h1 class="text-center">{{ title }}</h1>
<el-button type="primary" @click="handleLogin">模拟登录</el-button>
<el-button @click="getUserList">获取用户列表</el-button>
<el-table :data="userList" class="mt-20">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" />
<el-table-column prop="nickname" label="昵称" />
<el-table-column prop="email" label="邮箱" />
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { userApi } from '@/api/user'
import type { UserInfo } from '@/api/user'
const title = ref('Vue3 + TypeScript 项目')
const userList = ref<UserInfo[]>([])
const handleLogin = async () => {
try {
const result = await userApi.login({
username: 'admin',
password: '123456'
})
console.log('登录成功:', result)
} catch (error) {
console.error('登录失败:', error)
}
}
const getUserList = async () => {
try {
const result = await userApi.getUserList({
page: 1,
size: 10
})
userList.value = result.list
} catch (error) {
console.error('获取用户列表失败:', error)
}
}
onMounted(() => {
getUserList()
})
</script>
<style lang="less" scoped>
.home-container {
padding: 20px;
h1 {
color: @primary-color;
margin-bottom: 20px;
}
}
</style>
14. Package.json 脚本配置
package.json
json
json
{
"scripts": {
"dev": "vite",
"dev:sit": "vite --mode sit",
"build": "vue-tsc && vite build",
"build:sit": "vue-tsc && vite build --mode sit",
"build:prod": "vue-tsc && vite build --mode production",
"preview": "vite preview"
}
}
15. 使用说明
-
开发环境运行:
bash
arduinonpm run dev -
SIT环境构建:
bash
arduinonpm run build:sit -
生产环境构建:
bash
arduinonpm run build:prod
主要特性
- ✅ Vue3 + TypeScript
- ✅ 环境配置(development/sit/production)
- ✅ Proxy 代理配置
- ✅ Axios 封装和拦截器
- ✅ Mock 数据模拟
- ✅ Less 预处理器
- ✅ 代码压缩优化
- ✅ 路由和状态管理
- ✅ Element Plus UI 组件库
- ✅ 类型安全
这个框架提供了完整的开发环境配置,您可以根据实际需求进一步调整和扩展。