[Front-end] Vue implements personal space

AcWing Web Application Course (ytotalyyds)

Vue3 - overall website layout, user dynamic page

1. Front-end rendering logic

Front-end rendering: Only when the page is opened for the first time, a request is sent to the server, and the server returns all js. Then when the page is opened, the front-end uses the returned js file to render the page.

2. vue statement

A vue file consists of three parts, html, js, css and
css part tags. With scoped, css selectors between different components will not affect each other.
Insert image description here

3. Component-based framework

  • Can be split and implemented
    Insert image description here
  • How to introduce components (root component App.vue)
    Insert image description here
    project exercises:
    Insert image description here

4. Some preparation

  • The introduction of bootstrap
    is also introduced in the root component app.vue
<script>
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap'
</script>

Insert image description here
There is a module that needs to be installed separately
Insert image description here

5. Implement navigation bar

  1. Implement the navigation bar component - directly use bootstrap to select the required elements and export the component
    Insert image description here

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. Insert image description here
    Insert image description here
    Introduce the implemented NavBar in the root component - introduce the path, add the component name in the components object, and introduce the component App.vue in the template.
<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. Write six widgets

For a relatively large component, it can be split into multiple components.
Home page component.
You can find a card component in bootstrap, and then wrap it with a container. The container is used to dynamically adjust the position. It is
Insert image description here
found that the html and css of this part are actually on each page. All the same, this common part should be extracted as a separate component to facilitate later overall modification.

Content can be rendered into the slot tag (slot can be used to obtain child elements): Insert image description here
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.view

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

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

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

<style scoped>


</style>

Images can also be rendered:

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

7. Add routing

  1. Import all components (pages)
    Insert image description here

  2. Just update the routing list

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. Implement front-end rendering properties

Insert image description here
If you want to achieve front-end rendering, replace the original a tag with the router-link tag, which has special attributes : to. Note that you need to use colons to bind attributes in vue :, and pass in name and param
Insert image description here
so that you can jump to the path corresponding to 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. Implementation of user dynamic pages (three components)

Insert image description here
userProfileInfo/userProfileWrite/userProfilePosts
使用bootstrap的grid

  • Divide the largest piece first
    Insert image description here
    Insert image description here
  • Then implement the user information component and divide the vue of user information; and so on.
  • For pictures:

Images in Bootstrap use .img-fluid. This applies to the image max-width: 100%;, height: auto; so that it scales with the parent width.

The image becomes circular:

img{
    
    
    border-radius: 50%;
}

Then put text and other elements in, adjust the style, and wrap it with cards to complete the module.

card:

<div class="card">
  <div class="card-body">
    This is some text within a card body.
  </div>
</div>
  • Post list
    requires parameters for the displayed personal information and post list. These three modules interact with each other. In this case, the data needs to be stored in the upper component.
    Insert image description here
    Upper component UserProfileView.vue:
  // setup:()=>{
    
    
  // }
  // 当一个对象是函数时,可以简写为以下:
  setup(){
    
    
    const user =reactive({
    
    
      id:1,
      username:"ZhuJiaxuan",
      lastName:"Zhu",
      firstName:"Jiaxuan",
      followerCountL:0,
      is_followed:false,

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

那么如何使用呢?(在不同组件之间传递信息)
Insert image description here
Insert image description here

子组件接收:(props)
Insert image description here
使用:
Insert image description here
如果想要的数值需要被计算,如果需要用到传过来的属性
使用computed,参数是一个函数
Insert image description here

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

    }

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

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

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

  • 对于帖子列表,同理
    v-for
    Insert image description here
  • 发帖区也是同理:
    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>
  • 现在要将发帖区的内容显示在帖子列表区域
    子组件向父组件传递信息,然后父向子传递信息?!
    Insert image description here
  • 首先要获取textarea里的信息
    Insert image description here
    利用v-model,v-model的标签与内容绑定起来
    Insert image description here
    举例:
    Insert image description here
    Insert image description here
  • 还需要一个触发函数。当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>

Use bootstrap's Grid system for layout:
Insert image description here
fine-tune the style,
Insert image description here
and jump to 404 when accessing a page that does not exist:
Insert image description here

To add an id to the link,
Insert image description here
you need to add parameters to the route. Use :
Insert image description here
the corresponding UserProfileView to obtain this parameter by using the provided useRoute interface.
Insert image description here
Insert image description here

11. Implement login page

Two variables username need to be bound in two directions, and
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
the value in password ref needs .value access
Insert image description here
to prevent the default behavior.

11.1 vuex

Because many front-end behaviors need to obtain user information, the logged-in user information must be stored in global variables - vuex
interaction is required at this time:
Insert image description here
Global unique object created by vuex:
/store/index.js
Insert image description here
Insert image description here
Insert image description here
access:Insert image description here

11.1.1 Traditional login method

Insert image description here
Stored in cookies, it is difficult to handle when crossing domains.

11.1.2 JWT method

json web token
Insert image description here

11.2 Implement login

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

Dispatch calls events in the store
Insert image description here
. Note: Using ajax requires import $ from 'jquery';\

Insert image description here
To decode the user information from the obtained access string, you need to install a decoding package
Insert image description here
and introduce it in user.js
import jwt_decode from 'jwt-decode';

use:
const access_obj=jwt_decode(access);

Pure memorization: used for authorization.
Insert image description here
Insert image description here
You can successfully obtain the user information currently logged in:
Insert image description here
After having this information, you need to save this information in the state:
However, the action cannot be updated directly, it must be updated through mutations.

Insert image description here
Full version 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 Use refresh to refresh access

Because access will expire in 5 minutes, you need to use refresh to refresh.

Method 1. When accessing, it is found that the access has expired. At this time, get a new access.
Method 2. Get the access every five minutes and call the interface of brushing the access, once every five minutes.

                 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. Jump to the user list page after logging in

  1. Introduce router
    Insert image description here

  2. use api
    Insert image description here

13. Modify the display page after logging in

Modify NavBar. Make these pages logical.
If you want to get the global variables in the store, you can use$ xxx
Insert image description here
Insert image description here

14. Implement exit

Just write an event.
Insert image description here
Whenever you want to modify the global state, you must write the event into action/mutations (requires store)
Insert image description here

15. Jump when not logged in


Insert image description here
The function is triggered when userList.vue is clicked:
Insert image description here

16. User dynamics are obtained from the cloud

The user dynamic page changes according to the change of userId.
Or ajax!
Insert image description here

17. Determine whether the post module is needed

You can only post on your own page.
Insert image description here

18. Use complete links to determine whether the pages are the same

By default, App.vue
Insert image description here
is judged by the name of the page, which is called userprofile. Therefore, when you go to someone else's page and click your own, it will not refresh or jump.

19. From front-end deletion to real deletion

Insert image description here

Insert image description here

20. Deployment

  1. build is packaged into a releaseable version

Insert image description here
2. Package
Insert image description here
3. Transfer to server

Guess you like

Origin blog.csdn.net/qq_39679772/article/details/126367296