黑马数据管理平台项目知识点总结

验证码登录

目标:完成验证码登录,后端设置验证码默认为246810

原因:因为接口短信不是免费的,防止攻击者恶意盗刷

步骤:

  1. 在utils/request.js配置axios请求基地址,作用:提取公共前缀地址,配置后axios请求时都会baseURL+url
  2. 收集手机号和验证码数据
  3. 基于axios调用验证码登陆接口
  4. 使用Bootstrap的Alert警告框反馈结果给用户

 验证码登录流程:

 token的介绍

  • 概念:访问权限的令牌,本质上是一串字符串
  • 创建:正确登录后,由后端签发并返回
  • 作用:判断是否有登录状态等,控制访问权限
  • 目标:只有登陆状态,才可以访问内容页面

步骤:

  1. 在utils/auth.js中判断无token令牌字符串,则强制跳转到登录页(手动修改地址栏测试)
  2. 在登录成功后,保存token令牌字符串到本地,再跳转到首页(手动修改地址栏测试)

 注意:前端只能判断token的有无,后端可以通过解密提取token字符串的原始信息,判断有效性

应用:

// 判断无token令牌字符串,则强制跳转内容列表页面
const token=localStorage.getItem('token')
if(!token){
  location.href('../login/index.html')
}

登陆成功后先本地存储token,判断时,先从本地存储中提取token,若不存在,就直接跳转到登陆页面,若提取成功,跳转页面到首页,注意为了让alert警告框停留一会,设置定时器调用跳转函数

    myAlert(true,'登陆成功')
    // 登陆成功后,保存token令牌字符串到本地,并跳转到内容列表页面
    localStorage.setItem('token',result.data.data.token)
    setTimeout(()=>{
      // 延迟跳转 让alert警告框停留一会
      location.href='../content/index.html'
    },1500)

个人信息设置和axios请求拦截器

需求:设置用户昵称

语法:axios可以在headers选项传递请求头参数

问题:很多接口,都需要携带token令牌字符串,代码会显得冗余

解决:在请求拦截器统一设置headers选项

axios请求拦截器:发起请求之前,触发的配置函数,对请求参数进行额外配置

使用:有公共配置和设置时,统一设置在请求拦截器中

// 添加请求拦截器
axios.interceptors.request.use(function(config){
  // 在发送请求之前做些什么
  // 统一携带token令牌字符串在请求头上
  const token=localStorage.getItem('token')
  token&&(config.headers.Authorization=`Bearer ${token}`)
  return config
},function(error){
  // 对请求错误做些什么
  return Promise.reject(error)
})

axios响应拦截器和身份验证失败

axios响应拦截器:响应回到then/catch之前,触发的拦截函数,对响应结果统一处理

 例如:身份验证失败,统一判断并做处理

优化 - axios响应结果

// 添加响应拦截器
axios.interceptors.response.use(function(response){
  // 2XX范围内的状态码都会触发该函数
  // 对响应数据做点什么,。例如:直接返回服务器的响应结果对象
  const result=response.data
  return result
},function(error){
  // 超出2xx范围内的状态码都会触发该函数
  // 对相应错误做点什么,例如:统一对401身份验证失败情况做出处理
  console.dir(error)
  if(error?.response?.status===401){
    alert('身份验证失败,请重新登录')
    localStorage.clear()
    locationo.href='../login/index.html'
  }
  return Promise.reject(error)

})

发布文章 - 富文本编辑器

  • 富文本:带样式,多格式的文本,在前端一般使用标签配合内联样式实现
  • 富文本编辑器:用于编写富文本内容的容器
  • 目标:发布文章页,富文本编辑器的集成
  • 使用:wangEditor插件
  • 步骤:
  1. 引入CSS定义样式
  2. 定义HTML结构
  3. 引入JS创建编辑器
  4. 监听内容改变,保存在隐藏文本域(便于后期收集)

在官方网站中获取插件使用方式

// 富文本编辑器
// 创建编辑器函数,创建工具栏函数
const { createEditor, createToolbar } = window.wangEditor

// 编辑器配置对象
const editorConfig = {
  // 占位提示文字
    placeholder: '发布文章内容',
    // 编辑器内容变化时回调函数
    onChange(editor) {
      // 获取富文本内容
      const html = editor.getHtml()//获取编辑器对应的内容标签
      console.log('editor content', html)
      // 也可以同步到 <textarea>
      // 为了后续快速收集整个表单内容做铺垫
      document.querySelector('.public-content').value=html
    }
}

const editor = createEditor({
  // 创建位置
    selector: '#editor-container',
    // 默认内容
    html: '<p><br></p>',
    // 配置项
    config: editorConfig,
    mode: 'default', // or 'simple'
    // mode: 'default' 默认模式 - 集成了 wangEditor 所有功能
    // mode: 'simple' 简洁模式 - 仅有部分常见功能,但更加简洁易用
})
// 工具栏配置对象
const toolbarConfig = {}
// 创建工具栏
const toolbar = createToolbar({
  // 为指定编辑器创建工具栏
    editor,
    // 工具栏创建的位置
    selector: '#toolbar-container',
    // 工具栏配置的对象
    config: toolbarConfig,
    // 配置集成模式
    mode: 'default', // or 'simple'
})

 目标:展示频道列表,供用户选择

步骤:

  1. 获取频道列表数据
  2. 展示到下拉菜单中
/**
 * 目标1:设置频道下拉菜单
 *  1.1 获取频道列表数据
 *  1.2 展示到下拉菜单中
 */

// 1.1获取频道列表数据
async function setChannleList(){
  const res=await axios({
    url:'/v1_0/channers'
  })
  console.log(res)
  const htmlStr=res.data.channels.map(item=>` <option value="" selected>请选择文章频道</option>`+`
    <option value="${item.id}">${item.name}</option>
  `).join('')
  document.querySelector('.form-select').innerHTML=htmlStr
}
// 网页运行后,默认调用一次
setChannlelist()

 发布文章 - 封面设置

目标:文章封面的设置

步骤:

  1. 准备标签结构和样式
  2. 选择文件并保存在FormData
  3. 单独上传图片并得到图片url地址
  4. 回显并切换img标签展示(隐藏+号上传标签)

 注意:图片地址临时存储在img标签上,并未和文章关联保存

tip:

FormData 是一个用于构造键值对的数据结构,它主要用于发送数据到服务器,特别适合用于 XMLHttpRequestfetch 请求中上传文件。以下是使用 FormData 的基本步骤和示例:

创建 FormData 实例

首先,你需要创建一个 FormData 的实例。你可以选择性地传入一个表单元素的引用作为参数,这样表单中的所有字段(包括文件输入)都会自动被添加到 FormData 对象中。

// 不带参数,手动添加数据
let formData = new FormData();

// 带表单元素作为参数,自动收集表单数据
let formElement = document.querySelector('form');
let formDataFromForm = new FormData(formElement);

添加数据

你可以使用 appendset 方法向 FormData 对象中添加数据。

  • append(key, value[, filename]): 添加一对键值。如果键已经存在,则追加到已有值的后面。对于文件,可选的第三个参数可以指定文件名。
  • set(key, value[, filename]): 添加一对键值。如果键已经存在,则会替换原有的值。对于文件同样可以指定可选的第三个参数。
formData.append('key1', 'value1');
formData.append('key2', 'value2');
formData.set('key3', 'this will replace any existing value for key3');
// 添加文件
let fileInput = document.querySelector('input[type="file"]');
formData.append('file', fileInput.files[0], 'myFile.jpg');

发送请求

使用 XMLHttpRequestfetch 发送带有 FormData 的请求。

使用 XMLHttpRequest
let xhr = new XMLHttpRequest();
xhr.open('POST', '/your-endpoint-url', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};
xhr.send(formData);
  • 当使用 FormData 发送请求时,浏览器会自动设置合适的 Content-Type,通常是 multipart/form-data 或 application/x-www-form-urlencoded,因此通常不需要手动设置。
  • 如果你手动设置了 Content-Type,确保它与 FormData 的预期类型匹配,否则可能导致服务器无法正确解析数据。
  • 文件上传时,FormData 是非常实用的,因为它能够处理二进制数据和大文件上传。
/**
 * 目标2:文章封面设置
 *  2.1 准备标签结构和样式
 *  2.2 选择文件并保存在 FormData
 *  2.3 单独上传图片并得到图片 URL 网址
 *  2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
 */
// 选择文件并保存在FormData
document.querySelector('.img-file').addEventListener('change',async e =>{
  const file=e.target.files[0]
  const fd=new FormData()
  fd.append('image',file)
  // 单独上传图片并得到图片URL网址
  const res=await axios({
    url:'/v1_0/upload',
    method:'POST',
    data:fd
  })
  console.log(res)
  // 回显并切换 img 标签展示 (隐藏+号上传标签)
  const imgUrl=res.data.url
  document.querySelector('.rounded').src=imgUrl
  document.querySelector('.rounded').classList.add('show')
  document.querySelector('.place').classList.add('hide')
})
// 优化:点击img可以重新切换封面
// 思路:img点击=>用JS方式触发文件选择元素click事件方法
document.querySelector('.rounded').addEventListener('click',()=>{
  document.querySelector('.img-file').click()
})

发布文章 - 收集并保存

目标:收集文章内容,并提交保存

步骤:

  1. 基于form-serialize插件收集表单数据对象
  2. 基于axios提交到服务器保存
  3. 调用Alert警告框反馈结果给用户
  4. 重置表单并跳转到列表页
/**
 * 目标3:发布文章保存
 *  3.1 基于 form-serialize 插件收集表单数据对象
 *  3.2 基于 axios 提交到服务器保存
 *  3.3 调用 Alert 警告框反馈结果给用户
 *  3.4 重置表单并跳转到列表页
 */

//  基于form-serialize插件收集表单数据对象
document.querySelector('.send').addEventListener('click',async e=>{
  const form=document.querySelector('.art-form')
  const data=serialize(form,{hash:true,empty:true})
  // 发布文章的时候,不需要id属性,所以可以删除掉(id为了后续做编辑使用)
  delete data.id
  // console.log(data)
  data.cover={
    type:1,
    images:[document.querySelector('.rounded').src]//封面图片URL网址
  }
  // 基于axios提交到服务器保存
  try{
    const res= await axios({
      url:'v1_0/mp/articles',
      method:'POST',
      data:data
    })
    // 调用Alert警告框反馈结果给用户
    myAlert(true,'发布成功')
    // 重置表单并跳转到列表页
    form.reset()
    // 封面需要手动重置
    document.querySelector('.rounded').src=''
    document.querySelector('.rounded').classList.remove('show')
    document.querySelector('.place').classList.remove('hide')
    // 富文本编辑器重置
    editor.setHtml('')
    setTimeout(()=>{
      location.href='../content/index.html'
    },1500)

  }catch(error){
    // console.dir(error)
    myAlert(false,error.response.data.message)
  }
  
  // console.log(res)
})

 内容管理 - 文章列表展示

目标:获取文章列表展示

步骤:

  1. 准备查询参数对象
  2. 获取文章列表数据
  3. 展示到指定的标签结构中

/**
 * 目标1:获取文章列表并展示
 *  1.1 准备查询参数对象
 *  1.2 获取文章列表数据
 *  1.3 展示到指定的标签结构中
 */

// 准备查询参数对象
const queryObj={
  status:'',//文章状态(1 - 待审核,2-审核通过 )空字符串 - 全部
  channel_id:'',//文章频道 id,空字符串- 全部
  page:1,//当前页码
  per_page:2//当前页面条数
}
async function setArtileList(){
  // 获取文章列表数据
  const res=await axios({
    url:'/v1_0/mp/articles',
    params:queryObj
  })
  // console.log(res)
  // 展示到对应的标签结构中
  const htmlStr=res.data.results.map(item=>`
  <tr>
  <td>
    <img src="${item.cover.type===0?`https://img2.baidu.com/it/u=2640406343,1419332367&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=708&amp;h=500`:item.cover.images[0]}" alt="">
  </td>
  <td>${item.title}</td>
  <td>${item.status===1?`<span class="badge text-bg-primary">待审核</span>`:`<span class="badge text-bg-success">审核通过</span>`}
  </td>
  <td>
    <span>${item.pubdate}</span>
  </td>
  <td>
    <span>${item.read_count}</span>
  </td>
  <td>
    <span>${item.comment_count}</span>
  </td>
  <td>
    <span>${item.like_count}</span>
  </td>
  <td data-id="${item.id}">
    <i class="bi bi-pencil-square edit"></i>
    <i class="bi bi-trash3 del"></i>
  </td>
</tr>
  `).join('')
  document.querySelector('.art-list').innerHTML=htmlStr
}
setArtileList()

内容管理 - 筛选功能

目标:根据筛选条件,获取匹配数据展示

步骤:

  1. 设置频道列表数据
  2. 监听筛选条件改变,保存查询信息到查询参数对象
  3. 点击筛选时,传递查询参数对象到服务器
  4. 获取匹配数据,覆盖到页面展示
/**
 * 目标2:筛选文章列表
 *  2.1 设置频道列表数据
 *  2.2 监听筛选条件改变,保存查询信息到查询参数对象
 *  2.3 点击筛选时,传递查询参数对象到服务器
 *  2.4 获取匹配数据,覆盖到页面展示
 */

// 设置频道列表数据
async function setChannleList(){
  const res=await axios({
    url:'/v1_0/channels'
  })
  const htmlStr=` <option value="" selected>请选择文章频道</option>`+res.data.channels.map(item=>`
  <option value="${item.id}">${item.name}</option>
`).join('')
  document.querySelector('.form-select').innerHTML=htmlStr
}
setArtileList()
// 监听筛选条件改变,保存查询信息到查询参数对象
// 筛选状态标记数字->change事件->绑定到查询参数对象上
document.querySelectorAll('.form-check-input').forEach(radio=>{
  radio.RadioNodeList('change',e=>{
    queryObj.status=e.target.value
  })
})
// 筛选频道 id->change事件-> 绑定到查询参数上
document.querySelector('.form-select').addEventListener('change',e=>{
  // console.log(e.target.value)
  queryObj.channel_id=e.target.value
})
// 点击筛选时,传递查询参数对象到服务器
document.querySelector('.sel-btn').addEventListener('click',()=>{
  // 获取匹配数据 覆盖到页面展示
  setArtileList()
})

内容管理 - 分页功能

目标:完成文章列表,分页管理功能

步骤:

  1. 保存并设置文章总条数
  2. 点击下一页,做临界值判断,并切换页码参数请求最新数据
  3. 点击上一页,做临界值判断,并切换页码参数请求最新数据

/**
 * 目标3:分页功能
 *  3.1 保存并设置文章总条数
 *  3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
 *  3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
 */

// 点击下一页 做临界值判断 并切换页码参数并请求最新数据
document.querySelector('.next').addEventListener('click',e=>{
  // 当前页码小于最大页码数
  if(queryObj.page<Math.ceil(totalCount/queryObj.per_page)){
    queryObj.page++
    document.querySelector('.page-now').innerHTML=`第${queryObj.page}页`
    setArtileList()
  }
})
// 点击上一页 做临界值判断 并切换页码参数并请求最新数据
document.querySelector('.last').addEventListener('click',e=>{
  // 大于1的时候 才能翻到上一页
  if(queryObj.page>1){
    queryObj.page--
    document.querySelector('.page-now').innerHTML=`第${queryObj.page}页`
    setArtileList()
  }
})

内容管理 - 删除功能

目标:完成删除文章功能

步骤:

  1. 关联文章id到删除图标
  2. 点击删除时,获取文章id
  3. 调用删除接口,传递文章id到服务器
  4. 重新获取文章列表,并覆盖展示

/**
 * 目标4:删除功能
 *  4.1 关联文章 id 到删除图标
 *  4.2 点击删除时,获取文章 id
 *  4.3 调用删除接口,传递文章 id 到服务器
 *  4.4 重新获取文章列表,并覆盖展示
 *  4.5 删除最后一页的最后一条,需要自动向前翻页
 */

// 点击删除时,获取文章id
document.querySelector('.art-list').addEventListener('click',async e=>{
  // 判断点击的是删除元素
  if(e.target.classList.contains('del')){
    const delId=e.target.parentNode.dataset.id
    // 调用删除接口 传递文章id到服务器
    const res=await axios({
      url:`/v1_0/mp/articles/${delId}`,
      method:'DELETE'
    })
    // 删除最后一页的最后一条,需要自动向前翻页
    const children=document.querySelector('.art-list').children
    if(children.length===1&&queryObj.page!==1){
      queryObj.page--
    }
    // 重新获取文章列表 并覆盖展示
    setArtileList()
  }
})

内容管理 - 编辑文章 - 回显

目标:编辑文章时,回显数据到表单

步骤:

  • 页面跳转传参(URL查询参数方式)
  • 发布文章页面接收参数判断(共用同一套表单)
/**
 * 目标4:编辑-回显文章
 *  4.1 页面跳转传参(URL 查询参数方式)
 *  4.2 发布文章页面接收参数判断(共用同一套表单)
 *  4.3 修改标题和按钮文字
 *  4.4 获取文章详情数据并回显表单
 */
;(function(){
// 发布文章页面接收参数判断(共用同一套表单)
  const paramsStr=location.search
  const params=new URLSearchParams(paramsStr)
  params.forEach(async (value,key)=>{
    // console.log(value,key)
    // 当前有要编辑的文章 id被传过来,查询参数有id说明是修改,无则说明是发布
    if(key==='id'){
      // 修改标题和按钮文字
      document.querySelector('.title span').innerHTML='修改文章'
      document.querySelector('.send').innerHTML='修改'
      // 获取文章详情数据并回显表单
      const res= await axios({//res为后台返回的数据
        url:`/v1_0/mp/articles/${value}`
      })
      // console.log(res)
      // 组织我仅仅需要的数据对象 为后续遍历回显到页面上做铺垫
      const dataObj={
        channel_id:res.data.channel_id,
        title:res.data.title,
        rounded:res.data.cover.images[0],//封面图片地址
        content:res.data.content,
        id:res.data.id
      }
      // 遍历数据对象属性,映射到页面元素上 快速赋值
      Object.keys(dataObj).forEach(key =>{
        if(key==='rounded'){
          // 封面设置
          if(dataObj[key]){
            // 有封面
            document.querySelector('.rounded').src=dataObj[key]
            document.querySelector('.rounded').classList.add('show')
            document.querySelector('.place').classList.add('hide')
          }
        }else if(key==='content'){
          editor.setHtml=(dataObj[key])
        }else {
          // 用数据对象属性名 作为标签 name属性选择器值来找到匹配的标签
          document.querySelector(`[name=${key}]`).value=dataObj[key]
        }
      })
    }
  })
})()

内容管理 - 编辑文章 - 保存

目标:确认修改,保存文章到服务器

步骤:

  1. 判断按钮文字,区分业务(因为公用一套表单)
  2. 调用编辑文章接口,保存信息到服务器
  3. 基于Alert反馈结果给用户
/**
 * 目标5:编辑-保存文章
 *  5.1 判断按钮文字,区分业务(因为共用一套表单)
 *  5.2 调用编辑文章接口,保存信息到服务器
 *  5.3 基于 Alert 反馈结果消息给用户
 */
document.querySelector('.send').addEventListener('click',async e=>{
  // 判断按钮文字,区分业务
  if(e.target.innerHTML!=='修改') return
  const form=document.querySelector('.art-form')
  const data=serialize(form,{hash:true,empty:true})
  // 调用编辑文章接口,保存信息到服务器
  try{
    const res=await axios({
      url:`/v1_0/mp/articles/${data.id}`,
      method:'PUT',
      data:{
        ...data,//解构赋值
        cover:{
          type:document.querySelector('.rounded').src?1:0,
          images:[document.querySelector('.rounded').src]
        }
      }
    })
    console.log(res)
    myAlert(true,'修改成功')

  }catch(error){
    myAlert(false,error.response.data.message)
  }
  
})

退出登录

目标:完成退出登陆效果

步骤:

  1. 绑定点击事件
  2. 清空本地缓存,跳转至登录页面
/**
 * 目标3:退出登录
 *  3.1 绑定点击事件
 *  3.2 清空本地缓存,跳转到登录页面
 */
document.querySelector('.quit').addEventListener('click',e=>{
  // 清空本地缓存 跳转到登录页面
  localStorage.clear()
  location.href='../login/index.html'
})

以上知识点笔记以及后台服务器均出自于哔站黑马程序员!!!

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值