创建项目
使用vue 的图形化界面创建一个新的vue3项目如下图所示
装两个新的插件——router和vuex插件
该过程的可能有点久,需要耐心等待。
再装一些需要的依赖
需要用到的依赖: boostrap和poperjs/core(bootstrap是提供给不会做美工的程序员的一个新的好选择)第二个依赖是使用bootstrap时需要用到的一个依赖。
到此为止,该项目需要用到的东西已经装完。
项目结构
如上图所示的项目
main.js是这一整个项目的入口文件,在里面可以看见如下的一些语句, 上面的一句话意思是根据App.vue这个组件创建一个虚拟dom元素挂载到了id=app的区域里面,对应public文件夹下面的index.html里面的如下图所示的div区域的id就是app。
至于新装的两个依赖router和vuex在上面也有体现出来,上面的store对应的就是vuex插件。
基本概念
后端渲染模型:
每一次打开一个新的页面,都会由客户端client向服务器发送一次请求,服务器将请求的页面返回给客户端。
前端渲染模型:
只有在最开始的时候会请求一次服务器,服务器一次性将所有页面都返回给客户端,后面每一次切换页面都是从本地加载。
需求分析
需要完成的一个项目最后效果图如下所示
需求实现
要使用bootstrap要先在App.vue里面进行引入,这里要连带js,css文件一起引入,不然就无法使用
bootstrap里面的一些交互样式
1.导航栏部分实现
在components文件夹下面新建一个NavBar.vue组件,并加上固定模板语句
随后去bootstrap官网里面去找一些喜欢的导航栏样式
为了能够让别的组件能够引入,这里NavBar组件需要将自己暴露出去并给定一个name属性。
此处的name属性必须有两个大写的字母,否则语法检查过不了。
下面的style scoped可以让这个组件的样式不会影响别的组件里面的样式。
因为每一个页面都需要导航栏,因此可以直接将NavBar组件引入到根组件App.vue里面,为了能使用NavBar,还需要在components区域标注出来,然后就可以直接在template区域里面使用它了。
达成效果
代码实现:
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="#">鼠鼠空间</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">
<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>
为了实现网站美化,可以用一个框将网页内容都包裹起来的。为此使用到一个bootstrap里的一个模块——Card卡片
2.首页页面实现
代码:
实现效果
这里再加上一个container将里面内容括起来使得页面布局更对称,container也是bootstrap里面定义好的一个class,可以响应式的调整中间内容区域的大小。
代码:
实现效果:
可以看见card区域已经和上面导航栏区域对齐。这是因为上面的导航栏区域也是有一个container
导航栏部分代码:
3.公共ContentBase部分实现
由上面的这个图可以看出,所有的页面都会有一个content区域,如果每一个页面都要修改外边距就要全部修改,但是利用一个单独的ContentBase.vue组件就可以实现一次修改在所有页面都生效。
在components文件夹下新建一个ContentBase.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: "ContentBase"
}
</script>
<style scoped></style>
随后在首页的组件HomeView.vue当中改成如下,将ContentBase.vue引入并在ContentBase标签里面填充当前页面的信息。
在<ContenBase></ContenBase>里面可以渲染任意的内容,如上面的效果图就是
这里container被定义在了ContentBase里面,所以要将样式也移动到ContentBase.vue下面
知识点
这里要想在Content.vue里面展示不同页面插入的信息,需要用到一个<slot></slot>标签,
可以将子组件的元素都渲染到slot里面。这个也叫做插槽
Vue3——路由的query参数和命名路由以及默认插槽slot的使用_vue3 路由query_北岭山脚鼠鼠的博客-CSDN博客
4.剩下的页面一起实现(登录、注册、404、鼠友列表,鼠友动态五个页面)
先将剩下的所有需要的页面组件都建好,然后将home页面的内容都复制粘贴进去,每一个都要改name属性。
注意点:所有的name属性都一定要有两个大写字母,否则语法检查过不去。
模板页面代码:
<template>
<ContentBase>
</ContentBase>
</template>
<script>
import ContentBase from '@/components/ContentBase.vue';
export default {
name: 'XXXX',
components: {
ContentBase
}
}
</script>
<style scope></style>
配置不同页面的路由信息:
现在所有的页面都准备好了,要能够根据不同的url后缀跳转不同的页面组件需要到Router里面配置
配置信息如下图所示
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import UserListView from '../views/UserListView.vue'
import UserProfileView from '../views/UserProfileView.vue'
import LoginView from '../views/LoginView.vue'
import NotFoundView from '../views/NotFoundView.vue'
import RegisterView from '../views/RegisterView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/userlist',
name: 'UserList',
component: UserListView
},
{
path: '/userprofile',
name: 'userprofile',
component: UserProfileView
},
{
path: '/login',
name: 'login',
component: LoginView
},
{
path: '/register',
name: 'register',
component: RegisterView
},
{
path: '/404',
name: '404',
component: NotFoundView
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
配置完之后能够跳转相应页面就算成功
配置导航栏的跳转标签
这里设置到的router-link标签的使用可以到一下文章去学习一下,这里的router-link有两种配置方式。
这里直接放结果代码:Navbar.vue代码如下所示
<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:{} }">鼠友动态</router-link>
</li>
</ul>
<ul class="navbar-nav">
<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>
</div>
</div>
</nav>
</template>
<script >
export default {
name: "NavBar",
}
</script>
<style scoped></style>
到此为止,总体的布局已经完成了。
下面都是单独的页面的实现。
鼠友动态页面:
需求说明:
此处的三个模块使用三个不同的组件进行实现:
在Compontents文件夹下面新建三个组件如图
页面布局,这里使用到bootstrap的grid系统 这玩意是将一个单独的块划分为12份,可以支持调整多个块和每一块的大小。
此处需要grid外部是有container的才可以,因为这个是对container进行划分,在contentbase里面已经有了,说错了,这里不是对container进行划分,而是对一个新的块进行划分,就是上图中<div class="row">这个块。
这个row和container还有row之间的关系如下图所示
效果图:
下面这个是进行了3-9分的,在class处使用了col-3和col-9
代码部分:
用户信息组件实现
区域也要分成两个部分,左边区域显示头像,右边区域显示名字
这里同样使用了39分的grid布局,在头像显示那里使用到了一个bootstrap提供的类属性可以让头像自适应布局的大小。
效果演示
此处再将头像变成圆形,设置CSS样式如下可以将图片变成圆形
头像部分完成了,右边的个人信息部分的有三个东西,姓名,粉丝数和一个关注按钮
按钮使用bootstrap提供的一个样式
效果如下
用户信息部分总体代码: 这里的个人信息也使用了一个card将其包围起来
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-3">
<img class="img-fluid" src="https://cdn.acwing.com/media/user/profile/photo/150603_lg_5b00f49635.jpg"
alt="我图片呢" />
</div>
<div class="col-9">
<div class="name">yanghuiyu</div>
<div class="fans">粉丝数:123</div>
<button type="button" class="btn btn-primary btn-sm">关注</button>
<!---此处的btn-primary是控制按钮颜色,btn-sm是控制按钮大小 -->
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "UserInfo",
}
</script>
<style scoped>
img {
border-radius: 50%;
}
.username {
/* 变成粗体 */
font-weight: bold;
}
.fans {
font-size: 15px;
color: gray;
}
button {
padding: 2px, 4px;
font-size: 15px
}
</style>
UserProfileView.vue和UserInfo.vue之间传递信息实现
这里UserProfile页面需要有很多信息,这些信息都需要存储在UserProfile这个组件里面。
知识点
这里信息存储不再是data形式的vue2写法,而是使用一个新的api里面set up函数这个东西.
在setup函数里面可以定义变量,方法。
使用方法,
如果在一个对象里面的一个属性是函数,也可以简写成另外一个格如下所示
定义需要的变量
这里定义变量有两种方式:
一个使用ref,另一个是使用reative
两个的效果差不多,但是ref的运行效率会比reative慢一些。
- ref定义变量,可以用.value属性重新赋值
- reactive定义对象,不可重新赋值
根据上面,可以知道需要一个user的对象,这里用到了reative这个东西需要先进行引入
然后这个user是需要在子组件里面使用的,所以这里又有一个新的知识点,组件之间的信息传递。
知识点
- props存储父组件传递过来的数据
- context.emit():触发父组件绑定的函数
使用:
详细的内容可以看下面这个
Vue3----props和emit的使用_vue3接收props_北岭山脚鼠鼠的博客-CSDN博客
父组件里面
子组件标签里面使用 :属性=“值” 方式进行传递。
子组件里面
将接收到的参数都放到props的属性里面进行接收
在父组件里面的name信息应该是由两个部分组合而成的,也就是需要动态计算的
有需要用到一个新的东西
- computed:动态计算某个数据
具体实现:(需要用到setup进行传值)
在setup当中是不能使用this.value这种东西的,所以要将props作为参数传进去,动态计算出一个fullname
然后return出去后就可以直接使用了
关注按钮实现
在一个个人页面中,如果是已经关注了应该显示以关注,如果是未关注应该显示取消关注。
这里使用到一个v-if标签,根据父组件传过来的信息判断显示哪一个按钮
然后需要点击修改状态,绑定两个函数
这两个函数需要去修改父组件的is_followed这个东西的状态,这里用到另外一个东西
- context.emit():触发父组件绑定的函数
在父组件当中
@follow="follow"左边是子组件里面的函数,右边是父组件里面的函数,要在下面定义,涉及到修改粉丝数之类的。
这里两个事件的名字不一定是一样的,只要能和对应组件对应上就可以了
在子组件当中
使用context.emit(),上下文触发父组件里面的函数,这里要使用context要通过setup的参数列表先传进来,然后才能使用
帖子列表组件实现
帖子列表那一栏要渲染一堆从父组件传过来的数据,也要用到props
先在父组件定义好数据:
定义的这个数组级的要返回
然后在组件的标签里面绑定子组件里面的posts
在子组件当中
首先要到props中去接收一个posts对象,然后使用到一个v-for标签进行数组内容的遍历
在vue和react当中都不推荐使用下标来当做key
<template>
<div class="card">
<div class="card-body">
<div v-for="(post, index) in posts.posts" :key="index">
<div class="card single-post">
<div class="card-body">
{{ post.content }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "UserPosts",
props: {
posts: {
type: Object,
required: true,
}
}
}
</script>
<style scoped>
.single-post {
margin-bottom: 10px;
}
</style>
编辑发帖组件实现
在上面新建好的UserWrite.vue组件里面准备好格式在父组件中引入并使用
在该组件内部就直接到bootstrap里面copy一个编辑框的样式下来直接就可以了
<template>
<div class="card edit-filed">
<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-primary btn-sm">发表</button>
</div>
</div>
</template>
<script>
export default {
name: "UserWrite"
}
</script>
<style scoped>
.edit-filed {
margin-top: 15px;
}
button {
margin-top: 10px
}
</style>
效果图:
在发表的时候需要获取到textarea里面的内容,并将其存储起来返回到父组件的数据post当中,这里首先要在子组件当中准备一个变量接收数据内容,使用到一个v-model标签绑定textarea标签里面的内容和一个变量.
然后又使用到emit这个东西去绑定父组件里面定义的函数,并将帖子内容传了过去新增到post数组当中。
此处使用到ref时必须使用.value获取内容值。
<template>
<div class="card edit-filed">
<div class="card-body">
<label for="edit-post" class="form-label">编辑你的帖子 </label>
<textarea v-model="content" class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
<button @click="post_a_post" type="button" class="btn btn-primary btn-sm">发表</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: "UserWrite",
setup(props, context) {
let content = ref('');
const post_a_post = () => {
context.emit("post_a_post", content.value)
content.value = "";
};
return {
content,
post_a_post
}
}
}
</script>
<style scoped>
.edit-filed {
margin-top: 15px;
}
button {
margin-top: 10px
}
</style>
父组件中
这里的发帖以及帖子以后都要存储到数据库里面,然后今后都是从数据库获取数据来更新页面。