Vue3——简易个人空间(下半部分)

书接上回:

好友列表页面实现:

 根据提供的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

猜你喜欢

转载自blog.csdn.net/m0_62327332/article/details/130692687