项目(面试自用)

225 阅读6分钟

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对话框,将输入的用户信息保存,发起添加用户的请求

权限管理

角色列表

image.png 权限展示,是通过elmentui的展开列制作的,结合栅格系统,遍历出获取的权限数据,动态渲染

分配权限的树形结构,通过el-tree制作的

image.png treetable第三方工具

image.png 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相关

image.png

image.png

image.png require查找规则

image.png

image.png 模块加载过程

image.png

image.png

image.png 内置模块path

image.png 内置模块fs(文件系统)

image.png

image.png

image.png 包管理

创建 npm init -y

image.png 全局安装与局部安装

image.png 项目的安装

image.png package-lock.json是package.json的缓存

image.png

image.png

image.png

image.png

image.png 通过流的方式读取,写入,使得过程更加可控(开始结束位置,监听读取写入的结束)

创建服务器

http模块      
http.createServe((request,responce)=>{
​
responce.end('hdsfhs')
​
})
​
server.listen(8000,主机地址,()=>{
​
cosnoole.log('服务器启动成功')
​
})

image.png

image.png

数据库

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命令

image.png

1. HTML5 新特性

  1. 拖拽(Drap and drop) :API ondrop
  • 拖放是一种常见的特性,即抓取对象以后拖到另一个位置。
  • 在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放。
  1. 自定义属性:data-id
  2. 语义化更好的内容标签:header,nav,footer ,aside, article, section
  3. 音频 ,视频:audio, video
  • 如果浏览器不支持自动播放怎么办?

    • 在属性中添加autoplay
  1. 画布:Canvas
  • getContext() 方法返回一个用于在画布上绘图的环境。Canvas.getContext(contextID) 参数 contextID 指定了您想要在画布上绘制的类型。当前唯一的合法值是 “2d”,它指定了二维绘图,并且导致这个方法返回一个环境对象,该对象导出一个二维绘图 API。
  • cxt.stroke() 如果没有这一步 线条是不会显示在画布上的
  • canvas和image在处理图片的时候有什么区别?
  • image是通过对象的形式描述图片的,canvas通过专门的API将图片绘制在画布上.
  1. 地理(Geolocation)API
  2. 本地存储:localStorage 长期存储数据 浏览器关闭后数据不丢失
  3. 会话存储:sessionStorage 的数据在浏览器关闭后自动删除
  4. 表单控件:calendar , date , time , email , url , search , tel , file , number
  5. 新的技术:webworker, websocket , Geolocation

2、H5移除元素

(1) 纯表现的元素

<basefont> 默认字体,不设置字体,以此渲染
<font> 字体标签
<center> 水平居中
<u> 下划线
<big> 大字体
<strike> 中横线
<tt> 文本等宽
复制代码

(2) 框架集

<frameset>
<noframes>
<frame>
复制代码

3. CSS3 新特性

  1. 颜色:新增RGBA , HSLA模式
  2. 文字阴影:text-shadow
  3. 边框:圆角(border-radius) 边框阴影 : box-shadow
  4. 盒子模型: box-sizing
  5. 背景:background-size background-origin background-clip
  6. 渐变:linear-gradient , radial-gradient
  7. 过渡:transition 可实现动画
  8. 自定义动画:animate @keyfrom
  9. 媒体查询 多栏布局: @media screen and (width:800px) {…}
  10. border-image
  11. 2D转换:transform: translate(x,y) rotate(x,y) skew(x,y) scale(x,y)
  12. 3D转换:transform: translate3d(x,y) rotate3d(x,y) scale3d(x,y)
  13. 字体图标:font-face
  14. 弹性布局: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地址,图片懒加载