Element Plus(最主流、上手最简单)
- 官网:https://element-plus.org/
- 依赖:
element-plus@^2.5.0+、@element-plus/icons-vue(配套图标库,后台系统必备)
1、创建项目
npm create vite@latest

核心业务依赖(生产环境,pnpm add 直接安装 latest)
pnpm add vue@latest vue-router@latest pinia@latest axios@latest element-plus@latest
辅助业务依赖(生产环境,配套工具也用 latest)
pnpm add @element-plus/icons-vue@latest pinia-plugin-persistedstate@latest
开发环境依赖(仅开发 / 构建使用,同样指定 latest)
pnpm add typescript@latest vue-tsc@latest @vitejs/plugin-vue@latest @types/node@latest -D
清除包:
pnpm store prune
安装pnpm:
npm install -g pnpm
安装rimraf
pnpm add rimraf@latest -D
2、运行相关命令:
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"preview:port": "vite preview --port 8080",
"type-check": "vue-tsc --noEmit",
"clean:dist": "rimraf dist 2>nul",
"clean:packages": "rimraf node_modules 2>nul && rimraf pnpm-lock.yaml 2>nul",
"clean:all": "rimraf dist node_modules pnpm-lock.yaml 2>nul",
"reinstall": "pnpm run clean:packages && pnpm install"
}
3、vite.config.ts:
import { defineConfig, ConfigEnv, UserConfig } from 'vite' // 导入vite的类型定义
import vue from '@vitejs/plugin-vue'
import path from 'path'
// 生成唯一时间戳(精确到毫秒,确保每次打包都不同)
const buildTimestamp = new Date().getTime()
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
return {
plugins: [vue()],
base: './', // 保持相对路径,无需关心CDN地址
resolve: {
alias: {
// 明确指定路径别名的类型,避免TS类型提示警告
'@': path.resolve(__dirname, 'src')
}
},
build: {
assetsDir: 'assets',
outDir: 'dist',
assetsInlineLimit: 4096,
// 禁用打包缓存,确保每次打包重新计算哈希/时间戳
cache: false,
rollupOptions: {
output: {
// 方案1:时间戳 + hash(推荐,双重保证唯一性)
assetFileNames: `assets/[name].${buildTimestamp}.[hash:8].[ext]`,
chunkFileNames: `assets/[name].${buildTimestamp}.[hash:8].js`,
entryFileNames: `assets/[name].${buildTimestamp}.[hash:8].js`,
// 方案2:仅时间戳(极简,绝对唯一,可选)
// assetFileNames: `assets/[name].${buildTimestamp}.[ext]`,
// chunkFileNames: `assets/[name].${buildTimestamp}.js`,
// entryFileNames: `assets/[name].${buildTimestamp}.js`
}
},
sourcemap: false
},
server: {
allowedHosts: ['www.test.com'],
proxy: {
'/api': {
target: 'http://localhost:8967',
changeOrigin: true,
rewrite: (path) => path.replace('/api', '/api') // 修复代理路径
}
}
}
}
})
4、tsconfig.app.json:
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
5、在src目录下建立router文件夹 在router文件夹下创建router.js:
import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router'
import { useUserStore } from '@/stores/user'
// 路由懒加载
const Login = () => import('@/views/Login.vue')
const Home = () => import('@/views/Home.vue')
// 创建 Router 实例(基础配置,后续可扩展)
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'login',
component: Login,
meta: {
title: '登录',
requiresAuth: false // 登录页不需要认证
}
},
{
path: '/',
name: 'home',
component: Home,
meta: {
title: '首页',
requiresAuth: true // 需要登录
}
},
{
path: '/:pathMatch(.*)*',
redirect: '/'
}
]
})
// 路由守卫
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next) => {
const userStore = useUserStore()
const isLoggedIn = userStore.checkLogin()
// 设置页面标题
if (to.meta.title) {
document.title = `${to.meta.title} - CMS 系统`
}
// 检查路由是否需要登录
if (to.meta.requiresAuth) {
if (!isLoggedIn) {
// 未登录,跳转到登录页,并保存当前路径用于登录后跳转
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
// 如果已登录,访问登录页时重定向到首页
if (to.path === '/login' && isLoggedIn) {
next('/')
} else {
next()
}
}
})
export default router
types.d.ts:
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
}
}
6、在src目录下创建stores/user.ts
pina:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export interface UserInfo {
username: string
token: string
}
export const useUserStore = defineStore(
'user',
() => {
// 用户信息
const userInfo = ref<UserInfo | null>(null)
const isLoggedIn = ref(false)
// 登录
const login = async (username: string, password: string) => {
// TODO: 这里应该调用实际的登录 API
// 模拟登录请求
return new Promise<boolean>((resolve) => {
setTimeout(() => {
if (username && password) {
userInfo.value = {
username,
token: `token_${Date.now()}`
}
isLoggedIn.value = true
resolve(true)
} else {
resolve(false)
}
}, 500)
})
}
// 登出
const logout = () => {
userInfo.value = null
isLoggedIn.value = false
}
// 检查登录状态
const checkLogin = () => {
// 从持久化存储中恢复登录状态
return isLoggedIn.value && userInfo.value !== null
}
return {
userInfo,
isLoggedIn,
login,
logout,
checkLogin
}
},
{
persist: true // 启用持久化存储
}
)