0%

AcWing SpringBoot项目实战07

学习平台

AcWing SpringBoot框架课

更新对战积分

在对战结束后,需要首先更新两个玩家的对战积分,赢的一局加5分,输一局减2分。然后分别两个玩家的对战积分。然后再将对战记录加入数据库中。

代码实现

  • 修改backend/consumer/utils/Game中的saveDatabase函数。首先获取两个玩家原来的对战积分(需要将WebSocketServer中的userMapper设为public),然后通过loser判断输赢,输的一方减2分,赢得一方加5分。然后调用updateUserRating函数进行rating更新。
  • 使用userMapper进行更新。
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
/**
* 对局结束后,更新玩家的对战积分
* @param player 玩家
* @param rating 对战积分
*/
private void updateUserRating(Player player, Integer rating) {
User user = WebSocketServer.userMapper.selectById(player.getId());
user.setRating(rating);
WebSocketServer.userMapper.updateById(user);
}


/**
* 将对战记录保存到数据库中。在对战结束后先更新两个玩家的对战积分。
*/
private void saveToDatabase() {
Integer ratingA = WebSocketServer.userMapper.selectById(playerA.getId()).getRating();
Integer ratingB = WebSocketServer.userMapper.selectById(playerB.getId()).getRating();

// 赢一局加5分,输一局减2分。
if ("A".equals(loser)) {
ratingA -= 2;
ratingB += 5;
} else if ("B".equals(loser)){
ratingA += 5;
ratingB -= 2;
}

updateUserRating(playerA, ratingA);
updateUserRating(playerB, ratingB);

Record record = new Record(
null,
playerA.getId(),
playerA.getSx(),
playerA.getSy(),
playerB.getId(),
playerB.getSx(),
playerB.getSy(),
playerA.getStepsString(),
playerB.getStepsString(),
getMapString(),
loser,
new Date()
);
// 保存在数据库
WebSocketServer.recordMapper.insert(record);
}

实现效果

可以看到对局结束后,两个玩家的对战积分产生了变化。

image-20230329150002053

创建对战列表与排行榜页面

添加分页配置

backend/config中添加MybatisConfig分页配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

/**
* @author xzt
* @version 1.0
*/
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

对战列表页面实现

后端代码实现

  • 实现backend/service/record/GetRecordListService。因为需要分页,所以需要传入两个参数:pageNum当前页码,pageSize每页显示数量。
1
2
3
4
5
6
7
8
9
10
11
package com.kob.backend.service.record;

import com.alibaba.fastjson2.JSONObject;

/**
* @author xzt
* @version 1.0
*/
public interface GetRecordListService {
JSONObject getList(Integer pageNum, Integer pageSize);
}
  • 实现backend/service/impl/record/GetRecordListServiceImpl
    • 因为数据库中record表中只存储了两个玩家的id,因此还需要根据id找到两个玩家的用户名和头像。
    • 同时,前端还需要直到总共可以分为多少页,因此需要将对战记录的总数也传给前端。具体实现如下。
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
package com.kob.backend.service.impl.record;

import com.alibaba.fastjson2.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.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
@Service
public class GetRecordListServiceImpl implements GetRecordListService {

@Autowired
private RecordMapper recordMapper;

@Autowired
private UserMapper userMapper;

@Override
public JSONObject getList(Integer pageNum, Integer pageSize) {
IPage<Record> recordIPage = new Page<>(pageNum, pageSize);

QueryWrapper<Record> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id"); // 按照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());
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;
}
}
  • 实现backend/controller/record/GetRecordListController。就是用来接收前端传来的参数并调用service层函数来进行查找和数据封装,最后将封装好的数据返回给前端。
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
package com.kob.backend.controller.record;

import com.alibaba.fastjson2.JSONObject;
import com.kob.backend.service.record.GetRecordListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
* @author xzt
* @version 1.0
*/
@RestController
@RequestMapping("/record")
public class GetRecordListController {
@Autowired
private GetRecordListService getRecordListService;

@GetMapping("/getlist")
JSONObject getList(@RequestParam Map<String, String> data) {
Integer pageNum = Integer.parseInt(data.get("pageNum"));
Integer pageSize = Integer.parseInt(data.get("pageSize"));

return getRecordListService.getList(pageNum, pageSize);
}
}

前端代码实现

前端需要实现一个带分页功能的表格,因此引入table组件和nav分页组件,并编写对应的css样式。下面主要介绍js中实现的函数。

  • 首先当打开页面时,需要调用pull_page函数来向后端发送请求,来获取所有的record,并将获取到的record保存在records中,总数量保存在total_records中。
  • 然后对组件tr使用v-for遍历将所有的record显示在表格中。
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<template>
<ContentField>
<table class="table 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">
&nbsp;
<span class="record-user-username">{{ record.a_username }}</span>
</td>
<td>
<img :src="record.b_photo" alt="" class="record-user-photo">
&nbsp;
<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>
<nav aria-label="...">
<ul class="pagination" style="float: right;">
<li class="page-item">
<a class="page-link" href="#" @click="click_page(-2)">前一页</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">
<a class="page-link" href="#" @click="click_page(-1)">后一页</a>
</li>
</ul>
</nav>
</ContentField>
</template>


<script>
import ContentField from '@/components/ContentField.vue'
import { useStore } from 'vuex';
import { ref } from 'vue';
import $ from 'jquery'
import router from '@/router/index';

export default {
components: {
ContentField
},
setup() {
const store = useStore();
let records = ref([])
let current_page = 1;
let total_records = 0;

let pages = ref([]);

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;
}

const pull_page = pageNum => {
current_page = pageNum;
$.ajax({
url: "http://127.0.0.1:3000/record/getlist/",
data: {
"pageNum": pageNum,
"pageSize": 10,
},
type: "get",
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);

// 将地图从String转为数组
const StringTo2D = map => {
let g = [];
for(let i = 0, k = 0; i < 13; i ++) {
let line = [];
for(let j = 0; j < 14; j ++, k ++) {
if(map[k] === '0') line.push(0);
else line.push(1);
}
g.push(line);
}
console.log(g);
return g;
}


const open_record_content = recordId => {
// 这边可以直接点击按钮是将当前的record对象传过来就可。
for(const record of records.value) { // 在records列表中找到当前的record
if(record.record.id === recordId) {
store.commit("updateIsRecord", true);
console.log(record.record);
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;
}
}
}

return {
records,
total_records,
open_record_content,
pages,
click_page,
}
}
}

</script>

<style scoped>

img.record-user-photo {
width: 4vh;
border-radius: 50%;
}
th, td {
text-align: center;
vertical-align: middle; /* 垂直居中 */
}

</style>

分页效果实现

为了实现分页效果,实现了两个函数:

  • update_pages()函数:需要将当前页码的前两页和后两页页码显示在分页组件中,因此需要实现一个pages数组,用来保存所有可以显示的页码,并且在函数中进行判断,如果当前页码和目标页码相同,则当前页码高亮(class中加上active)。这个函数需要在pull_page()从后端获取到数据后进行调用。
  • clickPage(page)函数:首先设置【前一页】按钮传入参数-2,【后一页】按钮传入-1。则当按其他页面时则是正整数。然后根据传入的页码调用pull_page函数从后端获取当前页面的数据。
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
<script>
let current_page = 1;
let total_records = 0;

let pages = ref([]);

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;
}
</script>

实现效果

实现效果如下:

image-20230329195958635

查看录像功能实现

录像功能主要是当用户点击查看录象时,会展示所查看的对局”回放”

代码实现

  • 首先需要实现一个全局对象store/record.js,里面保存四个参数,以及实现对应的update函数
    • is_record:表示是否是录像,false表示不是录像,true表示是录像。
    • a_steps:保存玩家A在对局中所进行的步骤。
    • b_steps:保存玩家B在对局中所进行的步骤。
    • record_loser:保存当前对局的输家。
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

export default {
state: {
is_record: false,
a_steps: "",
b_steps: "",
record_loser: "",
},
getters: {
},
mutations: { // 用来给state赋值,相当于set(), 但是是私有的,
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: {
}
}
  • views/record/RecordIndexView.vue中实现函数open_record_content函数,当点击【查看录像】按钮时,进行嗲用,需要传当前对局的id。然后在当前页面所有对局records中找到该record。
    • 调用updateIsRecord函数将record.js中的is_record设为true.这里还需要修改PkIndexView.vue,在打开pk页面时将is_record设为false
    • 调用updateGame函数,设置store/pk.js对应的对局信息,包括游戏地图,玩家A/B的id,起始位置。这里后端传的地图时字符串,需要借助StringTo2D将地图转为二维数组。
    • 调用updateSteps函数将record.js中的A、B进行步骤进行初始化。
    • 调用updateRecordLoser函数,设置record.js中的record_loser
    • 然后跳转到RecordContentView.vue页面,并将当前对局的id传过去。
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
<script>
// 将地图从String转为数组
const StringTo2D = map => {
let g = [];
for(let i = 0, k = 0; i < 13; i ++) {
let line = [];
for(let j = 0; j < 14; j ++, k ++) {
if(map[k] === '0') line.push(0);
else line.push(1);
}
g.push(line);
}
console.log(g);
return g;
}


const open_record_content = recordId => {
// 这边可以直接点击按钮是将当前的record对象传过来就可。
for(const record of records.value) { // 在records列表中找到当前的record
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;
}
}
}
</script>
  • 实现views/record/RecordContentView.vue页面。引入PlayGround组件,页面会根据pk.js中的gamemap创建游戏地图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<PlayGround></PlayGround>
</template>

<script>

import PlayGround from '@/components/PlayGround.vue'

export default {
components: {
PlayGround,
},
setup() {

},
}

</script>

<style scoped>

</style>
  • RecordContentView.vue加入router/index.js,才能在其他页面进行调用。
1
2
3
4
5
6
7
8
9
10
11
12
import RecordContentView from '@/views/record/RecordContentView'

const routes = [
{
path: "/record/:recordId/", // 路由中加参数
name: "record_content",
component: RecordContentView,
meta: {
requestAuth: true,
}
},
]
  • 修改scripts/GameMap.js中的add_listening_events函数,这个函数本来是用来匹配成功后监听用户从键盘的输入。所以现在需要加一个判断,只有当不是录像的时候才监听用户从键盘的输入。否则从record.js中的a_stepsb_steps来读取两条蛇的输入。这里需要通过record.js中的is_record来判断。

    • this.store.state.record.is_record是true时,需要加载录像。首先获取a_stepsb_steps以及loser,还有两条蛇snake0, snake1。然后使用一个setInterval()来控制每300ms进行一次操作。

    当当前步骤是倒数第一不的时候,需要进行失败者的判断。然后停止调用clearInterval()

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
add_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) { // 判断到倒数第二步
console.log(loser);
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); // 每300ms设置下一步的移动方向
} else {
this.ctx.canvas.focus(); // 添加 canvas 聚焦

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,
}));
}
});
}
}

实现效果

下图是录像执行的效果:

image-20230329202435829

最后会正确显示当前对局的失败者。

image-20230329202445929

排行榜页面实现

后端代码实现

  • 实现backend/service/ranklist/GetRanklistService。因为需要分页,所以需要传入两个参数:pageNum当前页码,pageSize每页显示数量。
1
2
3
4
5
6
7
8
9
10
11
12
package com.kob.backend.service.ranklist;

import com.alibaba.fastjson2.JSONObject;

/**
* @author xzt
* @version 1.0
*/
public interface GetRanklistService {
JSONObject getList(Integer pageNum, Integer pageSize);
}

  • 实现backend/service/impl/ranklist/GetRanklistServiceImpl
    • 因为数据库中record表中只存储了两个玩家的id,因此还需要根据id找到两个玩家的用户名和头像。
    • 同时,前端还需要直到总共可以分为多少页,因此需要将对战记录的总数也传给前端。具体实现如下。
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
package com.kob.backend.service.impl.ranklist;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.ranklist.GetRanklistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author xzt
* @version 1.0
*/
@Service
public class GetRanklistServiceImpl implements GetRanklistService {

@Autowired
private UserMapper userMapper;
@Override
public JSONObject getList(Integer pageNum, Integer pageSize) {
IPage<User> page = new Page<>(pageNum, pageSize);

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("rating");
List<User> users = userMapper.selectPage(page, 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;
}
}

  • 实现backend/controller/ranklist/GetRanklistController。就是用来接收前端传来的参数并调用service层函数来进行查找和数据封装,最后将封装好的数据返回给前端。
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
package com.kob.backend.controller.ranklist;

import com.alibaba.fastjson2.JSONObject;
import com.kob.backend.service.ranklist.GetRanklistService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
* @author xzt
* @version 1.0
*/
@RestController
@RequestMapping("/ranklist")
public class GetRanklistController {
@Autowired
private GetRanklistService getRanklistService;

@GetMapping("/getlist")
public JSONObject getList(@RequestParam Map<String, String> data) {
int pageNum = Integer.parseInt(data.get("pageNum"));
int pageSize = Integer.parseInt(data.get("pageSize"));
return getRanklistService.getList(pageNum, pageSize);
}
}

前端代码实现

需要实现views/ranklist/RankListIndexView.vue,和RecordIndexView.vue实现一样。

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<template>
<ContentField>
<table class="table table-hover">
<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">
&nbsp;
<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">
<a class="page-link" href="#" @click="click_page(-2)">前一页</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">
<a class="page-link" href="#" @click="click_page(-1)">后一页</a>
</li>
</ul>
</nav>
</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 users = ref([])
let current_page = 1;
let total_users = 0;
let pages = ref([]);

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;
}

const pull_page = pageNum => {
current_page = pageNum;
$.ajax({
url: "http://127.0.0.1:3000/ranklist/getlist/",
data: {
"pageNum": pageNum,
"pageSize": 3,
},
type: "get",
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,
total_users,
pages,
click_page,
}
}
}

</script>

<style scoped>

img.record-user-photo {
width: 4vh;
border-radius: 50%;
}
th, td {
text-align: center;
vertical-align: middle; /* 垂直居中 */
}

</style>

实现效果

image-20230329202839657

优化

当匹配成功后,现在没有提示哪条蛇是自己操作的,这样不合理,所以在这里对这部分的内容进行优化。

实现步骤

  • 首先再pk.js中添加一个变量birthLocation来记录当前玩家的出生位置,初始为none,出生在左下角时为left,出生在右上角时为right

  • 实现一个组件components/BirthLocation.vue来提示用户操作的蛇出现在哪里。

    • 只有当当前页面时"matching"匹配页面并且出生位置已经确定为"left"时,显示[你将出生在左下角]。
    • 只有当当前页面时"matching"匹配页面并且出生位置已经确定为"right"时,显示[你将出生在右上角]。
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
<template>
<div class="result-board">
<div class="result-board-text" v-if="$store.state.pk.status === 'matching' && $store.state.pk.birthLocation === 'left'">
你将出生在左下角
</div>
<div class="result-board-text" v-if="$store.state.pk.status === 'matching' && $store.state.pk.birthLocation === 'right'">
你将出生在右上角
</div>
</div>
</template>

<script>

export default {
setup() {

}
}

</script>

<style scoped>
div.result-board{
height: 20vh;
width: 30vw;
background-color: rgba(50, 50, 50, 0.5);
position: absolute;
top: 35vh;
left: 35vw;
}
div.result-board-text {
text-align: center;
color: white;
font-size: 50px;
font-weight: 600;
font-style: italic;
padding-top: 5vh;
}
</style>
  • 修改views/pk/PkIndexView.vue页面
    • 首先引入组件BirthLocation。当页面状态时"matching"时并且birthLocation不是"none"时,就显示该组件。
    • 修改socket.onmessage函数,当匹配成功后,首先通过store.state.user.ida_id或者b_id来判断,然后通过updateBirthLocation来更新pk.js中的birthLocation
    • 需要注意的是,在将页面状态改为playing时也需要将birthLocation设为none,不然会产生错误。下面给出部分代码。
    • 因为2s后才进入对战页面,所以提示也会存在两秒。
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
<template>
<PlayGround v-if="$store.state.pk.status === 'playing'"></PlayGround>
<MatchGround v-if="$store.state.pk.status === 'matching'"></MatchGround>
<ResultBoard v-if="$store.state.pk.loser != 'none'"></ResultBoard>
<BirthLocation v-if="$store.state.pk.status === 'matching' && $store.state.pk.birthLocation != 'none'"></BirthLocation>
</template>

<script>

import PlayGround from '@/components/PlayGround.vue'
import MatchGround from '@/components/MatchGround.vue'
import ResultBoard from '@/components/ResultBoard.vue'
import BirthLocation from '@/components/BirthLocation.vue'
import { onMounted, onUnmounted } from 'vue';
import { useStore } from 'vuex';
socket.onmessage = msg => {
const data = JSON.parse(msg.data);
if(data.event === "success-matching") { // 表示匹配成功
store.commit("updateOpponent", {
username: data.opponent_username,
photo: data.opponent_photo,
});
let a_id = data.game.a_id;
let b_id = data.game.b_id;
if(store.state.user.id == a_id)
store.commit("updateBirthLocation", "left");
else if(store.state.user.id == b_id)
store.commit("updateBirthLocation", "right");

setTimeout(() => {
store.commit("updateStatus", "playing");
store.commit("updateBirthLocation", "none");
}, 2000);
store.commit("updateGame", data.game);
}
}

</script>

实现效果

匹配成功后则会显示。进入对战页面则会自动消失。

image-20230329204128240

正在加载今日诗词....