AcWing Web Application Course (ytotalyyds)
Article directory
- Vue3 - overall website layout, user dynamic page
-
- 1. Front-end rendering logic
- 2. vue statement
- 3. Component-based framework
- 4. Some preparation
- 5. Implement navigation bar
- 6. Write six widgets
- 7. Add routing
- 8. Implement front-end rendering properties
- 9. Implementation of user dynamic pages (three components)
- 10. User list page implementation
- 11. Implement login page
- 12. Jump to the user list page after logging in
- 13. Modify the display page after logging in
- 14. Implement exit
- 15. Jump when not logged in
- 16. User dynamics are obtained from the cloud
- 17. Determine whether the post module is needed
- 18. Use complete links to determine whether the pages are the same
- 19. From front-end deletion to real deletion
- 20. Deployment
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.
3. Component-based framework
- Can be split and implemented
- How to introduce components (root component App.vue)
project exercises:
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>
There is a module that needs to be installed separately
5. Implement navigation bar
- Implement the navigation bar component - directly use bootstrap to select the required elements and export the component
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>
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
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):
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
-
Import all components (pages)
-
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
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
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)
userProfileInfo/userProfileWrite/userProfilePosts
使用bootstrap的grid
- Divide the largest piece first
- 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.
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,
}
}
那么如何使用呢?(在不同组件之间传递信息)
子组件接收:(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. 用户列表页面实现
从云端将用户列表读进来
- 安装
npm i jquery
使用ajax - 页面中
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:
fine-tune the style,
and jump to 404 when accessing a page that does not exist:
To add an id to the link,
you need to add parameters to the route. Use :
the corresponding UserProfileView to obtain this parameter by using the provided useRoute interface.
11. Implement login page
Two variables username need to be bound in two directions, and
the value in password ref needs .value access
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:
Global unique object created by vuex:
/store/index.js
access:
11.1.1 Traditional login method
Stored in cookies, it is difficult to handle when crossing domains.
11.1.2 JWT method
json web token
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
. Note: Using ajax requires import $ from 'jquery';
\
To decode the user information from the obtained access string, you need to install a decoding package
and introduce it in user.js
import jwt_decode from 'jwt-decode';
use:
const access_obj=jwt_decode(access);
Pure memorization: used for authorization.
You can successfully obtain the user information currently logged in:
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.
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
-
Introduce router
-
use api
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
14. Implement exit
Just write an event.
Whenever you want to modify the global state, you must write the event into action/mutations (requires store)
15. Jump when not logged in
The function is triggered when userList.vue is clicked:
16. User dynamics are obtained from the cloud
The user dynamic page changes according to the change of userId.
Or ajax!
17. Determine whether the post module is needed
You can only post on your own page.
18. Use complete links to determine whether the pages are the same
By default, App.vue
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
20. Deployment
- build is packaged into a releaseable version
2. Package
3. Transfer to server