Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • 面试

  • 工具

    • Linux
    • Git
    • Apipost
    • IDEA
    • Docker
    • 快捷键
    • 炼丹环境配置
    • 博客搭建
    • CDN
    • Vue3+Vite+Pinia+Element-Plus项目搭建
      • yarn项目初始化构建
      • Sass集成
      • Router路由集成
      • 全局utils
        • mixin混入
        • 全局过滤器
      • Element Plus集成
      • Pinia集成+浏览器持久化存储
        • pinia持久化删除数据的问题
        • pinia使用
        • toRef和toRefs
      • axios封装集成
      • 进度条插件
      • 页面布局组件
      • El-upload组件图片上传
        • 方式一:使用组件action上传
        • 方式二:封装request请求
        • 前端服务器的图片上传
    • 资源合集
    • LLM入门
  • 项目

  • 关于

  • 更多
  • 工具
phan
2023-10-12
目录

Vue3+Vite+Pinia+Element-Plus项目搭建

# Vue3+Vite+Pinia+Element-Plus项目搭建

# yarn项目初始化构建

使用yarn的模板进行项目构建:

npm create vite@latest my-vue3-app -- --template vue
1

其中yarn提供可多种不同模板,包括vue,react,同时提供js版本和ts版本。

# Sass集成

本质上是一种CSS样式的使用框架。

安装Sass,开发阶段依赖

npm install sass --save-dev
1
  • --save-dev,-D:表示依赖仅安装在开发环境。生产环境打包不会包含该依赖。
  • --save,-S:表示开发阶段+生产阶段都会使用到的依赖,会加入到package.json

# Router路由集成

安装依赖

npm install vue-router@4
1

在main.js导入

import router from '@/router';
app.use(router);
1
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;
1
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')
1
2
3

# 全局utils

# mixin混入

抽取全局方法、属性,全局使用js文件暴露出来的变量

在main.js中导入混入的js文件

import mixin from '@/utils/mixin'
app.mixin(mixin)
1
2

方法使用时,需要调用getCurrentInstance()方法拿到代理对象proxy,通过代理对象使用方法

# 全局过滤器

将后端传入的性别数据0和1,转化为前端显示的数据男和女。本质上也是在js文件定义方法并暴露出来。

在main.js中导入过滤器js文件

import { filters } from '@/utils/filters.js';
app.config.globalProperties.$filters = filters;
1
2

# Element Plus集成

安装依赖

npm install element-plus --save
npm install @element-plus/icons-vue
1
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);
}
1
2
3
4
5
6
7

# Pinia集成+浏览器持久化存储

变量存储库,允许跨组件/页面共享状态。

安装依赖

npm install pinia
1

安装持久化存储依赖

npm install pinia-persistedstate
1

在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;
1
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();
}
1
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);
  };
});
1
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 };
});
1
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
    });
}
1
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);
1
2
3

# axios封装集成

安装依赖

npm install axios
1

在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;
1
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==',
      // },
    });
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 进度条插件

安装依赖

yarn add nprogress
1

# 页面布局组件

在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"
>
1
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()
1

拿到后端响应的文件后,触发on-success事件,将base64对象转换成blob对象,并用于在前端展示。

# 方式二:封装request请求

  • 通过onchange对象拿到用户上传的图片raw对象,并保存为一个文件变量。
  • 创建FormData表单,将文件变量加入里面。
  • 发送request请求,data为表单数据

# 前端服务器的图片上传

首先需要将图片转换成blob对象,可以采用fetch的方法。

然后再将blob图片对象加入表单数据,通过request发送请求。

编辑 (opens new window)
#工具
上次更新: 2023/12/15, 15:49:57
CDN
资源合集

← CDN 资源合集→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式