【前端】Vue实现个人空间

AcWing Web应用课 (y总yyds)

Vue3——网站整体布局、用户动态页面

1. 前端渲染逻辑

前端渲染:只有第一次打开页面的时候,向服务器发送请求,服务器返回所有js, 之后再打开页面,前端用返回的js文件将页面渲染出来。

2. vue文件

一个vue文件由三部分组成,html,js,css
css部分标签 ,加上scoped,不同组件之间的css选择器就不会相互影响到了。
在这里插入图片描述

3. 组件化的框架

  • 可以拆分实现
    在这里插入图片描述
  • 引入组件的方式(根组件App.vue)
    在这里插入图片描述
    项目练习:
    在这里插入图片描述

4. 一些准备

  • 引入bootstrap
    也是根组件app.vue中引入
<script>
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap'
</script>

在这里插入图片描述
有一个模块需要单独装
在这里插入图片描述

5. 实现导航栏

  1. 实现导航栏组件——直接使用bootstrap选取需要的元素,将组件export出去
    在这里插入图片描述

NavBar.vue

<template>
    <nav class="navbar navbar-expand-lg bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">MySpace</a>
    <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">
          <a class="nav-link active" aria-current="page" href="#">首页</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">好友列表</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">用户动态</a>
        </li>
      </ul>
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link" href="#">登录</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">注册</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
</template>

<script>
    export default{
    
    
        name: "NavBar",
    }
</script>

<style scoped>

</style>
  1. 在根组件将 实现的NavBar引入——引入路径,在components对象中添加组件名,同时在template中引入组件
    在这里插入图片描述
    在这里插入图片描述
    App.vue
<template>
  <NavBar/>

  <router-view/>
</template>

<script>
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap';
import NavBar from './components/NavBar';

export default{
    
    
  name:"App",
  components:{
    
    
    NavBar
}
}
</script>

<style>
#app {
    
    
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

6. 写六个小组件

对于一个比较大的组件,可以拆分为多个组件
首页组件
可以在bootstrap中找个card组件,然后用container包起来,container是用来动态调位置的
在这里插入图片描述
发现这一部分的html和css其实每个页面都一样,要将这块公共部分提取出来作为单独的组件,方便后期整体修改。

可以将内容渲染到 slot标签中(slot可以用来获取子元素):在这里插入图片描述
contentElem.vue

<template>
  <div class="home">
    <div class="container">
        <div class="card">
          <div class="card-body">
            <slot></slot>
          </div>
        </div>
    </div>
  </div>
</template>

<script>
export default {
    
    
    name: "contentElem",
}
</script>

<style scoped>
    .container{
    
    
        margin-top: 20px;
    }
</style>

HomeView.vue

<template>
  <contentElem>
    首页
  </contentElem>
</template>

<script>
import contentElem from "../components/contentElem.vue";

export default {
    
    
  name: 'HomeView',
  components: {
    
    
      contentElem,
  }
}
</script>

<style scoped>


</style>

图片也是可以渲染的:

<template>
  <contentElem>
    首页
    <img src="https://cdn.acwing.com/media/user/profile/photo/108069_sm_fb7fff1e8d.jpg" alt="my head pic">
  </contentElem>
</template>

7. 添加路由

  1. 引入所有组件(页面)
    在这里插入图片描述

  2. 更新路由列表即可

import {
    
     createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue'
import NotFoundView from '../views/NotFoundView.vue'
import RegisterView from '../views/RegisterView.vue'
import UserList from '../views/UserList.vue'
import UserProfile from '../views/UserProfile.vue'

const routes = [
  {
    
    
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    
    
    path: '/login',
    name: 'login',
    component: LoginView
  },
  {
    
    
    path: '/404',
    name: '404',
    component: NotFoundView
  },
  {
    
    
    path: '/register',
    name: 'register',
    component: RegisterView
  },
  {
    
    
    path: '/userlist',
    name: 'userlist',
    component: UserList
  },
  {
    
    
    path: '/userprofile',
    name: 'userprofile',
    component: UserProfile
  },

  
]

const router = createRouter({
    
    
  history: createWebHistory(),
  routes
})

export default router

8. 实现前端渲染的属性

在这里插入图片描述
想要实现前端渲染,将原本的a标签换成router-link标签,有特殊的属性 : to ,注意在vue中绑定属性需要用冒号: ,传入name,param
在这里插入图片描述
这样就能跳到home对应的路径。
NavBar.vue

<template>
    <nav class="navbar navbar-expand-lg bg-light">
  <div class="container">
    <router-link class="navbar-brand" :to="{name:'home',param:{}}">MySpace</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" aria-current="page" :to="{name:'home',param:{}}">首页</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name:'userlist',param:{}}">好友列表</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name:'userprofile',param:{}}">用户动态</router-link>
        </li>
      </ul>
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <router-link class="nav-link" :to="{name:'login',param:{}}">登录</router-link>
        </li>
        <li class="nav-item">
          <router-link class="nav-link" :to="{name:'register',param:{}}">注册</router-link>
        </li>
      </ul>
    </div>
  </div>
</nav>
</template>

<script>
    export default{
    
    
        name: "NavBar",
    }
</script>

<style scoped>

</style>

9. 用户动态页面的实现(三个组件)

在这里插入图片描述
userProfileInfo/userProfileWrite/userProfilePosts
使用bootstrap的grid

  • 先对最大的一块进行划分
    在这里插入图片描述
    在这里插入图片描述
  • 再实现用户信息组件,对用户信息这个vue进行划分;以此类推
  • 对于图片:

Bootstrap 中的图像使用.img-fluid. 这适用于图像max-width: 100%;,height: auto;以便它随父宽度缩放。

图片变为圆形:

img{
    
    
    border-radius: 50%;
}

然后把文字等元素放进去,调调样式,用卡片包起来,即可完成这个模块。

卡片:

<div class="card">
  <div class="card-body">
    This is some text within a card body.
  </div>
</div>
  • 帖子列表
    对于展示的个人信息、帖子列表都需要参数,这三个模块是相互交互的,对于这样的情况,需要将数据存到上层组件中。
    在这里插入图片描述
    上层组件UserProfileView.vue:
  // setup:()=>{
    
    
  // }
  // 当一个对象是函数时,可以简写为以下:
  setup(){
    
    
    const user =reactive({
    
    
      id:1,
      username:"ZhuJiaxuan",
      lastName:"Zhu",
      firstName:"Jiaxuan",
      followerCountL:0,
      is_followed:false,

    });
    //要用到的属性需要return
    return {
    
    
      user,
    }
  }

那么如何使用呢?(在不同组件之间传递信息)
在这里插入图片描述
在这里插入图片描述

子组件接收:(props)
在这里插入图片描述
使用:
在这里插入图片描述
如果想要的数值需要被计算,如果需要用到传过来的属性
使用computed,参数是一个函数
在这里插入图片描述

    setup(props){
    
    
        let fullname=computed(()=>props.user.lastName+" "+props.user.firstName);
        return {
    
    
            fullname
        }

    }

使用:直接使用{ {fullname}}即可

  • 实现关注按钮
    逻辑: 如果没有关注,则显示 “关注”;如果已经关注,则显示 “取消关注”字样
    实现: 使用template的v-if

在这里插入图片描述
关注完以后,还需要更新user状态,此时需要定义事件处理函数。
举例:
在这里插入图片描述
将这两个函数绑定起来:
在这里插入图片描述
子组件要向父组件传递消息:
父组件:
在这里插入图片描述
在这里插入图片描述
子组件:用context.emit可以出发父组件的事件
在这里插入图片描述

  • 对于帖子列表,同理
    v-for
    在这里插入图片描述
  • 发帖区也是同理:
    UserProfileWrite.vue
<template>
  <div class="card edit-field">
    <div class="card-body">
        <label for="edit-post" class="form-label">编辑帖子</label>
        <textarea class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
        <button type="button" class="btn btn-outline-primary btn-sm">发帖</button>
    </div>
  </div>
</template>

<script>
export default {
    
    
    name:"UserProfileWrite",

}
</script>

<style>
.edit-field{
    
    
    text-align: left;
    margin-top: 20px;
}

textarea{
    
    
    margin-bottom: 15px;
}

button{
    
    
    float:right;
    padding:2px 4px;
    font-size: 12px;

}

</style>
  • 现在要将发帖区的内容显示在帖子列表区域
    子组件向父组件传递信息,然后父向子传递信息?!
    在这里插入图片描述
  • 首先要获取textarea里的信息
    在这里插入图片描述
    利用v-model,v-model的标签与内容绑定起来
    在这里插入图片描述
    举例:
    在这里插入图片描述
    在这里插入图片描述
  • 还需要一个触发函数。当click的时候,将textarea里的内容发成帖子。
    父组件需要一个函数, 并把这个函数暴露给按钮页即可
    const post_a_post =(content)=>{
    
    
      posts.count++;
      posts.posts.unshift({
    
    
        id:posts.count,
        userId:1,
        content:content,
      });
    }

10. 用户列表页面实现

从云端将用户列表读进来

  1. 安装 npm i jquery 使用ajax
  2. 页面中import $ from 'jquery';

从云端动态获取用户(使用AJAX)

<template>
    <contentElem>用户列表</contentElem>
</template>

<script>
import contentElem from "../components/contentElem.vue";
import $ from 'jquery';
import {
    
     ref } from 'vue';
export default {
    
    
  name: 'UserList',
  components: {
    
    
    contentElem,
  },
  setup(){
    
    
    let users= ref([]);
    $.ajax({
    
    
        url:"https://app165.acapp.acwing.com.cn/myspace/userlist/",
        type:"get",
        success(resp){
    
    
          console.log(resp);
        }
      });

    return {
    
    
      users
    }
  },
}
</script>

<style scoped>

</style>

使用bootstrap的 Grid system进行布局:
在这里插入图片描述
微调样式,
在这里插入图片描述
访问不存在的页面,则跳转到404:
在这里插入图片描述

给链接加上id
在这里插入图片描述
路由中需要添加参数,用:
在这里插入图片描述
对应的UserProfileView中,使用提供的useRoute接口,即可获取这个参数
在这里插入图片描述
在这里插入图片描述

11. 实现登录页面

需要双向绑定两个变量username,password
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ref中的值需要.value访问
在这里插入图片描述
阻止默认行为

11.1 vuex

因为很多前端行为需要获得用户信息,所以要将登录的用户信息存到全局变量中——此时需要vuex
交互:
在这里插入图片描述
vuex创建的全局唯一对象:
/store/index.js
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
访问:在这里插入图片描述

11.1.1 传统登录方式

在这里插入图片描述
存到cookie中,跨域时难处理。

11.1.2 JWT方式

json web token
在这里插入图片描述

11.2 实现登录

vuex:存储全局状态,全局唯一。
	state: 存储所有数据,可以用modules属性划分成若干模块
	getters:根据state中的值计算新的值
	mutations:所有对state的修改操作都需要定义在这里,不支持异步,可以通过$store.commit()触发
	actions:定义对state的复杂修改操作,支持异步,可以通过$store.dispatch()触发。注意不能直接修改state,只能通过mutations修改state。
	modules:定义state的子模块

dispatch调用store中的事件
在这里插入图片描述
附注:使用ajax需要import $ from 'jquery';\

在这里插入图片描述
从获得的access字符串中解码出用户信息,需要安装一个解码包
在这里插入图片描述
然后在user.js中引入
import jwt_decode from 'jwt-decode';

使用:
const access_obj=jwt_decode(access);

纯背过:用于授权
在这里插入图片描述
在这里插入图片描述
可以成功获得现在登录的用户信息:
在这里插入图片描述
有了这些信息后,要将这些信息存到state里面:
但是action里面是不能直接更新的,要通过mutations

在这里插入图片描述
完整版user.js

import $ from 'jquery';
import jwt_decode from 'jwt-decode';
const ModuleUser={
    
    
    state: {
    
    
        id:"",
        username:"",
        photo:"",
        followerCount:"",
        access:"",
        refresh:"",
        is_login:false,
    },
    getters: {
    
    
    },
    mutations: {
    
    
        updateUser(state,user){
    
    //第一个参数是state,第二个参数是自己定义的
            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;
        }

    },
    actions: {
    
    
        login(context,data){
    
     //context 传api,data传一些信息(dispatch的参数)
            $.ajax({
    
    
                url:"https://app165.acapp.acwing.com.cn/api/token/",
                type: "POST",
                data:{
    
    
                    username: data.username,
                    password: data.password,
                },
                success(resp){
    
    
                   // console.log(resp);
                //    const access=resp.access;
                //    const refresh=resp.refresh;
                   const {
    
    access,refresh}=resp; //ES6语法
                   const access_obj=jwt_decode(access);
                 //  console.log(access_obj,refresh);
                 //然后可以根据其中的user_id和我们的api去获取信息
                 $.ajax({
    
    
                    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,
                            access:access,
                            refresh:refresh,
                            is_login:true,
                        });
                        data.success();
                    },
                    error(){
    
    
                        data.error();
                    }
                 })

                }
                
            })
        }
    },
    modules: {
    
    
    }
    
};

export default ModuleUser;

LoginView.vue

<template>
    <contentElem>
      <div class="row justify-content-md-center">
            <div class="col-3">
              <form @click.prevent="login">
                <div class="mb-3">
                  <label for="username" class="form-label">用户名</label>
                  <input v-model="username" type="text" class="form-control" id="username">
                </div>
                <div class="mb-3">
                  <label for="password" class="form-label">密码</label>
                  <input v-model="password" type="password" class="form-control" id="password">
                </div>
                <div class="error-message">{
    
    {
    
    error_message}}</div>
                <button type="submit" class="btn btn-primary">登录</button>
              </form>
            </div>
      </div>

    </contentElem>
</template>

<script>
import contentElem from "../components/contentElem.vue";
import {
    
     ref } from "vue";
import {
    
     useStore } from "vuex";
export default {
    
    
  name: 'UserList',
  components: {
    
    
    contentElem,
},
setup(){
    
    
  let username=ref('');
  let password=ref('');
  let error_message=ref('');
  const store=useStore();
  const login=()=>{
    
    
    // console.log(username.value);
    // console.log(password.value);
    //如果想调用外面action中的一个api,用dispatch
    store.dispatch("login",{
    
    
      username:username.value,
      password:password.value,
      success(){
    
    
        console.log("success");
      },
      error(){
    
    
        console.log("fail");
      }
    })
    
  }

  return {
    
    
    username,
    password,
    error_message,
    login

  }

}
}
</script>

<style scoped>
.error-message{
    
    
  color: red;
}
button{
    
    
  float: right;
}

</style>

11.3 使用refresh刷新access

因为5分钟access会过期,所以需要用到refresh进行刷新。

方法1. 访问的时候发现access过期了,此时去获取一个新的access
方法2. 每隔五分钟获取一次access,调用刷线access的接口,每五分钟一次

                 setInterval(()=>{
    
    
                    $.ajax({
    
    
                        url:"https://app165.acapp.acwing.com.cn/api/token/refresh/",
                        type:"POST",
                        data:{
    
    
                            refresh:access_obj.refresh,
                        },
                        success(resp){
    
    
                            context.commit("updateAccess",resp.access);


                        },
                        error(){
    
    
                            data.error();
                        }

                    });

                 },4.5*60*1000);


12. 登录后跳转到用户列表页面

  1. 引入router
    在这里插入图片描述

  2. 使用api
    在这里插入图片描述

13. 登录后修改展示页面

NavBar修改一下。让这些页面都符合逻辑
想要获取store中的全局变量,可以使用$ xxx
在这里插入图片描述
在这里插入图片描述

14. 实现退出

只需要写一个事件即可。
在这里插入图片描述
凡是要修改全局state,要把事件写到action/mutations里面 (需要store)
在这里插入图片描述

15. 未登录时的跳转

userList.vue
在这里插入图片描述
点击时触发函数:
在这里插入图片描述

16. 用户动态从云端获取

用户动态页面根据userId的改变而改变。
还是ajax!
在这里插入图片描述

17. 判断发帖子模块是否需要

只有在自己的页面才能发帖子。
在这里插入图片描述

18. 用完整链接判断页面是否相同

App.vue
在这里插入图片描述
默认是用页面的name判断的,都叫userprofile,所以到别人页面,再点击自己的,就会出现不刷新不跳转的情况。

19. 从前端删除到真的删除

在这里插入图片描述

在这里插入图片描述

20. 部署

  1. build 打包成可以发布的版本

在这里插入图片描述
2. 打包
在这里插入图片描述
3. 传到服务器上

猜你喜欢

转载自blog.csdn.net/qq_39679772/article/details/126367296