Springboot-创建对战列表和排行榜页面

这部分的代码实现均在 backend 端。

天梯积分更新

随着对战的结束,要相应的更新用户的天梯积分

image-20220905101408875

更新代码如下:

image-20220905102514339

对局列表

后端 API

首先实现一个 API,从后端返回一个对局列表的 List。实现 API,需要依次编写 service, service.impl, controller。

由于对局列表可能很长,需要添加分页功能。

config.MybatisConfig中添加分页配置:

1
2
3
4
5
6
7
8
9
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

service.record中创建GetRecordListService

不需要将所有页面的信息均返回出来,用户需要展示第几页,就对应的返回第几页。

1
2
3
public interface GetRecordListService {
JSONObject getList (Integer page);//page表示页号 只需要返回对应页号的list
}

service.impl.record中创建GetRecordListServiceImpl

假定每页展示 10 条信息,所以如果 page = 1,返回 09 条;page = 2,返回 1019 条

直接借助 mabatisplus 中的工具 IPage 实现即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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);//每页size为10
QueryWrapper<Record> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");//按照id逆序排序
//将record按照ID逆序排序,并返回第page页的内容
List<Record> records = recordMapper.selectPage(recordIPage, queryWrapper).getRecords();
JSONObject resp = new JSONObject();//用于返回到前端
List<JSONObject> items = new LinkedList<>();
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());
String result = "平局";
if("A".equals(record.getLoser())) result = "B胜";
else if("B".equals(record.getLoser())) result = "A胜";
item.put("result", result);
item.put("record", record);
items.add(item);
}
resp.put("records", items);
resp.put("records_count", recordMapper.selectCount(null));

return resp;
}
}

controller.record中创建GetRecordListController

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class GetRecordListController {
@Autowired
private GetRecordListService getRecordListService;

@GetMapping("/record/getlist/")
private JSONObject getList (@RequestParam Map<String, String> data){
Integer page = Integer.parseInt(data.get("page"));
return getRecordListService.getList(page);
}
}

前端展示

获取列表

RecordIndexView.vue中,请求获取对局记录。

image-20220905114924342

测试如下:

与后端返回的格式一致,结果分两大部分 records 和 records_count

image-20220905114551579

其中 records.records 又包括

image-20220905114714223

接下来要将列表显示出来。

需要建立一个 tabel,遍历 records 将内容展示出来

image-20220905121909562

效果如下:image-20220905122017059

查看录像

为了方便展示录像,需要存储一些全局信息,包括是否展示录像,以及 a 和 b 的 steps

record.js

image-20220905194555531

同时,点击查看录像,需要进入一个新的页面,建立一个新的 vue 页面src\views\record\RecordContentView.vue

由于录像页面,和 PK 页面实际上大部分内容相同,因此,直接将 PK 页面的内容复制过来加以修改。‘

这里只需要用到PlayGround这一个组件,初始化为。

image-20220905195857088

并将这样一个页面,加入路由

注意,路由中,path可以加入参数,这里recordId就是传入的参数

1
2
3
4
5
6
7
8
9
10
import RecordContentView from '../views/record/RecordContentView.vue'
const routes = [
{
path:"/record/:recordId",
name:"record_content",
component:RecordContentView,
meta:{
requestAuth:true,
}
]

回到src\views\record\RecordIndexView.vue

点击查看录像按钮时,触发open_record_content,并且传入记录的 ID

image-20220905173240392

open_record_content函数中,需要通过updateIsrecord来标记为录像页面;

同时注意,在对战页面的 vue 页面中,取消标记

image-20220905192726799

open_record_content函数中同时调用updateGame,对游戏进行更新,调用updateSteps,更新a_stepb_step,调用updateRecordLoser,更新record_loser

image-20220905204953412

同时需要在 Gamemap.js 中添加一些逻辑判断。

因为不管是对战页面,还是录像页面,都共用了PlayGround组件

对于PlayGround组件,由GameMap组件构成

image-20220905173658505

在 GameMap 中要创建 GameMap 类的实例

image-20220905173816481

因此,不管是对战页面还是录像页面,都需要GameMap.js中执行相应的逻辑

这里需要在GameMap.js中判断是录像是否被标记,如果没有标记,就依然是之前对战的逻辑。

操作回放

如果录像被标记,也就是this.store.state.record.is_record为 true。

录像本质上是操作的回放,只需要根据两名玩家的 steps,重新将蛇移动一遍。

image-20220905204922888

这样,就实现了对局录像的展示。

分页展示

借助 Bootstrap 的 Pagination 组件

image-20220905205824432

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<nav aria-label="...">
<ul class="pagination">
<li class="page-item disabled">
<span class="page-link">Previous</span>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active" aria-current="page">
<span class="page-link">2</span>
</li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>

加入到 vue 页面中去,效果如下:

image-20220905210240683

image-20220905210248588

active标记的数字变蓝

我们的需求是,展示当前页,并且在分页栏中留出前后两页。

image-20220905214731291

image-20220905215642772

image-20220905215702172

此时结果符合预期

image-20220905215730382

并且,还需要点击哪个按钮,就要跳转到对应的页面。

image-20220905220558503

image-20220905220611554

image-20220905220620228

分页功能成功实现!

排行榜

后端 API

image-20220905222243643

image-20220905222259006

image-20220905222309156

前端展示

在对局页面src\views\record\RecordIndexView.vue逻辑一致,因此,代码复用率很高

主要就是将 records 和 total_records 改为 users 和 total_users,修改非常少量的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<template>
<ContentFieldVue>
<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="ranklist-user-photo">
&nbsp;
<span class="ranklist-user-username"> </span>
</td>
<td>

</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 in pages" :key="page.pageId" @click="click_page(page.pageId)">
<a class="page-link" href="#"></a>
</li>
<li class="page-item" @click="click_page(-1)">
<a class="page-link" href="#">后一页</a>
</li>
</ul>
</nav>
</ContentFieldVue>
</template>
<script>
import ContentFieldVue from '../../components/ContentField.vue'
import { useStore } from 'vuex';
import $ from 'jquery'
import { ref } from 'vue';
export default {
components: {
ContentFieldVue
},
setup() {
const store = useStore();
let current_page = 1;
let users = ref([]);
let total_users = 0;
let pages = ref([]);
const click_page = page => {
if(page === -2)
page = current_page - 1;
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({
pageId:i,
is_active:i === current_page ? "active" : ""
});
}
}
pages.value = new_pages;
}

const pull_page = page => {
current_page = page;
$.ajax({
url: "http://127.0.0.1:3000/ranklist/getlist/",
data: {
page,
},
type: "get",
headers: {
Authorization: "Bearer " + store.state.user.token,
},
success(resp) {
console.log(resp);
users.value = resp.users;
total_users = resp.user_count;
update_pages();
},
error(resp) {
console.log(resp);
}
})
}
pull_page(current_page);

return {
users,
total_users,
pages,
click_page
}
}
}
</script>

此时就可以实现

image-20220905230520878