AcWing Web应用课 (y总yyds)
前端渲染:只有第一次打开页面的时候,向服务器发送请求,服务器返回所有js, 之后再打开页面,前端用返回的js文件将页面渲染出来。
一个vue文件由三部分组成,html,js,css
css部分标签 ,加上scoped,不同组件之间的css选择器就不会相互影响到了。




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

有一个模块需要单独装


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>


<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>
对于一个比较大的组件,可以拆分为多个组件
首页组件
可以在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>
引入所有组件(页面)

更新路由列表即可
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

想要实现前端渲染,将原本的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>

userProfileInfo/userProfileWrite/userProfilePosts
使用bootstrap的grid


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>

// 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}}即可

关注完以后,还需要更新user状态,此时需要定义事件处理函数。
举例:

将这两个函数绑定起来:

子组件要向父组件传递消息:
父组件:


子组件:用context.emit可以出发父组件的事件


<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>





const post_a_post =(content)=>{
posts.count++;
posts.posts.unshift({
id:posts.count,
userId:1,
content:content,
});
}
从云端将用户列表读进来
npm i jquery 使用ajaximport $ 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接口,即可获取这个参数


需要双向绑定两个变量username,password






ref中的值需要.value访问

阻止默认行为
因为很多前端行为需要获得用户信息,所以要将登录的用户信息存到全局变量中——此时需要vuex
交互:

vuex创建的全局唯一对象:
/store/index.js



访问:

存到cookie中,跨域时难处理。
json web token

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>
因为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);
引入router

使用api

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


只需要写一个事件即可。

凡是要修改全局state,要把事件写到action/mutations里面 (需要store)

userList.vue

点击时触发函数:

用户动态页面根据userId的改变而改变。
还是ajax!

只有在自己的页面才能发帖子。

App.vue

默认是用页面的name判断的,都叫userprofile,所以到别人页面,再点击自己的,就会出现不刷新不跳转的情况。



2. 打包

3. 传到服务器上