SpringBoot(二) 实现vue前端
SpringBoot 、vue
文章目录
前端的基本框架
导航栏的基本架构:
KOB 对战 对局列表 排行榜 用户中心
NavBar 组件
-
因为导航栏多次复用,因此做页面的时候可以将导航栏专门提炼出来,在 vue 里面创造一个组件
-
在 vue 里面创组件一般是在 components 目录下,现在在其下面创建一个 NavBar.vue ,在 vue
里面创建组件名至少要有 两个 字母大写,否则会报错。
-
一个组件里面至少要有三个部分:html、js、css ;
<template> </template> <script> </script> <style scoped> // scoped 的作用是在这里面写的 css 它会加上一个随机字符串,使得这个样式不会影响到组件 // 以外的部分 </style>
用 Bootstrap 框架来做导航栏,Bootstrap 能够让程序员很轻松地就可以做到美工的效果,它提供了很好的工具,选就可以了,不需要自己去创造,为了让程序员写一个漂亮的网站。
在 Bootstrap 里面搜 Nav 选择一个合适的就行了,粘过来放在 template 里面。然后在 src/App.vue 的 script 里面导入 NavBar ,并在 template 里面引用一下
<template>
<NavBar />
<router-view />
</template>
<script>
import NavBar from '@/components/NavBar.vue'
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap/dist/js/bootstrap"
export default {
components: {
NavBar
}
}
</script>
- 然后在 script 里面导入一下 Boostrap 的 css 包 和 js 包 ,然后会产生一个报错信息,复制一下报错信息,去 vue 的控制台安装该依赖。
修改 Navbar 组件的样式
-
可以去 NavBar 组件里面修改一下样式:删掉 fluid 然后导航栏会变窄一点、将 light 改为 dark 导航栏背景会变暗
-
将 NavBar.vue 里面的商标和内容换一下:King Of Bots、对战、对局列表、排行榜,删掉 button 按钮
-
导航栏里面的聚焦控制一下 active 的位置,active 在的地方就会聚焦,先删掉,等下再看如何控 制
创建下拉菜单:
复制一下 id=“navbarText” 所在的 div 里面的 ul 的内容,覆盖掉下面的 span 标签 ,粘贴的 ul 里面的内容只保留一个 li 标签就可以了,并且删掉它里面控制宽度的 me-auto mb-2 mb-lg-0 的标签就可以了。
接着在 Bootstrap 页面里去找下拉菜单,替换掉导航栏右边的那个 li 标签
之后的每个页面一般也是写一个单独的组件
创建页面
① Pk ② Record ③ Ranklist ④ User/Bot ⑤ 404
页面的组件,一般喜欢放在 view 里面,放在 components 里面也行;以后每个页面里面也可能包含很多组件,推荐每个模块建一个文件夹。
然后分别创建文件夹 pk、 record、ranklist、user/bots;
- ① Pk 页面需要创建一个索引页面 PkIndexView.vue
- ② Record 创建一个索引页面 RecordIndexView.vue
- ③ Ranklist 创建一个索引页面 RanklistIndexView.vue
- ④ User/Bot 创建一个索引页面 UserBotIndexView.vue
- ⑤ 404 创建一个索引页面 NotFound.vue
每个页面都有三个部分:html、js、css,然后分别写上模板内容
链接对应页面
vue 里面的路径:
现在的主页(App.vue)里面包含两个内容:一个是 NavBar;另一个是 route-view ;
这个 route-view 它会自动更具我们的网址来变;它的变化方式在目录 route 下的 index.js 文件里面。
更改链接的步骤:
-
先在 index.js 文件里面导入我们写好的页面
-
import PkIndexView from '@/views/pk/PkIndexView' import RecordIndexView from '@/views/record/RecordIndexView' import RanklistIndexView from '@/views/ranklist/RanklistIndexView' import UserBot from '@/views/user/bots/UserBotIndexView' import NotFound from '@/views/error/NotFound'
然后在 const route 数组里面去添加路径
-
route 里面的路径都是一个对象({}),然后在域名后输入不同的路径会显示不同的页面,但是还有一个问题,只输入域名会什么都没有,所以希望能够重定向到对战页面 (“/pk/”) ,如果是乱七八糟的页面就重定向到 404 (/404/)
const route = [ { //重定向对战页面 path: "/", name: "home", redirect: "/pk/" }, { path: "/pk/", //域名之后的相对路径 name: "pk_index", //对路径起的名字 components: PkIndexView, //路径所对应的组件 }, //下面页面的路径也类似 { path: "/:catchAll(.*)", //上面的都没匹配上,该路径是没有被定义的,重定向到404 redirect: "/404/" //.* 可以匹配任意字符 } ]
-
修改 link(也就是 NavBar 组件 html 里面的 href,都是在域名后添加的路径) 实现点哪个网址就跳向那一个页面。
存在问题: 点完之后页面会刷新一下。但是一般来说这种前后端分离的项目可以实现点完之后不刷新, 这是可以做到的,它其实就是一个单页面的应用。
-
实现点击链接不刷新页面
vue-route 这个组件为我们提供了一个 route-link 标签
将 a 标签换成 route-link , href=" " 换成 :to="{name: " “}” 就可以实现了,注意这里的 name 是我们在写路径时设置的那个 name。
演示一个案例如下:
<a class="navbar-brand" href="/">King Of Bots</a>
改为:
<route-link class="navbar-brand" :to="{name:"home"}">King Of Bots</route-link>
框住每个页面
每个页面里面放一个白框
在 Bootstrap 里面搜索 card ,它可以将一片区域框起来,发现每个页面都需要有一个白框,所以它是公共的,因此可以把它拧出来作为一个组件叫 ContentField.vue。
白框说明: 首先我们要写一个 container 把它括起来,container 是一个自适应大小的容器,然后里面写一个 div 叫 card ,在 card 里面放一个 div 叫 card-body (参照于 Bootstrap),在里面写内容
快捷写法:
div.container>div.card>div.card-body
这个白框未来里面要被填充,填充的内容会渲染在 slot 里面
然后每个页面在 js 区里面 import 这个组件并在 html 模块里引用它
html:
<ContentField>
对战
</ContentField>
js:
import ContentField from '@/components/ContentField.vue'
export default {
components : {
ContentField
}
}
调整白框的样式
白框太靠上了,需要将它往下拉一点,在它的上面添加一个外边距;这里可以体现组件的优势
在 ContentField 组件里面为 container 再添加一个名字 content-field, 然后在 css 模块里写样式
css:
div.content-field {
margin-top:20px;
}
对导航栏实现聚焦(高亮)
首先判断当前在哪个页面,需要在 NavBar 组件的 JS 模块中写一个函数
此时需要导入一个 API ;
还需导入一个实时计算的 API computed ,当我们需要计算一个变量时就使用它;
js:
import { useRoute } from 'vue-router'
import { computed } from 'vue'
export default {
setup() {
const route = useRoute();
let route_name = computed(()=>route.name);
return {
route.name;
}
}
}
如果希望在某个属性前面使用表达式的话,需要在它的前面加上冒号:,这个冒号其实就是 v-bind: 的简写
然后对 NavBar 组件中的 html 模块中的 route-link 中的 class 使用三目运算符
原版
<router-link class="nav-link" aria-current="page" :to="{ name: 'pk_index' }">对战</router-link>
修改后:
<router-link class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link' " aria-current="page" :to="{ name: 'pk_index' }">对战</router-link>
这样就可以实现选中的导航栏高亮。
简易版网站
到这里其实就已经实现了一个简易版的网站,可用作各个网站的模板
游戏地图设计
地图大小是 13 * 13
地图是轴对称和中心对称的,然后左下角有一条蛇,右上角有一条蛇,中间会随机一些障碍物,是完全对称的
障碍物的数量可以动态地调,并且保证蛇可以从左下角走到右上角去,每次刷新都会得到一个新的地图
地图中的物品是如何动起来的?
- 地图是每秒钟刷新60次(帧)(可以调)
- 在每一帧里计算物体的位置,然后将它渲染出来,每一帧都会覆盖上一帧,超过24是能接受的
- 所以这里要实现一个基类,未来所有物品都要继承这个类,它实现了公共的功能(比如每秒刷新60帧)
游戏脚本
代码脚本一般都会放在 assets 里面,这也是秉承自 unity 的一些习惯
- 在 assets 里面创建一个目录叫 scripts 用来存储我们所有的脚本
- 再创建一个目录叫 images 用来存储图片,记得将 background 背景图片放进去
在 scripts 里面创建基类 AcGameObject.js
AcGameObject
为了每一秒钟让所有对象都刷新一遍,我们需要把所有游戏对象先存下来。
定义一个全局数组 const AC_GAME_OBJECTS = []
随后创建游戏基类:
const AC_GAME_OBJECTS = []
export class AcGameObject {
construct() {
AC_GAME_OBJECTS.push(this);
}
}
如何实现每秒钟刷新60次?
前端浏览器中有一个函数叫作 requestAnimationFrame,这个函数可以传一个回调函数,会在下一帧渲染之前执行一下这个回调函数(只执行一次),那么如何让它一直执行呢;可以将该回调函数写成一个迭代函数就行了。
const step = () => {
requestAnimation(step);
}
requestAnimation(step);
游戏对象要具备如下几个函数
start() { //只执行一次
}
update() { //每一帧执行一次,除了第一帧之外
}
on_destroy() { //删除之前执行
}
destory() { //删除当前对象
this.on_destroy();
for(let i in AC_GAME_OBJECTS) {
const obj = AC_GAME_OBJECTS[i];
if(obj === this) {
AC_GAME_OBJECTS.splice(i);
break;
}
}
}
属性:
一般让物体移动都会有速度的概念,移动速度一般都是以秒为单位,一秒钟移动几个像素或者距离。
每两帧之间的时间间隔不一定是均匀的,距离由速度*时间间隔得来的,因此要记录一下时间间隔
constructor() {
this.timedelta = 0; //这一时刻与上一时刻的时间间隔
this.has_called_start = false; //标记start函数执行过没
}
完成 step 函数的逻辑:
let last_tiemstamp; //上一次执行的时刻
const step = timestamp => {
for(let obj of AC_GAME_OBJECTS) {
if(!obj.has_called_start) {
obj.has_called_start = true;
obj.start();
} else {
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
last_timestamp = timestamp;
}
实现游戏地图
在 src/assets/scripts 目录下创建一个 GameMap.js 文件
GameMap.js :
地图要用到基类,所以导入 AcGameObject
import { AcGameObject } from '@/assets/scripts/AcGameObject'
注意事项:
有时候 import 要用到 {} ,有时候不用;它们的区别是:
-
如果是 export 的话,就需要用 {} 括起来
-
如果是 export default 或者全部导入的话,就不用**{}** , 每一个文件只能有一个 default,类似于 java 中的 public class
-
总结:导入部分,引入非 default 时,使用花括号
构造函数要传两个值 ctx 和 parent,画布和它的父元素,
- 前端游戏都是在画布标签里画
- parent 用来动态地修改画布的长、宽
因为未来游戏地图的大小是会动态变化的,因此不要用绝对距离,这里可以先存下每个格子的绝对距离,未来我们存的
位置是相对距离,这里用先用 this.L 来保存下每个格子的绝对距离。
因为它是游戏对象,所以它可以有两个函数 start 和 update
update 每一帧都要执行,每一帧都要渲染,我们可以写一个辅助函数 render ,渲染的意思就是把游戏对象画到地图上,
因此要在 update 里面执行 render
PK 页面
背景没啥用,可以先删了
pk 页面可以存一个游戏区域,为了方便可以写成一个组件命名为 PlayGround.vue
PlayGround.vue 的 html 区可以先写 一个 div class = “playground”
然后在 PkIndexView 页面导入该组件
为了方便调试,可以为 PlayGround.vue 添加一些样式
div.playground {
width: 60vw; //百分之60的浏览器宽度
height: 70vh; //百分之70的浏览器高度
background: lightblue; //背景颜色,浅蓝色
margin: 40px auto; // margin 表示与上、左的外边距,左用 auto 表示居中,此时地图大小可以自适应浏览器的大小
}
游戏区里面不光包含游戏地图,还有记分板,所以 playground(PlayGround组件里的) 里面还可以搞个板子
所以新建一个组件叫作 GameMap.vue ,然后在组件 PlayGround.vue 里面导入组件 GameMap.vue 并使用
GameMap.vue :
html:
<div ref="parent" class="gamemap">
<canvas ref="canvas"> //画布,游戏放在这里面,ref是为了与下面的js产生关联
</canvas>
</div>
js:
import { GameMap } from '@/assets/scripts/GameMap' //为了创建游戏对象
import { ref,onMounted } from 'vue' //为了将canvas引入进来,ref是为了使用变量
//onMounted是组件挂载完之后需要执行哪些操作
export default {
let parent = ref(null);
let canvas = ref(null);
onMounted(()=>{
new GameMap(canvas.value.getContext('2d'),parent.value);
});
return {
parent,
canvas
}
}
css:
div.gamemap {
width: 100%; //100%表示和它的父元素等长
height: 100%;
}
GameMap.js 增加一个辅助函数 update_size() 来更新最大正方形的边长,其他的简单逻辑就直接写了
实现障碍物
创建组件 Wall.js
然后按照逻辑写就行,更多的是逻辑上的思考,不好做笔记
更改图标
用提供的 logo 替换掉 项目的目录 kob/web/public 下的 favicon.ioc 文件
onMounted } from ‘vue’ //为了将canvas引入进来,ref是为了使用变量
//onMounted是组件挂载完之后需要执行哪些操作
export default {
let parent = ref(null);
let canvas = ref(null);
onMounted(()=>{
new GameMap(canvas.value.getContext('2d'),parent.value);
});
return {
parent,
canvas
}
}
css:
div.gamemap {
width: 100%; //100%表示和它的父元素等长
height: 100%;
}
**GameMap.js** 增加一个辅助函数 **update_size()** 来更新最大正方形的边长,其他的简单逻辑就直接写了
## 实现障碍物
创建组件 **Wall.js**
然后按照逻辑写就行,更多的是逻辑上的思考,不好做笔记
## 更改图标
用提供的 **logo** 替换掉 项目的目录 **kob/web/public** 下的 **favicon.ioc** 文件
更多推荐
所有评论(0)