Vue3+Vite+Pinia+Element-Plus项目搭建
# Vue3+Vite+Pinia+Element-Plus项目搭建
# yarn项目初始化构建
使用yarn的模板进行项目构建:
npm create vite@latest my-vue3-app -- --template vue
其中yarn提供可多种不同模板,包括vue,react,同时提供js版本和ts版本。
# Sass集成
本质上是一种CSS样式的使用框架。
安装Sass,开发阶段依赖
npm install sass --save-dev
- --save-dev,-D:表示依赖仅安装在开发环境。生产环境打包不会包含该依赖。
- --save,-S:表示开发阶段+生产阶段都会使用到的依赖,会加入到package.json
# Router路由集成
安装依赖
npm install vue-router@4
在main.js导入
import router from '@/router';
app.use(router);
2
src/router/index.js中添加路由页面
import {createRouter, createWebHashHistory} from 'vue-router';
// 本地静态路由
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
},
{
path: '/test',
component: () => import('@/views/test/index.vue'),
children:[]
},
];
// 创建路由
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes,
});
export default router;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在每个页面中通过拿到router对象,并修改路由展示不同的界面:
import { useRouter } from 'vue-router'
const router = useRouter()
router.push('/ocr')
2
3
# 全局utils
# mixin混入
抽取全局方法、属性,全局使用js文件暴露出来的变量
在main.js中导入混入的js文件
import mixin from '@/utils/mixin'
app.mixin(mixin)
2
方法使用时,需要调用getCurrentInstance()方法拿到代理对象proxy,通过代理对象使用方法
# 全局过滤器
将后端传入的性别数据0和1,转化为前端显示的数据男和女。本质上也是在js文件定义方法并暴露出来。
在main.js中导入过滤器js文件
import { filters } from '@/utils/filters.js';
app.config.globalProperties.$filters = filters;
2
# Element Plus集成
安装依赖
npm install element-plus --save
npm install @element-plus/icons-vue
2
在main.js导入
import ElementPlus from "element-plus";
import 'element-plus/dist/index.css';
app.use(ElementPlus);
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
2
3
4
5
6
7
# Pinia集成+浏览器持久化存储
变量存储库,允许跨组件/页面共享状态。
安装依赖
npm install pinia
安装持久化存储依赖
npm install pinia-persistedstate
在main.js导入
//pinia集成
import { createPinia } from 'pinia';
const pinia=createPinia();
// 持久化存储
import { createPersistedState } from 'pinia-plugin-persistedstate';
pinia.use(
createPersistedState({
auto: true, // 启用所有 Store 默认持久化
}),
);
//重写reset方法
pinia.use(({ store }) => {
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {
store.$patch(initialState);
};
});
app.use(pinia);
//store
import store from '@/store';
app.config.globalProperties.$store=store;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# pinia持久化删除数据的问题
选项式API:需要先调用$reset()方法,才能clear清空会话和本地存储的数据
import store from '@/store';
// 退出登录
function logout() {
isLogin.value = false;
// 清空当前store在pinia中持久化存储的数据
this.$reset();
// 其它store
store.settings.useSettingsStore().$reset();
// 最终真正清空storage数据
window.localStorage.clear();
window.sessionStorage.clear();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
组合式API需要在main.js中重写$reset方法
pinia.use(({ store }) => {
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {
store.$patch(initialState);
};
});
2
3
4
5
6
# pinia使用
在src/store/modules/user.js中定义保存在浏览器的变量和方法
import { defineStore } from 'pinia';
import { ref,computed } from 'vue';
const views = import.meta.glob('@/views/**/**.vue');
import { useRoute, useRouter } from 'vue-router';
import store from '@/store';
export const useUserStore = defineStore('user', () => {
const route = useRoute();
const router = useRouter();
let isLogin = ref(false);
let tokenObj = ref({});
let userObj = ref({});
// 登录
async function login(loginObj) {
if (isLogin.value) {
return;
}
let result = await sysUserApi.login({
username: loginObj.username.trim(),
password: loginObj.password.trim(),
});
isLogin.value = true;
tokenObj.value = result.data;
userObj.value={
'username': loginObj.username.trim(),
'password': loginObj.password.trim(),
'role':'0',
'type':[],
'email':''
}
}
// 退出登录
function logout() {
// 清空pinia存储的数据
this.$reset();
store.settings.useSettingsStore().$reset();
// window.localStorage.setItem('user2', 'hello');
// window.localStorage.removeItem('user2');
// tips: pinia持久化的无法通过这种方式清空数据,只能删除同样方式存储的值 eg: window.localStorage.setItem('user2', 'hello');
window.localStorage.clear();
window.sessionStorage.clear();
// 跳转登录页
router.push(`/login?redirect=${route.fullPath}`);
// window.location.href = '/login';
location.reload(); // 强制刷新页面
}
return { isLogin, login, logout, tokenObj, userObj };
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
在每个vue文件当中,通过proxy拿到全局引入的store对象,并调用对应存储的变量和方法:
import {ref,reactive,getCurrentInstance} from 'vue'
const {proxy} = getCurrentInstance();
const userStore = proxy.$store.user.useUserStore();
function handleLogin(){
userStore.login(form).then((result) => {
console.log(result)
proxy.$router.push({path:'/'})
}).catch((err) => {
isLoading.value = false
});
}
2
3
4
5
6
7
8
9
10
11
# toRef和toRefs
使用全局数据作为响应时,需要使用toRef和toRefs进行解构,在当前界面使用全局响应式数据。
- toRefs:复制响应式对象的每个属性,每个属性都是对应的ref,需要.value修改。
- toRef:复制某个响应式对象的属性,需要指定这个对象的哪个属性toRef (obj , 'name')。
const {proxy}=getCurrentInstance();
const userStore=proxy.$store.user.useUserStore()
let {messages} =toRefs(userStore);
2
3
# axios封装集成
安装依赖
npm install axios
在request.js中封装axios请求:
- 请求拦截器:每个前端异步请求发送之前,可以添加header,用户token认证
- 响应拦截器:根据后端拿到的code码,弹出消息框和对应的ElMessage信息
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import store from '@/store';
import { localStorage } from '@/utils/storage';
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000, // 请求超时时间:50s
headers: { 'Content-Type': 'application/json;charset=utf-8' },
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
if (!config.headers) {
throw new Error(`Expected 'config' and 'config.headers' not to be undefined`);
}
const { isLogin, tokenObj } = toRefs(store.user.useUserStore());
if (isLogin.value) {
// 授权认证
config.headers[tokenObj.value.tokenName] = tokenObj.value.tokenValue;
// 租户ID
config.headers['TENANT_ID'] = '1';
// 微信公众号appId
config.headers['appId'] = localStorage.get('appId');
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
const res = response.data;
const { code, msg } = res;
if (code === 200) {
return res;
} else {
// token过期
if (code === -1) {
handleError();
} else {
ElMessage({
message: msg || '系统出错',
type: 'error',
duration: 5 * 1000,
});
}
return Promise.reject(new Error(msg || 'Error'));
}
},
(error) => {
console.log('请求异常:', error);
const { msg } = error.response.data;
// 未认证
if (error.response.status === 401) {
handleError();
} else {
ElMessage({
message: '网络异常,请稍后再试!',
type: 'error',
duration: 5 * 1000,
});
return Promise.reject(new Error(msg || 'Error'));
}
},
);
// 统一处理请求响应异常
function handleError() {
const { isLogin, logout } = store.user.useUserStore();
if (isLogin) {
ElMessageBox.confirm('您的登录账号已失效,请重新登录', {
confirmButtonText: '再次登录',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
logout();
});
}
}
// 导出实例
export default service;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
封装对应的Api
import request from '@/utils/request';
export default {
// 获取验证码
getCaptcha() {
return request({
url: '/captcha?t=' + new Date().getTime().toString(),
method: 'get',
});
},
// 登录
login(data) {
return request({
url: '/login',
method: 'post',
data,
// headers: {
// // 客户端信息Base64明文:web:123456
// Authorization: 'Basic d2ViOjEyMzQ1Ng==',
// },
});
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 进度条插件
安装依赖
yarn add nprogress
# 页面布局组件
在components中定义AppAside.vue,AppHeader.vue,AppLayout.vue,侧边、头部和用于组件组合展示的组件。
中间content展示的内容界面通过router配置路由的children来定义。
# El-upload组件图片上传
组件常用的配置属性如下:
<el-upload
ref="uploadRef"
class="avatar-uploader"
action="http://localhost:50001/ocr"
:show-file-list="false"
:on-change="justSelect"
:before-upload="beforeAvatarUpload"
drag
:data="{ data: 'ppocrv3' }"
:auto-upload="false"
:on-success="handleSuccess"
>
2
3
4
5
6
7
8
9
10
11
12
大致流程包括如下,用户在前端界面拿到图片文件对象后,点击按钮发送给后端,后端拿到图片后进行算法处理,再将图片返回给后端。
这里设置auto-upload="false"不进行自动上传。
# 方式一:使用组件action上传
本质:点击按钮上传后,调用el-upload组件的ref对象方法引用进行上传,发送请求到action的路径地址。
on-change事件,将拿到图片通过URL.createObjectURL转成一个url,用于回显上传的图片,
uploadRef.value.submit()
拿到后端响应的文件后,触发on-success事件,将base64对象转换成blob对象,并用于在前端展示。
# 方式二:封装request请求
- 通过onchange对象拿到用户上传的图片raw对象,并保存为一个文件变量。
- 创建FormData表单,将文件变量加入里面。
- 发送request请求,data为表单数据
# 前端服务器的图片上传
首先需要将图片转换成blob对象,可以采用fetch的方法。
然后再将blob图片对象加入表单数据,通过request发送请求。