Vue3+TS+Vite+element-plus 后台管理项目搭建
·
效果图:

开始
# 新建项目
npm create vite@latest my-admin -- --template vue-ts
cd my-admin
# 安装依赖
npm install
# 安装Element Plus+图标(指定兼容版本)
npm install element-plus@2.4.3 @element-plus/icons-vue@2.3.2
# 安装vue-router
npm install vue-router@4
配置vite.config.ts的@别名:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})
配置tsconfig.json的paths:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
配置src/env.d.ts
// src/env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
App.vue
<template>
<router-view />
</template>
<style>
/* 重置默认样式,确保全屏 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #app {
width: 100%;
height: 100%;
max-width: none; /* 取消最大宽度限制 */
padding: 0;
text-align: left;
}
/* 修复Element Plus组件的默认间距 */
.el-form-item {
margin-right: 15px;
margin-bottom: 15px;
}
.el-table {
margin-top: 10px;
}
</style>
main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
// 引入Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 引入Element Plus图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
// 注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(router)
app.use(ElementPlus)
app.mount('#app')
src/router/index.ts
// src/router/index.ts 顶部修改为:
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router' // 单独用type导入类型
// 定义路由类型(RouteRecordRaw是Vue Router提供的路由类型)
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
},
{
path: '/',
redirect: '/home',
component: () => import('@/layout/Layout.vue'),
children: [
{ path: '/home', name: 'Home', component: () => import('@/views/Home.vue') },
{ path: '/order', name: 'Order', component: () => import('@/views/Order.vue') },
{ path: '/user', name: 'User', component: () => import('@/views/User.vue') }
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫也可以加类型
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path !== '/login' && !token) {
next('/login')
} else {
next()
}
})
export default router
src/layout/Layout.vue
<script setup>
// 引入图标
import { House, ShoppingCart, User } from '@element-plus/icons-vue'
</script>
<template>
<el-container style="height: 100vh;">
<!-- 侧边栏 -->
<el-aside width="200px" style="background-color: #2E3B4E;">
<el-menu
default-active="1"
class="el-menu-vertical-demo"
background-color="#2E3B4E"
text-color="#fff"
active-text-color="#ffd04b"
router
>
<!-- 首页:index绑定路由路径 -->
<el-menu-item index="/home">
<el-icon><House /></el-icon>
<span>首页</span>
</el-menu-item>
<!-- 订单管理:子菜单的index也绑定路径 -->
<el-sub-menu index="/order">
<template #title>
<el-icon><ShoppingCart /></el-icon>
<span>订单管理</span>
</template>
<el-menu-item index="/order">
<span>订单列表</span>
</el-menu-item>
</el-sub-menu>
<!-- 用户管理:index绑定路由路径 -->
<el-menu-item index="/user">
<el-icon><User /></el-icon>
<span>用户管理</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<!-- 顶部导航 -->
<el-header style="text-align: right; font-size: 12px">
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<span>管理员</span>
</el-header>
<!-- 内容区 -->
<el-main>
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<style scoped>
/* 补充全局样式,确保body/html占满屏幕 */
:root, body, html {
height: 100%;
margin: 0;
}
.el-container {
height: 100%; /* 确保Element Plus的容器继承高度 */
}
.el-header {
background-color: #fff;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
src/views/Home.vue
<template>
<el-card shadow="hover">
<h1>欢迎进入后台管理系统</h1>
<el-statistic title="今日订单数" :value="128" />
<el-statistic title="用户总数" :value="2048" style="margin-left: 20px" />
</el-card>
</template>
src/views/Login.vue
<template>
<div class="login-container">
<el-card class="login-card" shadow="hover">
<h2 style="text-align: center; margin-bottom: 20px">后台管理系统</h2>
<el-form :model="loginForm" label-width="80px">
<el-form-item label="账号">
<el-input v-model="loginForm.username" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin" style="width: 100%">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
const router = useRouter()
const loginForm = ref({
username: 'admin',
password: '123456'
})
const handleLogin = () => {
// 模拟登录逻辑
if (loginForm.value.username && loginForm.value.password) {
localStorage.setItem('token', 'fake-token')
ElMessage.success('登录成功')
router.push('/home')
} else {
ElMessage.error('请输入账号密码')
}
}
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f7fa;
}
.login-card {
width: 400px;
padding: 20px;
}
</style>
src/views/Order.vue
<template>
<div class="order-container">
<!-- 搜索栏 -->
<el-card shadow="hover" style="margin-bottom: 20px;">
<el-form inline @submit.prevent>
<el-form-item label="订单编号">
<el-input v-model="searchForm.orderNo" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="用户名称">
<el-input v-model="searchForm.userName" placeholder="请输入用户名称" />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="searchForm.status" placeholder="全部">
<el-option label="全部" value="" />
<el-option label="待支付" value="1" />
<el-option label="已支付" value="2" />
<el-option label="已发货" value="3" />
<el-option label="已完成" value="4" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 订单表格 -->
<el-card shadow="hover">
<el-table :data="orderList" border style="width: 100%">
<el-table-column prop="orderNo" label="订单编号" width="180" />
<el-table-column prop="userName" label="用户名称" width="120" />
<el-table-column prop="amount" label="订单金额" width="100" align="right">
<template #default="scope">¥{{ scope.row.amount.toFixed(2) }}</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column prop="status" label="订单状态" width="100">
<template #default="scope">
<el-tag :type="statusTagType(scope.row.status)">
{{ statusMap[scope.row.status] }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center">
<template #default="scope">
<el-button size="small" type="primary" @click="handleView(scope.row)">查看</el-button>
<el-button size="small" type="danger" @click="handleCancel(scope.row)" v-if="scope.row.status === '1'">取消</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 20px; text-align: right;"
/>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
// 搜索表单
const searchForm = ref({
orderNo: '',
userName: '',
status: ''
})
// 订单列表数据(模拟)
const orderList = ref([
{
orderNo: 'OD20251202001',
userName: '张三',
amount: 299.00,
createTime: '2025-12-02 10:30:00',
status: '2'
},
{
orderNo: 'OD20251202002',
userName: '李四',
amount: 599.00,
createTime: '2025-12-02 11:15:00',
status: '3'
},
{
orderNo: 'OD20251202003',
userName: '王五',
amount: 129.00,
createTime: '2025-12-02 14:20:00',
status: '1'
}
])
// 状态映射
const statusMap = {
'1': '待支付',
'2': '已支付',
'3': '已发货',
'4': '已完成'
}
// 状态标签类型
const statusTagType = (status) => {
switch (status) {
case '1': return 'warning'
case '2': return 'primary'
case '3': return 'success'
case '4': return 'info'
default: return ''
}
}
// 分页参数
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(3)
// 搜索
const handleSearch = () => {
ElMessage.info('执行搜索逻辑')
}
// 重置
const resetSearch = () => {
searchForm.value = { orderNo: '', userName: '', status: '' }
}
// 查看订单
const handleView = (row) => {
ElMessage.info(`查看订单:${row.orderNo}`)
}
// 取消订单
const handleCancel = (row) => {
ElMessage.success(`取消订单:${row.orderNo}`)
}
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val
}
const handleCurrentChange = (val) => {
currentPage.value = val
}
</script>
<style scoped>
.order-container {
padding: 0 10px;
}
</style>
src/views/User.vue
<template>
<div class="user-container">
<!-- 操作栏 -->
<el-card shadow="hover" style="margin-bottom: 20px;">
<el-button type="primary" @click="openUserModal">新增用户</el-button>
</el-card>
<!-- 用户表格 -->
<el-card shadow="hover">
<el-table :data="userList" border style="width: 100%">
<!-- 原有表格列不变 -->
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="用户ID" width="80" align="center" />
<el-table-column prop="userName" label="用户名" width="120" />
<el-table-column prop="nickName" label="昵称" width="120" />
<el-table-column prop="phone" label="手机号" width="150" />
<el-table-column prop="role" label="角色" width="100">
<template #default="scope">
<el-tag type="success">{{ scope.row.role }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="1"
inactive-value="0"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center">
<template #default="scope">
<el-button size="small" type="primary" @click="openUserModal(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页(原有不变) -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 20px; text-align: right;"
/>
</el-card>
<!-- 新增/编辑用户弹窗 -->
<el-dialog
v-model="userModalVisible"
:title="isEdit ? '编辑用户' : '新增用户'"
width="500px"
>
<el-form
ref="userFormRef"
:model="userForm"
label-width="80px"
:rules="userFormRules"
>
<el-form-item label="用户名" prop="userName">
<el-input
v-model="userForm.userName"
placeholder="请输入用户名"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="userForm.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="userForm.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="userForm.role" placeholder="请选择角色">
<el-option label="管理员" value="管理员" />
<el-option label="普通用户" value="普通用户" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="userForm.status">
<el-radio label="1">启用</el-radio>
<el-radio label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="!isEdit" label="密码" prop="password">
<el-input v-model="userForm.password" type="password" placeholder="请输入密码" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="userModalVisible = false">取消</el-button>
<el-button type="primary" @click="submitUserForm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage, ElMessageBox, ElForm } from 'element-plus'
// 用户列表数据(原有不变)
const userList = ref([
{
id: 1,
userName: 'admin',
nickName: '管理员',
phone: '13800138000',
role: '管理员',
status: '1'
},
{
id: 2,
userName: 'user001',
nickName: '普通用户1',
phone: '13800138001',
role: '普通用户',
status: '1'
},
{
id: 3,
userName: 'user002',
nickName: '普通用户2',
phone: '13800138002',
role: '普通用户',
status: '0'
}
])
// 分页参数(原有不变)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(3)
// -------- 新增/编辑弹窗相关 --------
// 弹窗显隐
const userModalVisible = ref(false)
// 是否为编辑模式
const isEdit = ref(false)
// 表单实例
const userFormRef = ref(null)
// 表单数据
const userForm = reactive({
id: '',
userName: '',
nickName: '',
phone: '',
role: '',
status: '1',
password: ''
})
// 表单校验规则
const userFormRules = reactive({
userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
nickName: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
],
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
})
// 打开弹窗(新增/编辑)
const openUserModal = (row = null) => {
// 重置表单
userFormRef.value?.resetFields()
// 区分新增/编辑
if (row) {
isEdit.value = true
// 赋值给表单(编辑)
Object.assign(userForm, row)
} else {
isEdit.value = false
// 清空表单(新增)
Object.keys(userForm).forEach(key => userForm[key] = '')
userForm.status = '1'
}
userModalVisible.value = true
}
// 提交表单
const submitUserForm = async () => {
try {
// 表单校验
await userFormRef.value.validate()
if (isEdit.value) {
// 编辑逻辑:更新列表中对应数据
const index = userList.value.findIndex(item => item.id === userForm.id)
if (index > -1) {
userList.value[index] = { ...userForm }
}
ElMessage.success('编辑用户成功!')
} else {
// 新增逻辑:生成新ID,添加到列表
const newId = Math.max(...userList.value.map(item => item.id)) + 1
userList.value.unshift({
...userForm,
id: newId
})
total.value++
ElMessage.success('新增用户成功!')
}
// 关闭弹窗
userModalVisible.value = false
} catch (err) {
// 校验失败不处理
}
}
// -------- 原有操作方法(部分调整) --------
// 删除用户(原有不变)
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm(
'此操作将永久删除该用户,是否继续?',
'提示',
{ type: 'warning' }
)
userList.value = userList.value.filter(item => item.id !== row.id)
total.value--
ElMessage.success('删除成功!')
} catch (err) {
ElMessage.info('已取消删除')
}
}
// 状态切换(原有不变)
const handleStatusChange = (row) => {
ElMessage.info(`${row.userName}的状态已切换为:${row.status === '1' ? '启用' : '禁用'}`)
}
// 分页事件(原有不变)
const handleSizeChange = (val) => {
pageSize.value = val
}
const handleCurrentChange = (val) => {
currentPage.value = val
}
</script>
<style scoped>
.user-container {
padding: 0 10px;
}
</style>
运行项目
npm run dev
以上就完成了一个简单的后台管理项目的搭建。但数据都是模拟的,接下来我们增加环境区分、接口请求、打包发布。
区分开发 / 测试 / 生产环境(Vite 通过.env文件区分环境,在项目根目录创建以下文件:)
开发环境:.env.development
# 开发环境
NODE_ENV=development
VITE_BASE_API=http://localhost:3000/api # 本地后端接口
测试环境:.env.test
# 测试环境
NODE_ENV=test
VITE_BASE_API=http://test.example.com/api # 测试服接口
生产环境:.env.production
# 生产环境
NODE_ENV=production
VITE_BASE_API=http://prod.example.com/api # 正式服接口
配置package.json的脚本命令
{
"name": "my-admin",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"build:test": "vite build --mode test --outDir dist-test",
"build:prod": "vite build --mode production --outDir dist-prod",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.13.2",
"element-plus": "^2.4.3",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/runtime-core": "^3.5.25",
"@vue/tsconfig": "^0.8.1",
"typescript": "~5.9.3",
"vite": "^7.2.4",
"vue-tsc": "^3.1.4"
}
}
打包后dist文件放服务器目录时,需在vite.config.ts里配置base目录路径(比如服务域名根目示下的文件目录路径:/oss/prod/myadmin/),以免找不到相关文件:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
base: '/oss/prod/myadmin/',
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
build: {
sourcemap: false // 禁用打包后的SourceMap文件(默认是false,生产环境建议关闭)
}
})
配置接口请求工具(以 Axios 为例)
npm install axios
创建接口请求封装文件 src/utils/request.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router' // 引入路由实例
// 创建Axios实例
const request = axios.create({
baseURL: import.meta.env.VITE_BASE_API, // 自动读取对应环境的接口地址
timeout: 5000
})
// 请求拦截器(添加Token等)
request.interceptors.request.use(
(config) => {
// 从localStorage中获取Token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器(统一处理错误)
request.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 200) {
// 处理Token过期(假设后端返回code=401表示未授权)
if (res.code === 401) {
ElMessage.error('登录已过期,请重新登录')
// 清除过期的Token
localStorage.removeItem('token')
// 跳转到登录页,并携带当前页面路径(方便登录后返回)
router.push({
path: '/login',
query: { redirect: router.currentRoute.value.fullPath }
})
} else {
ElMessage.error(res.msg || '请求失败')
}
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res.data
}
},
(error) => {
// 处理网络请求层面的401(比如后端直接返回401状态码)
if (error.response?.status === 401) {
ElMessage.error('登录已过期,请重新登录')
localStorage.removeItem('token')
router.push({
path: '/login',
query: { redirect: router.currentRoute.value.fullPath }
})
} else {
ElMessage.error(error.message || '网络异常')
}
return Promise.reject(error)
}
)
export default request
登录成功后跳回原页面
<template>
<div class="login-container">
<el-card class="login-card" shadow="hover">
<h2 style="text-align: center; margin-bottom: 20px">后台管理系统</h2>
<el-form :model="loginForm" label-width="80px">
<el-form-item label="账号">
<el-input v-model="loginForm.username" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleLogin" style="width: 100%">登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router' // 引入路由和当前路由实例
import { ElMessage } from 'element-plus'
import { loginApi } from '@/api/index' // 假设这是登录接口
const router = useRouter()
const route = useRoute() // 获取当前路由信息
const loginFormRef = ref()
const loginForm = ref({
username: 'admin',
password: '123456'
})
const handleLogin = async () => {
try {
// 调用登录接口,获取Token
const res = await loginApi(loginForm.value)
localStorage.setItem('token', res.data.token) // 存储Token
// 关键:读取redirect参数,决定跳转路径
const redirect = route.query.redirect as string
// 如果有redirect,跳转到该页面;否则跳首页
router.push(redirect ? redirect : '/home')
ElMessage.success('登录成功')
} catch (error) {
ElMessage.error('登录失败,请检查账号密码')
}
}
/* const handleLogin = () => {
// 模拟登录逻辑
if (loginForm.value.username && loginForm.value.password) {
localStorage.setItem('token', 'fake-token')
ElMessage.success('登录成功')
router.push('/home')
} else {
ElMessage.error('请输入账号密码')
}
} */
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f7fa;
}
.login-card {
width: 400px;
padding: 20px;
}
</style>
创建接口模块(以订单接口为例)src/api/api.ts
import request from '@/utils/request'
// 获取订单列表
export const getOrderList = (params: any) => {
return request({
url: '/order/list',
method: 'get',
params
})
}
// 取消订单
export const cancelOrder = (orderNo: string) => {
return request({
url: `/order/cancel/${orderNo}`,
method: 'post'
})
}
// 登录接口
export const loginApi = (data: { username: string; password: string }) => {
return request({
url: '/user/login',
method: 'post',
data
})
}
有些模块语法配置不兼容,修改tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"vue": ["node_modules/vue/dist/vue.esm-bundler.js"], // 强制指定Vue的模块路径
"vue-router": ["node_modules/vue-router/dist/vue-router.esm-bundler.js"],
"element-plus": ["node_modules/element-plus/dist/index.es.mjs"]
},
"module": "ESNext", // 或 "NodeNext"
"moduleResolution": "Bundler", // Vite项目推荐用Bundler
"types": ["vite/client", "@vue/runtime-core", "vue-router"] // 引入Vite的类型声明(包含import.meta.env)
}
}
src/env.d.ts
// src/env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
打包发现ts、vue生成了.js文件。通过tsconfig.json禁止 TS 自动编译
// tsconfig.json
{
"compilerOptions": {
"noEmit": true, // 禁止TS输出JS文件
"emitDeclarationOnly": false
}
}
Vite 中配置代理解决跨域问题,在 Vite 项目的 vite.config.js 文件中配置代理。
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 目标服务器地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};
更多推荐

所有评论(0)