本文目录:
- 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项目自然也要勾选上TypeScriptChoose 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风格的话,这里我选择的是nUse Babel alongside TypeScript
使用Babel与TypeScript一起用于自动检测的填充?这里我选择的n,建议可以选择yPick a linter / formatter config
ESlint with error prevention only // 只进行报错提醒
ESlint + Airbnb config // 不严谨模式
ESlint + Standard config // 正常模式
ESlint + Prettier // 严格模式
TSLint (deprecated) // TypeScript格式验证工具
这里选择了ESlint with error prevention onlyPick 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;
},