• AJAX——黑马头条-数据管理平台项目


    1.项目介绍

    功能:

    1. 登录和权限判断
    2. 查看文章内容列表(筛选,分页)
    3. 编辑文章(数据回显)
    4. 删除文章
    5. 发布文章(图片上传,富文本编辑器)

    2.项目准备

    技术:

    • 基于Bootstrap搭建网站标签和样式
    • 集成wangEditor插件实现富文本编辑器
    • 使用原生JS完成增删改查等业务
    • 基于axios与黑马头条线上接口交互
    • 使用axios拦截器进行权限判断
    • 准备配套的素材代码
    • 包含:html、css、js,静态图片,第三方插件等等

    目录管理:建议这样管理,方便查找

    • assets:资源文件夹(图片,字体等)
    • lib:资料文件夹(第三方插件,例如:form-serialize)
    • page:页面文件夹
    • utils:实用程序文件夹(工具插件)

    3.验证码登录

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

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

    步骤:

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

     index.js

    1. /**
    2. * 目标1:验证码登录
    3. * 1.1 在 utils/request.js 配置 axios 请求基地址
    4. * 1.2 收集手机号和验证码数据
    5. * 1.3 基于 axios 调用验证码登录接口
    6. * 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
    7. */
    8. // 1.2 收集手机号和验证码数据
    9. document.querySelector('.btn').addEventListener('click', () => {
    10. const form = document.querySelector('.login-form')
    11. const data = serialize(form, { hash: true, empty: true})
    12. console.log(data)
    13. // 1.3 基于 axios 调用验证码登录接口
    14. axios({
    15. url: '/v1_0/authorizations',
    16. method: 'POST',
    17. data: data
    18. }).then( result => {
    19. // 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
    20. myAlert(true, '登录成功')
    21. console.log(result)
    22. }).catch(error => {
    23. myAlert(false, error.response.data.message)
    24. console.dir(error.response.data.message)
    25. })
    26. })

    验证码登录流程

    4.token的介绍

    概念:访问权限的令牌,本质上是一串字符串

    创建:正确登录后,由后端签发并返回

    作用:判断是否有登录状态等,控制访问权限

    注意:前端只能判断token有无,而后端才能判断token的有效性

    5.token的使用

    目标:只有登录状态,才可以访问内容页面

    步骤:

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

    2.在登录成功后,保存token令牌字符串到本地,再跳转到首页(手动修改地址栏测试)

    1. // 权限插件(引入到了除登录页面,以外的其他所有页面)
    2. /**
    3. * 目标1:访问权限控制
    4. * 1.1 判断无 token 令牌字符串,则强制跳转到登录页
    5. * 1.2 登录成功后,保存 token 令牌字符串到本地,并跳转到内容列表页面
    6. */
    7. // 1.1 判断无 token 令牌字符串,则强制跳转到登录页
    8. const token = localStorage.getItem('token')
    9. if (!token) {
    10. location.href = '../login/index.html'
    11. }
    1. /**
    2. * 目标1:验证码登录
    3. * 1.1 在 utils/request.js 配置 axios 请求基地址
    4. * 1.2 收集手机号和验证码数据
    5. * 1.3 基于 axios 调用验证码登录接口
    6. * 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
    7. */
    8. // 1.2 收集手机号和验证码数据
    9. document.querySelector('.btn').addEventListener('click', () => {
    10. const form = document.querySelector('.login-form')
    11. const data = serialize(form, { hash: true, empty: true})
    12. console.log(data)
    13. // 1.3 基于 axios 调用验证码登录接口
    14. axios({
    15. url: '/v1_0/authorizations',
    16. method: 'POST',
    17. data: data
    18. }).then( result => {
    19. // 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
    20. myAlert(true, '登录成功')
    21. console.log(result)
    22. // 登录成功后,保存 token 令牌字符串到本地,并跳转到内容列表页面
    23. localStorage.setItem('token', result.data.data.token)
    24. setTimeout(() => {
    25. // 延迟跳转,让alert警告框停留一会
    26. location.href = '../content/index.html'
    27. },1500)
    28. }).catch(error => {
    29. myAlert(false, error.response.data.message)
    30. console.dir(error.response.data.message)
    31. })
    32. })

    token的作用?

    • 判断用户是否有登录状态等

    token的注意:

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

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

    需求:设置用户昵称

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

    问题:很多接口,都需要携带token令牌字符串

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

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

    对应代码

    1. /**
    2. * 目标2:设置个人信息
    3. * 2.1 在 utils/request.js 设置请求拦截器,统一携带 token
    4. * 2.2 请求个人信息并设置到页面
    5. */
    6. // 2.2 请求个人信息并设置到页面
    7. axios({
    8. url: '/v1_0/user/profile'
    9. }).then(result => {
    10. console.log(result)
    11. const username = result.data.data.name
    12. document.querySelector('.nick-name').innerHTML = username
    13. })
    1. // 添加请求拦截器
    2. axios.interceptors.request.use(function (config) {
    3. // 在发送请求之前做些什么
    4. // 统一携带 token 令牌字符串在请求头上
    5. const token = localStorage.getItem('token')
    6. token && (config.headers.Authorization = `Bearer ${token}`)
    7. return config;
    8. }, function (error) {
    9. // 对请求错误做些什么
    10. return Promise.reject(error);
    11. })

    总结

    1.什么是axios请求拦截器?

    发起请求之前,调用的一个函数,对请求参数进行设置

    2.axios请求拦截器,什么时候使用?

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

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

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

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

    1. // 添加响应拦截器
    2. axios.interceptors.response.use(function (response) {
    3. // 2xx 范围内前状态码都会触发该函数
    4. // 对响应数据做点什么
    5. return response;
    6. },function (error) {
    7. // 超出 2xx 范围的状态码都会触发该函数
    8. // 对响应错误数做点什么,例如:统一对 401 身份验证失败错误做出处理
    9. console.dir(error)
    10. if (error?.response?.status === 401) {
    11. alert('身份验证失败,请重新登录')
    12. localStorage.clear()
    13. location.href = '../login/index.html'
    14. }
    15. return Promise.reject(error)
    16. })

    总结

    1.什么是axios响应拦截器?

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

    2.axios响应拦截器,什么时候触发成功/失败的回调函数?

    状态为2xx触发成功回调,其他则触发失败的回调函数

     8.优化-axios响应结果

    目标:axios直接接收服务器返回的响应结果

    讲解:其实就是在响应拦截器里,response.data把后台返回的数据直接取出来统一返回给所有使用这个axios函数的逻辑页面位置的 then 的形参上

    好处:可以让逻辑页面少一层data就能拿到后端返回的真正数据对象

    对应代码

    1. axios.interceptors.response.use(function (response) {
    2. // 2xx 范围内的状态码都会触发该函数。
    3. // 对响应数据做点什么,例如:直接返回服务器的响应结果对象
    4. const result = response.data
    5. return result
    6. }, function (error) {
    7. // 超出 2xx 范围的状态码都会触发该函数。
    8. // 对响应错误做点什么,例如:判断响应状态为 401 代表身份验证失败
    9. if (error?.response?.status === 401) {
    10. alert('登录状态过期,请重新登录')
    11. window.location.href = '../login/index.html'
    12. }
    13. return Promise.reject(error);
    14. })

    9.发布文章-富文本编辑器

    富文本:带样式,多格式的文本,在前端一般使用标签配合内联样式实现

    富文本编辑器:用于编写富文本内容的容器

    目标:发布文章页,富文本编辑器的集成

    使用:wangEditor插件

    步骤:参考文档

    1. 引入CSS定义样式
    2. 定义HTML结构
    3. 引入JS创建编辑器
    4. 监听内容改变,保存在隐藏文本域(便于后期收集)

    对应代码

    1. // 富文本编辑器
    2. // 创建编辑器函数,创建工具栏函数
    3. const { createEditor, createToolbar } = window.wangEditor
    4. const editorConfig = {
    5. // 占位提示文字
    6. placeholder: '发布文章内容...',
    7. // 编辑器变化时回调函数
    8. onChange(editor) {
    9. // 获取富文本内容
    10. const html = editor.getHtml()
    11. console.log('editor content', html)
    12. // 也可以同步到 <textarea>
    13. // 为了后续快速收集整个表单内容做铺垫
    14. document.querySelector('.publish-content').value = html
    15. }
    16. }
    17. const editor = createEditor({
    18. // 创建位置
    19. selector: '#editor-container',
    20. // 默认内容
    21. html: '


      '
      ,
    22. // 配置项
    23. config: editorConfig,
    24. // 配置集成模式(default 全部) (simple 简洁)
    25. mode: 'default', // or 'simple'
    26. })
    27. // 工具栏配置对象
    28. const toolbarConfig = {}
    29. // 创建工具栏
    30. const toolbar = createToolbar({
    31. // 为指定编辑器创建工具栏
    32. editor,
    33. // 工具栏创建的位置
    34. selector: '#toolbar-container',
    35. // 工具栏配置对象
    36. config: toolbarConfig,
    37. // 配置集成模式
    38. mode: 'default', // or 'simple'
    39. })

    10.发布文章-频道列表

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

    步骤:

    1. 获取频道列表数据
    2. 展示到下拉菜单中
    1. /**
    2. * 目标1:设置频道下拉菜单
    3. * 1.1 获取频道列表数据
    4. * 1.2 展示到下拉菜单中
    5. */
    6. // 1.1 获取频道列表数据
    7. async function setChannleList() {
    8. const res = await axios({
    9. url: '/v1_0/channels'
    10. })
    11. // 1.2 展示到下拉菜单中
    12. const htmlStr = `<option value="" selected="">请选择文章频道</option>` + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join('')
    13. console.log(htmlStr)
    14. document.querySelector('.form-select').innerHTML = htmlStr
    15. }
    16. // 网页运行后,默认调用一次
    17. setChannleList()

    11.发布文章-封面设置

    目标:文章封面的设置

    步骤:

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

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

    1. /**
    2. * 目标2:文章封面设置
    3. * 2.1 准备标签结构和样式
    4. * 2.2 选择文件并保存在 FormData
    5. * 2.3 单独上传图片并得到图片 URL 网址
    6. * 2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
    7. */
    8. // 2.2 选择文件并保存在 FormData
    9. document.querySelector('.img-file').addEventListener('change', async e => {
    10. const file = e.target.files[0]
    11. const fd = new FormData()
    12. fd.append('image', file)
    13. // 2.3 单独上传图片并得到图片 URL 网址
    14. const res = await axios({
    15. url: '/v1_0/upload',
    16. method: 'POST',
    17. data: fd
    18. })
    19. console.log(res)
    20. // 2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
    21. const imgUrl = res.data.url
    22. document.querySelector('.rounded').src = imgUrl
    23. document.querySelector('.rounded').classList.add('show')
    24. document.querySelector('.place').classList.add('hide')
    25. })
    26. // 优化:点击 img 可以重新切换封面
    27. // 思路: img 点击 => 用JS方式触发文件选择元素 click 事件方法
    28. document.querySelector('.rounded').addEventListener('click', () => {
    29. document.querySelector('.img-file').click()
    30. })

    12.发布文章-收集并保存

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

    步骤:

    1.基于form-serialize插件收集表单数据对象

    2.基于axios提交到服务器保存

    3.调用Alert警告框反馈结果给用户

    1. /**
    2. * 目标3:发布文章保存
    3. * 3.1 基于 form-serialize 插件收集表单数据对象
    4. * 3.2 基于 axios 提交到服务器保存
    5. * 3.3 调用 Alert 警告框反馈结果给用户
    6. * 3.4 重置表单并跳转到列表页
    7. */
    8. // 3.1 基于 form-serialize 插件收集表单数据对象
    9. document.querySelector('.send').addEventListener('click', async e => {
    10. const form = document.querySelector('.art-form')
    11. const data = serialize(form, { hash: true, empty: true})
    12. console.log(data)
    13. // 发布文章的时候,不需要 id 属性,所以可以删除掉(id为了后续做编辑使用)
    14. delete data.id
    15. console.log(data)
    16. // 自己收集封面图片地址并保存到 data 对象中
    17. data.cover = {
    18. type: 1, // 封面类型
    19. images: [document.querySelector('.rounded').src] // 封面图书 URL网址
    20. }
    21. // 3.2 基于 axios 提交到服务器保存
    22. try {
    23. const res = await axios({
    24. url: '/v1_0/mp/articles',
    25. method:'POST',
    26. data: data
    27. })
    28. // 3.3 调用 Alert 警告框反馈结果给用户
    29. myAlert(true, '发布成功')
    30. // 3.4 重置表单并跳转到列表页
    31. form.reset()
    32. // 封面需要手动重置
    33. document.querySelector('.rounded').src = ''
    34. document.querySelector('.rounded').classList.remove('show')
    35. document.querySelector('.place').classList.remove('hide')
    36. // 富文本编辑器重置
    37. editor.setHtml('')
    38. setTimeout(() => {
    39. location.href = '../content/index.html'
    40. },1500)
    41. } catch (error) {
    42. console.dir(error)
    43. myAlert(false, error.response.data.message)
    44. }
    45. })

    13.内容管理-文章列表展示

    目标:获取文章列表并展示

    步骤:

    1.准备查询参数对象

    2.获取文章列表数据

    3.展示到指定的标签结构中

    1. /**
    2. * 目标1:获取文章列表并展示
    3. * 1.1 准备查询参数对象
    4. * 1.2 获取文章列表数据
    5. * 1.3 展示到指定的标签结构中
    6. */
    7. // 1.1 准备查询参数对象
    8. const queryObj = {
    9. status: '', // 文章状态(1-待审核,2-审核通过)空字符串-全部
    10. channel_id: '', // 文章频道id,空字符串-全部
    11. page: 1, // 当前页码
    12. per_page: 2 // 当前页面条数
    13. }
    14. async function setArtileList() {
    15. // 1.2 获取文章列表数据
    16. const res = await axios({
    17. url: '/v1_0/mp/articles',
    18. params: queryObj
    19. })
    20. console.log(res)
    21. // 1.3 展示到指定的标签结构中
    22. const htmlStr = res.data.results.map( item => `<tr>
    23. <td>
    24. <img src=" ${item.cover.type === 0 ? `https://img2.baidu.com/it/u=2640406343,1419332367&fm=253&fmt=auto&app=138&f=JPEG?w=708&h=500` : item.cover.images[0]}" alt="">
    25. </td>
    26. <td>${item.title}</td>
    27. <td>
    28. ${item.status === 1 ? `<span class="badge text-bg-primary">待审核</span>` : `<span class="badge text-bg-success">审核通过</span>`}
    29. </td>
    30. <td>
    31. <span>${ item.pubdate }</span>
    32. </td>
    33. <td>
    34. <span> ${ item.read_count } </span>
    35. </td>
    36. <td>
    37. <span> ${ item.comment_count } </span>
    38. </td>
    39. <td>
    40. <span> ${ item.like_count }</span>
    41. </td>
    42. <td>
    43. <i class="bi bi-pencil-square edit"></i>
    44. <i class="bi bi-trash3 del"></i>
    45. </td>
    46. </tr>
    47. `).join('')
    48. // console.log(htmlStr)
    49. document.querySelector('.art-list').innerHTML = htmlStr
    50. }
    51. setArtileList()

    14.内容管理-筛选功能

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

    步骤:

    1.设置频道列表数据

    2.监听筛选条件改变,保存查询信息到查询参数对象

    3.点击筛选时,传递查询参数对象到服务器

    4.获取匹配数据,覆盖到页面展示

    1. /**
    2. * 目标2:筛选文章列表
    3. * 2.1 设置频道列表数据
    4. * 2.2 监听筛选条件改变,保存查询信息到查询参数对象
    5. * 2.3 点击筛选时,传递查询参数对象到服务器
    6. * 2.4 获取匹配数据,覆盖到页面展示
    7. */
    8. async function setChannleList() {
    9. const res = await axios({
    10. url: '/v1_0/channels'
    11. })
    12. const htmlStr = `<option value="" selected="">请选择文章频道</option>` + res.data.channels.map(item => `<option value="${item.id}">${item.name}</option>`).join('')
    13. document.querySelector('.form-select').innerHTML = htmlStr
    14. }
    15. setChannleList()
    16. // 2.2 监听筛选条件改变,保存查询信息到查询参数对象
    17. // 筛选状态标记数字 -> change事件 -> 绑定到查询参数对象上
    18. document.querySelectorAll('.form-check-input').forEach(radio => {
    19. radio.addEventListener('change', e => {
    20. // console.log(e.target.value)
    21. queryObj.status = e.target.value
    22. })
    23. })
    24. // 筛选频道 id -> change事件 -> 绑定到查询参数对象上
    25. document.querySelector('.form-select').addEventListener('change', e =>
    26. {
    27. // console.log(e.target.value)
    28. queryObj.channel_id = e.target.value
    29. })
    30. // 2.3 点击筛选时,传递查询参数对象到服务器
    31. document.querySelector('.sel-btn').addEventListener('click', () => {
    32. // 2.4 获取匹配数据,覆盖到页面展示
    33. setArtileList()
    34. })

    15.内容管理-分页功能

    内容管理-分页功能

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

    步骤:

    1.保存并设置文章总条数

    2.点击下一页,做临界值判断,并切换页面参数请求最新数据

    3.点击上一页,做临界值判断,并切换页面参数请求最新数据

    1. /**
    2. * 目标3:分页功能
    3. * 3.1 保存并设置文章总条数
    4. * 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
    5. * 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
    6. */
    7. // 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
    8. document.querySelector('.next').addEventListener('click', e => {
    9. // 当前页码小于最大页码数
    10. if (queryObj.page < Math.ceil(totalCount / queryObj.per_page)) {
    11. queryObj.page++
    12. document.querySelector('.page-now').innerHTML = `第${queryObj.page}页`
    13. setArtileList()
    14. }
    15. } )
    16. // 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
    17. document.querySelector('.last').addEventListener('click', e => {
    18. // 大于 1 的时候,才能翻到上一页
    19. if (queryObj.page > 1) {
    20. queryObj.page--
    21. document.querySelector('.page-now').innerHTML = `第${queryObj.page}页`
    22. }
    23. setArtileList()
    24. })

    16.内容管理-删除功能

    目标:完成删除文章功能

    步骤:

    1. 关联文章id到删除图标
    2. 点击删除时,获取文章id
    3. 调用删除接口,传递文章id到服务器
    4. 重新获取文章列表,并覆盖展示
    1. /**
    2. * 目标4:删除功能
    3. * 4.1 关联文章 id 到删除图标
    4. * 4.2 点击删除时,获取文章 id
    5. * 4.3 调用删除接口,传递文章 id 到服务器
    6. * 4.4 重新获取文章列表,并覆盖展示
    7. * 4.5 删除最后一页的最后一条,需要自动向前翻页
    8. */
    9. // 4.2 点击删除时,获取文章 id
    10. document.querySelector('.art-list').addEventListener('click', async e => {
    11. // 判断点击的是删除元素
    12. if (e.target.classList.contains('del')) {
    13. const delId = e.target.parentNode.dataset.id
    14. console.log(delId)
    15. // 4.3 调用删除接口,传递文章 id 到服务器
    16. const res = await axios({
    17. url: `v1_0/mp/articles/${delId}` ,
    18. method: 'DELETE'
    19. })
    20. console.log(res)
    21. // 4.4 重新获取文章列表,并覆盖展示
    22. setArtileList()
    23. }
    24. })

    17.内容管理-删除最后一条

    目标:在删除最后一页,最后一条时有Bug

    1.删除成功时,判断DOM元素只剩一条,让当前页码 page--

    2.注意,当前页码为1时不能继续向前翻页

    3.重新设置页码数,获取最新列表展示

    1. // 4.5 删除最后一页的最后一条,需要自动向前翻页
    2. const children = document.querySelector('.art-list').children
    3. if (children.length === 1 && queryObj.page !== 1){
    4. queryObj.page--
    5. document.querySelector('.page-now').innerHTML = `第${queryObj.page }页`
    6. }

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

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

    步骤:

    1. 页面跳转传参(URL查询参数方式)
      1. // 点击编辑时,获取文章 id,跳转到发布文章页面传递文章 id 过去
      2. document.querySelector('.art-list').addEventListener('click', e => {
      3. if (e.target.classList.contains('edit')) {
      4. const artId = e.target.parentNode.dataset.id
      5. console.log(artId)
      6. location.href = `../publish/index.html?id=${artId}`
      7. }
      8. })
    2. 发布文章页面接收参数判断(共用同一套表单)
    3. 修改标题和按钮文字
    4. 获取文章详情数据并回显表单

    对应代码:

    1. /**
    2. * 目标4:编辑-回显文章
    3. * 4.1 页面跳转传参(URL 查询参数方式)
    4. * 4.2 发布文章页面接收参数判断(共用同一套表单)
    5. * 4.3 修改标题和按钮文字
    6. * 4.4 获取文章详情数据并回显表单
    7. */
    8. ;(function(){
    9. // 4.2 发布文章页面接收参数判断(共用同一套表单)
    10. const paramsStr = location.search
    11. const params = new URLSearchParams(paramsStr)
    12. params.forEach(async (value, key) => {
    13. // 当前有要编辑的文章 id 被传入过来
    14. if (key === 'id'){
    15. // 4.3 修改标题和按钮文字
    16. document.querySelector('.title span').innerHTML = '修改文章'
    17. document.querySelector('.send').innerHTML = '修改'
    18. // 4.4 获取文章详情数据并回显表单
    19. const res = await axios({
    20. url: `/v1_0/mp/articles/${value}`
    21. })
    22. console.log(res)
    23. // 组织我仅仅需要的数据对象,为后续遍历回显到页面上做铺垫
    24. const dataObj = {
    25. channel_id: res.data.channel_id,
    26. title: res.data.title,
    27. rounded: res.data.cover.images[0], // 封面图片地址
    28. content: res.data.content,
    29. id: res.data.id
    30. }
    31. // 遍历数据对象属性,映射到页面元素上,快速赋值
    32. Object.keys(dataObj).forEach(key => {
    33. if(key === 'rounded'){
    34. // 封面设置
    35. if(dataObj[key]){
    36. // 有封面
    37. document.querySelector('.rounded').src = dataObj[key]
    38. document.querySelector('.rounded').classList.add('show')
    39. document.querySelector('.place').classList.add('hide')
    40. }
    41. } else if( key === 'content'){
    42. // 富文本内容
    43. editor.setHtml(dataObj[key])
    44. } else {
    45. // 用数据对象属性名,作为标签 name 属性选择器值来找到匹配的标签
    46. document.querySelector(`[name=${key}]`).value = dataObj[key]
    47. }
    48. })
    49. }
    50. })
    51. })();

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

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

    步骤:

    1.判断按钮文字,区分业务(因为共用一套表单)

    2.调用编辑文章接口,保存信息到服务器

    3.基于Alert反馈结果消息给用户

    1. /**
    2. * 目标5:编辑-保存文章
    3. * 5.1 判断按钮文字,区分业务(因为共用一套表单)
    4. * 5.2 调用编辑文章接口,保存信息到服务器
    5. * 5.3 基于 Alert 反馈结果消息给用户
    6. */
    7. document.querySelector('.send').addEventListener('click', async e => {
    8. // 5.1 判断按钮文字,区分业务(因为共用一套表单)
    9. if (e.target.innerHTML !== '修改') return
    10. // 修改文章逻辑
    11. const form = document.querySelector('.art-form')
    12. const data = serialize(form, { hash: true, empty: true})
    13. // console.log(data)
    14. // 5.2 调用编辑文章接口,保存信息到服务器
    15. try {
    16. const res = await axios({
    17. url: `/v1_0/mp/articles/${data.id}`,
    18. method: 'PUT',
    19. data: {
    20. ...data,
    21. cover:{
    22. type: document.querySelector('.rounded').src ? 1 : 0,
    23. images: [document.querySelector('.rounded').src]
    24. }
    25. }
    26. })
    27. console.log(res)
    28. myAlert(true,'修改文章成功')
    29. } catch (error) {
    30. myAlert(false,error.response.data.message)
    31. }
    32. })

    20.退出登录

    目标:完成退出登录效果

    步骤:

    1.绑定点击事件

    2.清空本地缓存,跳转到登录页面

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

    项目素材代码点击这里:【免费】黑马头条数据管理平台的素材、代码资源-CSDN文库

  • 相关阅读:
    数字孪生与元宇宙在关键技术方面的异同
    程序包org.apache.ibatis.mapping不存在 符号找不到
    适合自学的网络安全基础技能“蓝宝书”:《CTF那些事儿》
    软考高级系统架构设计师系列论文九:论企业集成平台的技术与应用
    Vue路由&&无痕浏览 - nodeJs环境搭建
    这里聊聊扫地机的 IOT 开发
    时隔10年谷歌计划重启谷歌实验室,聚焦AR、VR项目
    Selenium:自动化测试必备工具
    flutter 谷歌的苹果系统消息推送
    Java多线程第十三篇--盘一盘晕头转向的Runnable、Callable、Future、RunnableFuture、FutureT
  • 原文地址:https://blog.csdn.net/weixin_48719464/article/details/138101638