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
      // axios 公共配置
      // 基地址
      axios.defaults.baseURL = 'http://geek.itheima.net'
  2. 收集手机号和验证码数据
  3. 基于axios调用验证码登录接口
  4. 使用Bootstrap的Alert警告框反馈结果给用户

 index.js

/**
 * 目标1:验证码登录
 * 1.1 在 utils/request.js 配置 axios 请求基地址
 * 1.2 收集手机号和验证码数据
 * 1.3 基于 axios 调用验证码登录接口
 * 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
 */

// 1.2 收集手机号和验证码数据

document.querySelector('.btn').addEventListener('click', () => {
    const form = document.querySelector('.login-form')
    const data = serialize(form, { hash: true, empty: true})
    console.log(data)
    // 1.3 基于 axios 调用验证码登录接口
    axios({
        url: '/v1_0/authorizations',
        method: 'POST',
        data: data
    }).then( result => {
        // 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
        myAlert(true, '登录成功')
        console.log(result)
    }).catch(error => {
        myAlert(false, error.response.data.message)
        console.dir(error.response.data.message)
    })
})

验证码登录流程

4.token的介绍

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

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

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

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

5.token的使用

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

步骤:

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

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

// 权限插件(引入到了除登录页面,以外的其他所有页面)
/**
 * 目标1:访问权限控制
 * 1.1 判断无 token 令牌字符串,则强制跳转到登录页
 * 1.2 登录成功后,保存 token 令牌字符串到本地,并跳转到内容列表页面
 */
// 1.1 判断无 token 令牌字符串,则强制跳转到登录页
const token = localStorage.getItem('token')
if (!token) {
    location.href = '../login/index.html'
}
/**
 * 目标1:验证码登录
 * 1.1 在 utils/request.js 配置 axios 请求基地址
 * 1.2 收集手机号和验证码数据
 * 1.3 基于 axios 调用验证码登录接口
 * 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
 */


// 1.2 收集手机号和验证码数据

document.querySelector('.btn').addEventListener('click', () => {
    const form = document.querySelector('.login-form')
    const data = serialize(form, { hash: true, empty: true})
    console.log(data)
    // 1.3 基于 axios 调用验证码登录接口
    axios({
        url: '/v1_0/authorizations',
        method: 'POST',
        data: data
    }).then( result => {
        // 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
        myAlert(true, '登录成功')
        console.log(result)

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

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

token的作用?

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

token的注意:

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

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

需求:设置用户昵称

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

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

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

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

对应代码

/**
 * 目标2:设置个人信息
 * 2.1 在 utils/request.js 设置请求拦截器,统一携带 token
 * 2.2 请求个人信息并设置到页面
 */
// 2.2 请求个人信息并设置到页面
axios({
    url: '/v1_0/user/profile'
}).then(result => {
    console.log(result)
    const username = result.data.data.name
    document.querySelector('.nick-name').innerHTML = username
})
// 添加请求拦截器
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);
})

总结

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

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

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

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

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

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

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

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

总结

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

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

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

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

 8.优化-axios响应结果

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

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

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

对应代码

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

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

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

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

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

使用: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('.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'
})

10.发布文章-频道列表

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

步骤:

  1. 获取频道列表数据
  2. 展示到下拉菜单中
/**
 * 目标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('')
    console.log(htmlStr)
    document.querySelector('.form-select').innerHTML = htmlStr
}
// 网页运行后,默认调用一次
setChannleList()

11.发布文章-封面设置

目标:文章封面的设置

步骤:

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

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

/**
 * 目标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
    })
    console.log(res)
    // 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()
})

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

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

步骤:

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

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

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

/**
 * 目标3:发布文章保存
 *  3.1 基于 form-serialize 插件收集表单数据对象
 *  3.2 基于 axios 提交到服务器保存
 *  3.3 调用 Alert 警告框反馈结果给用户
 *  3.4 重置表单并跳转到列表页
 */
// 3.1 基于 form-serialize 插件收集表单数据对象
document.querySelector('.send').addEventListener('click', async e => {
    const form = document.querySelector('.art-form')
    const data = serialize(form, { hash: true, empty: true})
    console.log(data)
    // 发布文章的时候,不需要 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) {
        console.dir(error)
        myAlert(false, error.response.data.message)
    }
    
})

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

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

步骤:

1.准备查询参数对象

2.获取文章列表数据

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

/**
 * 目标1:获取文章列表并展示
 *  1.1 准备查询参数对象
 *  1.2 获取文章列表数据
 *  1.3 展示到指定的标签结构中
 */
// 1.1 准备查询参数对象
const queryObj = {
    status: '', // 文章状态(1-待审核,2-审核通过)空字符串-全部
    channel_id: '', // 文章频道id,空字符串-全部
    page: 1, // 当前页码
    per_page: 2 // 当前页面条数
}
async function setArtileList() {
    // 1.2 获取文章列表数据
    const res = await axios({
        url: '/v1_0/mp/articles',
        params: queryObj
    })
    console.log(res)
    // 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>
      <i class="bi bi-pencil-square edit"></i>
      <i class="bi bi-trash3 del"></i>
    </td>
  </tr>
    `).join('')
    // console.log(htmlStr)
    document.querySelector('.art-list').innerHTML = htmlStr
}
setArtileList()

14.内容管理-筛选功能

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

步骤:

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

15.内容管理-分页功能

内容管理-分页功能

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

步骤:

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

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

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

/**
 * 目标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()
})

16.内容管理-删除功能

目标:完成删除文章功能

步骤:

  1. 关联文章id到删除图标
  2. 点击删除时,获取文章id
  3. 调用删除接口,传递文章id到服务器
  4. 重新获取文章列表,并覆盖展示
/**
 * 目标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
        console.log(delId)
        // 4.3 调用删除接口,传递文章 id 到服务器
        const res = await axios({
            url:  `v1_0/mp/articles/${delId}` ,
            method: 'DELETE'
        })
        console.log(res)
        // 4.4 重新获取文章列表,并覆盖展示
        setArtileList()
    }
})

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

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

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

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

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

        // 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 }页`
        }

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

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

步骤:

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

对应代码:

/**
 * 目标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]
                }
            })
        }
    })
})();

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

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

步骤:

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

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

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

/**
 * 目标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})
    // console.log(data)
    // 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)
    }
})

20.退出登录

目标:完成退出登录效果

步骤:

1.绑定点击事件

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

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

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

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

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

相关文章

【韩国】UE5的MetaHuman确实可以导入Blender进行编辑。

UE5的MetaHuman确实可以导入Blender进行编辑。根据网络上的信息&#xff0c;你可以将MetaHuman模型导出为FBX文件&#xff0c;然后在Blender中进行修改。修改完成后&#xff0c;你可以将其重新导入到Unreal Engine 5中4。请注意&#xff0c;当你在Blender中编辑模型时&#xff…

第12章 最佳的UI体验——Material Design实战

第12章 最佳的UI体验——Material Design实战 其实长久以来&#xff0c;大多数人都认为Android系统的UI并不算美观&#xff0c;至少没有iOS系统的美观。以至于很多IT公司在进行应用界面设计的时候&#xff0c;为了保证双平台的统一性&#xff0c;强制要求Android端的界面风格必…

使用Shell终端访问Linux

一、实验目的 1、熟悉Linux文件系统访问命令&#xff1b; 2、熟悉常用 Linux Shell的命令&#xff1b; 3、熟悉在Linux文件系统中vi编辑器的使用&#xff1b; 4、进一步熟悉虚拟机网络连接模式与参数配置&#xff01; 二、实验内容 1、使用root帐号登陆到Linux的X-windows…

artifactory配置docker本地存储库

​一、概述 本地 Docker 存储库是我们部署和托管内部 Docker 镜像的位置。实际上&#xff0c;它是一个 Docker 注册表&#xff0c;能够托管的 Docker 镜像的集合。通过本地存储库&#xff0c;你可以保存、加载、共享和管理自己的 Docker 镜像&#xff0c;而无需依赖于外部的镜像…

API提取IP

API代理作为IP代理的一项重要业务&#xff0c;在绕开地域网络限制&#xff0c;提高作业效率等方面提供强大的技术支持。它能够帮助用户快速实现软件与软件间的交流&#xff0c;无障碍连通不同应用程序逻辑开发的系统应用。API代理用途范围广泛&#xff0c;如使用API提取代理IP、…

AcWing 1264. 动态求连续区间和 ,详细讲解线段树与树状数组(Python,篇一)

本篇博客主要介绍一下什么是线段树与树状数组&#xff0c;它们的原理与结构是怎样&#xff0c;并通过实际题型来讲解&#xff0c;篇一主要讲解线段树&#xff0c;下一篇博客讲解树状数组。 线段树与树状数组的区别和特点&#xff1a; 它们的时间复杂度都是O(nlogn) 存储方式…

使用navicate演示在 PostgreSQL 中使用 for 循环语句

1、简单循环示例 do $$ beginfor cnt in 1..10 loopraise notice cnt: %, cnt;end loop; end; $$ navicate中执行 2、循环查询 do $$ declare_record record; beginfor _record in (SELECT version,description FROM flyway_schema_history ORDER BY installed_rank desc li…

ios CI/CD 持续集成 组件化专题一 iOS 将图片打包成bundle

一、 创建 选择 macos 下的Bundle 二 、取名点击下一步 三、Base SDK 选择ios 四 、Build Active Architecture Only 五、Installation后面的内容删除 六、Skip Install 选择NO 七、Strip Debug Symbols During Copy 中"Release"项设置为 "YES" 八、COM…

书生·浦语 大模型(学习笔记-7)LMDeploy 量化部署 LLM-VLM 实践

目录 一、模型的部署 二、模型部署面临的问题 三、如何解决&#xff08;两种方法&#xff09; 四、LMDeploy相关知识 创建conda环境(漫长的等待) 五、使用LMDeploy与模型对话 六、设置最大KV Cache缓存大小 七、W4A16量化 八、客户端连接API服务器 一、模型的部署 二、…

leetcode-二叉树的镜像-91

题目要求 思路1 1.遍历一遍二叉树&#xff0c;将左边的结点对应创建一个右边的结点 2.用此方法空间复杂度O(n)&#xff0c;并不是最优 思路2 1.将一个结点的左右子树进行交换&#xff0c;如果左子树还有左右结点&#xff0c;就再交换左子树的左右结点&#xff0c;以此递归下去…

【树莓派】yolov5 Lite,目标检测,行人检测入侵报警

延续之前的程序&#xff1a; https://qq742971636.blog.csdn.net/article/details/138172400 文章目录 播放声音pygame不出声音怎么办&#xff08;调节音量&#xff09;树莓派上的音乐播放器&#xff08;可选&#xff09;命令行直接放歌&#xff08;尝试放mp3歌曲&#xff09; …

linux 上 jps 列出一堆 jar,如何快速定位 jar 文件启动位置?

例如&#xff0c;在 /data下有一个 xxx.jar &#xff0c;如果是通过 "java -jar /data/xxx.jar" 方式启动&#xff0c;则 jps会列出的名字中带 xxx.jar&#xff0c;这时再 "ps -ef | grep xxx.jar" 就会列出 更详细的信息&#xff0c;例如 "java -ja…

[iOS]CocoaPods安装和使用

1.了解brew、rvm、ruby、gem、cocaspods之间的关系 在 macOS 环境中&#xff0c;Brew、RVM、Ruby、Gem 和 CocoaPods 之间存在以下关系&#xff1a; Homebrew (Brew)&#xff1a;Homebrew 是 macOS 上的包管理器&#xff0c;用于安装和管理各种开源软件包。它使您能够轻松地从…

Windows 本地直接使用 SSH,SFTP 以及 SFTP下载文件到 Windows/mac 本地或上传(没有客户端时)

windows 本地打开 ssh 以及 sftp 等的方式 1.win(windows图标那个键) r 直接搜 然后从打开的位置运行 如果是打开 sftp 前面的 ssh 换一下成sftp 就行 直接从地址栏输入也可以直接转过去 通过 windows 的工具直接访问 sftp 后将文件下载到自己的windows 或 mac 上 先通过…

微软在汉诺威工业博览会上推出新制造业Copilot人工智能功能,强化Dynamics 365工具集

在近日于德国汉诺威举行的盛大工业博览会上&#xff0c;微软向全球展示了其最新推出的制造业人工智能功能&#xff0c;这些功能以Dynamics 365工具集为核心&#xff0c;旨在通过先进的AI技术为制造业带来前所未有的变革。 此次推出的新功能中&#xff0c;最为亮眼的是支持AI的…

Linux之ebpf(1)基础使用

Linux之ebpf(1)基础使用 Author: Once Day Date: 2024年4月20日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可以参考专栏&#xff1a;Linux基础知识_Once-D…

Linux系统网络---DNS域名解析服务

目录 一、DNS的简介 DNS系统的分布式数据结构&#x1f447; DNS系统类 两种查询方式 二.正向解析实验 1.先关闭防火墙、selinux 2.安装bind 3.查看配置、修改配置 4.修改区域配置文件 正向解析&#x1f447; 反向解析&#x1f447; 5.修改 正向解析&#x1f…

装饰品模式介绍

装饰器模式是一种结构型设计模式&#xff0c;它允许用户在不改变现有对象的情况下向一个对象添加新的功能。在 Java 中&#xff0c;装饰器模式经常用来动态地给对象添加额外的行为&#xff0c;如日志记录、事务管理、安全检查等。 装饰器模式涉及四个主要角色&#xff1a;组件&…

公司服务器中的kafka消息中间件挂了,我是如何修复的?

今天的公司的system系统服务在运行过程中&#xff0c;提示连接不上kafuka的消息中间件。但是负责kafka的同事已经离职了&#xff0c;询问公司开发也不知道如何处理&#xff0c;我是如何重启kafka消息中间件使system系统服务正常运行&#xff1f; 查看kafka的安装位置 在下面的…

【C++】---STL之list的模拟实现

【C】---STL之list的模拟实现 一、list模拟实现思路二、结点类的实现三、list迭代器的实现1、ListIterator类2、构造函数3、operator*运算符重载5、operator->运算符重载6、operator&#xff01;运算符重载7、operator运算符重载8、前置9、后置10、前置--11、后置-- 四、lis…