Vue2门户项目 | 开发过程记录

作者: 前端辉羽 | 来源:发表于2021-01-28 16:37 被阅读0次

    本文目录:

    • 1.项目初始化
    • 2.封装axios
    • 3.验证码功能实现
    • 4.登录接口
    • 5.设置全局路由守卫拦截非法请求
    • 6.body绑定事件及时销毁
    • 7.transition动画及注意点
    • 8.Mixins快速扩展组件
    • 9.退出登录跳转首页报错处理
    • 10.使用jsconfig.json关联项目文件
    • 11.图片上传功能
    • 12.iconfont图标的使用和拓展
    • 13.路由高亮丢失问题
    • 14.自定义指令$pop
    • 15.自定义编辑器

    1.项目初始化

    当前计划开发的门户网站一共有四个项目,其中三个基于Javascript开发的前端项目,一个基于Node开发的后端项目,前端项目依赖环境:
    node 12.14.0
    npm版本 6.13.4
    ts版本 4.2.2
    vue/cli版本 4.5.1

    npx create siyezhou.... 直接通过脚手架工具去生成项目,这样可以更快速的投入到开发之中。

    生成项目的时候,会有一些选择项:

    • 1.Please pick a preset
      选择手动配置 Manually select features,如果有之前自己存的合适的生成模板,则可以选择相应的模板

    • Check the features needed for your project
      根据需要选上生成模板想要带上的 内容,像Vuex,Router,CSS预处理 这些都勾选上,TypeScript项目自然也要勾选上TypeScript

    • Choose a version of Vue.js that you want to start the project with
      根据需要选择2.x或者3.x (Preview)

    下面的一些选项会根据上面的选择情况出现:

    • Use class-style component syntax?
      这里询问的是是否使用class风格的组件语法,如果在项目中想要保持使用TypeScript的class风格的话,这里我选择的是n

    • Use Babel alongside TypeScript
      使用Babel与TypeScript一起用于自动检测的填充?这里我选择的n,建议可以选择y

    • Pick a linter / formatter config
      ESlint with error prevention only // 只进行报错提醒
      ESlint + Airbnb config // 不严谨模式
      ESlint + Standard config // 正常模式
      ESlint + Prettier // 严格模式
      TSLint (deprecated) // TypeScript格式验证工具
      这里选择了ESlint with error prevention only

    • Pick additional lint features
      Lint on save // 保存时检测
      Lint and fix on commit // 修复和提交时检测

    • Where do you prefer placing config for Babel, ESLint, etc.?
      In dedicated config files // 存放在专用配置文件中
      In package.json // 存放在package.json中

    • Save this as a preset for future projects? 是否将现在这个配置保存为模板?

    • Use history mode for router? ( Requires proper server setup for index fallback in production ) 使用hash模式还是history模式?

    生成完了项目之后,先把项目自带的一些多余的代码删除掉,这一步很简单,不再赘述

    项目选择element,以便快速生成网站样式
    npm i element-ui -S
    npm install babel-plugin-component -D
    按需引入,修改 .babelrc文件
    项目里自带了一个babel.config.js
    babel.config.js 和 .babelrc 对比
    Babel 有两种并行的配置文件格式,可以一起使用,也可以分开使用。

    自动生成的环境变量:
    使用vue-cli生成的项目,在package.json预先配置好的scripts中,运行serve和build指令会自动设置process.env.NODE_ENV

    "serve": "vue-cli-service serve", //本地开发运行,会把process.env.NODE_ENV设置为development
        "build": "vue-cli-service build", //默认打包模式,会把process.env.NODE_ENV设置为production
    

    这样的话在项目的其他地方,一些操作就可以通过process.env.NODE_ENV === 'development'来进行不同的处理了。

    路由跳转的时候,统一都使用name进行跳转导航,这样路径发生变化的时候,只要不修改name,都不需要再去页面逻辑处修改代码。

    2.封装axios

    作为一个完善的项目,和后台的数据交互是重要组成部分
    数据交互选择安装axios
    npm install axios --save-dev
    为了能方便的使用,我们先对axios进行一些封装处理:
    1.基于请求拦截器和响应拦截器做一些处理,包括添加token,统一错误处理等。
    2.创建实例,并封装常用的get,post方法

    因为在开发中会有多个环境,比如开发环境,生产环境,以及测试环境
    所以基础功能封装在axios.js中,在config文件夹下新建一个index.js文件

    export default {
      baseUrl: {
        dev: 'http://localhost:36742',
        pro: 'http://localhost:3000'
      },
      publicPath: [/^\/public/, /^\/login/]
    }
    

    axios.js导出的HttpRequest在request.js进行实例化,并传入config中配置的不同环境的baseUrl,把错误处理函数也单独在一个单独的文件中errorHandle.js

    3.验证码功能实现

    验证码数据后端使用Redis进行存储,key值需要有一个唯一值,这里使用uuid来生成唯一值作为验证码存储在Redis的key值
    将"uuid": "^3.4.0"添加到package.json的devDependencies属性中,然后npm i进行安装
    而这个uuid生成的值我们给存储到localstorage和vuex中,登录的时候需要将sid也传入给后端进行验证

    let sid = ''
    if (localStorage.getItem('sid')) {
      sid = localStorage.getItem('sid')
    } else {
      sid = uuid()
      localStorage.setItem('sid', sid)
    }
    this.$store.commit('setSid', sid)
    

    在api文件夹中的login.js文件封装一个getCode方法,用来请求后端获取到验证码

    const getCode = (sid) => {
      return axios.get('/public/getCaptcha', {
        params: {
          sid: sid
        }
      })
    }
    

    当用户点击验证码图片的时候,应该重新发起一个获取验证码的请求,获取一个新的验证码,并将获取到的data通过v-html显示到页面中

    <span class="svg" style="color: #c00;" @click="_getCode()" v-html="svg"></span>
    

    4.登录接口

    登录成功之后应该将用户信息,token,登录状态同步到vuex中

    this.$store.commit('setUserInfo', res.data)
    this.$store.commit('setToken', res.token)
    this.$store.commit('setIsLogin', true)
    

    vuex存储的内容是存储在内容中的,页面刷新之后也就没有了,所以登录信息这些东西应该要先保存在locationStorage
    而在vuex中,每一次修改自身的用户登录相关信息,都将其存储到localstorage中,防止用户刷新后丢失。

    5.设置全局路由守卫拦截非法请求

    配置路由:
    子路由的path路径开头不能加/,否则父路由设置的path公共路径就不起作用了。
    安装两个插件
    npm install dayjs jsonwebtoken --save-dev

    1.每次进入路由的时候,都先去判断一下有没有缓存的用户信息,如果有,就将用户的基本信息缓存到vuex在中,不论是在首页,还是在里面的设置页,只要用户没有去手动清除localstroge的缓存,都可以拿到缓存的用户信息。
    2.接下来需要判断路由上有没有requiresAuth状态量,没有这个状态量的代表不需要鉴权,直接next放行就可以了,如果requiresAuth为true,则根据isLogin判断其是否登录,登录的了就直接放行,没登录的就将其跳转到login页面。

    if (to.matched.some(record => record.meta.requiresAuth)) {
      const isLogin = store.state.isLogin
      if (isLogin) {
        // 已经登录的状态
        // 权限的判断,meta元数据
        next()
      } else {
        next('/login')
      }
    } else {
      // 放行不需要鉴权的路由
      next()
    }
    

    全局路由守卫中需要借助jsonwebtoken插件来判断token有没有过期

    const token = localStorage.getItem('token')
    const payload = jwt.decode(token)
    

    payload就是利用jwt解析出来的token数据,payload.exp * 1000 代表当前的毫秒时间。

    我们在前端请求中配置了统一处理错误的errorHandle方法,在api请求的时候可以设置catch来捕获当此请求的错误,进行个性化处理。
    用户的个人中心的相关页面,我们都是写在/personal的子路由之下,这样做的一大好处就是可以给/personal增加路由守卫,拦截掉没有登录状态的路由

    6.body绑定事件及时销毁

    页面中,尤其是body绑定事件是非常危险的,可以在组件销毁的时候解除相关事件的绑定

    mounted() {
      this.$nextTick(() => {
        document
          .querySelector('body')
          .addEventListener('click', this.handleBodyClick)
      })
    },
    beforeDestroy() {
      document
        .querySelector('body')
        .removeEventListener('click', this.handleBodyClick)
    }
    

    7.transition动画及注意点

    在样式中定义动画,如:
    @keyframes bounceIn和@keyframes bounceOut
    然后添加transition的自定义名字的动画:

    .fade-leave-active {
      animation: bounceOut 0.3s;
    }
    .fade-enter-active,
    .fade-enter-to {
      animation: bounceIn 0.3s;
    }
    

    给相应的元素设置上对应的自定义名字

    transition name="fade"
    

    注意点:
    如果transition中使用了v-show,则v-show应该写在transition标签包裹的那个元素上,否则动画效果会不生效。正确写法:

    <transition name="fade">
      <div  v-show="isShow">
        <div>
    

    而下面这样就展现不出来动画效果

    <transition name="fade">
      <div>
        <div v-show="isShow">
    

    8.Mixins快速扩展组件

    多个组件可以共享数据和方法,在使用mixin的组件中引入后,mixins中的方法和属性也就并入到该组件中,可以直接使用。
    钩子函数会两个都被调用,mixins中的钩子首先执行
    Mixins里的属性值和组件的属性值发生冲突的时候,以组件的优先
    Mixins的代码写在js文件中
    可以在文件import相应代码,并导出export default{}
    然后在相应的组件进行引入,如

    import CodeMix from "@/mixin/login";
    //挂载
    mixins: [CodeMix]
    

    9.退出登录跳转首页报错处理

    有一个逻辑是点击退出登录,页面会跳转到首页,但是如果在首页退出的话,控制台会有报错,意思不能导航到重复的路由,做法之一是设置一个空的回调函数,忽略掉这个错误

    this.$router.push({name:'index'},()=>{})
    

    10.使用jsconfig.json关联项目文件

    jsconfig.json文件:指定根目录和JavaScript服务提供的功能选项。
    在项目的根目录下新建一个文件jsconfig.json,加入以下代码,可以优化我们的开发体验。

    {
        "compilerOptions": {
            "target": "es6",
            "allowSyntheticDefaultImports": false,
            "baseUrl": "./",
            "paths": {
                "@/*": ["src/*"]
            }
        },
        "exclude": ["node_modules", "dist"]
    }
    

    接下来在项目ctrl+点击方法,可跳转到对应解释位置

    11.图片上传功能

    在上传组件内定义两个变量
    picLink:图片上传到服务器后进行拼接而成的路径
    另外一个是 picBinary,用来存储图片在本地上传图片后所转换成的二进制数据,并最终将这个二进制数据通过接口传递给后台
    首先在页面上放置一个原生的input标签

    <input
      type="file"
      name="file"
      id="uploadImg"
      class="real-upload-btn"
      @change="upload"
    />
    

    点击后就可以自动触发选择文件的弹窗,然后通过监听change事件,触发我们自定义的upload事件,upload事件可以拿到在本地上传图片后的数据e,通过实例化FormData拿到我们想要的blob二进制数据,然后触发接口

    upload(e) {
          let file = e.target.files;
          let formData = new FormData();
          if (file.length > 0) {
            formData.append("file", file[0]);
            this.picBinary = formData;
          }
          // 上传图片之后=>uploadImg
          uploadImg(this.picBinary).then((res) => {
            if (res.code === 200) {
              const baseUrl =
                process.env.NODE_ENV === "production"
                  ? config.baseUrl.pro
                  : config.baseUrl.dev;
              this.picLink = baseUrl + res.data;
              // 清空input里value,这样下次上传才能监听到上传事件
              document.getElementById("uploadImg").value = "";
            }
          });
    },
    

    12.iconfont图标的使用和拓展

    原本项目已经使用iconfont了,我们想自己加一些iconfont图标改怎么弄?
    在iconfont的项目页面,点击“更多操作”=>“编辑项目”,然后填写上自己私有的font-family,比如“iconmyself”此时再下载项目,把文件解压后放入项目中,此时再引用图标的前缀就不再是iconfont,而是“iconmyself”了

    13.路由高亮丢失问题

    linkActiveClass的生效条件是什么
    在router-view中的路由配置中加入linkActiveClass可以让当前的路由高亮,但是其中再次嵌套router-view,让进行其子路由的切换时,会导致第一级router-view的高亮丢失,方法之一是在第一级router-view标签中加入样式<router-link :to="{name:item.link}" :active-class="item.activeClass">,这样,只有路由还处于当前路由,其子路由的切换并不会导致自身高亮的丢失。

    14.自定义指令$pop

    在components文件夹下新建一个directives文件夹用来存放自定义的全局指令。
    在directives中新建一个pop文件夹,然后新建一个Pop.vue文件和index.js文件
    props中接收到的数据是通过index.js在instance中接收到的
    index.js的代码如下

    import PopComponent from './Pop.vue'
    const Pop = {}
    Pop.install = (Vue) => {
      // 注册Pop组件
      const PopConstructor = Vue.extend(PopComponent)
      const instance = new PopConstructor()
      instance.$mount(document.createElement('div'))
      document.body.appendChild(instance.$el)
      // 4. 添加实例方法,以供全局调用
      Vue.prototype.$pop = (type, msg) => {
        // 接收参数
        instance.type = type
        instance.msg = msg
        instance.isShow = true
      }
    }
    export default Pop
    

    然后在main.js中全局注册指令

    import Pop from './components/modules/pop'
    Vue.use(Pop)
    

    使用指令

    this.$pop('shake','请上传图片或输入图片链接')
    

    15.自定义编辑器

    v-for不止可以遍历数组,还可以编辑对象
    v-for="(value, key) in lists"

    用户插入或者输入内容的时候,光标不可能每次都是在内容的最后,所以需要在每次插入的时候,先捕捉到光标的位置
    用户每次插入内容的时候,文本区域textarea都会自动触发失去焦点和获得焦点的事件,所以在这两个事件中绑定getPos函数,自动计算当前光标所在的位置,并赋值给pos

    getPos() {
      let cursorPos = 0;
      let elem = document.getElementById("editContent");
      if (document.selection) {
        //IE
        let selectRange = document.selection.createRange();
        selectRange.moveStart("character", -elem.value.length);
        cursorPos = selectRange.text.length;
      } else if (elem.selectionStart || elem.selectionStart === "0") {
        cursorPos = elem.selectionStart;
      }
      this.pos = cursorPos;
    },
    

    以添加表情为例,当用户点击相应的表情,将item通过$emit传递给发帖页面父组件这里,编辑器触发自己的addFace事件,接收到用户选择插入的内容,然后拼接成实际需要的格式,将其插入到文本区域,然后
    把光标的位置根据插入内容的长度后移。

    addFace(item) {
      console.log(item);
      const insertContent = ` face${item}`;
      this.insert(insertContent);
      this.pos += insertContent.length;
    },
    

    相关文章

      网友评论

        本文标题:Vue2门户项目 | 开发过程记录

        本文链接:https://www.haomeiwen.com/subject/kfrdnktx.html