项目-新闻头条-数据管理平台-ajax综合案例

愿许秋风知我意,解我心中意难平。

项目介绍

项目准备

推荐使用,

每个程序员都有自己的管理方式。

验证码登录

HTML结构:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css">
  <link rel="stylesheet" href="./index.css">
  <title>黑马头条-数据管理平台</title>
</head>

<body>
  <!-- 警告框 -->
  <div class="alert info-box">
    操作结果
  </div>
  <!-- 登录页面 -->
  <div class="login-wrap">
    <div class="title">黑马头条</div>
    <div>
      <form class="login-form">
        <div class="item">
          <input type="text" class="form-control" name="mobile" placeholder="请输入手机号" value="13888888888">
        </div>
        <div class="item">
          <input type="text" class="form-control" name="code" placeholder="默认验证码246810" value="246810">
        </div>
        <div class="item">
          <button type="button" class="btn btn-primary btn">登 录</button>
        </div>
      </form>
    </div>
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.4/axios.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
  <script src="../../lib/form-serialize.js"></script>
  <script src="../../utils/request.js"></script>
  <script src="../../utils/alert.js"></script>
  <script src="./index.js"></script>
</body>

</html>

1.为什么要提取公共前缀地址(基地址),因为公司业务可能会更换服务器,如果你不想一条一条地修改请求地址的话。

后续使用axios时,url不需要再写前缀。

2.请求成功与失败

成功返回message“OK”还有token等(作用后续讲)

输入错误验证码,请求失败的时候,返回message“验证码不正确”

message可用于提示框文字显示

3.提示框控制

之前的案例已经做过了。把之前封装的函数放到utils下,

alert.js

// 弹窗插件
// 需要先准备 alert 样式相关的 DOM
/**
 * BS 的 Alert 警告框函数,2秒后自动消失
 * @param {*} isSuccess 成功 true,失败 false
 * @param {*} msg 提示消息
 */
function myAlert(isSuccess, msg) {
  const myAlert = document.querySelector('.alert')
  myAlert.classList.add(isSuccess ? 'alert-success' : 'alert-danger')
  myAlert.innerHTML = msg
  myAlert.classList.add('show')

  setTimeout(() => {
    myAlert.classList.remove(isSuccess ? 'alert-success' : 'alert-danger')
    myAlert.innerHTML = ''
    myAlert.classList.remove('show')
  }, 2000)
}

成功的提示

错误的提示

验证码登录-流程

token 的介绍

token的正确打开方式:

判断无token令牌字符串,则强制跳转到登录页

强制访问内容页面

被踢回登录页面

登录成功后保存token到本地

登录成功后跳转到内容页面

延时跳转,展示alert警告框

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

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

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

第二个函数的应用场景非常少,一般也可以不写第二个函数体

请求拦截器统一携带token令牌

如此使用

内容管理页面和发布文章页面的html文件都引入了auth.js

个人信息显示成功

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

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

Axios中文文档 | Axios中文网

请求拦截器,响应拦截器,拦截器:在做出对应动作前(在执行发送请求或 响应回到then/catch之前)进行拦截,补充额外的统一的操作

制作错误:修改token值(不可以删除,前端只判断有无token,无token将跳转登录界面;后端判断有效性)

console.dir打印错误信息

在response中找到响应状态码

对响应状态码进行判断 === 401 (注意使用可选链操作符):

对401身份验证失败情况做出处理:

①错误提示②清理过期token③跳转登录界面

TEST

先登录进去

修改token

点击确定后跳转,缓存被清空

实操代码(request.js):

// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'

// 添加请求拦截器
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.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么,例如:统一对 401 身份验证失败情况做出处理
  console.dir(error)
  if (error?.response?.status === 401) {
    alert('身份验证失败,请重新登录')
    localStorage.clear()
    location.href = '../login/index.html'
  }
  return Promise.reject(error);
});

优化-axios 响应结果

背景:axios中返回的result(请求成功的结果对象)如右上,为axios内部封装的结果对象,它把服务器返回的数据对象挂载到了data属性下。

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

注意:响应拦截器成功函数中的response参数即result结果对象

实践代码:

// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'

// 添加请求拦截器
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.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()
    location.href = '../login/index.html'
  }
  return Promise.reject(error);
});

修改之前使用服务器返回数据的写法(去掉一个.data)

测试效果:

修改代码后,个人信息正常展示

登录后token值正常保存

发布文章-富文本编辑器

什么叫富文本呢?——带样式、多格式的文本

使用插件wangEditor实现富文本编辑器

 123步骤在官方文档-快速开始:wangEditor

JS创建编辑器语句逐句解析:(独立存放于editor.js)

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

// 编辑器配置对象
const editorConfig = {
  // 占位提示文字
  placeholder: '发布文章内容...',
  // 编辑器变化时回调函数
  onChange(editor) {
    // 获取富文本内容
    const html = editor.getHtml()
    // 也可以同步到 <textarea>
    // 为了后续快速收集整个表单内容做铺垫
    document.querySelector('.publish-content').value = 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,
  // 配置集成模式
  mode: 'default', // or 'simple'
})

4.监听内容改变,保存在隐藏文本域(便于后期收集)

因为该富文本编辑器输入内容的地方实际上是p标签,而不是在textarea(隐藏)内

浏览器中检查可发现

需要准备textarea用于收集文本内容,将其隐藏

发布文章-频道列表

编写代码之前要想一想,这段代码要不要复用呢?

发布文章页面中需要展示频道列表

内容管理页面也需要展示频道列表

完整代码:

发布文章-封面设置

目标:点击加号框(或“封面”文字)上传图片作为封面

input type属性值为file 的表单元素很难改成加号框

所以,加号盒子(label,所以监听的是input元素的change事件),img封面盒子(img)和input表单元素(input)为三个标签

封面回显的时候,需要使加号盒子隐藏,img显示(rounded类选择器使其隐藏)

下面是提前写好的有关隐藏和显示的相关样式

上传图片接口要求传入表单数据FormData

上传请求的响应结果包含图片的URL地址,用于回显

优化:点击 img 可以重新切换封面

实践代码(publish/index.js):

/**
 * 目标1:设置频道下拉菜单
 *  1.1 获取频道列表数据
 *  1.2 展示到下拉菜单中
 */
// 1.1 获取频道列表数据
async function setChannleList() {
  const res = await axios({
    url: '/v1_0/channels'
  })
  // 1.2 展示到下拉菜单中
  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
}
// 网页运行后,默认调用一次
setChannleList()

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

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

发布文章-收集并保存

实现代码:

/**
 * 目标3:发布文章保存
 *  3.1 基于 form-serialize 插件收集表单数据对象
 *  3.2 基于 axios 提交到服务器保存
 *  3.3 调用 Alert 警告框反馈结果给用户
 *  3.4 重置表单并跳转到列表页
 */
// 3.1 基于 form-serialize 插件收集表单数据对象
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 })
  // 发布文章的时候,不需要 id 属性,所以可以删除掉(id 为了后续做编辑使用)
  delete data.id
  console.log(data)
  // 自己收集封面图片地址并保存到 data 对象中
  data.cover = {
    type: 1, // 封面类型
    images: [document.querySelector('.rounded').src] // 封面图片 URL 网址
  }

  // 3.2 基于 axios 提交到服务器保存
  try {
    const res = await axios({
      url: '/v1_0/mp/articles',
      method: 'POST',
      data: data
    })
    // 3.3 调用 Alert 警告框反馈结果给用户
    myAlert(true, '发布成功')
    // 3.4 重置表单并跳转到列表页
    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) {
    myAlert(false, error.response.data.message)
  }
})

await错误捕捉,使用try...catch语句进行

错误不捕捉:

捕捉错误并打印

内容管理-文章列表展示

实践代码(content/index.js):

/**
 * 目标1:获取文章列表并展示
 *  1.1 准备查询参数对象
 *  1.2 获取文章列表数据
 *  1.3 展示到指定的标签结构中
 */
// 1.1 准备查询参数对象
const queryObj = {
  status: '', // 文章状态(1-待审核,2-审核通过)空字符串-全部
  channel_id: '', // 文章频道 id,空字符串-全部
  page: 1, // 当前页码
  per_page: 2 // 当前页面条数
}
let totalCount = 0 // 保存文章总条数

// 获取并设置文章列表
async function setArtileList() {
  // 1.2 获取文章列表数据
  const res = await axios({
    url: '/v1_0/mp/articles',
    params: queryObj
  })
  // 1.3 展示到指定的标签结构中
  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

内容管理-筛选功能

实践代码:

/**
 * 目标2:筛选文章列表
 *  2.1 设置频道列表数据
 *  2.2 监听筛选条件改变,保存查询信息到查询参数对象
 *  2.3 点击筛选时,传递查询参数对象到服务器
 *  2.4 获取匹配数据,覆盖到页面展示
 */
// 2.1 设置频道列表数据
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
}
setChannleList()
// 2.2 监听筛选条件改变,保存查询信息到查询参数对象
// 筛选状态标记数字->change事件->绑定到查询参数对象上
document.querySelectorAll('.form-check-input').forEach(radio => {
  radio.addEventListener('change', e => {
    queryObj.status = e.target.value
  })
})
// 筛选频道 id -> change事件 -> 绑定到查询参数对象上
document.querySelector('.form-select').addEventListener('change', e => {
  queryObj.channel_id = e.target.value
})
// 2.3 点击筛选时,传递查询参数对象到服务器
document.querySelector('.sel-btn').addEventListener('click', () => {
  // 2.4 获取匹配数据,覆盖到页面展示
  setArtileList()
})

内容管理-分页功能

实践代码:

/**
 * 目标3:分页功能
 *  3.1 保存并设置文章总条数
 *  3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
 *  3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
 */
// 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
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()
  }
})
// 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.last').addEventListener('click', e => {
  // 大于 1 的时候,才能翻到上一页
  if (queryObj.page > 1) {
    queryObj.page--
    document.querySelector('.page-now').innerHTML = `第 ${queryObj.page} 页`
    setArtileList()
  }
})

内容管理-删除功能

实践代码:

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

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

    // 4.4 重新获取文章列表,并覆盖展示
    setArtileList()
  }
})

内容管理-删除最后一条

代码:

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

实践代码:

/**
   * 目标4:编辑-回显文章
   *  4.1 页面跳转传参(URL 查询参数方式)
   *  4.2 发布文章页面接收参数判断(共用同一套表单)
   *  4.3 修改标题和按钮文字
   *  4.4 获取文章详情数据并回显表单
   */
  ; (function () {
    // 4.2 发布文章页面接收参数判断(共用同一套表单)
    const paramsStr = location.search
    const params = new URLSearchParams(paramsStr)
    params.forEach(async (value, key) => {
      // 当前有要编辑的文章 id 被传入过来
      if (key === 'id') {
        // 4.3 修改标题和按钮文字
        document.querySelector('.title span').innerHTML = '修改文章'
        document.querySelector('.send').innerHTML = '修改'
        // 4.4 获取文章详情数据并回显表单
        const res = await axios({
          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]
          }
        })
      }
    })
  })();

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

给按钮绑定了两个函数,都会走一遍

但需要进行判断

/**
 * 目标5:编辑-保存文章
 *  5.1 判断按钮文字,区分业务(因为共用一套表单)
 *  5.2 调用编辑文章接口,保存信息到服务器
 *  5.3 基于 Alert 反馈结果消息给用户
 */
document.querySelector('.send').addEventListener('click', async e => {
  // 5.1 判断按钮文字,区分业务(因为共用一套表单)
  if (e.target.innerHTML !== '修改') return
  // 修改文章逻辑
  const form = document.querySelector('.art-form')
  const data = serialize(form, { hash: true, empty: true })
  // 5.2 调用编辑文章接口,保存信息到服务器
  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)
  }
})

退出登录

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/306733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线性回归(Linear Regression)

什么是机器学习 线性回归是一种用于建立变量之间线性关系的统计模型。在简单线性回归中&#xff0c;我们考虑一个自变量和一个因变量的关系&#xff0c;而在多元线性回归中&#xff0c;我们考虑多个自变量和一个因变量之间的关系。 简单线性回归 简单线性回归模型可以表示为…

vue购物车案例、v-model进阶、与后端交互

一 购物车案例 - 结算 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>购物车结算</title><script src"https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></scr…

Real Fire Smoke

资产使用高分辨率的动画Spritesheet,并结合了Shuriken粒子系统。具有3种风格的火焰和爆炸。 效果分为7类。 主要特点: - 3种火 - 预制板共计80块 - 5种爆炸声 - 3个循环的火灾声音 - 7张火焰和烟雾的精灵图 - 8x8 帧的 spritesheet,分辨率在 2048 到 8192 像素之间 效果类别…

Spring cloud聚合父工程project

文章目录 本次微服务版本一. 新建父工程project1.1设置字符集utf-81.2注解生效激活1.3. Java8编译版本 二. 父工程 pom.xml 本次微服务版本 一. 新建父工程project 1.1设置字符集utf-8 1.2注解生效激活 1.3. Java8编译版本 二. 父工程 pom.xml <?xml version"1.0&quo…

【深度学习每日小知识】Logistic Loss 逻辑回归:损失和正则化

逻辑回归的损失函数 线性回归的损失函数是平方损失。逻辑回归的损失函数是对数损失&#xff0c;定义如下&#xff1a; L o g L o s s ∑ ( x , y ) ∈ D − y log ⁡ ( y ′ ) − ( 1 − y ) log ⁡ ( 1 − y ′ ) LogLoss\sum_{(x,y)\in D}-y\log(y)-(1-y)\log(1-y) LogLoss…

漫谈与人类智能相关数学知识的不足之处

客观地说&#xff0c;没有数学就没有当前的大语言模型、多模态大模型&#xff0c;甚至压根就没有人工智能。对人工智能而言&#xff0c;数学就是“天”&#xff01;但是&#xff0c;对于人类智能而言&#xff0c;数学虽然起到了很重要的作用&#xff0c;同样也起到了阻碍作用&a…

Application为啥不能作为Dialog的context?

大家好&#xff0c;相信大家在使用Dialog时&#xff0c;都有一个非常基本的认知&#xff1a;就是Dialog的context只能是Activity&#xff0c;而不能是Application&#xff0c;不然会导致弹窗崩溃&#xff1a; 这个Exception几乎属于是每个Android开发初学者都会碰到的&#xff…

FFmpeg获取音视频流信息

文章目录 前言一、需求二、源码三、运行结果 前言 本文记录用 FFmpeg 获取视频流音频流的信息&#xff08;编码格式、分辨率、帧率、播放时长…&#xff09;&#xff0c;所用的工程基于上个博客编译成功的工程&#xff1a;使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c 一、需求…

扩展边界opencv

扩展图像的边缘&#xff08;如上边增加50像素&#xff09;通常是通过添加额外的像素行来实现的 使用cv2.copyMakeBorder函数 valueborder_color指定了边框的颜色 import cv2 import numpy as np# 读取图像 image cv2.imread(th.jpg)# 设置边框宽度 top_border_width 50 # …

序列模型(4)—— Scaling Laws

本文介绍 LLM 训练过程中重要的 Scaling Laws&#xff0c;这是一个经验规律&#xff0c;指出了固定训练成本&#xff08;总计算量FLOPs&#xff09; C C C 时&#xff0c;如何调配模型规模&#xff08;参数量&#xff09; N N N 和训练 Token 数据量 D D D&#xff0c;才能实现…

【Emgu.CV教程】4.3、无缝融合应用之SeamlessClone()

SeamlessClone()函数才是真正的无缝克隆&#xff0c;它可以将一张小一点的图片&#xff0c;复制到另一张大一点的图片中&#xff0c;并且复制的位置可以用户自己定义&#xff0c;先看一下它的函数介绍&#xff1a; public static void SeamlessClone(IInputArray src, // 输入…

【STM32】| 01——常用外设 | USART

系列文章目录 【STM32】| 01——常用外设 | USART 失败了也挺可爱&#xff0c;成功了就超帅。 文章目录 前言1. 基础理论1.1 并行通信和串行通信1.2 同步通信和异步通信1.3 单工/半双工/全双工1.4 电平信号(RS232/TTL)和差分信号(RS485)1.5 端口(COM) 2. 串口理论2.1 串口物理…

Android - CrashHandler 全局异常捕获器

官网介绍如下&#xff1a;Thread.UncaughtExceptionHandler (Java Platform SE 8 ) 用于线程因未捕获异常而突然终止时调用的处理程序接口。当线程由于未捕获异常而即将终止时&#xff0c;Java虚拟机将使用thread . getuncaughtexceptionhandler()查询该线程的UncaughtExceptio…

cisp难不难?cisp如何备考通过率高?

*CISP 全称为Certified Information Security Professional&#xff0c;是国际上广受欢迎的信息安全专业认证之一。 对于许多信息安全领域的从业者来说&#xff0c;CISP认证是他们职业生涯中的重要一步。那么&#xff0c;CISP难不难呢?如何备考通过率更高呢?接下来&#xf…

electron自定义窗口和右键菜单样式

前言 electron默认沿用系统UI&#xff0c;并没有提供很多接口供使用者定制样式&#xff0c;如果想要完全自定义的样式&#xff0c;目前我能想到的方案只能是通过前端自定义样式&#xff0c;然后通过进程通信来实现系统基础功能&#xff1a;最大/小化、关闭、拖动窗口等。 效果…

关于java的冒泡排序

关于java的冒泡排序 我们前面的文章中了解到了数组的方法类Arrays&#xff0c;我们本篇文章来了解一下最出名的排序算法之一&#xff0c;冒泡排序&#xff01;&#x1f600; 冒泡排序的代码还是非常简单的&#xff0c;两层循环&#xff0c;外层冒泡轮数&#xff0c;里层依次比…

伺服电机:编码器原理与分类

什么是编码器&#xff1f; 编码器是将旋转位置的改变转换为电气信号。 编码器是伺服系统闭环控制不可缺少的部件&#xff0c;编码器应用在轴的闭环控制和大多数的自动化控制中。编码器为闭环控制提供位置或速度的实际测量值。 一、编码器的分类 从编码器的原理和产生的信号类…

Cypress.io:快速简单可靠的浏览器测试工具 | 开源日报 No.142

cypress-io/cypress Stars: 45.5k License: MIT Cypress.io 是一个快速、简单和可靠的浏览器测试工具&#xff0c;可以用于任何在浏览器中运行的内容。它支持 Mac、Linux 和 Windows 系统&#xff0c;并提供了安装指南。 hrvach/deskhop Stars: 4.1k License: GPL-3.0 DeskH…

ansible基础概念

一、【写在前面】 前面断更了几天&#xff0c;笔者被流感给干倒了&#xff0c;去拍了个核磁&#xff0c;给我脑子干成脱髓鞘了&#xff0c;也不知道是之前新冠导致的还是如何&#xff0c;哎要变成愚蠢的低级动物了……稍微恢复一点体力&#xff0c;今天赶快来博客水一水文章。…

代码随想录-刷题第五十二天

300. 最长递增子序列 题目链接&#xff1a;300. 最长递增子序列 思路&#xff1a;动态规划五步曲&#xff1a; dp[i]表示从0到i&#xff0c;以nums[i]结尾的最长递增子序列的长度。 递推公式&#xff1a;if(nums[i]>nums[j]) dp[i] max(dp[i], dp[j] 1) 位置i的最长升序…