前言
第二章丰富登录页面的功能,使得用户的信息得以被安全保护、也避免了用户的非正规操作即设置了路由守卫并给数据生成token令牌、创建了注册的功能让新用户的数据注入数据库。
正文
完成了登录和注册的所有内容,接下来就开始编写我们的主页面,丰富页面的内容,实现一些功能
- 设置路由白名单(三个页面不在路由守卫范围内)
- 主页面的编写(原始页面NoteClass.vue、子组件个人信息页面Menu.vue)
- 主页面中的笔记列表Note.vue(前后端的完成)
- 列表页面中的具体笔记NoteDeail.vue(前后端的完成)
设置路由白名单
在日常使用的很多app中会发现这么一个现象,即使没有登陆也依然可以访问首页,当点进进入商品或者文章时才弹出登录框,这是因为app为一些页面设置了白名单,即没有用户信息也能直接访问,当发送其他未进入白名单的页面时才会受到路由守卫的拦截
在路由守卫设置白名单
const whilePath = ['/login','/register','/noteClass']
主页面
原始页面NoteClass.vue
<template>
<div class="note-class-wrapper">
<div class="note-class" :class="{ hide: isShowMenu }">
<header>
<div @click="isShowMenu = true">
<van-icon name="wap-nav" />
</div>
<div>
<van-icon name="edit" @click="router.push('/notePublish')"/>
<van-icon name="like-o" />
<van-icon name="search" />
</div>
</header>
<section>
<div class="note-item" v-for="(item, index) in noteClassList"
:key="index" :style="{ backgroundColor: item.bgColor }" @click="goNoteList(item.title)">
<span class="title">{{ item.title }}</span>
</div>
</section>
</div>
<!-- menu -->
<Menu class="menu" @hidden="handle" :class="{ show: isShowMenu }"></Menu>
</div>
</template>
分为两部分头部和身体部分
- 头部由几个图标组成,分别为个人信息标签、写笔记标签、收藏标签、搜索标签(这些后续东江设置为点击事件,分别跳转至相应的页面)
- 身体部分为所有的笔记的分类
头部
头部的图标可以在vant库中找到
Icon 图标 - Vant 4 (vant-ui.github.io)
main.js中引入Icon
import { createApp } from 'vue';
import { Icon } from 'vant';
const app = createApp();
app.use(Icon);
点击想要的图标点击复制,再粘贴使用即可
身体
<div class="note-item" v-for="(item, index) in noteClassList"
:key="index" :style="{ backgroundColor: item.bgColor }" @click="goNoteList(item.title)">
<span class="title">{{ item.title }}</span>
</div>
列表中的每个项都是noteClassList数组中的一个对象。使用v-for指令来遍历数组,并为每个对象生成一个带有类名note-item的div元素。
div元素的key属性设置为数组项的索引,style属性设置为对象的bgColor属性值作为背景色。当用户点击div元素时,会触发goNoteList方法,并将对象的title属性作为参数传递给该方法。
循环noteClassList数组中的每一个对象
const noteClassList = [
{ bgColor: '#f0aa84', title: '美食' },
{ bgColor: '#dcf189', title: '旅行' },
{ bgColor: '#e0c2f1', title: '恋爱' },
{ bgColor: '#c2ebf1', title: '学习' },
{ bgColor: '#949c9d', title: '吵架' }
]
个人信息标签
头部由几个图标组成,其中个人信息标签是设置在原始页面NoteClass.vue中属于它的子组件,而其他三个标签是新的页面,是一种有趣的交互体验。
当点击个人信息标签时,将父组件隐藏且子组件显露,反之在子组件点返回时将子组件隐藏父组件显露
将子组件Menu.vue引入父组件
<Menu class="menu" @hidden="handle" :class="{ show: isShowMenu }"></Menu>
import Menu from '../components/menu.vue'
个人信息页面Menu.vue
<template>
<div class="menu-wrap">
<div class="back" @click="hideMenu">
<van-icon name="arrow-left" size="26px"/>
</div>
<section class="header">
<div class="avatar">
<img src="https://ts1.cn.mm.bing.net/th/id/R-C.d0cdb390350600169835c8343480b0af?rik=gLkyDf9xwbXdQg&riu=http%3a%2f%2finews.gtimg.com%2fnewsapp_match%2f0%2f15103659087%2f0&ehk=zK0MTj34tChhjHcpXaZER22pNZjchBBOnrkMTllNa0w%3d&risl=&pid=ImgRaw&r=0" alt="">
</div>
<p class="user">坤坤</p>
</section>
<div class="setting">
<div class="set-item">
<van-icon name="contact" size="0.4rem"/>
<span>个人主页</span>
</div>
<div class="set-item">
<van-icon name="bullhorn-o" size="0.4rem"/>
<span>通知</span>
</div>
<div class="set-item">
<van-icon name="revoke" size="0.4rem"/>
<span>退出登录</span>
</div>
</div>
</div>
</template>
父组件和子组件的关系如同这个样子,争抢一块屏幕
属于是个典型的父子路由通信,可以使用多种方法来传递数值,比如创建事件监听,父子组件互相订阅一个变量,监听并实时改变
在父组件中设置
动态绑定CSS类:div和Menu元素的class属性通过:class动态绑定,其中{ hide: isShowMenu }表示如果isShowMenu的值为true,则div会添加hide这个CSS类。
组件事件监听:Menu组件被包含在这个div中,其@hidden事件被绑定到handle函数。这意味着当Menu组件的hidden事件触发时,handle函数会被调用。
状态管理: isShowMenu是一个使用ref创建的响应式引用,初始值为false。这通常用于控制某些UI元素的显示或隐藏。
事件处理函数:handle函数接收一个事件参数e(虽然在这个函数体中没有使用),当调用时,它会将isShowMenu的值设置为false,从而可能触发div元素上的hide类的添加,导致菜单隐藏。
在子组件中设置
事件发射器定义:使用defineEmits定义了一个可以发射的自定义事件hidden。这意味着组件可以通过emits对象发射hidden事件,通常用于父组件监听子组件的状态变化。
事件处理函数:hideMenu函数在被调用时,会通过emits对象发射hidden事件,并传入参数false。这通常意味着组件想要通知外部(比如父组件)它的某个状态(如菜单的显示状态)发生了变化。
事件触发:在HTML模板中,<div class="back">元素绑定了一个@click事件处理器,即hideMenu函数。这意味着当用户点击这个div元素时,hideMenu函数会被调用,进而发射hidden事件。
使用css4中的transform: translateX(100%)动态实现下次细讲
数组中的对象Note.vue
前端
在主页面的脚本部分添加跳转的路由
// 跳转笔记
const goNoteList = (title) => {
router.push({ path: '/note', query: {title} });
}
点击事件@click="goNoteList(item.title)传入当前点击的title值
当goNoteList函数被调用时,它会根据传入的title值,跳转到'/note'页面,并设置路由传参在URL中附加title作为查询参数。这样,目标页面就可以根据这个title参数来决定展示哪些笔记,只展示属于特定分类的笔记,这样就不需要为每一个title设置一个新页面。
路由传参query: {title}:
模板部分
<template>
<div class="note-list">
<ul>
<li v-for="item in noteList" :key="item.id" @click="goNoteDetail(item.id)">
<div class="img">
<img :src="item.head_img" alt="">
</div>
<div class="time">{{ item.m_time }}</div>
<div class="title">{{ item.title }}</div>
</li>
</ul>
</div>
</template>
- 列表渲染: 使用
v-for指令遍历noteList数组中的每一项,为每一条笔记生成一个<li>元素。每个元素都有一个唯一的key属性,这里设置为item.id,以提高列表更新的性能。 - 图片展示: 每个列表项包含一个
<img>标签,其src属性绑定到item.head_img,展示笔记的头像或缩略图。 - 时间显示: 显示笔记的时间戳
item.m_time。 - 标题显示: 显示笔记的标题
item.title。 - 点击事件: 当列表项被点击时,触发
goNoteDetail方法,并将item.id作为参数传递,以便导航到笔记详情页。
脚本部分
<script setup>
import { useRoute } from 'vue-router';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import axios from '../api/index';
// const res = await axios.get(`/findNoteListByType?note_type=${route.query.title}`)
const route = useRoute();
const router = useRouter()
const noteList = ref([])
axios.get('/findNoteListByType',{
params: {
note_type:route.query.title
}
}).then(res=>{
noteList.value = res.data
console.log(noteList.value);
})
const goNoteDetail = (id) => {
router.push({ path: '/noteDetail', query: {id} });
}
</script>
-
依赖导入:
useRoute和useRouter从vue-router导入,用于获取当前路由信息和进行路由跳转。ref从vue导入,用于创建响应式引用。axios从项目中的API模块导入,用于发送HTTP请求。
(其中的
useRoute()函数用于获取当前活跃路由的信息。它返回一个响应式的路由对象,包含了当前路由的所有信息,route对象被用来获取URL中的查询参数title,然后将其用于API请求中,以获取与该title相关的笔记列表。)
-
数据获取:
- 使用
axios发送GET请求到/findNoteListByType端点,参数note_type从当前路由的查询参数title获取。 - 请求成功后,响应数据存储在
noteList响应式引用中,这将触发视图更新,显示获取到的笔记列表。
- 使用
-
路由跳转:
goNoteDetail方法接收一个id参数,使用router.push方法导航至/noteDetail路径,并将id作为查询参数附加,以便目标页面能够获取到具体笔记的ID。
后端
请求/findNoteListByType端点
使用axios发送GET请求到/findNoteListByType端点
和在第二章设置的的用户登录注册路由管理类似
@koa/router中间件的使用
新建note.js,在
rotes/note.js文件中添加得到/findNoteListByType路由请求的数据,因此使用的是get
在HTTP协议中:
- GET请求:通常用于请求资源,数据(如果有的话)会作为URL的一部分(查询字符串)附加在URL后面。因此,如果你使用GET方法发送请求,并且想要传递额外的数据,这些数据就会被编码成查询字符串的形式。在服务器端,这些数据可以通过
ctx.request.query来访问。- POST请求:通常用于提交数据给服务器,数据不是放在URL中,而是放在请求体(request body)中。在服务器端,这些数据可以通过
ctx.request.body来访问。
router.get('/findNoteListByType',jwt.verify(),async (ctx,next) => {
// 先检验Token合理再去数据库查找数据
const { note_type } = ctx.request.query
// console.log(res);
try{
const res = await findNoteListByType(note_type,ctx.userId)
if(res.length){
ctx.body = {
code:800,
data:res,
msg:'查询成功'
}
}else{
ctx.body = {
code:813,
data:null,
msg:'查询失败'
}
}
}catch(error){
ctx.body = {
code:814,
data:error,
msg:'服务器异常'
}
}
})
中间件验证
router.get(...)定义了一个处理GET请求的路由规则。jwt.verify()是一个中间件,用于验证请求中的JWT(JSON Web Token)。这意味着在访问此路由之前,必须先通过JWT验证。如果验证失败,请求不会到达后续的处理逻辑。
请求处理
-
const { note_type } = ctx.request.query:从请求的查询参数中解析出note_type,这是要查询的笔记类型。 -
使用try-catch块,用于捕获并处理可能出现的错误:
1、调用findNoteListByType函数,传入note_type和从上下文中获取的userId。这是一个异步操作,使用await关键字等待结果。
2、如果findNoteListByType函数成功返回数据并且数据长度不为0,则设置响应体ctx.body,返回一个成功的响应,包含状态码800、数据res以及消息'查询成功'。反之失败
别忘记在index.js中启动
const userRouter = require('./routes/note')
app.use(noteRouter.routes())
app.use(noteRouter.allowedMethods())
jwt.verify()中间件
验证请求中的JWT(JSON Web Token)。这意味着在访问此路由之前,必须先通过JWT验证。如果验证失败,请求不会到达后续的处理逻辑。
哈哈,反正文章没人看,等下次有时间多再补充说明,还有token令牌得再细细分析一下,以及这篇文章的更多细节,路由传参啦,route和router的区别啦等等
设置findNoteListByType函数取出数据库数据
在controllers/index模块添加findNoteListByType函数来查询并得到相应的笔记类型数据
const findNoteListByType = (note_type,id) => {
const _sql = `select * from note where note_type="${note_type}" and userId="${id}";`
return allService.query(_sql)
}
从note表中选择所有列,但仅返回那些note_type等于传入的note_type参数,且userId等于传入的id参数的记录。
findNoteListByType函数将返回一个Promise,这个Promise最终将解析为查询结果,即符合特定类型和用户ID的笔记列表。
于是能得到数据库中的这三条笔记
具体笔记NoteDetail.vue
在对象Note.vue中还能查询具体的笔记信息
在每一条笔记中设置了路由跳转:goNoteDetail 方法接收一个id参数,使用router.push方法导航至/noteDetail路径,并设置了路由传参将id作为查询参数附加,以便目标页面能够获取到具体笔记的ID。
和上面的Note.vue无比相像,就改改传递的值,然后根据这个值去数据库找数据(Note.vue是title,NoteDetail.vue是id),于是这里我就直接偷懒了。
前端
模板部分
就是简单的拿数据再显示.....
<template>
<div class="note-detail">
<div class="note-img">
<img :src="noteDetail.head_img">
</div>
<div class="note-content">
<div class="tab">
<span class="note-type">{{noteDetail.note_type}}</span>
<span class="author">{{noteDetail.nickname}}</span>
</div>
<p class="title">{{noteDetail.title}}</p>
<div class="content" v-html=" noteDetail.note_content "></div>
</div>
</div>
</template>
- 笔记图像:
src属性绑定到noteDetail.head_img,确保显示正确的图像。 - 笔记类型和作者: 分别显示笔记的类型和作者的昵称。
- 标题: 显示笔记的标题。
- 内容: 内容部分要注意,不能直接使用noteDetail中的数据,会发现存在着
<p>的换行符,因此需要使用v-html指令将noteDetail.note_content中的HTML内容直接插入到DOM中,允许富文本或HTML格式的内容显示。
脚本部分
<script setup>
import { useRoute } from 'vue-router';
// import { useRouter } from 'vue-router';
import { ref } from 'vue';
import axios from '../api/index';
const route = useRoute();
// const router = useRouter()
const noteDetail = ref({})
axios.get('/findNoteDetail',{
params: {
id:route.query.id
}
}).then(res=>{
noteDetail.value = res.data[0]
console.log(noteDetail.value);
})
</script>
- 依赖导入
- 数据获取:
- 使用
axios发送GET请求到/findNoteDetail端点,参数id从当前路由的查询参数id获取。 - 请求成功后,响应数据的第一项存储在
noteDetail响应式引用中,这将触发视图更新,显示获取到的笔记详情。
- 使用
后端
和上面的请求/findNoteListByType端点简直不要太像吧,于是就简单写一下。
router.get('/findNoteDetail',jwt.verify(),async (ctx,next) => {
// 先检验Token合理再去数据库查找数据
const { id } = ctx.request.query
try{
const res = await findNoteDetail(id)
if(res.length){
ctx.body = {
code:800,
data:res,
msg:'查询成功'
}
}else{
ctx.body = {
code:813,
data:null,
msg:'查询失败'
}
}
}catch(error){
ctx.body = {
code:814,
data:error,
msg:'服务器异常'
}
}
})
请求处理
-
const { id } = ctx.request.query:从请求的查询参数中解析出id,这是要查询的笔记编号。 -
使用try-catch块,用于捕获并处理可能出现的错误:
1、调用findNoteDetail函数,传入id。这是一个异步操作,使用await关键字等待结果。
2、如果findNoteDetail函数成功返回数据并且数据长度不为0,则设置响应体ctx.body,返回一个成功的响应,包含状态码800、数据res以及消息'查询成功'。反之失败
设置findNoteDetail函数取出数据库数据
在controllers/index模块添加findNoteDetail函数来查询并得到相应的笔记类型数据
findNoteDetail函数将返回一个Promise,这个Promise最终将解析为查询结果,即符合特定类型和用户ID的笔记列表。
于是能得到数据库中的这条笔记的详细信息
结语
第三章编写了我们的主页面丰富了页面的内容,并实现一些功能: 先设置路由白名单,让页面不在路由守卫范围内,即不登录也能浏览主页面,接着开始主页面的编写,原始页面及子组件个人信息页面、主页面中的笔记列表,再到列表页面中的具体笔记,完成了笔记数据可视化,接下来的章节将继续完善和丰富主页面的功能,例如写笔记往数据库添加数据等。