功能: 完成验证码登录,后端设置验证码默认为:246810
// axios 公共配置
// 基地址
axios.defaults.baseURL = 'http://geek.itheima.net'
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}).then(result => {
console.log(result)
// 1.4 使用 Bootstrap 的 Alert 警告框反馈结果给用户
// 自己封装好了一个弹框插件,直接调用即可
myAlert(true,'登录成功')
}).catch(error => {
console.dir(error.response.data.message)
myAlert(false,error.response.data.message)
})
})
// 弹窗插件
// 需要先准备 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)
}
创建: 在正确的登录后,由后端签发并返回。
作用:判断是否有登录状态,控制访问权限。
在登录状态时,是否能够访问内容页面
目标1:访问权限控制
const token = localStorage.getItem('token')
if(!token) {
location.href = '../login/index.html'
}
localStorage.setItem('token',result.data.data.token)
然后通过令牌跳转页面
// 延迟时间跳转,停留提示后
setTimeout(() => {
// 登录成功能够通过令牌权限后,进行跳转到内容页
location.href = '../content/index.html'
},1500)
axios请求拦截器:在发起请求之前,触发的配置函数,对请求参数进行额外的配置。
问题:很多接口都需要携带 token 令牌字符串。
解决:在请求拦截器统一设置公共样式headers选项。
axios({
//因为我们给axios配置了基地址
url: '目标资源路径',
headers: {
Authorization:`Bearer${localStorage.getItem('token')}`
}
})
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 统一携带 token 令牌字符串在请求头上
const token = localStorage.getItem('token')
// 本地有token的话,则在配置对象中配置token令牌权限
token && (config.headers.Authorization = `Bearer ${token}`)
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
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);
});
对响应的结果做出处理
axios直接接受服务器返回的响应结果
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么.例如:直接返回服务器的响应结果对象
const result = response.data
return result;
这样result === response.data 我们再写的时候可以省略这部分。
富文本:带样式,多格式的文本,在前端一般使用标签配合内联样式实现。
<div id="editor—wrapper">
<div id="toolbar-container">div>
<div id="editor-container">div>
div>
<textarea name="content" class="publish-content" hidden>textarea>
文档:https://www.wangeditor.com/
步骤
代码示例
// 1.1 获取频道列表数据
async function setChannleList() {
const res = await axios({
url: '/v1_0/channels'
})
console.log(res)
// 1.2 展示到下拉菜单中
const htmlStr = `` + res.data.channels.map(item => `">${item.name}`).join('')
console.log(htmlStr)
document.querySelector('.form-select').innerHTML = htmlStr
}
setChannleList()
事先考虑是否需要复用,需要则使用函数配和async和await来修饰。
步骤:
注意:图片地址临时存储在img标签上,并未和文章关联保存。
// 2.1 准备标签结构和样式
// 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
// 显示图片,添加show
document.querySelector('.rounded').classList.add('show')
// 隐藏基础的 + 号 上传标签 添加hide类
document.querySelector('.place').classList.add('hide')
})
// 优化:点击 img 可以重新切换封面
// 思路:img 点击 => 用 js 方式触发文件选择元素 click 事件方法
document.querySelector('.rounded ').addEventListener('click', () => {
// 调用click()方法可以模拟用户触发该类绑定的点击事件。通过调用click()方法,就相当于以编程方式触发了与该类相关联的点击事件。
document.querySelector('.img-file').click()
})
步骤:
// 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 })
//发布文章的时候才不需要 id 属性,所以可以删除掉(id 为了后续做编辑使用)
delete data.id
console.log(data)
// 自己收集封面图片地址,并保存到 data 对象中
data.cover = {
type: 1,
images: [document.querySelector('.rounded').src]
}
// 3.2 基于 axios 提交到服务器保存
try {
const result = await axios({
url: '/v1_0/mp/articles',
method: 'POST',
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)
}
})
* 1.1 准备查询参数对象
* 1.2 获取文章列表数据
* 1.3 展示到指定的标签结构中
注意:查询参数不能封装到函数里面,因为他不是永远固定的,然后再将获取和渲染封装到函数里面,后面很多操作都需要使用到获取和渲染,方便我们操作,下次直接调用函数即可。
// 1.1 准备查询参数对象
const queryObj = {
status: '', //文章的状态 (1-待审核 2-通过审核 ) 空字符串全部
channel_id: '', //文章频道, id 空字符串全部
page: 1, //当前页码
per_page: 2 //当前页面的条数
}
// 1.2 获取文章列表数据
async function setArtileList() {
const res = await axios({
url: '/v1_0/mp/articles',
params: queryObj
})
console.log(res)
// 1.3 展示到指定的标签结构中
const htmlStr = res.data.results.map(item => `
${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="">
${item.title}
${item.status === 1 ? `待审核` : `审核通过`}
${item.pubdate}
${item.read_count}
${item.comment_count}
${item.like_count}
`).join('')
document.querySelector('.art-list').innerHTML = htmlStr
}
setArtileList()
* 2.1 设置频道列表数据
* 2.2 监听筛选条件改变,保存查询信息到查询参数对象
* 2.3 点击筛选时,传递查询参数对象到服务器
* 2.4 获取匹配数据,覆盖到页面展示
// 2.1 设置频道列表数据
async function setChannleList() {
const res = await axios({
url: '/v1_0/channels'
})
console.log(res)
// 1.2 展示到下拉菜单中
const htmlStr = `` + res.data.channels.map(item => `">${item.name}`).join('')
console.log(htmlStr)
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.1 保存并设置文章总条数
* 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
* 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
* 3.1 保存并设置文章总条数
let totalCount = 0 //保存文章总条数
// 3.1 保存并设置文章总条数
const totalStr = totalCount = res.data.total_count
// 将获取到的返回数据个数插入到页面中
document.querySelector('.total-count').innerHTML = `共${totalStr}条`
// 3.1 保存并设置文章总条数
// 3.2 点击下一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.next').addEventListener('click', () => {
// 当前页码小于最大页码数才能翻页
if (queryObj.page < Math.ceil(totalCount / queryObj.per_page)) {
// 当前页增加
queryObj.page++
document.querySelector('.page-now').innerHTML = `第${queryObj.page}页`
// 调用函数,渲染最新数据
setArtileList()
}
})
// 3.3 点击上一页,做临界值判断,并切换页码参数并请求最新数据
document.querySelector('.page-link').addEventListener('click', () => {
// 判断当前页码大于1的时候才能够执行
if (queryObj.page > 1) {
queryObj.page--
document.querySelector('.page-now').innerHTML = `第${queryObj.page}页`
// 调用函数,渲染最新数据
setArtileList()
}
})
// 4.1 关联文章 id 到删除图标
<td data-id="${item.id}">
<i class="bi bi-pencil-square edit"></i>
<i class="bi bi-trash3 del"></i>
</td>
// 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',
})
// 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.2 发布文章页面接收参数判断(共用同一套表单)
* 4.3 修改标题和按钮文字
* 4.4 获取文章详情数据并回显表单
//跨页面传参 传递id值
// 点击编辑时,获取文章 id,跳转到发布文章页面传递文章 id 过去(使用事件委托)
document.querySelector('.art-list').addEventListener('click', e => {
// 判断用户点击的是哪一个标签对象
if (e.target.classList.contains('edit')) {
// 拿到事件对象父级中的自定义属性 id值
const artId = e.target.parentNode.dataset.id
location.href = `../publish/index.html?id=${artId}`
}
})
// 4.1 页面跳转传参(URL 查询参数方式) 立即执行函数,自己调用自己,防止变量污染
;(function () {
// console.log(location.search)//查看查询参数字符串,就是?号后面的
// 4.2 发布文章页面接收参数判断(共用同一套表单)
const paramsStr = location.search
const params = new URLSearchParams(paramsStr) //分隔网址后面的查询参数
params.forEach(async (value,key) => {
// console.log(value,key)
// 4.3 修改标题和按钮文字
if (key === 'id') {
// 当前有要编辑的文章 id 被传入过来
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 => {
console.log(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.1 判断按钮文字,区分业务(因为共用一套表单)
* 5.2 调用编辑文章接口,保存信息到服务器
* 5.3 基于 Alert 反馈结果消息给用户
document.querySelector('.send').addEventListener('click', async e => {
// 5.1 判断按钮文字,区分业务(因为共用一套表单)
if (e.target.innerHTML !== '修改') return
// 修改文章逻辑
// 5.2 调用编辑文章接口,保存信息到服务器
try {
const form = document.querySelector('.art-form')
const data = serialize(form, { hash: true, empty: true })
console.log(data)
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.2 清空本地缓存,跳转到登录页面
// 3.1 绑定点击事件
document.querySelector('.quit').addEventListener('click', e => {
// 3.2 清空本地缓存,跳转到登录页面
localStorage.clear()
location.href = '../login/index.html'
})