前端搭建 (Vue 3 + Vite + UnoCSS + Element Plus)
这是重头戏,我们将使用 2025 年主流的“自动引入”配置。
1. 初始化 Vue 3 项目
打开一个新的终端窗口(保持后端运行):
npm create vite@latest vue3-admin-web
# 选项选择:
# Framework: Vue
# Variant: JavaScript (或 TypeScript,这里为了通用性选 JS)
gemini 3 pro 给出的
cd vue3-admin-web
npm install
npm create vite@latest admin
VUE JS NO
npm install vue vue-router pinia element-plus @element-plus/icons-vue
npm install unocss -D
npm install axios
npm install unplugin-auto-import unplugin-vue-components -D
2. 安装核心插件
一次性安装所有需要的依赖:
# UI库 和 图标
npm install element-plus @element-plus/icons-vue
# CSS 引擎 (替代 WindiCSS)
npm install unocss -D
# 路由 和 状态管理
npm install vue-router pinia
# 网络请求
npm install axios
# 自动导入插件 (极力推荐,2025年标配,省去大量 import 语句)
npm install unplugin-auto-import unplugin-vue-components -D3. 配置 Vite (vite.config.js)
这是现代前端工程化的核心。我们将配置 Element Plus 的按需自动引入和 UnoCSS。
修改 vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
// UnoCSS 插件
UnoCSS(),
// 自动导入 Vue API (ref, reactive 等) 和 Element Plus
AutoImport({
imports: ['vue', 'vue-router'],
resolvers: [ElementPlusResolver()],
}),
// 自动注册组件
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
port: 3000, // 前端端口
}
})
4. 配置 UnoCSS (uno.config.js)
在根目录新建 uno.config.js。这比 WindiCSS 更灵活。
import { defineConfig, presetUno, presetAttributify } from 'unocss'
export default defineConfig({
presets: [
presetUno(), // 默认预设 (兼容 Tailwind/Windi)
presetAttributify(), // 属性化模式 (例如 <div text="red">)
],
rules: [
// 自定义规则示例
['bg-login', { 'background-image': 'linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%)' }]
]
})
5. 初始化入口文件 (src/main.js)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// 引入 UnoCSS
import 'virtual:uno.css'
// 引入 Element Plus 样式 (虽然组件自动引入,但基础样式建议全局引入以免丢失)
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
第四阶段:编写业务代码
1. 配置路由 (src/router/index.js)
新建此文件:
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue') // 暂时留空
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2. 封装 Axios (src/utils/request.js)
新建 src/utils/request.js:
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: 'http://127.0.0.1:8000', // 指向 ThinkPHP 端口
timeout: 5000
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.msg || 'Error')
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
},
error => {
ElMessage.error(error.message)
return Promise.reject(error)
}
)
export default service
3. 编写漂亮的登录页面 (src/views/Login.vue)
新建 src/views/Login.vue。我们将利用 UnoCSS 的强大样式能力和 Element Plus 的组件。
注意:由于配置了 unplugin-auto-import,代码中不需要 import { ref } from 'vue' 或 import { User, Lock } from ...,直接用即可,VS Code 可能会提示未定义,但编译是可以通过的(或者安装 Vue Official 插件并配置 eslint)。
<template>
<!-- 使用 UnoCSS 类名:全屏、Flex居中、自定义背景 -->
<div class="w-screen h-screen flex justify-center items-center bg-login">
<!-- 登录卡片 -->
<div class="w-96 bg-white rounded-xl shadow-2xl p-8 animate-fade-in-up">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-700">管理系统登录</h1>
<p class="text-gray-400 text-sm mt-2">Vue3 + TP8 + UnoCSS</p>
</div>
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" size="large">
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名: admin"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码: 123456"
show-password
:prefix-icon="Lock"
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<!-- w-full 是 UnoCSS 类,让按钮宽度100% -->
<el-button
type="primary"
:loading="loading"
class="w-full font-bold"
@click="handleLogin"
>
登 录
</el-button>
</el-form-item>
</el-form>
<div class="text-center text-xs text-gray-300 mt-4">
© 2025 Future Admin System
</div>
</div>
</div>
</template>
<script setup>
// 自动导入生效,无需手动 import ref, reactive 等
// 只有 Icons 需要手动引入或通过自动导入解析器配置,这里为了演示简单,手动引入图标
import { User, Lock } from '@element-plus/icons-vue'
import request from '../utils/request'
import { useRouter } from 'vue-router'
const router = useRouter()
const loginFormRef = ref(null)
const loading = ref(false)
const loginForm = reactive({
username: '',
password: ''
})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
// 调用 ThinkPHP 接口
const res = await request.post('/login/index', loginForm)
ElMessage.success('登录成功') // ElMessage 也是自动导入的
// 存储 Token (实际开发建议用 Pinia)
localStorage.setItem('token', res.data.token)
// 跳转
// router.push('/dashboard')
console.log('跳转到 Dashboard', res)
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
})
}
</script>
<style scoped>
/* 自定义动画示例 */
.animate-fade-in-up {
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
4. 清理 App.vue
修改 src/App.vue,只保留路由出口:
<template>
<router-view></router-view>
</template>
<style>
/* 重置默认的 margin */
body {
margin: 0;
padding: 0;
}
</style>
第五阶段:运行与测试
启动后端:
确保 ThinkPHP 在运行:cd tp8-admin-api php think run启动前端:
确保 Vue 在运行:cd vue3-admin-web npm run dev浏览器测试:
打开前端地址(通常是
http://localhost:3000)。你应该能看到一个带有渐变背景的现代化登录卡片。
测试失败情况:输入随意账号密码,右上角应弹出 Element Plus 的错误提示(红色)。
测试成功情况:输入
admin/123456。查看浏览器的 Network (F12),你会看到一个发往
http://127.0.0.1:8000/login/index的请求,并且返回200 OK。页面提示“登录成功”。
总结 (2025 视角)
这个搭建方案的特点:
Vite + UnoCSS: 极速的开发体验,原子化 CSS 是构建通用后台的最高效方式。
Auto Import: 彻底告别了并在文件头部写一大堆
import的时代,代码极简。ThinkPHP 8: 依然是 PHP 生态中最适合快速开发 API 的框架之一,PHP 8.2+ 的性能也非常强劲。
Pinia: 状态管理的标准答案。
这套架构非常适合作为通用后台管理系统(CMS/CRM/ERP)的基石。
基于当前最稳定且在未来一两年内依然是主流的技术栈进行构建。
重要说明:关于 WindiCSS
在开始之前,有一个重要的技术选型调整。WindiCSS 官方团队已于2023年停止维护,并建议用户迁移到 UnoCSS。到2025年,WindiCSS 将完全过时。因此,本教程将使用 UnoCSS,它是 Vue 核心团队成员 Antfu 开发的,被认为是 WindiCSS 的精神继承者,更快、更轻量,且完美适配 Vite。
以下是基于 Windows 系统,从零开始搭建 Vue 3 + ThinkPHP 8 前后端分离通用后台登录页的详细步骤。
第一阶段:环境准备 (Windows)
在 2025 年,你需要以下基础环境(请确保已安装):
Node.js: 建议版本 v20.x 或 v22.x (LTS)。
PHP: 建议版本 8.2 或 8.3 (ThinkPHP 8 需要 PHP >= 8.0)。
Composer: PHP 包管理器。
MySQL: 数据库 (建议 8.0)。
VS Code: 编辑器。
Git Bash 或 PowerShell: 用于运行命令。
第二阶段:后端搭建 (ThinkPHP 8)
我们将创建一个只负责提供 API 的后端。
1. 安装 ThinkPHP 8
在你的工作目录下(例如 D:\Projects),打开终端:
composer create-project topthink/think tp8-admin-api
cd tp8-admin-api
2. 处理跨域 (CORS)
前后端分离必须解决跨域问题。ThinkPHP 8 内置了中间件,但默认没开启。
步骤:找到
app/middleware.php,去掉注释或添加:
<?php
// 全局中间件定义文件
return [
// ... 其他中间件
// 跨域请求支持
\think\middleware\AllowCrossDomain::class,
];
3. 创建简单的登录接口
为了演示,我们创建一个简单的控制器返回 Token。
文件:
app/controller/Login.php
<?php
namespace app\controller;
use think\facade\Request;
use think\Response;
class Login
{
public function index()
{
// 获取 POST 数据
$params = Request::post();
$username = $params['username'] ?? '';
$password = $params['password'] ?? '';
// 模拟验证 (实际项目中请查询数据库并验证 Hash 密码)
if ($username === 'admin' && $password === '123456') {
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => 'eyJh...mock_token_2025...' // 模拟 Token
]
]);
}
return json(['code' => 400, 'msg' => '用户名或密码错误']);
}
}
4. 启动后端
在终端运行:
php think run
后端将在 http://127.0.0.1:8000 运行。
第三阶段:前端搭建 (Vue 3 + Vite + UnoCSS + Element Plus)
这是重头戏,我们将使用 2025 年主流的“自动引入”配置。
1. 初始化 Vue 3 项目
打开一个新的终端窗口(保持后端运行):
npm create vite@latest vue3-admin-web
# 选项选择:
# Framework: Vue
# Variant: JavaScript (或 TypeScript,这里为了通用性选 JS)
cd vue3-admin-web
npm install
2. 安装核心插件
一次性安装所有需要的依赖:
# UI库 和 图标
npm install element-plus @element-plus/icons-vue
# CSS 引擎 (替代 WindiCSS)
npm install unocss -D
# 路由 和 状态管理
npm install vue-router pinia
# 网络请求
npm install axios
# 自动导入插件 (极力推荐,2025年标配,省去大量 import 语句)
npm install unplugin-auto-import unplugin-vue-components -D
3. 配置 Vite (vite.config.js)
这是现代前端工程化的核心。我们将配置 Element Plus 的按需自动引入和 UnoCSS。
修改 vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
// UnoCSS 插件
UnoCSS(),
// 自动导入 Vue API (ref, reactive 等) 和 Element Plus
AutoImport({
imports: ['vue', 'vue-router'],
resolvers: [ElementPlusResolver()],
}),
// 自动注册组件
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
port: 3000, // 前端端口
}
})
4. 配置 UnoCSS (uno.config.js)
在根目录新建 uno.config.js。这比 WindiCSS 更灵活。
import { defineConfig, presetUno, presetAttributify } from 'unocss'
export default defineConfig({
presets: [
presetUno(), // 默认预设 (兼容 Tailwind/Windi)
presetAttributify(), // 属性化模式 (例如 <div text="red">)
],
rules: [
// 自定义规则示例
['bg-login', { 'background-image': 'linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%)' }]
]
})
5. 初始化入口文件 (src/main.js)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// 引入 UnoCSS
import 'virtual:uno.css'
// 引入 Element Plus 样式 (虽然组件自动引入,但基础样式建议全局引入以免丢失)
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
第四阶段:编写业务代码
1. 配置路由 (src/router/index.js)
新建此文件:
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue') // 暂时留空
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2. 封装 Axios (src/utils/request.js)
新建 src/utils/request.js:
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: 'http://127.0.0.1:8000', // 指向 ThinkPHP 端口
timeout: 5000
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.msg || 'Error')
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
},
error => {
ElMessage.error(error.message)
return Promise.reject(error)
}
)
export default service
3. 编写漂亮的登录页面 (src/views/Login.vue)
新建 src/views/Login.vue。我们将利用 UnoCSS 的强大样式能力和 Element Plus 的组件。
注意:由于配置了 unplugin-auto-import,代码中不需要 import { ref } from 'vue' 或 import { User, Lock } from ...,直接用即可,VS Code 可能会提示未定义,但编译是可以通过的(或者安装 Vue Official 插件并配置 eslint)。
<template>
<!-- 使用 UnoCSS 类名:全屏、Flex居中、自定义背景 -->
<div class="w-screen h-screen flex justify-center items-center bg-login">
<!-- 登录卡片 -->
<div class="w-96 bg-white rounded-xl shadow-2xl p-8 animate-fade-in-up">
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-700">管理系统登录</h1>
<p class="text-gray-400 text-sm mt-2">Vue3 + TP8 + UnoCSS</p>
</div>
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" size="large">
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名: admin"
:prefix-icon="User"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码: 123456"
show-password
:prefix-icon="Lock"
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item>
<!-- w-full 是 UnoCSS 类,让按钮宽度100% -->
<el-button
type="primary"
:loading="loading"
class="w-full font-bold"
@click="handleLogin"
>
登 录
</el-button>
</el-form-item>
</el-form>
<div class="text-center text-xs text-gray-300 mt-4">
© 2025 Future Admin System
</div>
</div>
</div>
</template>
<script setup>
// 自动导入生效,无需手动 import ref, reactive 等
// 只有 Icons 需要手动引入或通过自动导入解析器配置,这里为了演示简单,手动引入图标
import { User, Lock } from '@element-plus/icons-vue'
import request from '../utils/request'
import { useRouter } from 'vue-router'
const router = useRouter()
const loginFormRef = ref(null)
const loading = ref(false)
const loginForm = reactive({
username: '',
password: ''
})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
// 调用 ThinkPHP 接口
const res = await request.post('/login/index', loginForm)
ElMessage.success('登录成功') // ElMessage 也是自动导入的
// 存储 Token (实际开发建议用 Pinia)
localStorage.setItem('token', res.data.token)
// 跳转
// router.push('/dashboard')
console.log('跳转到 Dashboard', res)
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
})
}
</script>
<style scoped>
/* 自定义动画示例 */
.animate-fade-in-up {
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
4. 清理 App.vue
修改 src/App.vue,只保留路由出口:
<template>
<router-view></router-view>
</template>
<style>
/* 重置默认的 margin */
body {
margin: 0;
padding: 0;
}
</style>
第五阶段:运行与测试
启动后端:
确保 ThinkPHP 在运行:cd tp8-admin-api php think run启动前端:
确保 Vue 在运行:cd vue3-admin-web npm run dev浏览器测试:
打开前端地址(通常是
http://localhost:3000)。你应该能看到一个带有渐变背景的现代化登录卡片。
测试失败情况:输入随意账号密码,右上角应弹出 Element Plus 的错误提示(红色)。
测试成功情况:输入
admin/123456。查看浏览器的 Network (F12),你会看到一个发往
http://127.0.0.1:8000/login/index的请求,并且返回200 OK。页面提示“登录成功”。
总结 (2025 视角)
这个搭建方案的特点:
Vite + UnoCSS: 极速的开发体验,原子化 CSS 是构建通用后台的最高效方式。
Auto Import: 彻底告别了并在文件头部写一大堆
import的时代,代码极简。ThinkPHP 8: 依然是 PHP 生态中最适合快速开发 API 的框架之一,PHP 8.2+ 的性能也非常强劲。
Pinia: 状态管理的标准答案。
这套架构非常适合作为通用后台管理系统(CMS/CRM/ERP)的基石。
评论区