【前端】VUE3.2学习详细版

本文详细介绍了如何在Vue3.2项目中使用element-plus,包括按需导入与全局导入的配置,以及Vue3的新特性。同时,文章讲解了CSS初始化、登录页面的实现、SVG组件的封装、登录请求的处理、请求与响应拦截器的设置,还有路由守卫、动态面包屑、头像退出、i18n、全屏功能和表格内容的处理等关键功能的实现方法。
摘要由CSDN通过智能技术生成

0. 学习视频

1. 前期工作

  • cmd
  • vue ui
  • 创建项目
    • 这里没有选择使用代码严谨
      在这里插入图片描述
    • 安装axios依赖
    • 前期依赖一览
      在这里插入图片描述
  • 代码格式化
    • 如果没有生效 【右键 …配置格式化文档
      在这里插入图片描述

2. 项目

1. element-plus

npm install element-plus --save

1. 按需导入element

npm install -D unplugin-vue-components unplugin-auto-import

这里出现 bug
node版本和其不匹配 需要node升级
所以这边配置使用的是全局引入

在这里插入图片描述

  • 按需导入的Webpack设置
module.exports = {
    configureWebpack: (config) => {
        config.plugins.push(
          AutoImport({
            resolvers: [ElementPlusResolver()]
          })
        )
        config.plugins.push(
          Components({
            resolvers: [ElementPlusResolver()]
          })
        )
      },
}
  • script中引入所需element
import { ElMessage, ElMessageBox } from "element-plus";
2. 全局导入
  • 在main.js中引入
// elementplus全局配置
import 'element-plus/dist/index.css'
import ElementPlus from 'element-plus'
app.use(ElementPlus).mount('#app')
2. vue3新特性
  1. 异步写法
  • 使用then
getAllNews().then((res) => {});
  • 没有then这个操作 就要用async和await
  1. data

vue2中的data值 就是 vue3中的const常量

切记 vue3取值都是从value取的

  1. 不需要根标签包裹(尽量还是进行包裹)
  2. css可以直接绑定js变量(如下)
    在这里插入图片描述
3. css初始化

在这里插入图片描述

  • main.js中导入
// 全局配置css
import '@/styles/index.scss'
4. 初始化测试
  • element-plus版本更新太快,修改:

npm i element-plus@1.3.0-beta.5

在这里插入图片描述

1. login页面 + login路由

在这里插入图片描述

  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/login/index.vue')
  },
  • 双向数据绑定 :model=“form”
<el-form ref="formRef" :model="form" class="login-form" :rules="rules"></el-form>
  • 从vue中导入ref【绑定静态数据】
import { ref } from 'vue'
  • 获取form表单
const form = ref({
  // 将数据固定 方便
  username: 'admin',
  password: '123456'
})

ref 后面在 vue3 的官方博客里有发布说大幅提升了性能,所以后面的无论什么数据类型都会使用 ref

2. SVG组件

封装成组件 提高利用率

在这里插入图片描述

  1. 安装插件

npm i --save-dev svg-sprite-loader@6.0.9

  • 修改webpack配置vue.config.js
    在这里插入图片描述
  1. 在main.js中引入
import SvgIcon from '@/icons'
SvgIcon(app)
  • 获取方法【iconfont】
    在这里插入图片描述
    • 文件自命名 使用的时候用icon=“文件名”
      在这里插入图片描述
  1. 使用
<svg-icon icon="user" class="svg-container"></svg-icon>
<svg-icon icon="password"></svg-icon>
  1. 关于密码

ElementPlus 自带切换功能,<el-input type=“password” show - password />即可

3. 登录页面的基本设置【请求前】
  1. 定制rules校验规则 在form中绑定
  2. 点击登录按钮 实现校验事件

vue3.2的语法

const handleLogin = () => {
 formRef.value.validate(async (valid) => { 
	if (valid) {}
	else {}
	})
}
  1. 发起请求操作

src\api\request.js

在这里插入图片描述

  • 生产环境

.env.development

ENV = 'development'

VUE_APP_BASE_API = '/api'
  • 开发环境

.env.production

ENV = 'production'

VUE_APP_BASE_API = '/prod-api'
  • 跨域问题

vue.config.js

  devServer: {
    https: false,
    // hotOnly改为hot
    hot: false,
    proxy: {
      '/api': {
        target: 'http://43.143.0.76:8889/api/private/v1/',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },

在这里插入图片描述

  • 设置login请求
    在这里插入图片描述
4. 登录页面的基本设置【请求中】
  • login页面 点击登录按钮 触发事件
  • app是命名空间,因为分模块了,login在app命名空间下,所以使用app/login
const formRef = ref(null)
const handleLogin = () => {
  formRef.value.validate(async (valid) => {
    if (valid) {
      // 这里没有进行保存个人信息的操作 只保存了token
      // 利用 vuex 进行异步请求【action】 进行路由跳转
      store.dispatch('app/login', form.value)
      console.log(form.value);
      // 直接进行异步请求 查看返回数据
      const res = await login(form.value)
      console.log(res);
    } else {
      console.log('error submit!!')
      return false
    }
  })
}
5. 请求拦截器和响应拦截器
  1. 响应拦截器

解构数据 将返回的信息变成我们希望的格式

// 响应请求拦截器
// 例将res.data.data中的data直接取出
service.interceptors.response.use(
  (response) => {
    console.log(response);
    // 解构 取出 data和meta
    const { data, meta } = response.data
    // 进行判断
    if (meta.status === 200 || meta.status === 201) {
      // 如果meta值正确返回data
      return data
    } else {
      // 错误时消息提醒
      ElMessage.error(meta.msg)
      return Promise.reject(new Error(meta.msg))
    }
  },
  // 如果没有响应
  (error) => {
    console.log(error.response)
    error.response && ElMessage.error(error.response.data)
    return Promise.reject(new Error(error.response.data))
  }
)
  1. 通过vuex二次存储token

在登录操作的时候 已经获取了token 并在localStorage存储

    state: () => ({
      token: localStorage.getItem('token') || '', // 如果localStorage中有就取出 否则为空
    }),
    // 同步方法
    mutations: {
      setToken(state, token) {
        state.token = token
        localStorage.setItem('token', token)
      },
    },

action 中完成异步方法

        // 接受一个用户信息
      login({ commit }, userInfo) {
        // 发起请求
        return new Promise((resolve, reject) => {
          loginApi(userInfo) //接口
            .then((res) => {
              console.log(res)
            //   如果成功 调用mutations中的setToken 将res.token传值
              commit('setToken', res.token)
              setTokenTime()
            //   成功之后路由跳转
              router.replace('/')
              resolve()
            })
            .catch((err) => {
              reject(err)
            })
        })
      },

index.js中导入app

import { createStore } from 'vuex'
import app from './modules/app'
import getters from './getters'
export default createStore({
  modules: {
    app
  },
  getters
})

在这里插入图片描述

  1. 请求拦截器

设置请求头

// 请求拦截器+路由守卫
service.interceptors.request.use(
  (config) => {
    // 为每一个接口加上独有的token
    // 这里后面会有一个对token时间的判断
    config.headers.Authorization = localStorage.getItem('token')
    return config
  },
  (error) => {
    return Promise.reject(new Error(error))
  }
)
  1. 路由守卫

src\router\permission.js

此之前先设置vuex getters 方便获取数据

在这里插入图片描述

// 白名单 用户没有登录也可以访问的页面
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
  // token存在
  if (store.getters.token) {
    // 路径是否在login
    if (to.path === '/login') {
      // 登陆了就无需留在登录页面
      next('/')
    } else {
      next()
    }
  } else {
    // 没有token 
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
})

在main.js中导入路由守卫

这是一份js文件,引入js文件的时候不用名字接收,就等于直接执行这个文件的内容
结合起来,就是建立一个全局路由守卫。

import '@/router/permission'
5. layout
  • 组件
  • 路由
    在这里插入图片描述
    在这里插入图片描述
  1. 在webpack配置css
  css: {
    loaderOptions: {
      sass: {
        // 8版本用prependData:
        prependData: `
          @import "@/styles/variables.scss";  // scss文件地址
          @import "@/styles/mixin.scss";     // scss文件地址
        `
      }
    }
  }

layout中的el-main使用< router-view > < /router-view >标签

6. 导入菜单

在这里插入图片描述

const menusList = ref([])
const initMenusList = async () => {
  console.log("=========================");
  menusList.value = await menuList()
      // const res = await menuList()
      // console.log(res);
  console.log(menusList);
}
initMenusList()

在这里插入图片描述

  • 配置路由
  • 定义一个默认的展开项
//这个值不能被写死 每次点击的时候会发生改变
const defaultActive = ref(sessionStorage.getItem('path') || '/users')
  • 点击事件【对后台返回的菜单列表进行循环操作 取出每一个菜单】
    在这里插入图片描述
  • 每一次点击的时候 就会将这个路径存储
// 将path值存储
const savePath = (path) => {
  sessionStorage.setItem('path', `/${path}`)
}
  • 所以在存在路径的时候就选择已经存储的路径 否则默认/users
// 定义一个默认的展开项
const defaultActive = ref(sessionStorage.getItem('path') || '/users')
7. 被动退出 当token过期
import { TOKEN_TIME, TOKEN_TIME_VALUE } from './constant'

// 登录时设置时间
export const setTokenTime = () => {
  localStorage.setItem(TOKEN_TIME, Date.now())
}

// 获取
export const getTokenTime = () => {
  return localStorage.getItem(TOKEN_TIME)
}

// 是否已经过期
export const diffTokenTime = () => {
  const currentTime = Date.now()
  console.log("currentTime=",currentTime);
  const tokenTime = getTokenTime()
  console.log("tokenTime=",tokenTime);
  return currentTime - tokenTime > TOKEN_TIME_VALUE
}

export const TOKEN_TIME = 'tokenTime'

export const TOKEN_TIME_VALUE = 2 * 60 * 60 * 1000

// 设置常量 设置有效期时间 6000 = 6s

在这里插入图片描述

在请求拦截器中查看是否过期 超过有效时间则自动退出

// 请求拦截器+路由守卫
service.interceptors.request.use(
  (config) => {
    if (localStorage.getItem('token')) {
      if (diffTokenTime()) {
        // 通过vuex实现一个退出
        store.dispatch('app/logout')
        return Promise.reject(new Error('token 失效了'))
      }
    }
    // 为每一个接口加上独有的token
    config.headers.Authorization = localStorage.getItem('token')
    return config
  },
  (error) => {
    return Promise.reject(new Error(error))
  }
)
      // 退出 将token值清空 存储清空 跳转到登录页面
      logout({ commit }) {
        commit('setToken', '')
        localStorage.clear()
        router.replace('/login')
      }

登录的时候设置时间 setTokenTime()

      login({ commit }, userInfo) {
        // 发起请求
        return new Promise((resolve, reject) => {
          loginApi(userInfo) //接口
            .then((res) => {
              console.log(res)
            //   如果成功 调用mutations中的setToken 将res.token传值
              commit('setToken', res.token)
              setTokenTime()
            //   成功之后路由跳转
              router.replace('/')
              resolve()
            })
            .catch((err) => {
              reject(err)
            })
        })
      },
8. 汉堡按钮伸缩项

src\layout\headers\components\hamburger.vue

  • 点击事件【一下为具体操作】
    在这里插入图片描述
    1. 引入useStore 使用vuex
import { useStore } from 'vuex'
import { computed } from 'vue'
    1. 点击事件切换侧边栏【这里是使用vuex中的同步方法】
const store = useStore()
const toggleClick = () => {
  store.commit('app/changeSiderType')
}
    1. 更换icon
// 根据值切换图标 动态 :icon
const icon = computed(() => {
  return store.getters.siderType ? 'hamburger-opened' : 'hamburger-closed'
})

在这里插入图片描述

  • vuex中siderType相关
    state: () => ({
      // 默认值是一个true值
      siderType: true,
    }),
    mutations: {
    // 取反
      changeSiderType(state) {
        state.siderType = !state.siderType
      },
    },
  • 折叠菜单绑定【element中有这个属性】
    在这里插入图片描述
    在这里插入图片描述
:collapse="!$store.getters.siderType"
  • 【只设置这个发现文字可以缩下去但是菜单栏的宽度没有改变】
  • 所以el-aside也需要设置值,不止是menu
    在这里插入图片描述
    在这里插入图片描述
// 菜单的宽度应该是一个动态值 可以伸缩 所以用computed而不是ref
// const asideWidth = ref(variables.sideBarWidth)
const asideWidth = computed(() => {
  return store.getters.siderType
    ? variables.sideBarWidth
    : variables.hideSideBarWidth
})
  • el-container 同步做出伸缩【利用css】
    在这里插入图片描述
.container {
  width: calc(100% - $sideBarWidth);
  height: 100%;

  position: fixed;
  top: 0;
  right: 0;
  z-index: 9;
  transition: all 0.28s;
  // 这里已经设置的宽度
  &.hidderContainer {
    width: calc(100% - $hideSideBarWidth);
  }
}

在这里插入图片描述

10. 动态面包屑
  1. 获取路由
  2. 更新操作 watch
// 每次点击不同的标题需要更新路由 使用watch
import { watch, ref } from 'vue'
// 动态获取路由表
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()

const breadcrumbList = ref([])

const initBreadcrumbList = () => {
  breadcrumbList.value = route.matched
  // 路由表
  console.log(route.matched)
}
// 跳转页面
const handleRedirect = (path) => {
  router.push(path)
}

watch(
  route,
  () => {
    initBreadcrumbList()
  },
  { deep: true, immediate: true }
)
<!-- 动态面包屑 上级可以点 最后一级不能点-->
  <el-breadcrumb separator="/">
    <el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
      <!-- 如果是最后一项 -->
      <span class="no-redirect" v-if="index === breadcrumbList.length - 1">
        {{$t(`menus.${item.name}`)}}</span> //这里用了i18n
        <!-- {{$item.name}}</span> -->
        <!-- 否则都可以点击跳转 -->
      <span class="redirect" v-else @click="handleRedirect(item.path)">
        {{$t(`menus.${item.name}`)}}</span>
        <!-- {{item.name}}</span> -->
    </el-breadcrumb-item>
  </el-breadcrumb>

不使用点击事件切换路径 也可以使用to属性

< el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index" :to="item.path">
10. 头像退出
  <el-dropdown>
    <span class="el-dropdown-link">
      <!-- 头像组成 -->
      <el-avatar shape="square" :size="40" :src="squareUrl"></el-avatar>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <!-- 退出事件 调用action vuex-->
        <el-dropdown-item @click="logout">退出</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
  1. 退出点击事件@click=“logout”
  2. 使用vuex异步请求【action异步方法】
import { useStore } from 'vuex'
const store = useStore()
const logout = () => {
  store.dispatch('app/logout')
}
11. i18n

引入到了main.js 所以在初始化就会实现

npm install vue-i18n@next

src\i18n\index.js

在这里插入图片描述

const getCurrentLanguage = () => {
  const UAlang = navigator.language // 浏览器中文zh-CN
  // 是否为中文
  const langCode = UAlang.indexOf('zh') !== -1 ? 'zh' : 'en'
  // 存储语言
  localStorage.setItem('lang', langCode)
  return langCode
}
const i18n = createI18n({
  legacy: false,
  globalInjection: true,
  locale: getCurrentLanguage() || 'zh',
  messages: messages
})

export default i18n
  • 在main.js中导入
// 国际化全局配置
import i18n from '@/i18n'
app.use(store).use(router).use(i18n).use(ElementPlus).mount('#app')
  • 子菜单中的中英文更改【因为这里后台返回的路径名字,即是,我们设置的目录名字(因果关系可以也倒过来)】
    在这里插入图片描述在这里插入图片描述
  • 切换语言事件【封装为组件】

src\layout\headers\components\lang.vue

在这里插入图片描述

import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
import { useStore } from 'vuex'
const i18n = useI18n()
const store = useStore()
const currentLanguage = computed(() => { // 绑定 i18n.locale.value
  return i18n.locale.value
})

const handleCommand = (val) => {
  console.log(val);
  i18n.locale.value = val
  store.commit('app/changLang', val)
  localStorage.setItem('lang', val)
}
  • vuex中二次存储语言【先在main.js中 引入的文件已经存储到内存中 所以是二次存储】
    state: () => ({
      // 已经存储了则直接从缓存中保存
      lang: localStorage.getItem('lang') || 'zh'
    }),
    mutations: {
      changLang (state, lang) {
        state.lang = lang
      }
    },

getters中方便获取的方法

lang: (state) => state.app.lang
12. 全屏功能

下载插件 screenfull
降低版本

13. 引导页

下载插件 drive.js
降低版本

14. 表格内容

在这里插入图片描述

1. 初始化数据
const initGetUsersList = async () => {
  // console.log(queryForm.value);
  // 有搜索值传query值
  const res = await getUser(queryForm.value);
  console.log(res);
  total.value = res.total;
  tableData.value = res.users;
};
initGetUsersList();
  1. 利用for循环将多条el-table-column变成一条【设置了一个js】
  • 新学的方法 思路扩展 可实践
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  1. 插槽问题
    在这里插入图片描述
2. dayjs

下载看这

全局属性

const dayjs = require('dayjs')
// 时间的过滤
const filetrTimes = (val,format = 'YYYY-MM-DD') => {
    if(!isNull(val)){
        val = parseInt(val) * 1000
        return dayjs(val).format(format)
    }else{
        return '--'
    }
}
// 是否为空
export const isNull = (date) =>{
    if(!date) return true
    if(JSON.stringify(date) === '{}') return true
    if(JSON.stringify(date) === '[]') return true
}

// 进行全局定义
export default app => {
    app.config.globalProperties.$filters = {
        filetrTimes
    }
}
  • 导入在main.js中
import filters from './utils/filters'
filters(app)
3. 分页器
      <el-pagination
        v-model:currentPage="queryForm.pagenum"
        v-model:page-size="queryForm.pagesize"
        :page-sizes="[2, 5, 10, 15]"
        :small="small"
        :disabled="disabled"
        :background="background"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
const queryForm = ref({
  query: "",
  pagenum: 1,
  pagesize: 2,
});
const total = ref(0);
const handleSizeChange = (pageSize) => {
  queryForm.value.pagenum = 1;
  queryForm.value.pagesize = pageSize;
  initGetUsersList();
};

const handleCurrentChange = (pageNum) => {
  queryForm.value.pagenum = pageNum;
  initGetUsersList();
};
4. 修改用户状态/添加按钮/编辑按钮
  • 修改用户状态
export const changeUserState = (uid,type) => {
    return request({
      url: `/users/${uid}/state/${type}`,
      method:'put'
    })
  }
import { changeUserState } from "@/api/users"; //导入api
import { useI18n } from "vue-i18n"; // 使用内部的i18n
const i18n = useI18n();

简化操作 用async await

const changeState = async (info) => {
  await changeUserState(info.id, info.mg_state);
  // 利用i18n
  ElMessage({
    message: i18n.t("message.updeteSuccess"),
    type: "success",
  });
};

之前使用的是内部i18n 这里使用自己的i18n
src\i18n\index.js

import i18n from '@/i18n'
const t = i18n.global.t
doneBtnText: t('driver.doneBtnText'), // Text on the final button
  • 添加用户的对话框
    在这里插入图片描述
import Dialog from "./components/dialog.vue";
const dialogVisible = ref(false);
const dialogTitle = ref("");
const dialogTableValue = ref({});
    <Dialog
      v-model="dialogVisible"
      :dialogTitle="dialogTitle"
      v-if="dialogVisible"
      @initUserList="initGetUsersList()"
      :dialogTableValue="dialogTableValue"
    />
  • 当点击按钮事件
const handleDialogValue = (row) => {
  dialogVisible.value = true;
  if(isNull(row)){ // 判断是否为空
  dialogTitle.value = "添加用户";
     dialogTableValue.value = {}
  }else{
    dialogTitle.value = "编辑用户"
    dialogTableValue.value = JSON.parse(JSON.stringify(row))
  }
};
  • 使用model-value
    在这里插入图片描述
// 需要修改这里的值update
// modelValue
import { defineEmits } from "vue";
const emits = defineEmits(["update:modelValue", "initUserList"]);

const handleClose = () => {
  emits("update:modelValue", false);
};

在这里插入图片描述

  • 确认按钮
const handleConfirm = () => {
  formRef.value.validate(async (valid) => {
    if (valid) {
      props.dialogTitle === "添加用户"
        ? await addUser(form.value)
        : await editUser(form.value);

      ElMessage({
        message: i18n.t("message.updeteSuccess"),
        type: "success",
      });
      emits("initUserList");
      handleClose();
    } else {
      console.log("error submit!!");
      return false;
    }
  });
};

从父组件传入到子组件

import { defineProps } from "vue";
const props = defineProps({
  dialogTitle: {
    type: String,
    default: "",
    required: true,
  },
  dialogTableValue: {
    type: Object,
    default: () => {},
  },
});

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

里面 再加一个
dialogVisible: { type: String, default: “”, required: false }
这样控制台就没有报错了

  • 整体校验
  formRef.value.validate(async (valid) => {
    if (valid) {
    handleClose()
    } else {
    return false;
    }
  });

在这里插入图片描述

  • 判断row是否为空栏区分增加和修改

  • 监听值的改变

watch(
  () => props.dialogTableValue,
  () => {
    console.log(props.dialogTableValue);
    // 写的位置要注意 这里已经用到了form
    form.value = props.dialogTableValue;
  },
  { deep: true, immediate: true }
);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值