首页 > 其他分享 >hm头条-admin

hm头条-admin

时间:2024-06-01 11:00:43浏览次数:13  
标签:axios const admin querySelector hm document data id 头条

本节目标

完成项目

  • 项目介绍
  • 验证码登录
  • 统一处理请求
  • 富文本编辑器
  • 频道下拉菜单
  • 封面上传
  • 文章列表展示
  • 筛选功能
  • 分页功能
  • 删除功能
  • 编辑文章(回显)
  • 编辑文章(保存)
  • 退出登录

项目介绍

介绍

头条数据管理平台: 对IT资源移动网站的数据进行管理

移动网站(演示): 极客园

主要功能

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

技术选型

  1. 基于Bootstrap搭建网站标签和样式
  2. 集成wangEditor插件, 实现富文本编辑器
  3. 使用原生JS完成增删改查业务
  4. 基于axios进行前后端交互
  5. 使用axios拦截器进行权限判断

项目准备

html, css, js, 图片, 第三方插件

目录结构

assets: 资源文件夹(图片/字体)

lib: 资料文件夹(第三方插件)

page: 页面文件夹

utils: 工具文件夹(自定义js等)

验证码登录

登录流程

实现登录

// axios 公共配置
// 基地址
axios.defaults.baseURL = 'https://geek.itheima.net'
/**
 * 目标1:验证码登录
 * 1.1 在 utils/request.js 配置 axios 请求基地址
 * 1.2 收集手机号和验证码数据
 * 1.3 基于 axios 调用验证码登录接口
 * 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
 */
document.querySelector('.btn').addEventListener('click', async function () {
  try {
    const form = document.querySelector('.login-form')
    const data = serialize(form, { hash: true, empyt: true })
    const login = await axios({ url: '/v1_0/authorizations', method: 'post', data })
    myAlert(true, '登录成功')

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

})

权限控制

token是访问权限的令牌, 本质是一个字符串, 前端只能判断有无token, 后端判断token的有效性

/**
 * 目标1:访问权限控制
 * 1.1 判断无 token 令牌字符串,则强制跳转到登录页
 * 1.2 登录成功后,保存 token 令牌字符串到本地,并跳转到内容列表页面
 */
const token = localStorage.getItem('token')
// 没有token,返回登录页
if (!token) {
  location.href = '../login/index.html'
}
document.querySelector('.btn').addEventListener('click', async function () {
  try {
    ... ...
    myAlert(true, '登录成功')
    localStorage.setItem('token', login.data.data.token)
    setTimeout(() => {
      location.href = '../content/index.html'
    }, 1200)
  } 
})

统一处理请求

请求拦截器

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

拦截流程

设置请求头参数

1,在拦截器中统一添加请求头参数

// 请求拦截器
// 语法:   axios.interceptors.request.use(函数1, 函数2)
// 函数1:  请求成功的函数
// 函数2:  请求失败的函数(可省略,很少用)
axios.interceptors.request.use(function (config) {
  // 请求之前的处理
  const token = localStorage.getItem('token')
  token && (config.headers.Authorization = `Bearer ${token}`)

  return config
}, function (error) {
  // 请求错误的处理
  return Promise.reject(error)
})

2, 请求时单独添加请求头参数

axios({
  url: '',
  headers: {
    Authorization: 'Bearer xxxxxxxx'
  }
})

响应拦截器

服务器响应结果后, 首先触发响应拦截器函数, 可以对响应结果进行统一处理, 再回到then/catch中

拦截流程

处理响应结果

  1. 简化axios的响应数据结构 (注意会影响之前的数据读取)
  2. 理登录过期的状态
// 响应拦截器
// axios.interceptors.request.use(函数1, 函数2)
// 函数1:  请求成功的函数
// 函数2:  请求失败的函数
axios.interceptors.response.use(function (response) {
  // 状态码2xx范围内的请求触发成功处理函数
  // 简化axios的响应数据结构
  return response.data

}, function (error) {
  // 超出2xx范围内的请求触发失败处理函数
  console.dir(error)
  // 处理登录过期的状态
  if (error?.response?.status === 401) {
    alert('登录过期, 请重新登录')
    localStorage.clear()
    location.href = '../login/index.html'
  }

  return Promise.reject(error)
})

富文本编辑器

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

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

使用wangEditor插件, 完成富文本编辑器的集成

官网: wangEditor

使用步骤

  1. 引入css样式
  2. 定义html结构
  3. 引入js创建编辑器
  4. 监听内容变化, 进行处理
  5.  富文本编辑器重置内容: editor.setHtml('')
<link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
<style>
/* 富文本编辑器 */
#editor—wrapper {
 border: 1px solid #ccc;
 z-index: 100; /* 按需定义 */
}
#toolbar-container { border-bottom: 1px solid #ccc; }
#editor-container { height: 500px; }
</style>

<body>
  <!-- 富文本编辑器位置 -->
  <div id="editor—wrapper">
    <div id="toolbar-container"><!-- 工具栏 --></div>
    <div id="editor-container"><!-- 编辑器 --></div>
  </div>

  <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
</body>
// 富文本编辑器
// 创建编辑器函数,创建工具栏函数
const { createEditor, createToolbar } = window.wangEditor

// 编辑器配置文件
const editorConfig = {
  // 占位提示文字
  placeholder: '请输入文章内容',
  // 内容改变事件
  onChange(editor) {
    const html = editor.getHtml()
    console.log('editor content', html)
    // 也可以同步到 <textarea>
    // 目的: 方便使用插件统一收集数据
    document.querySelector('.publish-content').innerHTML = html
  }
}
// 创建编辑器
const editor = createEditor({
  // 指定创建位置
  selector: '#editor-container',
  // 默认内容
  html: '<p><br></p>',
  // 添加配置对象
  config: editorConfig,
  // 设置模式 default(完整) simple(简洁)
  mode: 'default', // or 'simple'
})

// 工具栏配置文件
const toolbarConfig = {}
// 创建工具栏
const toolbar = createToolbar({
  // 为指定的编辑器创建工具栏
  editor,
  // 指定创建位置
  selector: '#toolbar-container',
  // 添加配置对象
  config: toolbarConfig,
  // 设置模式 default(完整) simple(简洁)
  mode: 'default', // or 'simple'
})

频道下拉菜单

/**
 * 目标1:设置频道下拉菜单
 *  1.1 获取频道列表数据
 *  1.2 展示到下拉菜单中
 */
async function initChannel() {
  const { data } = await axios.get('/v1_0/channels')
  const channelStr = '<option value="" selected="">请选择文章频道</option>' + data.channels.map(item => {
    return `<option value="${item.id}" selected="">${item.name}</option>`
  }).join('')
  document.querySelector('.form-select').innerHTML = channelStr
}
initChannel()

封面上传

/**
 * 目标2:文章封面设置
 *  2.1 准备标签结构和样式
 *  2.2 选择文件并保存在 FormData
 *  2.3 单独上传图片并得到图片 URL 网址
 *  2.4 回显并切换 img 标签展示(隐藏 + 号上传标签)
 */
document.querySelector('.img-file').addEventListener('input', async function (e) {
  const file = e.target.files[0]
  const fd = new FormData()
  fd.append('image', file)
  const res = await axios.post('/v1_0/upload', fd)

  document.querySelector('.rounded').src = res.data.url
  document.querySelector('.rounded').classList.add('show')
  document.querySelector('.place').classList.add('hide')
})
// 点击封面重新上传图片
document.querySelector('.rounded ').addEventListener('click', function () {
  document.querySelector('.img-file').click()
})

发布文章

/**
 * 目标3:发布文章保存
 *  3.1 基于 form-serialize 插件收集表单数据对象
 *  3.2 基于 axios 提交到服务器保存
 *  3.3 调用 Alert 警告框反馈结果给用户
 *  3.4 重置表单并跳转到列表页
 */
document.querySelector('.send').addEventListener('click', async function () {
  try {
    const form = document.querySelector('.art-form')
    const formData = serialize(form, { hash: true, empty: true })
    delete formData.id
    // 手动补充封面数据
    formData.cover = {
      type: 1,
      images: [document.querySelector('.rounded').src]
    }
    // 发布文章
    const res = await axios({
      url: '/v1_0/mp/articles',
      method: 'post',
      data: formData
    })

    // 提示用户
    myAlert(true, '发布成功')
    // 清空表单 
    form.reset()
    // 富文本编辑器重置内容
    editor.setHtml('')
    document.querySelector('.rounded').src = ''
    document.querySelector('.rounded').classList.remove('show')
    document.querySelector('.place').classList.remove('hide')
    // 跳转页面
    setTimeout(() => {
      location.href = '../content/index.html'
    }, 1200)
  } catch (err) {
    console.dir(err)
    myAlert(false, err.response.data.message)
  }

})

文章列表展示

/**
 * 目标1:获取文章列表并展示
 *  1.1 准备查询参数对象
 *  1.2 获取文章列表数据
 *  1.3 展示到指定的标签结构中
 */
const params = {
  status: '', // 1-待审核, 2-审核通过, 不传为全部
  channel_id: '', // 频道id,不传为全部
  page: 1,
  per_page: 2
}
const onl oad = async () => {
  const res = await axios({
    url: '/v1_0/mp/articles',
    params
  })

  const listStr = res.data.results.map(item => {
    return `<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>
      <i class="bi bi-pencil-square edit"></i>
      <i class="bi bi-trash3 del"></i>
    </td>
  </tr > `
  }).join('')

  document.querySelector('.art-list').innerHTML = listStr
}

onLoad()

筛选功能

/**
 * 目标2:筛选文章列表
 *  2.1 设置频道列表数据
 *  2.2 监听筛选条件改变,保存查询信息到查询参数对象
 *  2.3 点击筛选时,传递查询参数对象到服务器
 *  2.4 获取匹配数据,覆盖到页面展示
 */
// 设置频道列表数据
async function initChannel() {
  const { data } = await axios.get('/v1_0/channels')
  const channelStr = '<option value="" selected="">请选择文章频道</option>' + data.channels.map(item => {
    return `<option value="${item.id}" selected="">${item.name}</option>`
  }).join('')
  document.querySelector('.form-select').innerHTML = channelStr
}
initChannel()

// 监听筛选条件改变
document.querySelectorAll('.form-check-input').forEach(item => {
  item.addEventListener('click', e => {
    params.status = e.target.value
  })
});

// 监听筛选条件改变
document.querySelector('.form-select').addEventListener('change', e => {
  params.channel_id = e.target.value
})

// 点击筛选时
document.querySelector('.sel-btn').addEventListener('click', () => {
  onl oad()
})

分页功能

/**
 * 目标3:分页功能
 *  3.1 保存并设置文章总条数
 *  3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
 *  3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
 */
// 点击下一页
document.querySelector('.next').addEventListener('click', function () {
  if (params.page < Math.ceil(tatalCount / params.per_page)) {
    params.page++
    onl oad()
    document.querySelector('.page-now').innerHTML = `第 ${params.page} 页`
  }
})
// 点击上一页
document.querySelector('.last').addEventListener('click', function () {
  if (params.page > 1) {
    params.page--
    onl oad()
    document.querySelector('.page-now').innerHTML = `第 ${params.page} 页`
  }
})

删除文章

/**
 * 目标4:删除功能
 *  4.1 关联文章 id 到删除图标
 *  4.2 点击删除时,获取文章 id
 *  4.3 调用删除接口,传递文章 id 到服务器
 *  4.4 重新获取文章列表,并覆盖展示
 *  4.5 删除最后一页的最后一条,需要自动向前翻页
 */
document.querySelector('.art-list').addEventListener('click', async function (e) {
  // 确定点击删除按钮
  if (e.target.classList.contains('del')) {
    const id = e.target.parentNode.dataset.id
    // 删除操作
    await axios({
      url: `/v1_0/mp/articles/${id}`,
      method: 'delete',
    })

    // 删除最后一页最后一条,自动翻页
    if (tatalCount % params.per_page === 1 && params.page > 1) {
      params.page--
    }

    // 刷新页面
    onl oad()
  }
})

编辑文章(回显)

/**
 * 目标5:编辑功能
 * 步骤:  点击编辑时,获取文章 id,跳转到发布文章页面, 传递文章 id 过去
 */
document.querySelector('.art-list').addEventListener('click', async function (e) {
  // 确定点击修改按钮
  if (e.target.classList.contains('edit')) {
    const id = e.target.parentNode.dataset.id
    // 页面跳转并传参
    location.href = `../publish/index.html?id=${id}`
  }
})
/**
   * 目标4:编辑-回显文章
   *  4.1 页面跳转传参(URL 查询参数方式)
   *  4.2 发布文章页面接收参数判断(共用同一套表单)
   *  4.3 修改标题和按钮文字
   *  4.4 获取文章详情数据并回显表单
   */
  ; (function () {
    const paramsObj = new URLSearchParams(location.search)
    paramsObj.forEach(async (value, name) => {
      // value: 查询参数值  name: 查询参数名
      // id存在,编辑文章
      if (name === 'id') {
        // 修改文字
        document.querySelector('.title').innerHTML = `<span>编辑文章</span>`
        document.querySelector('.send').innerHTML = '编辑'
        // 查询详情
        const res = await axios({
          url: `/v1_0/mp/articles/${value}`,
        })
        console.log(res);
        // 转存数据(精简后台冗余数据)
        const resObj = {
          id: res.data.id,  // 文章id
          channel_id: res.data.channel_id, // 频道
          content: res.data.content, // 文章内容
          cover: res.data.cover.images[0],  // 封面图片
          title: res.data.title, //标题
        }
        // 回显数据
        Object.keys(resObj).forEach(key => {
          if (key === 'cover') {
            // 单独处理图片回显
            document.querySelector('.rounded').src = resObj[key]
            document.querySelector('.rounded').classList.add('show')
            document.querySelector('.place').classList.add('hide')
          } else if (key === 'content') {
            // 单独处理文章内容
            editor.setHtml(resObj[key])
          } else {
            // 无需单独处理
            // 用数据对象属性名, 作为标签name属性选择器的值, 匹配标签
            document.querySelector(`[name=${key}]`).value = resObj[key]
          }
        })
      }
    })

  })()

编辑文章(保存)

/**
 * 目标5:编辑-保存文章
 *  5.1 判断按钮文字,区分业务(因为共用一套表单)(添加的事件中也要加判断)
 *  5.2 调用编辑文章接口,保存信息到服务器
 *  5.3 基于 Alert 反馈结果消息给用户
 */
document.querySelector('.send').addEventListener('click', async function () {
  if (document.querySelector('.send').innerHTML !== '编辑') return

  try {
    const form = document.querySelector('.art-form')
    const formData = serialize(form, { hash: true, empty: true })

    await axios({
      url: `/v1_0/mp/articles/${formData.id}`,
      method: 'put',
      data: {
        ...formData,
        cover: {
          type: document.querySelector('.rounded').src ? 1 : 0,
          images: [document.querySelector('.rounded').src]
        }
      }
    })
    myAlert(true, '编辑成功')
  } catch (error) {
    console.dir(error)
    myAlert(false, error.response.data.message)
  }

})

退出登录

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

标签:axios,const,admin,querySelector,hm,document,data,id,头条
From: https://blog.csdn.net/CSDN20221005/article/details/139367617

相关文章

  • [数据结构+二叉树+B-Tree和红黑树与B+Tree与hashMap原理+ concurrentHashMap的原理]析
    目录数据结构:你了解过哪些数据结构:这些数据结构的优缺点:二叉树:特点:二叉树适合做磁盘存储吗: 缺点:B-Tree:b-树的查找过程:思考:特点:B+Tree: B+树搜索过程与B树的查询过程没有区别。但实际上有三点不一样:(B+Tree优势)简述B+Tree:不经历IO的情况下,可以直接......
  • Java--hashmap如何根据value排序
    java中map根据value排序在Java中,Map是一种非常常用的数据结构,它通过键值对的形式存储数据,Map本身是无序的,但是在实际应用中,我们有时需要根据Map的值来进行排序,本文将介绍如何使用Java中的Map来实现根据Value排序。Map转TreeMap在Java中,可以使用TreeMap来根据HashMap的值......
  • 将 Spring data JPA jars 升级到 v3.3.0 后出现 java.lang.NoSuchMethodError 错误
    在我将springdatajpa和commonsjar升级到我们产品的3.3.0版本后,我遇到了这个错误。Causedby:java.lang.NoSuchMethodError:'voidcom.org.application.server.services.workorder.database.dao.UpdateWorkOrder._persistence_checkFetchedForSet(java.lang.String)'......
  • 如何初始化 FIrebase 云函数,以便使用凭据和 JSON 验证 Firebase Admin SDK 服务账户?
    我觉得我已经阅读了所有可用的资料,但我仍然无法理解这一点。我非常喜欢Google的产品,但有时其文档的简洁性令人头疼。我阅读了这个令人难以置信的雄辩答案,这个答案的作者和我一样毫无头绪,但他觉得有必要写一本循序渐进的儿童指南。不幸的是,他的回答过于针对他的项目,而不是我的项......
  • 深入探索Java HashMap底层源码:结构、原理与优化
    引言简述HashMap在Java集合框架中的地位及其应用场景。阐明学习HashMap底层原理的重要性,特别是在面试、性能调优和解决并发问题方面的价值。1.HashMap基础概念数据结构:介绍HashMap的核心——哈希表,包括数组加链表/红黑树的结构。线程安全性:强调HashMap是非线程安全的,以及在......
  • 翻译《The Old New Thing》- Consequences of the scheduling algorithm: Low priorit
    Consequencesoftheschedulingalgorithm:Lowprioritythreadscantake100%CPU-TheOldNewThing(microsoft.com)https://devblogs.microsoft.com/oldnewthing/20071220-00/?p=24093 RaymondChen 2007年12月20日调度算法的控制:低优先级线程也可能占用100%的......
  • 通过admin监视任务
    通过admin监视任务在控制台监控任务执行情况,还不是很方便,最好是能够通过web界面看到任务的执行情况,如有多少任务在执行,有多少任务执行失败了等这个Celery也是可以做到了,就是将任务执行结果写到数据库中,通过web界面显示出来。安装插件pipinstalldjango-celery-results注册......
  • 通过admin配置定时任务
    通过admin配置定时任务安装包pipinstalldjango-celery-beat#使用这个的前提是你已经安装了其他包了pipinstallDjangopipinstallcelerypipinstallredispipinstalleventlet去app中注册INSTALLED_APPS=[ #其他包"django_celery_beat",]屏蔽掉原来......
  • 西门子HMI在线控制的问题
    最近遇到博图装好了,软件装成功后无法在线操控HMI的问题。解决如下图。1.控制面板中打开PG/PC接口,更改为电脑网口。 2.博图中打开仿真按钮,“连接已建立”则说明在线控制HMI成功。 ......
  • 【数据驱动】【航空航天结构的高效损伤检测技术】一种数据驱动的结构健康监测(SHM)方法,
     ......