P6. 对局列表和排行榜功能
-
- [0 概述](#0 概述)
- [1 对局列表功能](#1 对局列表功能)
-
- [1.1 分页配置](#1.1 分页配置)
- [1.2 后端按页获取对局列表接口](#1.2 后端按页获取对局列表接口)
- [1.3 前端展示传回来的对局列表](#1.3 前端展示传回来的对局列表)
- [1.4 录像回放功能](#1.4 录像回放功能)
-
- [1.4.1 录像回放的流程](#1.4.1 录像回放的流程)
- [1.4.2 录像回放的实现](#1.4.2 录像回放的实现)
- [1.5 前端分页展示](#1.5 前端分页展示)
- [2 排行榜功能](#2 排行榜功能)
-
- [2.1 排行榜的实现](#2.1 排行榜的实现)
0 概述
- 本节主要介绍了如何实现对局列表和排行榜 ,对局列表包含对局录像的回放的实现 ,最重要的是掌握怎么写分页功能。
1 对局列表功能
1.1 分页配置
由于对局数量很多,不可能放在同一页上展示,因此要实现分页功能。后端的 Mybatis
分页配置:
MybatisConfig
:
java
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
1.2 后端按页获取对局列表接口
service.impl
需要传入参数
page
,表示当前要获取的是哪一页的列表。
通过 MyBatis
提供的 API
来实现分页查询的功能,查询的页面按最新对局顺序排列。
java
@Override
public JSONObject getRecordList(Integer page) {
IPage<Record> recordIPage = new Page<>(page, 8); // 每页展示8条
QueryWrapper<Record> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<Record> records = recordMapper.selectPage(recordIPage, queryWrapper).getRecords();
List<JSONObject> items = new ArrayList<>();
for (Record record : records) {
JSONObject item = new JSONObject();
User userA = userMapper.selectById(record.getAId());
User userB = userMapper.selectById(record.getBId());
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);
String result = "Draw";
if ("A".equals(record.getLoser())) result = "B Win";
else if ("B".equals(record.getLoser())) result = "A Win";
item.put("result", result);
items.add(item);
}
JSONObject resp = new JSONObject();
resp.put("records", items);
resp.put("records_count", recordMapper.selectCount(null));
return resp;
}
1.3 前端展示传回来的对局列表
写一个表格来展示每个对局的信息,先获取一页的信息进行调试:
vue
<template>
<ContentField>
<table class="table table-striped table-hover" style="text-align: center;">
<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>s
<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 type="button" class="btn btn-secondary">查看录像</button>
</td>
</tr>
</tbody>
</table>
</ContentField>
</template>
<script>
import ContentField from '../../components/ContentField.vue'
import { useStore } from 'vuex';
import { ref } from 'vue';
import $ from 'jquery';
export default {
components: {
ContentField
},
setup() {
const store = useStore();
let records = ref([]);
let current_page = 1;
let total_records = 0;
console.log(total_records);
const pull_page = page => {
current_page = page;
$.ajax({
url: "http://127.0.0.1:3000/record/getlist/",
data: {
page,
},
type: "get",
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: 4vh;
border-radius: 50%;
}
</style>
1.4 录像回放功能
1.4.1 录像回放的流程
首先要明确录像回放需要哪些参数,将这些参数从后端获取后保存在前端。之后要明确前端在哪展示录像,以及如何展示录像。
- 后端传来的参数已在
1.2
节中说明,并且前端保存在records
数组中,records
中的每个record.record
包含了录像的具体信息,包括地图、双方玩家的操作信息、败者等。 - 前端需要开一个全局变量
record.js
进行保存,并且决定在PlayGround
中进行录像回放,因此需要一个变量记录当前是在pk
还是在播放record
。另外,它们共用的GameMap.js
也需要根据是record
还是pk
进行相应的判断,如果是pk
那就是前几节实现的,如果是record
则要播放录像。
1.4.2 录像回放的实现
-
首先在前端
record.js
记录全局变量,在点击"播放录像"按钮之后,应该将对应录像的信息保存到Game, Record
,并且跳转到新的页面播放对应录像,因此需要添加对应的路由(路由中带上对应录像的id
):javascriptpath: '/record/:recordId/', name: 'record_content', component: RecordContentView, meta: { requestAuth: true, }
javascriptconst open_record_content = recordId => { for (const record of records.value) { if (record.record.id === recordId) { 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, }); store.commit("updateRecordLoser", record.record.loser); router.push({ name: "record_content", params: { recordId } }) break; } } }
-
在
GameMap.js
中实现录像回放:javascriptadd_listening_events() { if (this.store.state.record.is_record) { let k = 0; const a_steps = this.store.state.record.a_steps; const b_steps = this.store.state.record.b_steps; const loser = this.store.state.record.record_loser; const [snake0, snake1] = this.snakes; 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页面的操作 */ } }
1.5 前端分页展示
分页要满足以下几点:
(1) 当前页高亮,并且返回当前页的对局列表。
(2) 展示前面2页和后面2页的信息,如果页数不存在就不展示。
(3) 点击对应编号之后会跳转到对应的页面。
vue
<template>
<ContentField>
<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.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>
export default {
setup() {
let current_page = 1;
let total_records = 0;
let pages = ref([]); // pages 记录当前要展示的所有页面
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 / 8));
if (page >= 1 && page <= max_pages) {
pull_page(page);
}
}
const udpate_pages = () => {
let max_pages = parseInt(Math.ceil(total_records / 8));
// 除的是每页展示多少个录像
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;
}
/* 每次 pull_page 之后都要记得 update_pages */
return {
pages,
click_page
}
}
}
</script>
2 排行榜功能
2.1 排行榜的实现
排行榜相对就简单多得多了,和对局列表功能相同,就是查出来用户列表,再在前端展示即可(前端和上面的差不多,不赘述了)。
java
@Override
public JSONObject getRanklist(Integer page) {
IPage<User> userIPage = new Page<>(page, 8);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("rating");
List<User> users = userMapper.selectPage(userIPage, queryWrapper).getRecords();
JSONObject resp = new JSONObject();
for (User user : users) user.setPassword(""); // 记得传回去之前要把密码清空
resp.put("users", users);
resp.put("users_count", userMapper.selectCount(null));
return resp;
}