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

本文目录:

  • 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显示到页面中


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标签包裹的那个元素上,否则动画效果会不生效。正确写法:


  

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


  

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标签


点击后就可以自动触发选择文件的弹窗,然后通过监听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标签中加入样式,这样,只有路由还处于当前路由,其子路由的切换并不会导致自身高亮的丢失。

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门户项目 | 开发过程记录)