书接上回:
好友列表页面实现:
根据提供的api从云端将10个用户读进来
根据提供的api获得如下的json格式的数据,里面有四个用户的信息。
这里使用ajax进行实现要先在项目中安装jquery,使用命令行安装
npm i jquery
然后在用户列表页面要先引入jquery
import $ from 'jquery';
又或者使用axios获取网络请求
有如下流程:
现在终端下载axios
npm i axios
封装axios。。。
算了,这里不实现了,好麻烦,以后有一会再来写,还是ajax方便点
效果图:
代码:
<template>
<ContentBase>
<div class="card" v-for="user in users" :key="user.id">
<div class="card-body">
<div class="row">
<div class="col-1">
<img class="img-fluid" :src="user.photo" alt="" /> <!--此处img-fluid实现了一个响应式布局,可以使图片随着父元素大小缩放-->
</div>
<div class="col-11">
<div class="username">{{ user.username }}</div>
<div class="follow-count">{{ user.followerCount }}</div>
</div>
</div>
</div>
</div>
</ContentBase>
</template>
<script>
import $ from 'jquery';
import { ref } from 'vue';
import ContentBase from '@/components/ContentBase.vue';
export default {
name: 'UserListView',
components: {
ContentBase
}, setup() {
let users = ref([]);
$.ajax({
url: "https://app165.acapp.acwing.com.cn/myspace/userlist/",
type: "get",
success(resp) {
users.value = resp;
}
});
return {
users
}
}
}
</script>
<style scope>
.img-fluid {
border-radius: 50%;
}
.username {
font-weight: bold;
height: 50%;
}
.follow-count {
font-size: 12px;
color: gray;
height: 50%;
}
.card {
margin-bottom: 20px;
/*间隔*/
cursor: pointer;
/*将鼠标变成手的效果*/
}
/*鼠标悬浮效果*/
.card:hover {
box-shadow: 2px 2px 10px gray;
transition: 500ms;
}
</style>
这里上面代码里面使用的是课程里面提供的链接。
此处上面的后端的userlist的链接也可以自己在本地实现,这里我本地新建了一个springboot的后端项目实现了一个userlist的获取url,效果图如下。
实现方法放在这篇文章里面:
Springboot——根据需求创建后端接口_北岭山脚鼠鼠的博客-CSDN博客
页面跳转404
在router当中加上一个额外正则表达式匹配,上面的所有url都匹配不上时就会重定向到404页面
.表示匹配任意字符,*表示匹配任意长度
用户动态页面完善
在用户动态页面需要根据两个用户的信息判断显示关注与否
在router当中修改一下,加上一个:userId,表示这个url后面需要一个userId,
然后在访问用户动态页面的时候是通过userId查询访问.
在NavBar.vue当中还需要进行如下修改,
先随便起一个userId
在UserProfileView.vue当中要想访问userId要先引入一个东西
import { useRoute} from 'vue-router'
然后在setup函数中获取url当中userId参数
演示,url当中输入任何数字都可以被useRoute捕获并输出到控制台上
登录页面实现
到bootstrap当中寻找表单
复制该表单的内容到login.vue当中
经过修改得到如下
<ContentBase>
<form>
<div class="mb-3">
<label for="username" class="form-label">鼠鼠名</label>
<input type="text" class="form-control" id="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">鼠鼠码</label>
<input type="password" class="form-control" id="exampleInputPassword1">
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</ContentBase>
此外要想登陆还要获取到姓名和密码发送到后端,所以这里还需要两个双向绑定的变量
username,password
此外还需要一个登陆错误报错error_message,以及一个点击登录会触发的一个函数login().
这四个东西都要贼setup中定义并返回,才能在template中使用。
最后得到如下的代码:
<template>
<ContentBase>
<div class="row justify-content-md-center">
<div class="col-3">
<form @submit.prevent="login"> <!--此处.prevent的作用是阻止其默认行为-->
<div class="mb-3">
<label for="username" class="form-label">鼠鼠名</label>
<input type="text" class="form-control" id="username" v-model="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">鼠鼠码</label>
<input type="password" class="form-control" id="exampleInputPassword1" v-model="password">
</div>
<div class="error-message">{{ error_message }}</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import { ref } from 'vue';
import ContentBase from '@/components/ContentBase.vue';
export default {
name: 'LoginView',
components: {
ContentBase
}, setup() {
let username = ref('');
let password = ref('');
let error_message = ref('');
const login = () => {
alert("登陆了");
}
return {
username,
password,
login,
error_message
}
}
}
</script>
<style scope>
button {
width: 100%;
}
.error-message {
color: red;
}
</style>
维护使用全局变量——vuex
vuex会维护一个state树,所有全局信息都会被维护到这个state树当中。
任意两个组件之间想通信都可以通过到state树当中查找对应变量。
使用
安装完vuex之后,就可以直接在store下的index.js里面定义全局变量了,
下面只是一个介绍样例
import { createStore } from 'vuex'
export default createStore({
state: { //此处用于存储所有全局数据,可以是对象也可以是一个属性
user: {
username: "",
id: "",
firstname: "",
lastname:""
}
},
getters: { //此处存放方法,用于计算需要组合使用的数据,只能读信息不能修改
fullname(state) {
return state.user.firstname + state.user.lastname;
}
},
mutations: { //定义所有对state的修改操作,但这里不能进行异步操作,比如从云端获取
update(state, user)
{
state.user = user;
}
},
actions: { //此处用于定义所有对于state的更新方式,这里不能直接对state进行修改,要调用mutations中的方法修改
updateUser(context) {
let resp; }
},
modules: { //对于多个对象可以使用不同modules进行维护,并在这里进行引入,这里以后用到再来详细讲
modulesA,
modulesB
}
})
现在先在store下新建一个user.js,里面存放如下信息
const ModuleUser = {
state: {
id: "",
username: "",
photo: "",
followerCount:0,
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
};
export default ModuleUser;
然后在index.js中进行如下的引入
import { createStore } from 'vuex'
import ModuleUser from './user'
export default createStore({
state: { //此处用于存储所有全局数据,可以是对象也可以是一个属性
},
getters: { //此处存放方法,用于计算需要组合使用的数据,只能读信息不能修改
},
mutations: { //定义所有对state的修改操作,但这里不能进行异步操作,比如从云端获取
},
actions: { //此处用于定义所有对于state的更新方式,这里不能直接对state进行修改,要调用mutations中的方法修改
},
modules: { //对于多个对象可以使用不同modules进行维护,并在这里进行引入,这里以后用到再来详细讲
user:ModuleUser
}
})
传统登录验证方式
在用户访问服务器时,服务器会返回一个session_id给用户并保存信息到数据库,用户端会保存该session_id,之后再访问服务器时(比如跳转别的页面)就带上session_id一起访问服务器,服务器会检查session是否存在于数据库。一般存到cookie当中,js无法访问。
因此就无法使用ajax维护登录状态,因为无法得到session_id.
新的登录验证方式——jwt方式:json web token
第一次访问时会将用户名和密码传给服务器。
然后服务器会返回一个有时长限制的令牌。
此后每次访问服务器都会带上令牌,服务器检查令牌是否在有效期内。
这个令牌就是一个字符串,但是并不会存到数据库中,但是服务器端可以验证jwt是否在有效期。
原理:就是类似与哈希算法,比对哈希结果。。。
在user.js的actions中加入如下
login(context, data) {
$.ajax({
url: "https://app165.acapp.acwing.com.cn/api/token/",
type: "POST",
data: {
username: data.username,
password: data.password
},
success(resp) {
console.log(resp);
}
})
}
在loginView当中再改login方法为如下:
要调用actions中的方法就要使用dispatch
const login = () => {
store.dispatch("login", {
username: username.value,
password: password.value,
success() {
console.log("success");
},
error() {
console.log("failed");
}
});
};
然后登陆验证输出成功,这里就成功获取到了账号123的令牌。
然后需要进行编码阶码先在终端下一个包
npm i jwt-decode
经过解码后得到
然后再根据userid获取用户信息
成功获取
顺序上是ajax获得jwt,jwt获得userid,userid获取用户信息。
然后获取到的jwt令牌会过期,所以写个周期函数每五分钟获取一次新的access。
最后loginView.vue变成如下
<template>
<ContentBase>
<div class="row justify-content-md-center">
<div class="col-3">
<form @submit.prevent="login"> <!--此处.prevent的作用是阻止其默认行为-->
<div class="mb-3">
<label for="username" class="form-label">鼠鼠名</label>
<input type="text" class="form-control" id="username" v-model="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">鼠鼠码</label>
<input type="password" class="form-control" id="exampleInputPassword1" v-model="password">
</div>
<div class="error-message">{{ error_message }}</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import { useStore } from 'vuex';
import { ref } from 'vue';
import router from '@/router/index';
import ContentBase from '@/components/ContentBase.vue';
export default {
name: 'LoginView',
components: {
ContentBase
}, setup() {
const store = useStore();
let username = ref('');
let password = ref('');
let error_message = ref('');
const login = () => {
store.dispatch("login", {
username: username.value,
password: password.value,
success() {
router.push({ name: 'userlist' }) //登录成功后自动跳转,这里的push是其自带的
},
error() {
error_message.value = "账号或密码错误"
}
});
};
return {
username,
password,
login,
error_message,
}
}
}
</script>
<style scope>
button {
width: 100%;
}
.error-message {
color: red;
}
</style>
user.js变成如下
其中actions的方法在js中通过store.dispath("方法名",参数)调用
其中muation中的方法在user.js中的actions中通过context.commit(“方法名”,参数)调用
import $ from 'jquery'
import jwt_decode from 'jwt-decode'
const ModuleUser = {
state: {
id: "",
username: "",
photo: "",
followerCount: 0,
access: "",
refresh: "",
is_login: false,
},
getters: {
},
mutations: {
updateUser(state, user) { //把下面通过ajax获取到的user信息放到全局变量
state.id = user.id;
state.username = user.username;
state.photo = user.photo;
state.followerCount = user.followerCount;
state.access = user.access;
state.refresh = user.refresh;
state.is_login = user.is_login;
}, updateAccess(state, access) {
state.access = access;
}
},
actions: {
login(context, data) {
$.ajax({
url: "https://app165.acapp.acwing.com.cn/api/token/", //第一层ajax,先尝试登录是否成功并获取令牌
type: "POST",
data: {
username: data.username,
password: data.password
},
success(resp) {
const { access,refresh } = resp;
const access_obj = jwt_decode(access); //成功获取后对令牌解码并获取用户id
setInterval(() => { //每隔五分钟获取一次令牌
$.ajax({
url: "https://app165.acapp.acwing.com.cn/api/token/refresh/",
type: "POST",
data: {
refresh
},
success(resp) {
context.commit("updateAccess", resp.access);
}
});
}, 4.5 * 60 * 1000);
$.ajax({ //第二层ajax根据id获取用户信息
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
type: "GET",
data: {
user_id:access_obj.user_id
},
headers: {
'Authorization': "Bearer " + access, //这里一定要注意空格
},
success(resp) {
context.commit("updateUser", {
...resp, //此处resp解构作用是将对象数组展开
access: access,
refresh: refresh,
is_login: true,
});
data.success(); //成功就调成功的回调哈数
}
})
}, error() {
data.error(); //失败就调失败的回调函数
}
})
}
},
modules: {
}
};
export default ModuleUser;
经过上面的操作,已经将用户的所有信息都存到了全局状态里面,登录成功之后还要将右上角的信息变成头像和昵称。
这时候就可以根据全局信息里面is_login判断是显示登录注册还是显示个人信息。
显示登陆者信息在右上角——实现登录和退出
在NavBar.vue当中根据is_login的信息选择显示什么,这里全局变量可以直接访问,
格式如下
这个是访问一个子模块里面的state
$store.stare.user.usernmae
还需要实现退出,退出只需要定义一个函数将jwt令牌删除即可。
所以这里需要需要修改全局state。还需要将修改的逻辑写到actions里面,把具体修改放到mutations里面。也可以直接写到mutation里面
在user.js中实现如下,在mutations里面加一个函数,传state进去修改
logout(state) {
state.id = "",
state.username = "",
state.photo = "",
state.followerCount = 0,
state.access = "",
state.refresh = "",
state.is_login = false;
}
在NavBar中实现如下:
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<router-link class="navbar-brand" :to="{ name: 'home', params: {} }">
鼠鼠空间
</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText"
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<router-link class="nav-link active" to="/">首页</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'userlist', params: {} }">鼠友列表</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link"
:to="{ name: 'userprofile', params: { userId: 2 } }">鼠友动态</router-link>
</li>
</ul>
<ul class="navbar-nav" v-if="!$store.state.user.is_login">
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'login', params: {} }">登录</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{ name: 'register', params: {} }">注册</router-link>
</li>
</ul>
<ul class="navbar-nav" v-else>
<li class="nav-item">
<router-link class="nav-link" :to="{
name: 'userprofile',
params: { userId: $store.state.user.id }
}">{{ $store.state.user.username }}</router-link>
</li>
<li class="nav-item">
<a class="nav-link" style="cursor:pointer" @click="logout">退出</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script >
import { useStore } from 'vuex';
export default {
name: "NavBar",
setup() {
const store = useStore();
const logout = () => {
store.commit("logout");
};
return {
logout,
}
}
}
</script>
<style scoped></style>
用户动态页面实现
用户动态应该根据进入的用户空间不同显示不同的用户页面,不能直接放在导航栏,因为需要传一个userId的参数,所以需要先将用户动态从导航栏删除
要在用户列表页面实现点击不同的用户并跳转不同的用户动态。
跳转就是跳到别的用户的页面,需要一个用户id,如下所示
router.push({ name: 'userprofile', params: { userId } })
用户动态页面根据传入的userId不同显示不同的内容,需要将信息动态从云端拉取下来,就是一个getbyid,再到获取一个人所有的帖子,也是根据id获取。
使用提供的api
userprofileView.vue页面
<template>
<ContentBase>
<div class="row">
<div class="col-3">
<UserInfo @follow="follow" @unfollow="unfollow" :user="user" />
<UserWrite v-if="is_me" @post_a_post="post_a_post" />
</div>
<div class="col-9">
<UserPosts :posts="posts" />
</div>
</div>
</ContentBase>
</template>
<script>
import { computed } from 'vue';
import UserInfo from '../components/UserInfo.vue'
import UserPosts from '../components/UserPosts.vue'
import UserWrite from '../components/UserWrite.vue'
import { reactive } from 'vue';
import { useRoute } from 'vue-router'
import $ from 'jquery';
import { useStore } from 'vuex';
import ContentBase from '@/components/ContentBase.vue';
export default {
name: 'UserProfileView',
components: {
ContentBase,
UserInfo,
UserPosts,
UserWrite
}, setup() {
const route = useRoute();
const userId = route.params.userId;
const user = reactive({});
const posts = reactive({});
const store = useStore();
$.ajax({ //获取某个用户的信息
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/",
type: "GET",
data: {
user_id: userId
},
headers: {
'Authorization': "Bearer " + store.state.user.access,
}, success(resp) {
user.id = resp.id;
user.username = resp.username;
user.photo = resp.photo;
user.followcount = resp.followerCount;
user.is_followd = resp.is_followd;
}
})
$.ajax({ //获取某个用户发过的所有帖子
url: "https://app165.acapp.acwing.com.cn/myspace/post/",
type: "GET",
data: {
user_id: userId
},
headers: {
'Authorization': "Bearer " + store.state.user.access,
},
success(resp) {
posts.posts = resp
}
});
const post_a_post = (content) => {
posts.count++;
posts.posts.unshift({
id: posts.count,
userId: 1,
content: content
})
}
const follow = () => {
if (user.is_followd) return;
user.is_followd = true;
user.followcount++;
}
const unfollow = () => {
if (!user.is_followd) return;
user.is_followd = false;
user.followcount--;
}
const is_me = computed(() => userId == store.state.user.id);
return {
is_me,
user,
follow,
unfollow,
posts,
post_a_post
}
}
}
</script>
<style scope></style>
Userinfo页面
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-3 img-field">
<img class="img-fluid" :src="user.photo" alt="我图片呢" />
</div>
<div class="col-9">
<div class="username">
{{ user.username }}
</div>
<div class="fans">粉丝数:{{ user.followcount }}</div>
<button v-if="!user.is_followd" @click="follow" type="button" class="btn btn-primary btn-sm">关注</button>
<button v-if="user.is_followd" @click="unfollow" type="button"
class="btn btn-primary btn-sm">取消关注</button>
<!---此处的btn-primary是控制按钮颜色,btn-sm是控制按钮大小 -->
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "UserInfo",
props: {
user: {
type: Object, //参数的类型
required: true, //是否必须
}
}, setup(props, context) {
const follow = () => {
context.emit('follow');
};
const unfollow = () => {
context.emit('unfollow');
}
return {
follow,
unfollow
}
}
}
</script>
<style scoped>
img {
border-radius: 50%;
}
.username {
/* 变成粗体 */
font-weight: bold;
}
.fans {
font-size: 15px;
color: gray;
}
button {
padding: 2px, 4px;
font-size: 15px
}
.img-field {
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
在App.vue当中
<template>
<NavBar></NavBar>
<router-view :key="$route.fullPath" /> <!--用完整路径判重,这里重复路径不会跳转,判断有点神奇-->
</template>
<script>
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap';
import NavBar from './components/NavBar.vue'
export default {
name: "App",
components: {
NavBar
}
}
</script>
<style></style>
实现了用户动态页面的展示,以及判断自己或者别人的页面下展示内容不同
添加帖子和删除帖子以及登录和关注功能实现
太麻烦了,建议去报课看吧
acwing