前言
之前,我写过一片文章
教你如何制作vue+springboot项目
,教授小白可以很快的上手搭建vue和springboot项目,那么现在我感觉自己对vue相关的知识点更加熟悉,想着可以开一篇文章,教授大家如何去搭建vue3项目
如对我的上述提到的感兴趣,可参考:
教你如何制作vue+springboot项目
本项目不会教学搭建springboot,如果希望掌握springboot相关搭建,也可以参考:
那么接下来开始正式教学
实际项目演示
如下我给出我搭建之后的项目界面,在结尾我会放我这个项目的demo的git链接
没有特别优化界面,只是写一个差不多的界面
登录之后
Vue3特性
vue3
项目是可以适用vue2
的语法的,但是如果在vue3采用vue2的写法来搭建vue3项目就没有意义了
相较于Vue2,Vue3也得到了一些优化
函数式编程
: Vue 3 引入了 Composition API,允许开发者通过函数组合逻辑,而不是依赖于 Options API(Vue 2 的标准写法)。这使得代码更易于组织和重用,尤其在复杂组件中
性能优化
: Vue 3 在性能上进行了显著改善,初始渲染速度更快,同时更新响应更高效
TypeScript支持
:Vue 3 原生支持 TypeScript,使得 TypeScript 用户能够更好地利用类型检查和 IDE 提示
以及其他…
Vue3搭建框架
在正式开始之前,我们需要梳理一下vue3项目需要用到什么
在这次教学的项目中,我们会用到这些
Vite
: 具有非常快的热重载和构建速度的构建工具
Pinia
: Pinia 是 Vue 3 的状态管理库,作为 Vuex 的替代品,提供了更轻便和现代的 API 进行状态管理,也就是我们的Store
TypeScript
: TypeScript是JavaScript的上层语言,可以被编译成JavaScript。在TypeScript中,可以定义类,类是一种面向对象编程的基本结构
Element Plus
: 本次教学项目的前端框架,如有其他需要可选别的框架
axios
: 基于promise的网络请求库,可以和后端进行连接
PS: Vue3最低要求需要Node.js 12.x
或更高版本,不过,为了确保最佳的兼容性和性能,建议使用最新的稳定版本的 Node.js 和 npm,当前我搭建该项目的Node版本是18.17.1
创建项目
✨创建Vite框架项目
npm create vite@latest
✨ 然后会提示你,需要按照vite依赖包,我们填y回车
Need to install the following packages:
create-vite@5.5.4
Ok to proceed? (y)
✨然后会让你输入项目名,根据自己需求修改和填写
? Project name: » vite-project
? Package name: >> vite-project
✨上下箭头选择
vue
框架
? Select a framework: » - Use arrow-keys. Return to submit.
Vanilla
> Vue
React
Preact
Lit
Svelte
Solid
Qwik
Angular
Others
✨选择TypeScript
? Select a variant: » - Use arrow-keys. Return to submit.
> TypeScript
JavaScript
Customize with create-vue ↗
Nuxt ↗
✨然后会提示你启动项目
Done. Now run:
cd vue3Demo
npm install
npm run dev
✨使用提示的指令进行切路径,安装依赖和运行项目
✨然后按照提示打开运行的vite项目即可
打开之后项目运行是这样
此时我们的项目代码结构如下:
node_modules
public
- vite.svg
src
- assets
- vue.svg
components
- HelloWorld.vue
App.vue
main.ts
style.css
vite-env.d.ts
.gitignore
index.html
package-lock.json
package.json
README.md
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts
必要依赖安装
为了能够顺利开发我们的vue3项目,我们需要在最开始安装依赖
less/scss
vite它提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持,但必须安装相应的预处理器依赖;
这次项目使用 less , scss
自动导入
本次项目,会安装自动导入的依赖,来减少导入相关的冗余代码量
一个用于自动导入 Vue 组件的插件,一个用于自动导入常用的函数
router
vue的路由依赖,用于界面的跳转,传参
pinia
Pinia是Vue的存储库,它允许您跨组件/页面共享状态,作用类似于Vuex的Store
axios
和后端交互依赖
element-plus
前端界面框架,用于构筑界面
汇总依赖安装指令
npm add -D less
npm add -D sass
npm i postcss-nesting
npm install -D unplugin-vue-components unplugin-auto-import
npm install vue-router@4
npm install pinia
npm install axios
npm install element-plus --save
npm install @types/node
npm install @element-plus/icons-vue
配置逻辑添加
为了项目正常搭建运行,接下来我们将作一些配置新增和修改
vite.config.ts
未修改前
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})
修改后
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/dist/vite'
import Components from 'unplugin-vue-components/dist/vite'
import postcssNesting from "postcss-nesting"
import { resolve } from "path"; // 这里需要引入进来,否则不会识别到@别名
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
base: "./",// 打包时打包位置,不按照这个会找不到项目关联文件位置
plugins: [
vue(),
// 自动引入配置
AutoImport({
imports: ['vue', 'vue-router'],
dts: 'src/auto-import.d.ts'
}),
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts'
})
],
// 配置scss,样式
css: {
postcss: {
plugins: [
postcssNesting
]
}
},
server: {
proxy: {
// 后端配置地址,通过特定路径映射到对应后端ip端口
'/back': {
target: 'http://127.0.0.1:8081',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/back/, '')
}
}
},
// 配置@别名,在引入文件的时候可以用@代替到
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
"@types": resolve(__dirname, "./src/types")
}
}
})
routes的index.ts
在src下新建routes文件夹,在里面建立index.ts文件
import { createRouter, createWebHistory } from "vue-router";
let routes= [
{
path: '/',
name: 'login',
//使用如下方式进行路由懒加载,防止白屏
component: () => import('../view/login.vue')
},
{
path: '/home',
name: 'home',
component: () => import('@/view/home.vue'),
},
{
path: '/:catchAll(.*)',
name: '404',
component: () => import('../view/notFound.vue'),
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
这里我引入了三个路由,一个登录界面,一个主页面还有一个404界面,你引入了几个,相应需要在对应路径建立新增对应的vue文件
store的home.ts
在src下新增store文件夹,在里面建立home.ts文件
import { defineStore } from 'pinia';
export const useMain = defineStore('main', {
// 相当于vue2的data
state: () => {
return {
title: 'vue3Demo',
userName: '',
count: 0
}
},
// 相当于计算属性
getters: {
// count累加
countAdd: (state) => {
return state.count++;
}
},
actions: {
// 相当于vuex的mutation + action,可以写同步和异步,这里示例赋值用户名
setUserName(userName: string) {
this.userName = userName;
}
}
});
request的request.ts
在src下新建request文件夹,里面新增request.ts代码,封装axios
import axios from 'axios'
import { CreateAxiosDefaults } from 'axios';
// import {ElMessage} from "element-plus";
import { useRouter } from "vue-router";
const router = useRouter();
export const baseUrl = '/sso'; // 设置后端基础路径
// export const baseUrl = 'http://127.0.0.1:8081'; // 如果使用这个,vite.config.ts的proxy就不需要
interface CustomAxiosDefaults extends CreateAxiosDefaults {
headers: {
'Content-Type': 'application/json',
'token': string,
'X-Requested-With': string,
};
}
const request = axios.create({
baseURL: baseUrl, // 基础路径
timeout: 60000, // 超时断开连接
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'token': 'x-auth-token',
'X-Requested-With': 'XMLHttpRequest',
},
} as CustomAxiosDefaults);
// request拦截器
request.interceptors.request.use(
config => {
return config
},
error => {
// 对请求错误做些什么
Promise.reject(error)
}
)
// response 拦截器
request.interceptors.response.use(
response => {
// 根据自己后端实际响应体封装是否要报错提示
// if (response.data && response.data.header) {
// const code = response.data.header.code
// if (code !== 0) {
// ElMessage.error(response.data.header.message)
// return;
// }
// }
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
return Promise.reject(error)
}
)
export default request
Element-puls.d.ts
在src下新建文件Element-puls.d.ts
export const ElMessage: any;
declare global {
const ElMessage:typeof import('element-plus')['ElMessage']
const ElLoading:typeof import('element-plus')['ElLoading']
}
main.ts
初始main.ts代码如下
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
修改后:
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
import router from "@/routes/index";
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const pinia = createPinia();
const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(pinia);
app.use(router);
app.mount('#app');
package.json
初始为:
{
"name": "vue3demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.7",
"element-plus": "^2.8.6",
"postcss-nesting": "^13.0.1",
"pinia": "^2.2.4",
"vue": "^3.5.12",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"less": "^4.2.0",
"sass": "^1.80.4",
"typescript": "~5.6.2",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.9",
"vue-tsc": "^2.1.6"
}
}
需要把打包的build改掉,其他不动,因为不去掉,在打包的时候你会被折磨死
{
"name": "vue3demo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",// 修改这里
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.7",
"element-plus": "^2.8.6",
"postcss-nesting": "^13.0.1",
"pinia": "^2.2.4",
"vue": "^3.5.12",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"less": "^4.2.0",
"sass": "^1.80.4",
"typescript": "~5.6.2",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.9",
"vue-tsc": "^2.1.6"
}
}
tsconfig.json
初始
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
修改后:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"skipLibCheck": true,
"strict": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"Element-plus.d.ts"
],
"vueCompilerOptions": {
"target": 3
}
}
App.vue
初始
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
修改后
<script setup lang="ts">
</script>
<template>
<router-view />
</template>
<style scoped>
</style>
api的api.ts
为了映射后端实体,我们先在src下新建bean文件夹,在里面建立一个User.ts
假设我后端是springboot项目,对应有登录之后返回用户信息,用户信息数据是:
@Data
public class User {
private String userId;
private String avatar;
private String userName;
private String password;
private String email;
private String phone;
private String sex;
private Integer age;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
那么我对应前端该User.ts代码为:
export interface User {
userId: string;
avatar?: string;
userName: string;
password: string;
email?: string;
phone?: string;
sex?: string;
age?: number;
status?: number;
createTime?: string;
updateTime?: string;
}
// ?代表可以不赋值
在src下新建api文件夹,在里面新建api.ts
// 我这里是直接返回成功之后的参数,不超过的在request.ts统一报错了,大家可根据自己实际项目调整
import request from "@/request/request";
import { User} from "@/bean/User"
export const loginApi = {
// 无参后端调用
async test() {
const res = (await request.get('/back/user/test')) as void
return res.value as any
},
// 有参get请求
async login(userId: string, password: string) {
const res = (await request.get('/sso/user/login?userId=' + userId + '&password=' + password)) as User
return res.value as User
},
// 有参post请求
async register(user: User) {
const res = (await request.post('/sso/user/register', user)) as void
return res.value as void
},
}
vue界面新增
在src下新建view文件夹,在里面分别建立三个文件
login.vue
home.vue
notFound.vue
其中
login.vue代码
<template>
<div class="login-container">
<h1 class="title">{{ mainStore.title }}</h1>
<form @submit.prevent="handleLogin" class="login-form">
<div class="form-item">
<label for="username">用户名</label>
<input
type="text"
id="username"
v-model="data.loginForm.username"
required
placeholder="请输入用户名"
/>
</div>
<div class="form-item">
<label for="password">密码</label>
<input
type="password"
id="password"
v-model="data.loginForm.password"
required
placeholder="请输入密码"
/>
</div>
<button type="submit" class="login-button">登录</button>
</form>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { useMain } from '@/store/home';
import { useRouter } from 'vue-router';
import { loginApi } from '@/api/api';
import { User } from '@/bean/User';
const router = useRouter();
const mainStore = useMain();
const data = reactive({
loginForm: {
username: '',
password: '',
}
});
const apiTest = () => {
// 演示使用调用后端接口
loginApi.login('用户名', '密码').then(val => {
let user = val as User
console.log('登录之后的用户信息:', user);
})
}
const handleLogin = () => {
mainStore.setUserName(data.loginForm.username);
router.push({ path: '/home' });
};
</script>
<style scoped>
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: linear-gradient(135deg, #e0eafc, #cfdef3);
font-family: 'Arial', sans-serif;
}
.title {
font-size: 28px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
}
.login-form {
display: flex;
flex-direction: column;
width: 350px;
padding: 20px;
border-radius: 8px;
background-color: white;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.form-item {
margin-bottom: 20px;
}
label {
margin-bottom: 8px;
font-weight: bold;
color: #555;
}
input {
padding: 12px;
border: 2px solid #ccc;
border-radius: 5px;
font-size: 16px;
transition: border-color 0.3s;
}
input:focus {
border-color: #007bff;
outline: none;
}
.login-button {
padding: 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.login-button:hover {
background-color: #0056b3;
transform: translateY(-2px);
}
</style>
主界面home.vue
<template>
<el-container style="height: 100vh;">
<el-aside width="200px" class="aside">
<el-menu :default-active="activeMenu" class="sidebar" @select="handleMenuSelect">
<el-menu-item index="1" class="menu-item">仪表盘</el-menu-item>
<el-menu-item index="2" class="menu-item">用户管理</el-menu-item>
<el-menu-item index="3" class="menu-item">产品管理</el-menu-item>
<el-menu-item index="4" class="menu-item">订单管理</el-menu-item>
</el-menu>
</el-aside>
<el-container style="width: 100%;">
<el-header class="header">
<h2>后台管理系统</h2>
<el-button @click="handleLogout" type="warning" style="float: right;">注销</el-button>
</el-header>
<el-main>
<h3>{{ currentMenu }}</h3>
<p>登录用户: {{ mainStore.userName }}</p>
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useMain } from '@/store/home';
import { useRouter } from 'vue-router';
const router = useRouter();
const mainStore = useMain();
const activeMenu = ref('1');
const currentMenu = ref('仪表盘');
const handleMenuSelect = (index: string) => {
activeMenu.value = index;
switch (index) {
case '1':
currentMenu.value = '仪表盘';
break;
case '2':
currentMenu.value = '用户管理';
break;
case '3':
currentMenu.value = '产品管理';
break;
case '4':
currentMenu.value = '订单管理';
break;
}
};
// 处理注销
const handleLogout = () => {
console.log('用户注销');
router.push({path: '/login'})
};
</script>
<style scoped>
.aside {
background-color: #2c3e50;
color: #ecf0f1;
width: 200px; /* 侧边栏的固定宽度 */
}
.header {
background-color: #34495e;
color: #ecf0f1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
flex: 0 0 auto; /* 固定header大小 */
}
.el-main {
flex: 1;
overflow-y: auto;
}
.sidebar .el-menu-item {
color: #ecf0f1;
}
.sidebar .el-menu-item:hover {
background-color: #1abc9c;
}
.sidebar .el-menu-item.is-active {
background-color: #16a085;
}
h2 {
margin: 0;
}
h3 {
margin: 20px 0;
}
</style>
notFound.vue界面
<template>
<div class="not-found-container">
<el-result
icon="error"
title="404"
sub-title="抱歉,您访问的页面不存在。"
class="result"
>
<template #extra>
<el-button type="primary" @click="goBack">返回上一页</el-button>
<el-button @click="goHome">返回首页</el-button>
</template>
</el-result>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.push({path: '/'})
};
const goHome = () => {
router.push({path: '/home'})
};
</script>
<style scoped>
.not-found-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f2f5;
}
.result {
text-align: center;
}
.el-button {
margin-left: 10px;
}
</style>
git链接
gitee项目demo: vue3Demo
结语
以上就是我进行搭建vue3项目的教学文章,如有遗漏会进行补充