重点是分页的实现
1.上次收尾工作
游戏结束更新积分
将userMapper
修改为public
,因为要在对局结束,修改玩家积分
2.主要内容
2.1加上mybatis分页配置
package com.kob.backend.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2.2对局列表和回放
在类内定义属性时,使用spring自动注入的话,若是在service或controller定义,则不用定义static。翻译过来,意思就是,若当前类本身是单例,则属性本身就只会有一份,无论加不加静态变量,效果都一样;若是第三方类,也就是自己写的类,一般都是会定义多个对象,那么就要思考属性到底是属于类的,还是属于对象的。
A: 后端(实现返回对战记录列表)【*】
接口
package com.kob.backend.service.record;
import com.alibaba.fastjson.JSONObject;
public interface GetRecordListService {
public JSONObject getList(Integer page);//传入第几页,返回JSONObject
}
实现接口
package com.kob.backend.service.impl.record;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kob.backend.mapper.RecordMapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.Record;
import com.kob.backend.pojo.User;
import com.kob.backend.service.record.GetRecordListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class GetRecordListServiceImpl implements GetRecordListService {
@Autowired
private RecordMapper recordMapper ;
@Autowired
private UserMapper userMapper;
@Override
public JSONObject getList(Integer page) {
IPage<Record> recordIPage = new Page<>(page,10);//API
QueryWrapper<Record> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");//按照降序排列
List<Record> records = recordMapper.selectPage(recordIPage,queryWrapper).getRecords();//一页
JSONObject resp = new JSONObject();
List<JSONObject> items = new ArrayList<>();
for(Record record:records){
User userA = userMapper.selectById(record.getAId());
User userB = userMapper.selectById(record.getBId());
JSONObject item = new JSONObject();
item.put("a_photo",userA.getPhoto());
item.put("a_username",userA.getUsername());
item.put("b_photo",userB.getPhoto());
item.put("b_username",userB.getUsername());
item.put("record",record);
items.add(item);
}
resp.put("records",items);
resp.put("records_count",recordMapper.selectCount(null));
return resp;
}
}
控制器
package com.kob.backend.controller.record;
import com.alibaba.fastjson.JSONObject;
import com.kob.backend.service.record.GetRecordListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class GetRecordListController {
@Autowired
private GetRecordListService getRecordListService;
@GetMapping("/record/getlist/")
public JSONObject getList(@RequestParam Map<String,String> data){
Integer page = Integer.parseInt(data.get("page"));
return getRecordListService.getList(page);
}
}
B: 前端(实现接收展示对战记录列表)
查询一下后端数据
将列表显示出来
展示table,把之前的table复制过来即可
<template>
<ContentField>
<div class="container">
<div class="row">
<div class="card-body">
<table class="table table-striped table-hover">
<!-- 表头 -->
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>对战结果</th>
<th>对战时间</th>
<th>操作</th>
</tr>
</thead>
<!-- 表身 -->
<tbody>
<tr v-for="record in records" :key="record.record.id">
<th>
<img :src="record.a_photo" alt="" class="record-user-photo">
{{record.a_username}}
</th>
<th>
<img :src="record.b_photo" alt="" class="record-user-photo">
{{record.b_username}}
</th>
<th>
{{record.result}}
</th>
<th>
{{record.record.createtime}}
</th>
<th>
<button type="button" class="btn btn-secondary">查看录像</button>
</th>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</ContentField>
</template>
<script>
import ContentField from "@/components/ContentField"
import $ from "jquery"
import { useStore } from "vuex"
import { ref } from "vue"
export default {
components:{
ContentField
},
setup(){
const store = useStore();
let total_records = 0 ;
let current_page = 1 ;
let records = ref([]);
console.log("total_records:" + total_records, " current_page:" + current_page)
const pull_page = page =>{
current_page = page ;
$.ajax({
url:"http://localhost:3000/record/getlist/",
type:"get",
data:{
page,
},
headers:{
Authorization:"Bearer " + store.state.user.token,
},
success(resp){
records.value = resp.records ;
total_records = resp.records_count ;
},
error(resp){
console.log(resp)
}
})
}
pull_page(current_page);
return {
records,
}
}
}
</script>
<style scoped>
img.record-user-photo {
width: 5vh;
border-radius: 50% ;
}
</style>
2.3.实现查看录像功能
点击后跳转页面,需要写一个新的View
直接将pk界面复制过来,需要判断是录像还是对战
只需要一个PlayGround组件就可以了
把页面加到路由里面点开Router加一个路由
store/record.js
// import $ from "jquery"
export default {
state: {
is_record : false ,
a_steps:"",
b_steps:"",
record_loser:"",
},
getters: {
},
mutations: {
updateIsRecord(state,is_record){
state.is_record = is_record;
},
updateSteps(state,data){
state.a_steps = data.a_steps;
state.b_steps = data.b_steps;
},
updateRecordLoser(state,loser){
state.record_loser = loser ;
}
},
actions: {
},
modules: {
}
}
RecordIndexView.vue
<template>
<ContentField>
<table class="table table-striped table-hover">
<!-- 表头 -->
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>对战结果</th>
<th>对战时间</th>
<th>操作</th>
</tr>
</thead>
<!-- 表身 -->
<tbody>
<tr v-for="record in records" :key="record.record.id">
<td>
<img :src="record.a_photo" alt="" class="record-user-photo">
<span class="record-user-username">{{ record.a_username }}</span>
</td>
<td>
<img :src="record.b_photo" alt="" class="record-user-photo">
<span class="record-user-username">{{ record.b_username }}</span>
</td>
<td>{{ record.result }}</td>
<td>{{ record.record.createtime }}</td>
<td>
<button @click="open_record_content(record.record.id)" type="button" class="btn btn-secondary">查看录像</button>
</td>
</tr>
</tbody>
</table>
</ContentField>
</template>
<script>
import ContentField from "@/components/ContentField"
import $ from "jquery"
import { useStore } from "vuex"
import { ref } from "vue"
import router from "@/router/index"
export default {
components:{
ContentField
},
setup(){
const store = useStore();
let total_records = 0 ;
let current_page = 1 ;
let records = ref([]);
console.log("total_records:" + total_records, " current_page:" + current_page)
const pull_page = page =>{
current_page = page ;
$.ajax({
url:"http://127.0.0.1:3000/record/getlist/",
type:"get",
data:{
page,
},
headers:{
Authorization:"Bearer " + store.state.user.token,
},
success(resp){
console.log("&&&&&&&&&&&&&")
records.value = resp.records ;
total_records = resp.records_count ;
},
error(resp){
console.log(resp)
}
})
}
pull_page(current_page);
const stringTo2D = map =>{
let g = [] ;
for(let i = 0,k = 0;i < 13 ;i ++ ){
let line = [];
for(let j = 0 ;j < 14 ;j++){
if(map[k] === '0')line.push(0);
else line.push(1);
k++;
}
g.push(line);
}
return g ;
}
const open_record_content = recordId => {
for(const record of records.value){
if(record.record.id === recordId){
console.log(record)
store.commit("updateIsRecord",true);
store.commit("updateGame",{
map:stringTo2D(record.record.map),
a_id:record.record.aid,
a_sx:record.record.asx,
a_sy:record.record.asy,
b_id:record.record.bid,
b_sx:record.record.bsx,
b_sy:record.record.bsy,
});
store.commit("updateSteps",{
a_steps : record.record.asteps,
b_steps : record.record.bsteps,
});
console.log(record.record.map,stringTo2D(record.record.map))
store.commit("updateRecordLoser",record.record.loser);
router.push({
name:"record_content",
params:{
recordId:recordId,//path中的参数,在router中
}
})
break;
}
}
}
return {
records,
open_record_content,
}
}
}
</script>
<style scoped>
img.record-user-photo {
width: 5vh;
border-radius: 50% ;
}
</style>
Gamemap.js
//获取键盘输入
add_listening_events(){
if(this.store.state.record.is_record){//record
let k = 0 ;//第k步
const [snake0, snake1] = this.snakes ;
const a_steps = this.store.state.record.a_steps ;
const b_steps = this.store.state.record.b_steps ;
console.log(this.store.state.record) ;
const loser = this.store.state.record.record_loser ;
const interval_id = setInterval(()=>{
if(k >= a_steps.length - 1){
if(loser === "all" || loser === "A"){
snake0.status = "die";
}
if(loser === "all" || loser === "B"){
snake1.status = "die";
}
clearInterval(interval_id);
} else {
snake0.set_direction(parseInt(a_steps[k]));
snake1.set_direction(parseInt(b_steps[k]));
}
k ++ ;
},300)
}
else//PK
{
this.ctx.canvas.focus();//cts[DOM] canvas[画板]
//为画板绑定keydown事件
this.ctx.canvas.addEventListener("keydown",e => {
let d = -1;
if(e.key === "w") d = 0;
else if(e.key === "d") d = 1;
else if(e.key === 's') d = 2;
else if(e.key === 'a') d = 3;
if(d >= 0){
this.store.state.pk.socket.send(JSON.stringify({
event:"move",
direction:d,
}))
}
})
}
}
2.4.(重点)实现对战记录的分页功能_前端(后端已经在【*】处实现)
把前端功能加上一个跳转的样式
Bootstrap组件Pagination
放到table外面就可以了
框架
<nav aria-label="..." >
<ul class="pagination" style="float: right;">
<li class="page-item" @click="click_page(-2)">
<a class="page-link" href="#" >前一页</a>
</li>
<li :class="'page-item ' + page.is_active " v-for="page of pages" :key="page.number" @click="click_page(page.number)">
<a class="page-link" href="#">{{page.number}}</a>
</li>
<li class="page-item" @click="click_page(-1)">
<a class="page-link" href="#">后一页</a>
</li>
</ul>
</nav>
逻辑
<script>
import ContentField from "@/components/ContentField"
import $ from "jquery"
import { useStore } from "vuex"
import { ref } from "vue"
import router from "@/router/index"
export default {
components:{
ContentField
},
setup(){
const store = useStore();
let total_records = 0 ;
let current_page = 1 ;
let records = ref([]);
let pages = ref([]);
//实现:点击分页,传入page-->加载新页面数据-->更新前端分页数
//选择第几分页,传page
const click_page = (page) => {
//只有前一页&&后一页才会用到<<当前页>>
if(page === -2) page = current_page - 1 ;
else if(page === -1) page = current_page + 1 ;
let max_pages = parseInt(Math.ceil(total_records / 10));
if(page >= 1 && page <= max_pages){
pull_page(page);//加载一个新分页
}
}
//更新前端的分页显示数量,同时选择的页数激活
const update_pages = () => {
let max_pages = parseInt(Math.ceil(total_records / 10));
let new_pages = [] ;
for(let i = current_page -2 ; i <= current_page +2 ; i ++ ){
if(i >=1 && i <= max_pages){
new_pages.push({
number : i,
is_active : i === current_page ? "active" : "",
});
}
}
pages.value = new_pages ;
}
//接受后端第page分页的数据,同时更新前端的分页显示数量
const pull_page = page =>{
console.log(total_records,current_page);
current_page = page ;
$.ajax({
url:"http://127.0.0.1:3000/record/getlist/",
type:"get",
data:{
page,
},
headers:{
Authorization:"Bearer " + store.state.user.token,
},
success(resp){
records.value = resp.records ;
total_records = resp.records_count ;
update_pages();//更新前端的小方块页数
},
error(resp){
console.log(resp)
}
})
}
pull_page(current_page);
return {
pages,
click_page,
}
}
}
</script>
2.5.后端排行榜的分页功能(后端+前端)
5.1后端
玩家天梯分一共两项
接口
实现接口
控制器
前端
<template>
<ContentField>
<table class="table table-striped table-hover" style="text-align:center;">
<!-- 表头 -->
<thead>
<tr>
<th>玩家</th>
<th>天梯分数排名</th>
</tr>
</thead>
<!-- 表身 -->
<tbody>
<tr v-for="user in users" :key="user.id">
<td>
<img :src="user.photo" alt="" class="record-user-photo">
<span class="record-user-username">{{ user.username }}</span>
</td>
<td>
{{user.rating}}
</td>
</tr>
</tbody>
</table>
<nav aria-label="..." >
<ul class="pagination" style="float: right;">
<li class="page-item" @click="click_page(-2)">
<a class="page-link" href="#" >前一页</a>
</li>
<li :class="'page-item ' + page.is_active " v-for="page of pages" :key="page.number" @click="click_page(page.number)">
<a class="page-link" href="#">{{page.number}}</a>
</li>
<li class="page-item" @click="click_page(-1)">
<a class="page-link" href="#">后一页</a>
</li>
</ul>
</nav>
</ContentField>
</template>
<script>
import ContentField from "@/components/ContentField"
import $ from "jquery"
import { useStore } from "vuex"
import { ref } from "vue"
export default {
components:{
ContentField
},
setup(){
const store = useStore();
let total_users = 0 ;
let current_page = 1 ;
let users = ref([]);
let pages = ref([]);
//实现:点击分页,传入page-->加载新页面数据-->更新前端分页数
//选择第几分页,传page
const click_page = (page) => {
//只有前一页&&后一页才会用到<<当前页>>
if(page === -2) page = current_page - 1 ;
else if(page === -1) page = current_page + 1 ;
let max_pages = parseInt(Math.ceil(total_users / 3));
if(page >= 1 && page <= max_pages){
pull_page(page);//加载一个新分页
}
}
//更新前端的分页显示数量,同时选择的页数激活
const update_pages = () => {
let max_pages = parseInt(Math.ceil(total_users / 3));
let new_pages = [] ;
for(let i = current_page -2 ; i <= current_page +2 ; i ++ ){
if(i >=1 && i <= max_pages){
new_pages.push({
number : i,
is_active : i === current_page ? "active" : "",
});
}
}
pages.value = new_pages ;
}
//接受后端第page分页的数据,同时更新前端的分页显示数量
const pull_page = page =>{
current_page = page ;
$.ajax({
url:"http://127.0.0.1:3000/ranklist/getlist/",
type:"get",
data:{
page:page,
},
headers:{
Authorization:"Bearer " + store.state.user.token,
},
success(resp){
users.value = resp.users ;
total_users = resp.users_count ;
update_pages();//更新前端的小方块页数
},
error(resp){
console.log(resp)
}
})
}
pull_page(current_page);
return {
users,
pages,
click_page,
}
}
}
</script>
<style scoped>
img.record-user-photo {
width: 5vh;
border-radius: 50% ;
}
</style>
2.6.给Bot数量加上限制
QueryWrapper<Bot> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",user.getId());
if(botMapper.selectCount(queryWrapper)>=10){
map.put("error_message","每个用户最多创建10个Bot!");
return map;
}