vue2后台管理项目
技术栈:
vue2+elementui+路由+axios+less+Echarts+阿里字体图标
搭建
通过脚手架创建项目,安装路由,elementui,axios,初始化git远程仓库,将本地项目托管到gitee(咋做的?)
后台用的别人的,本地安装环境(phpstudy),使用postman测试接口是否正常
axios初始化
安装axios,直接配置baseUrl,在请求拦截器里,请求头添加token(登录凭证),然后直接挂载到vue原型对象上
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
// 在挂载到原型之前先预处理一下,请求拦截器添加token,保证后续访问数据的权限,请求头携带一个token
axios.interceptors.request.use(config => {
config.headers.Authorization = window.sessionStorage.getItem('token')
return config
})
// axios挂载到vue原型对象上作为一个属性,这样每个vue的组件都能使用axios vue2中this指向?
// $http 是自定义的
Vue.prototype.$http = axios
登录功能
整体逻辑,输入用户名密码,将数据保存到data中(双向数据绑定),进行表单校验,判断输入是否合法。然后点击登录后,还要进行表单的预校验(使用elmentui表单组件的api),不通过,直接返回,通过,发起登录请求,根据返回状态码判断是否登录成功,失败,抛出错误,成功,显示登录成功,获取到返回的token保存到sessionStorage中。然后通过编程式导航跳转到后台主页。
this.$refs.formLoginRef.validate(async vali => {
if (!vali) {
return
} else {
const { data: res } = await this.$http.post('login', this.Login_form)
if (res.meta.status != 200) {
return this.$message.error('登录失败!')
} else {
this.$message.success('登录成功!')
// 登录成功后的操作行为
// 1,将登录成功之后的token保存到sessionStorage中
window.sessionStorage.setItem('token', res.data.token)
// 2:localStorage会保存到本地,sessionStorage只在打开会话的时候会保存
// 3:通过编程式导航跳转到后台主页
this.$router.push('/home')
路由导航守卫
用户访问登录页直接放行,非登录页判断是否有token(是否登录),无token强制跳转到登录页。
// 设置导航守卫控制访问权限
router.beforeEach((to,from,next)=>{
// 访问的是登录页直接放行
if(to.path === '/Login'){
return next()
}else{
const tokenStr = window.sessionStorage.getItem('token')
if(!tokenStr){
// 没有token强制跳转到登录页
return next('/Login')
}else{
next()
}
}
})
退出登录
清空token,跳转到登录页面。
整体布局
使用的是elementui的布局容器,头部,侧边栏,页面主区域。
左侧边栏的菜单数据是通过接口获取的,有一级,二级菜单,通过el-menu,使用v-for动态创建出来的。
通过el-menu的router属性,实现点击不同菜单跳转到不同页面(主体页面区域放路由占位符)。
用户列表
面包屑
table表格区域:功能有展示用户列表,搜索,增加,删除,修改用户信息,分配角色,状态是否启用。
整体用的elementui的栅格系统。
自定义表格里面的内容用的是 table里的作用域插槽。
修改,删除用户,状态的修改都是拿到对应的用户id,发起相应的请求。分配角色则是先获取所有的角色列表,渲染出来供用户选择,选择好后调用修改角色的api(也是根据用户的的id)
添加用户
点击添加用户弹出dilog对话框,将输入的用户信息保存,发起添加用户的请求
权限管理
角色列表
权限展示,是通过elmentui的展开列制作的,结合栅格系统,遍历出获取的权限数据,动态渲染
分配权限的树形结构,通过el-tree制作的
treetable第三方工具
echarts
vue3后台管理项目
主要是封装了一些可以复用的组件
技术栈:vue3+elementplus+路由4+vuex4+axios+css预编译less
配置elementplus: 导入elementplus,按需注册为vue的全局组件,导出一个函数(函数里用for of循环注册),传入vue的实例app就可以注册啦。
对axios进行的封装:
导出一个类,通过实例化不同的对象,传入不同的配置,满足不同的baseUrl情况或者拦截器
可以实现在不同环境下配置不同的baseurl
// 在不同环境(开发,生产,测试)下设置不同的baseurl等配置
let BASE_URL
if(process.env.NODE_ENV === 'development'){
BASE_URL = '/api'
}else if(process.env.NODE_ENV === 'production'){
BASE_URL = 'http://152.136.185.210:5000'
}
export {BASE_URL}
具体做法
import axios from "axios"
class Zlrequest {
// 要做到在创建实例的时候能传入config配置,请求拦截器配置,响应拦截器配置
instance
constructor(config,requestInterceptor,responceInterceptor){
// 根据传入config配置,创建axios实例
this.instance = axios.create(config)
// 如果有传拦截器,加上
if(requestInterceptor){
this.instance.interceptors.request.use(requestInterceptor)
}
if(responceInterceptor){
this.instance.interceptors.response.use(responceInterceptor)
}
// 发起请求
request(config2){
return new Promise((resolve,reject)=>{
// axios请求返回的是promise对象
this.instance.request(config2).then((res)=>{
resolve(res)
}).catch((err)=>{
reject(err)
})
})
}
}
export default Zlrequest
import ZLrequest from "./request"
import { BASE_URL } from "./request/config"
import localCatch from '@/utils/catch.js'
const zlrequest = new ZLrequest({
baseURL:BASE_URL
},(config)=>{
const token = localCatch.getCatch('token')
if(token){
// config.headers.Authorization = `Bearer${token}`
config.headers.Authorization = token
}
return config
})
网络请求的处理大多是放在vuex的action中的,在页面中就直接dispatch
登录后有把用户信息保存到本地(localStorage)
持久化存储(初始化store),在action里定义相应的方法,拿出存储在storage里的数据,赋值到state里的数据。最后在main.js中调用
为了解决本地跨域问题,使用devserver里的prox代理
配置高阶组件
高级检索,也就是一个表单,通过传入不同的配置可以生成不同的表单项,如输入用户id,密码,创建时间等。。
譬如,对一些elementui表单进行二次封装,只需要在使用的时候传入配置文件就能生成不同表单组件
{
filed: 'name',
type: 'input',
label: '用户名',
placeholder: '请输入用户名',
},
那么在外部如何拿到数据呢?
在封装的组件上绑定v-modle,父组件写好相应的数据格式接受数据,然后在子组件里写相应数据格式并且双向绑定到各个表单组件上,使用侦听器侦听输入的数据变化,发出事件到父组件并且把最新的值发送出去,这样不违反单向数据流
请求列表数据,数据可以放在vuex里,也可以放在当前模块里,放在vuex里做好在action中写请求数据的逻辑调用service中写好的真正请求数据的逻辑
仿网易云音乐
技术栈:vue3+elementplus+路由4+vuex4+axios+css预编译less
难点:
给请求的url加上时间戳,不然请求会被缓存,注意是url上
url:`/login/cellphone?${Date.now()}`,
替换从后台请求的数据里面的字段,先json.stringfy转化为字符串,再replace(””,””)替换,然后在反序列化。
搜索框哪里,关于点击事件和失去焦点事件的顺序
为* click是由按下和抬起两个动作合成的,当按下的时候就执行**blur事件了,在抬起的时候才执行**click*事件
解决办法是 1-1 blur延迟
1-2 采用mousedown代替click,mousedown 优先与blur执行
\2. mousedown mouseup click
执行顺序为mousedown>mouseup>click
要一开始就操作dom元素,需要在onmunted()里,组件挂载到dom上才能获取到dom元素
做项目的时候,界面显示正常,但是浏览器仍然报出“TypeError: Cannot read property 'RcA1' of undefined”错误,数据未定义.
在vue渲染机制中:异步数据先显示初始数据,再显示带数据的数据,所以上来加载Result的时候还是一个空数组。因为我们再data里面初始化赋值的是时候是写Result:[]。渲染完成后才加载异步的数据。 所以在渲染时,出现的三层表达式在Result中取Result[0]数组中的RcA1的对象还不存在,再在这个对象中取其他值自然会报错,但是渲染完成后,Result中的值加载好了,自然可以取到,这也就解释了为什么界面正常显示,但浏览器会报错的原因。
最后我上面一个div中添加v-if判断条件,如果Result为空,则不加载该div即可解决。(提醒一下,不能用v-show,v-show的机制是加载后,根据条件判断是否显示)
重新定义一个新数据也能解决 比如title.value = data.res[0].xxx 然后用title渲染
或者给他一个初始值
侦听器:如果侦听对象,那么侦听器的回调函数的两个参数是一样的结果,表示最新的对象数据
如果侦听对象中当个属性,需要使用函数方式,侦听更少的数据有利于提高性能。
watch(() => obj.age, (v1, v2) => {
console.log(v1, v2)
})
如果要侦听的是对象中的对象,要进行深度监听(也是函数格式,添加配置项**deep:true, immediate: true, )
异步组件
video标签里的source里面的src不生效,url是动态获取的。
原因:source标签渲染了一次后,就不会重新进行渲染了,你要在添加src标签时,动态添加source标签,如何动态创建一个元素插入?
解决办法
const getMvUrl = async (mvid) => {
const { data } = await nowGetMvPlay(mvid)
mvUrl.value = data.data.url
// 动态创建video里的source标签
videoRef.value.innerHTML = '<source class="source" src="' + mvUrl.value + '" type="video/mp4">'
videoRef.value.play()
}
由此也可以知道,setup中异步操作是在onMounted之后因为异步里可以获取到dom元素
codHurb项目
怎么做的,项目结构,iapp文件夹下导入koa,创建实例对象,导出,在main中listen中监听服务。在router文件夹下创建相应的router文件,导入koa-router,创建router实例对象,导出,在index下导入,直接app.use使用路由。在router中,有一些中间件,比如进行权限的校验,全部放在middware文件夹下。而在中间件中的函数又提取到controller文件夹下,最后,有关数据库的操作提取到service里面。在app文件夹下的database中,导入mysql2创建一个连接池,测试是否链接成功,导出一个connection.promise(),注意,数据库的操作应该是异步的。然后在对应service中导入connection,创建相应的方法,在方法里执行sql语句,返回响应结果。
注册,创建注册的路由,先写进行校验的中间件(如如是否合法,是否有重名,拿到用户名在数据库做一次查询)next,对密码进行加密的中间件,使用node自带的库crypto进行md5加密next,通过两次校验在调用创建爱用户的中间件,拿到用户名密码,在数据库插入。
登录,与注册差不多的逻辑,封装进行校验的中间件,输入是否合法,用户是否存在(数据库里查询),最后是用户名和密码是否匹配。通过校验,next,进入登录的中间件,使用jwt颁发验证token(http是无状态的,需要登录凭证)(为什么不用coookie和session,因为coolie会附着在每个请求上,无形中增加了流量,cookie是明文传输的,存在安全性问题,cookie限制4k)。使用的是非对称加密,私钥颁发令牌,公钥验证令牌。(可以设置过期时间)
验证是否携带token的中间件:token在请求头的authorization中,如果用户根本没有传入token,就根本没有authorization这个字段,注意,要去除掉Bearer,jwt.verify进行校验,通过将用户信息保存到 ctx.user await next()
发表动态,首先验证用户是否登录(有无token),调用校验token的中间件。通过 拿到用户id和content // 将他们插入数据库
发表评论, 分为几种情况,一是动态下的评论,二是对于评论的评论,当动态删除,评论也应该删除,评论删除,评论的评论也应该删除
表设计
//分为几种情况,一是动态下的评论,二是对于评论的评论,当动态删除,评论也应该删除,评论删除,评论的评论也应该删除
CREATE TABLE IF NOT EXISTS `comment`(
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(1000) NOT NULL,
moment_id INT NOT NULL,
user_id INT NOT NULL,
comment_id INT DEFAULT NULL,//评论的评论相关信息,有,评论id,无,null
FOREIGN KEY(moment_id) REFERENCES `moment`(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(user_id) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(comment_id) REFERENCES `comment`(id) ON DELETE CASCADE ON UPDATE CASCADE
);
也是要校验是否登录,通过,在中间件中 拿到评论人id,内容,评论的动态id,插入到数据库,
回复评论,
controller
// 回复评论
async replay(ctx,next){
const { momentId,content} = ctx.request.body
const { id } = ctx.user
const { commentId } = ctx.params
const res = await replay(id,content,momentId,commentId)
ctx.body = res
}
service
// 评论回复
async replay(id,content,momentId,commentId){
const statement = `INSERT INTO comment (content,user_id,moment_id,comment_id) VALUES (?,?,?,?);`
const [result] = await connections.execute(statement,[content,id,momentId,commentId])
return result
}
}
修改动态的话要经过两个中间件验证。1是验证用户是否登录,2是验证是否有修改当前动态的权限,一般来说用户只有修改自己动态的权限。拿到用户id,要修改的动态的id在数据库查询,查询到,有权限修改
文件相关接口
头像相关接口
逻辑分析:1,要有头像上传的接口,服务端保存。2,提供一个接口,让用户获取图片。3,将URL存储到用户信息中。4,获取信息时获取用户头像。
头像上传,保存
//使用koa-multer库 它上传文件保存的数一个Buffer
//提取到middlwar里,注意single('avater'),必须与前端字段保持一致avater
const multer = require('koa-multer')
const avaterMulter = multer({
dest:'./uploads/avater'
})
const avaterHandler = avaterMulter.single('avater')
module.exports = {
avaterHandler
}
fileRouter.post('/avater',verifyAuth,avaterHandler)
保存图像信息到数据库
avater表
CREATE TABLE IF NOT EXISTS avater(
id INT PRIMARY KEY AUTO_INCREMENT,
//文件名,文件类型,大小
filename VARCHAR(255) NOT NULL UNIQUE,
mimetype VARCHAR(30),
size INT,
user_id INT,
createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES USER(id) ON DELETE CASCADE ON UPDATE CASCADE
);
controller 将图像url保存到用户表
class FileController {
// 保存图像信息到数据库
async creatAvater(ctx,next){
// 获取图像信息
const { mimetype,filename,size } = ctx.req.file
const { id } = ctx.user
const res = await create(mimetype,filename,size,id)
// 保存图像url到用户表
const avaterUrl = `http://localhost:8000/users/${id}/avater`
await saveAvaterUrlByuid(avaterUrl,id)
ctx.body = '上传头像成功~'
}
// 保存动态配图到数据库
async createPicture(ctx,next){
// 上传的是很多张图片组成的数组
const filesmessage = ctx.req.files
const { id } = ctx.user
const { momentid } = ctx.query
for(let item of filesmessage){
const { mimetype,filename,size } = item
await savePicture(filename,mimetype,size,id,momentid)
}
ctx.body = '动态配图上传完成~'
}
}
service
class FileService{
// 保存图像信息
async create( mimetype,filename,size,userid){
const statement = `INSERT INTO avater (filename,mimetype,size,user_id) VALUES (?,?,?,?);`
const [result] = await connections.execute(statement,[filename,mimetype,size,userid])
return result
}
}
用户获取文件(图片)
用户相关,因此放在了userRouter里
// 根据userid获取用户头像
useRouter.get('/:userid/avater',avaterInfo)
usercontroller
// 用户获取头像
async avaterInfo(ctx,next){
// 根据用户id获取头像信息
const { userid } = ctx.params
const avaterMessage = await FileService.getAvateByUserid(userid)
// 设置响应的文件类型,浏览器可以直接解析,否则浏览器会直接下载
ctx.response.set('content-type',avaterMessage.mimetype)
ctx.body = fs.createReadStream(`./uploads/avater/${avaterMessage.filename}`)
}
service
// 根据userid查询图像信息
async getAvateByUserid(userid){
const statement = `SELECT * FROM avater WHERE user_id = ?;`
const [result] = await connections.execute(statement,[userid])
return result[0]
}
}
nodejs相关
require查找规则
模块加载过程
内置模块path
内置模块fs(文件系统)
包管理
创建 npm init -y
全局安装与局部安装
项目的安装
package-lock.json是package.json的缓存
通过流的方式读取,写入,使得过程更加可控(开始结束位置,监听读取写入的结束)
创建服务器
http模块
http.createServe((request,responce)=>{
responce.end('hdsfhs')
})
server.listen(8000,主机地址,()=>{
cosnoole.log('服务器启动成功')
})
数据库
create database if not exsits;
create table if ......
主键 唯一 永不为空
唯一键
not null
defalt
auto_increment
为表添加新列
alert table xxx ADD xxx 数据类型
创建时间,更新时间
删除表中所有数据 delete from xxx
满足条件的 delete from xx where fsjdf=dhds
修改表数据 update table xxx set xxx = xxx
查询 select xx from xxx
常用git命令
1. HTML5 新特性
- 拖拽(Drap and drop) :API ondrop
- 拖放是一种常见的特性,即抓取对象以后拖到另一个位置。
- 在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放。
- 自定义属性:data-id
- 语义化更好的内容标签:header,nav,footer ,aside, article, section
- 音频 ,视频:audio, video
-
如果浏览器不支持自动播放怎么办?
- 在属性中添加autoplay
- 画布:Canvas
- getContext() 方法返回一个用于在画布上绘图的环境。Canvas.getContext(contextID) 参数 contextID 指定了您想要在画布上绘制的类型。当前唯一的合法值是 “2d”,它指定了二维绘图,并且导致这个方法返回一个环境对象,该对象导出一个二维绘图 API。
- cxt.stroke() 如果没有这一步 线条是不会显示在画布上的
- canvas和image在处理图片的时候有什么区别?
- image是通过对象的形式描述图片的,canvas通过专门的API将图片绘制在画布上.
- 地理(Geolocation)API
- 本地存储:localStorage 长期存储数据 浏览器关闭后数据不丢失
- 会话存储:sessionStorage 的数据在浏览器关闭后自动删除
- 表单控件:calendar , date , time , email , url , search , tel , file , number
- 新的技术:webworker, websocket , Geolocation
2、H5移除元素
(1) 纯表现的元素
<basefont> 默认字体,不设置字体,以此渲染
<font> 字体标签
<center> 水平居中
<u> 下划线
<big> 大字体
<strike> 中横线
<tt> 文本等宽
复制代码
(2) 框架集
<frameset>
<noframes>
<frame>
复制代码
3. CSS3 新特性
- 颜色:新增RGBA , HSLA模式
- 文字阴影:text-shadow
- 边框:圆角(border-radius) 边框阴影 : box-shadow
- 盒子模型: box-sizing
- 背景:background-size background-origin background-clip
- 渐变:linear-gradient , radial-gradient
- 过渡:transition 可实现动画
- 自定义动画:animate @keyfrom
- 媒体查询 多栏布局: @media screen and (width:800px) {…}
- border-image
- 2D转换:transform: translate(x,y) rotate(x,y) skew(x,y) scale(x,y)
- 3D转换:transform: translate3d(x,y) rotate3d(x,y) scale3d(x,y)
- 字体图标:font-face
- 弹性布局:flex
项目优化
1、v-for 正确设置key值
2、封装复用的模块(http请求)、组件(ui库)
3、路由懒加载:component:() => import('./xxx.vue')
4、productionSourceMap: false
5、启用gzip压缩,打包体积更小
6、keep-alive 缓存不活跃组件
7、插件CDN方式引入,减小项目体积
8、图片使用CDN地址,图片懒加载