parent
2c381678af
commit
d4e0391f83
30 changed files with 1998 additions and 76 deletions
@ -1,26 +1,75 @@ |
|||||||
<template> |
<template> |
||||||
<img alt="Vue logo" src="./assets/logo.png"> |
<div class="game-body"> |
||||||
<HelloWorld msg="Welcome to Your Vue.js App"/> |
<MenuView v-if="$store.state.router.router_name === 'menu'" /> |
||||||
|
<PkIndexView v-else-if="$store.state.router.router_name === 'pk'" /> |
||||||
|
<RanklistIndexView |
||||||
|
v-else-if="$store.state.router.router_name === 'ranklist'" |
||||||
|
/> |
||||||
|
<RecordIndexView v-else-if="$store.state.router.router_name === 'record'" /> |
||||||
|
<RecordContentView |
||||||
|
v-else-if="$store.state.router.router_name === 'record_content'" |
||||||
|
/> |
||||||
|
<UserBotIndexView |
||||||
|
v-else-if="$store.state.router.router_name === 'user_bot'" |
||||||
|
/> |
||||||
|
</div> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<script> |
<script> |
||||||
import HelloWorld from './components/HelloWorld.vue' |
import { useStore } from "vuex"; |
||||||
|
import MenuView from "./views/MenuView.vue"; |
||||||
|
import PkIndexView from "./views/pk/PkIndexView.vue"; |
||||||
|
import RanklistIndexView from "./views/ranklist/RanklistIndexView.vue"; |
||||||
|
import RecordIndexView from "@/views/record/RecordIndexView.vue"; |
||||||
|
import RecordContentView from "@/views/record/RecordContentView.vue"; |
||||||
|
import UserBotIndexView from "@/views/user/bot/UserBotIndexView.vue"; |
||||||
|
|
||||||
export default { |
export default { |
||||||
name: 'App', |
|
||||||
components: { |
components: { |
||||||
HelloWorld |
MenuView, |
||||||
|
PkIndexView, |
||||||
|
RanklistIndexView, |
||||||
|
RecordIndexView, |
||||||
|
RecordContentView, |
||||||
|
UserBotIndexView, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
// 如果localStorage中保存了token,那就读取出来并和后端信息比对,比对成功进入首页 |
||||||
|
const jwt_token = |
||||||
|
"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0YTMyOTdhYzE0YmY0ZDVkYmU2ODAxZDVkNTk3ODhiYyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjYyMTAwNCwiZXhwIjoxNjYzODMwNjA0fQ.rKZG8n3U_U50Aql25nB2k27cZWtxSpE4__a8bnSP3Fs"; |
||||||
|
if (jwt_token) { |
||||||
|
store.commit("updateToken", jwt_token); |
||||||
|
store.dispatch("getInfo", { |
||||||
|
// 向后端请求信息 |
||||||
|
success() { |
||||||
|
store.commit("updatePullingInfo", false); |
||||||
|
}, |
||||||
|
error() { |
||||||
|
store.commit("updatePullingInfo", false); |
||||||
|
}, |
||||||
|
}); |
||||||
|
} else { |
||||||
|
store.commit("updatePullingInfo", false); |
||||||
} |
} |
||||||
} |
}, |
||||||
|
}; |
||||||
</script> |
</script> |
||||||
|
|
||||||
<style> |
<style scoped> |
||||||
#app { |
body { |
||||||
font-family: Avenir, Helvetica, Arial, sans-serif; |
margin: 0; |
||||||
-webkit-font-smoothing: antialiased; |
} |
||||||
-moz-osx-font-smoothing: grayscale; |
|
||||||
text-align: center; |
div.game-body { |
||||||
color: #2c3e50; |
background-image: url("@/assets/images/background.png"); |
||||||
margin-top: 60px; |
background-size: cover; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
div.window { |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
} |
} |
||||||
</style> |
</style> |
||||||
|
After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,50 @@ |
|||||||
|
const AC_GAME_OBJECTS = []; |
||||||
|
|
||||||
|
export class AcGameObject { |
||||||
|
constructor() { |
||||||
|
AC_GAME_OBJECTS.push(this); // 数组添加元素
|
||||||
|
this.timedelta = 0; // 注意单位是毫秒,转化为秒需要除于1000
|
||||||
|
this.has_called_start = false; |
||||||
|
} |
||||||
|
|
||||||
|
start() { // 只执行一次
|
||||||
|
} |
||||||
|
|
||||||
|
update() { // 每一帧执行一次,除了第一帧之外
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
on_destroy() { // 删除之前执行
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
destroy() { |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let last_timestamp; // 上一次执行的时刻
|
||||||
|
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; // 将当前时间更新为上一帧执行时刻
|
||||||
|
requestAnimationFrame(step) // 递归调用该函数
|
||||||
|
} |
||||||
|
|
||||||
|
requestAnimationFrame(step) |
@ -0,0 +1,9 @@ |
|||||||
|
export class Cell{ |
||||||
|
constructor(r,c) { // 传入蛇的坐标(r,c)
|
||||||
|
this.r = r; |
||||||
|
this.c = c; |
||||||
|
this.x = c + 0.5; // 将蛇的左上角坐标(r,c)转化为画布上的圆心坐标(x,y)
|
||||||
|
this.y = r + 0.5; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
import { AcGameObject } from "./AcGameObject"; // 导入js的export class
|
||||||
|
import { Snake } from "./Snake"; |
||||||
|
import { Wall } from "./Wall"; |
||||||
|
|
||||||
|
export class GameMap extends AcGameObject { |
||||||
|
constructor(ctx, parent, store) { |
||||||
|
super(); // 继承类一直要先调用父类的构造函数
|
||||||
|
|
||||||
|
this.ctx = ctx; |
||||||
|
this.parent = parent; |
||||||
|
this.L = 0; |
||||||
|
|
||||||
|
this.rows = 13; |
||||||
|
this.cols = 14; |
||||||
|
|
||||||
|
this.store = store; |
||||||
|
|
||||||
|
this.inner_walls_count = 20; // 障碍物的数量(最大建议80)
|
||||||
|
this.walls = []; // 所有障碍物组成的数组
|
||||||
|
|
||||||
|
// 创建两条蛇
|
||||||
|
|
||||||
|
this.snakes = [ // 蓝蛇和红蛇
|
||||||
|
new Snake({ id: 0, color: "#4876EC", r: this.rows - 2, c: 1 }, this), |
||||||
|
new Snake({ id: 1, color: "#F94848", r: 1, c: this.cols - 2 }, this), |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
create_walls() { // 判断是否生成有效的地图
|
||||||
|
const g = this.store.state.pk.gamemap; |
||||||
|
for (let r = 0; r < this.rows; r++) { |
||||||
|
for (let c = 0; c < this.cols; c++) { |
||||||
|
if (g[r][c]) { |
||||||
|
// 每次添加一个障碍物
|
||||||
|
this.walls.push(new Wall(r, c, this)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
add_listening_events() { // 绑定监听事件
|
||||||
|
if (this.store.state.record.is_record === true) { // 播放录像
|
||||||
|
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; |
||||||
|
|
||||||
|
let 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 { |
||||||
|
this.ctx.canvas.focus(); // 聚焦
|
||||||
|
// 用wasd控制方向
|
||||||
|
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; |
||||||
|
this.store.state.pk.socket.send(JSON.stringify({ |
||||||
|
event: "move", |
||||||
|
direction: d, |
||||||
|
})); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
start() { |
||||||
|
this.create_walls(); |
||||||
|
this.add_listening_events(); |
||||||
|
} |
||||||
|
|
||||||
|
update_size() { // 每过一帧重新生成新的地图尺寸
|
||||||
|
// 取整数
|
||||||
|
this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows)); |
||||||
|
this.ctx.canvas.width = this.L * this.cols; |
||||||
|
this.ctx.canvas.height = this.L * this.rows; |
||||||
|
} |
||||||
|
|
||||||
|
update() { // 更新地图:每隔一帧都要重新渲染
|
||||||
|
this.update_size(); // 更新地图大小
|
||||||
|
if (this.check_ready()) { |
||||||
|
this.next_step(); // 进入下一回合
|
||||||
|
} |
||||||
|
this.render(); // 重新渲染
|
||||||
|
} |
||||||
|
|
||||||
|
check_ready() { // 判断两条蛇是否准备好进行下一步
|
||||||
|
for (const snake of this.snakes) { |
||||||
|
if (snake.status !== "idle") return false; |
||||||
|
if (snake.direction === -1) return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
next_step() { // 让两条蛇进入下一回合
|
||||||
|
for (const snake of this.snakes) { |
||||||
|
snake.next_step(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
check_vaild(cell) { // 检测蛇的目标位置是否合法:是否撞到两条蛇的身体和障碍物
|
||||||
|
for (const wall of this.walls) { |
||||||
|
if (wall.r === cell.r && wall.c === cell.c) { // 撞到墙了
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (const snake of this.snakes) { |
||||||
|
let k = snake.cells.length; |
||||||
|
// 特殊处理!!!
|
||||||
|
if (!snake.check_tail_increasing()) { // 当蛇为会前进的时候,蛇尾不需要判断,因为蛇头可以占据蛇尾的位置
|
||||||
|
k--; |
||||||
|
} |
||||||
|
for (let i = 0; i < k; i++) { // 依次判断是否会撞到两条蛇的身体
|
||||||
|
if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c) { // 撞到蛇的身体了
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const color_even = "#AAD751", color_odd = "#A2D149"; |
||||||
|
for (let r = 0; r < this.rows; r++) { |
||||||
|
for (let c = 0; c < this.cols; c++) { |
||||||
|
if ((r + c) % 2 == 0) { |
||||||
|
this.ctx.fillStyle = color_even; |
||||||
|
} else { |
||||||
|
this.ctx.fillStyle = color_odd; |
||||||
|
} |
||||||
|
this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,168 @@ |
|||||||
|
import { AcGameObject } from "./AcGameObject"; |
||||||
|
import { Cell } from "./Cell"; |
||||||
|
|
||||||
|
export class Snake extends AcGameObject { |
||||||
|
constructor(info,gamemap) { // 传入蛇的信息和地图
|
||||||
|
super(); |
||||||
|
|
||||||
|
this.id = info.id; // 哪条蛇
|
||||||
|
this.color = info.color; // 蛇的颜色
|
||||||
|
this.gamemap = gamemap; // 方便调用地图的有关参数
|
||||||
|
this.cells = [new Cell(info.r,info.c)] // cells存放蛇的坐标,cells[0]存放蛇头
|
||||||
|
|
||||||
|
this.speed = 5; // 定义蛇的速度,每秒走5个格子
|
||||||
|
this.direction = -1; // -1表示没有指令,0,1,2,3分别表示左右下左
|
||||||
|
this.status = "idle"; // idle表示静止,move表示移动,die表示死亡
|
||||||
|
this.next_cell = null; // 下一步的目标位置
|
||||||
|
|
||||||
|
// 0,1,2,3:上右下左
|
||||||
|
this.dr = [-1,0,1,0]; // 行方向的偏移量
|
||||||
|
this.dc = [0,1,0,-1]; // 列方向的偏移量
|
||||||
|
|
||||||
|
this.step = 0; // 蛇当前的回合数
|
||||||
|
this.eps = 1e-2; // 允许误差为0.01
|
||||||
|
|
||||||
|
this.eye_direction = 0; // 左下角的蛇的眼睛朝上
|
||||||
|
|
||||||
|
if (this.id === 1) this.eye_direction = 2; // 右上角的蛇的眼睛朝下
|
||||||
|
|
||||||
|
// 定义蛇左右眼的x和y坐标的偏移量
|
||||||
|
this.eyes_dx = [ |
||||||
|
[-1,1], |
||||||
|
[1,1], |
||||||
|
[-1,1], |
||||||
|
[-1,-1] |
||||||
|
]; |
||||||
|
|
||||||
|
this.eyes_dy = [ |
||||||
|
[-1,-1], |
||||||
|
[-1,1], |
||||||
|
[1,1], |
||||||
|
[-1,1] |
||||||
|
]; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
start() { // 第一帧调用该函数
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
update() { // 更新下一帧
|
||||||
|
if (this.status === "move") { // 只有下一步的状态为移动才可以移动
|
||||||
|
this.update_move(); // 蛇进行移动
|
||||||
|
} |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
check_tail_increasing() { // 前10步每步增加1,后面每三步增加1
|
||||||
|
if (this.step <= 10) return true; |
||||||
|
if (this.step % 3 === 1) return true; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
next_step() { // 将蛇的状态置为下一步
|
||||||
|
const d = this.direction; |
||||||
|
// 更新下一步的位置
|
||||||
|
this.next_cell = new Cell(this.cells[0].r+this.dr[d],this.cells[0].c + this.dc[d]); |
||||||
|
this.direction = -1; // 清空当前方向
|
||||||
|
this.status = "move"; // 改为移动
|
||||||
|
this.eye_direction = d; // 更新蛇的眼睛方向
|
||||||
|
this.step++; // 回合数加一
|
||||||
|
|
||||||
|
const k = this.cells.length; |
||||||
|
for (let i = k; i > 0; i--) { |
||||||
|
// 每个数组元素先后移动一位,相当于复制了一份第一个元素
|
||||||
|
// 1,2,3 => 1,1,2,3
|
||||||
|
this.cells[i] = JSON.parse(JSON.stringify(this.cells[i-1]));
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set_direction(d) { // 通过读取用户输入来设置方向
|
||||||
|
this.direction = d; |
||||||
|
} |
||||||
|
|
||||||
|
update_move() { // 蛇进行移动
|
||||||
|
// // 蛇向右移动5个格子
|
||||||
|
// this.cells[0].x += this.speed * this.timedelta / 1000; // 注意每一帧不一定是1s
|
||||||
|
|
||||||
|
const dx = this.next_cell.x - this.cells[0].x; |
||||||
|
const dy = this.next_cell.y - this.cells[0].y; |
||||||
|
|
||||||
|
const distance = Math.sqrt(dx * dx+dy * dy);
|
||||||
|
if (distance < this.eps) { // 移动到了目标点
|
||||||
|
this.cells[0] = this.next_cell; // 添加一个新蛇头
|
||||||
|
this.next_cell = null; |
||||||
|
this.status = "idle"; // 走完了,停下来
|
||||||
|
if (!this.check_tail_increasing()) { // 蛇不变长,去掉蛇尾
|
||||||
|
this.cells.pop(); // 弹出蛇尾
|
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
const move_distance = this.speed * this.timedelta / 1000; //移动距离
|
||||||
|
this.cells[0].x += move_distance * dx / distance; |
||||||
|
this.cells[0].y += move_distance * dy / distance; |
||||||
|
|
||||||
|
if (!this.check_tail_increasing()) { // 蛇不变长,蛇尾需要移动到目标位置
|
||||||
|
const k = this.cells.length; |
||||||
|
const tail = this.cells[k-1],tail_target = this.cells[k-2]; |
||||||
|
const tail_dx = tail_target.x - tail.x; |
||||||
|
const tail_dy = tail_target.y - tail.y; |
||||||
|
tail.x += move_distance * tail_dx / distance; |
||||||
|
tail.y += move_distance * tail_dy / distance; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const L = this.gamemap.L; // 单位距离
|
||||||
|
const ctx = this.gamemap.ctx; // 画布
|
||||||
|
|
||||||
|
ctx.fillStyle = this.color; // 填充颜色
|
||||||
|
|
||||||
|
if (this.status === "die") { |
||||||
|
ctx.fillStyle = "white"; |
||||||
|
} |
||||||
|
|
||||||
|
for (const cell of this.cells) { // 将蛇用圆表示
|
||||||
|
ctx.beginPath(); |
||||||
|
// 前两个参数时圆心坐标,后面的
|
||||||
|
ctx.arc(cell.x * L,cell.y * L,L / 2 * 0.8, 0, Math.PI * 2); |
||||||
|
ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
for (let i = 1; i < this.cells.length; i++) { |
||||||
|
const a = this.cells[i-1],b = this.cells[i]; |
||||||
|
if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y-b.y) < this.eps) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (Math.abs(a.x - b.x) < this.eps) { |
||||||
|
// 矩形起始点的 x轴坐标、y轴坐标、矩形width、矩形height
|
||||||
|
// 用Math.min()是因为这两个矩阵上下关系不知道
|
||||||
|
ctx.fillRect((a.x - 0.4) * L,Math.min(a.y,b.y) * L,L*0.8,Math.abs(a.y-b.y) * L); |
||||||
|
} |
||||||
|
else { |
||||||
|
ctx.fillRect(Math.min(a.x,b.x) * L,(a.y - 0.4) * L,Math.abs(a.x-b.x) * L,L*0.8); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 画出蛇的眼睛
|
||||||
|
|
||||||
|
ctx.fillStyle = "black"; |
||||||
|
|
||||||
|
for (let i = 0; i < 2 ; i++) { |
||||||
|
const eye_x = (this.cells[0].x + this.eyes_dx[this.eye_direction][i] * 0.2) * L; |
||||||
|
const eye_y = (this.cells[0].y + this.eyes_dy[this.eye_direction][i] * 0.2) * L; |
||||||
|
|
||||||
|
// 画眼睛
|
||||||
|
|
||||||
|
ctx.beginPath(); |
||||||
|
ctx.arc(eye_x,eye_y,L*0.05,0,Math.PI*2); |
||||||
|
ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import { AcGameObject } from "./AcGameObject"; |
||||||
|
|
||||||
|
export class Wall extends AcGameObject { |
||||||
|
constructor(r, c, gamemap) { |
||||||
|
super(); |
||||||
|
|
||||||
|
this.r = r; |
||||||
|
this.c = c; |
||||||
|
this.gamemap = gamemap; |
||||||
|
this.color = "#B37226"; // 障碍物的颜色
|
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const L = this.gamemap.L; |
||||||
|
const ctx = this.gamemap.ctx; |
||||||
|
|
||||||
|
ctx.fillStyle = this.color; |
||||||
|
ctx.fillRect(this.c * L, this.r * L, L, L); // 将对应位置填充为障碍物
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
<template> |
||||||
|
<div class="content-field"> |
||||||
|
<slot></slot> |
||||||
|
<div class="back" @click="handleClickBack">返回</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { useStore } from "vuex"; |
||||||
|
|
||||||
|
export default{ |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
const handleClickBack = () => { |
||||||
|
store.commit("updateRouterName","menu"); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
handleClickBack, |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.content-field { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
div.back { |
||||||
|
position: absolute; |
||||||
|
right: 5vw; |
||||||
|
bottom: 5vh; |
||||||
|
cursor: pointer; |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
font-style: italic; |
||||||
|
color: white; |
||||||
|
user-select: none; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
div.back:hover { |
||||||
|
scale: 1.2; |
||||||
|
transition: 400ms; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,38 @@ |
|||||||
|
<template> |
||||||
|
<div ref="parent" class="gamemap"> <!--使用下面返回的parent--> |
||||||
|
<!--tabindex=0获取用户的输入操作--> |
||||||
|
<canvas ref="canvas" tabindex="0"></canvas> <!--使用下面返回的canvas--> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { GameMap } from "@/assets/scripts/GameMap"; // 从js中导入GameMap类(public) |
||||||
|
import { ref, onMounted } from 'vue' |
||||||
|
import { useStore } from 'vuex' |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
let parent = ref(null); |
||||||
|
let canvas = ref(null); |
||||||
|
const store = useStore(); |
||||||
|
onMounted(() => { |
||||||
|
store.commit("updateGameObject", new GameMap(canvas.value.getContext('2d'), parent.value,store)); |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
parent, |
||||||
|
canvas |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.gamemap { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
</style> |
@ -1,58 +0,0 @@ |
|||||||
<template> |
|
||||||
<div class="hello"> |
|
||||||
<h1>{{ msg }}</h1> |
|
||||||
<p> |
|
||||||
For a guide and recipes on how to configure / customize this project,<br> |
|
||||||
check out the |
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. |
|
||||||
</p> |
|
||||||
<h3>Installed CLI Plugins</h3> |
|
||||||
<ul> |
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li> |
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li> |
|
||||||
</ul> |
|
||||||
<h3>Essential Links</h3> |
|
||||||
<ul> |
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li> |
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li> |
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li> |
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li> |
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li> |
|
||||||
</ul> |
|
||||||
<h3>Ecosystem</h3> |
|
||||||
<ul> |
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li> |
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li> |
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li> |
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li> |
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li> |
|
||||||
</ul> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
export default { |
|
||||||
name: 'HelloWorld', |
|
||||||
props: { |
|
||||||
msg: String |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only --> |
|
||||||
<style scoped> |
|
||||||
h3 { |
|
||||||
margin: 40px 0 0; |
|
||||||
} |
|
||||||
ul { |
|
||||||
list-style-type: none; |
|
||||||
padding: 0; |
|
||||||
} |
|
||||||
li { |
|
||||||
display: inline-block; |
|
||||||
margin: 0 10px; |
|
||||||
} |
|
||||||
a { |
|
||||||
color: #42b983; |
|
||||||
} |
|
||||||
</style> |
|
@ -0,0 +1,199 @@ |
|||||||
|
<template> |
||||||
|
<div class="matchground_field"> |
||||||
|
<div class="matchground"> |
||||||
|
<div class="matchground-head"> |
||||||
|
<div> |
||||||
|
<div class="user_photo"> |
||||||
|
<img :src="$store.state.user.photo" alt="" /> |
||||||
|
</div> |
||||||
|
<div class="username"> |
||||||
|
{{ $store.state.user.username }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="select-bot"> |
||||||
|
<select |
||||||
|
class="form-select" |
||||||
|
v-model="select_bot" |
||||||
|
aria-label="Default select example" |
||||||
|
> |
||||||
|
<option value="-1">用户出战</option> |
||||||
|
<option v-for="bot in bots" :key="bot.id" :value="bot.id"> |
||||||
|
{{ bot.title }} |
||||||
|
</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div> |
||||||
|
<div class="user_photo"> |
||||||
|
<img :src="$store.state.pk.opponent_photo" alt="" /> |
||||||
|
</div> |
||||||
|
<div class="username"> |
||||||
|
{{ $store.state.pk.opponent_username }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="mybtn"> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
v-if="is_matched" |
||||||
|
class="match-btn" |
||||||
|
@click="click_match_btn" |
||||||
|
> |
||||||
|
{{ match_btn_info }} |
||||||
|
</button> |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
v-else |
||||||
|
class="cancel-btn" |
||||||
|
@click="click_match_btn" |
||||||
|
> |
||||||
|
{{ match_btn_info }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref } from "vue"; |
||||||
|
import { useStore } from "vuex"; |
||||||
|
import $ from "jquery"; |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
|
||||||
|
let match_btn_info = ref("开始匹配"); |
||||||
|
let is_matched = ref(true); |
||||||
|
let bots = ref([]); |
||||||
|
let select_bot = ref("-1"); |
||||||
|
|
||||||
|
const click_match_btn = () => { |
||||||
|
if (match_btn_info.value === "开始匹配") { |
||||||
|
match_btn_info.value = "取消匹配"; |
||||||
|
is_matched.value = false; |
||||||
|
console.log(select_bot.value); |
||||||
|
// 给后端发送一个json信息表示当前属于哪个事件 |
||||||
|
store.state.pk.socket.send( |
||||||
|
JSON.stringify({ |
||||||
|
event: "start-matching", |
||||||
|
bot_id: select_bot.value, |
||||||
|
}) |
||||||
|
); |
||||||
|
} else { |
||||||
|
match_btn_info.value = "开始匹配"; |
||||||
|
is_matched.value = true; |
||||||
|
store.state.pk.socket.send( |
||||||
|
JSON.stringify({ |
||||||
|
event: "stop-matching", |
||||||
|
}) |
||||||
|
); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const refresh_bots = () => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/bot/getlist/", |
||||||
|
type: "GET", |
||||||
|
headers: { |
||||||
|
Authorization: "Bearer " + store.state.user.token, // 任何需要登录才能显示的都要加这个验证 |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
// 将后端得到的数据传给bot |
||||||
|
bots.value = resp; |
||||||
|
}, |
||||||
|
}); |
||||||
|
}; |
||||||
|
refresh_bots(); |
||||||
|
return { |
||||||
|
match_btn_info, |
||||||
|
is_matched, |
||||||
|
click_match_btn, |
||||||
|
bots, |
||||||
|
select_bot, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.matchground_field { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
div.matchground { |
||||||
|
width: 60%; |
||||||
|
height: 60%; |
||||||
|
background-color: rgba(123, 197, 212, 0.5); |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-around; |
||||||
|
} |
||||||
|
|
||||||
|
div.matchground-head { |
||||||
|
display: flex; |
||||||
|
justify-content: space-evenly; |
||||||
|
} |
||||||
|
|
||||||
|
img { |
||||||
|
margin-top: 10vh; |
||||||
|
border-radius: 50%; |
||||||
|
width: 12vh; |
||||||
|
} |
||||||
|
|
||||||
|
.user_photo { |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.username { |
||||||
|
margin-top: 30px; |
||||||
|
font-size: 20px; |
||||||
|
font-weight: bold; |
||||||
|
color: black; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.select-bot { |
||||||
|
width: 15vw; |
||||||
|
text-align: center; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.select-bot > select { |
||||||
|
font-size: 18px; |
||||||
|
width: 20vw; |
||||||
|
border-radius: 5px; |
||||||
|
height: 6vh; |
||||||
|
} |
||||||
|
|
||||||
|
.mybtn { |
||||||
|
text-align: center; |
||||||
|
margin-bottom: 10vh; |
||||||
|
} |
||||||
|
|
||||||
|
.mybtn > .match-btn { |
||||||
|
background: #198754; |
||||||
|
border: none; |
||||||
|
border-color: #198754; |
||||||
|
font-size: 20px; |
||||||
|
padding: 5px 5px; |
||||||
|
border-radius: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.mybtn > .cancel-btn { |
||||||
|
background: #dc3545; |
||||||
|
border: none; |
||||||
|
border-color: #dc3545; |
||||||
|
font-size: 20px; |
||||||
|
padding: 5px 5px; |
||||||
|
border-radius: 5px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,24 @@ |
|||||||
|
<template> |
||||||
|
<div class="playground"> |
||||||
|
<GameMap /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import GameMap from './GameMap.vue' // 导入GameMap组件(.vue结尾) |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { // 使用GameMap组件 |
||||||
|
GameMap, |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
/* div标签的样式 */ |
||||||
|
div.playground { |
||||||
|
width: 60vw; |
||||||
|
height: 70vh; |
||||||
|
margin: 40px auto; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,78 @@ |
|||||||
|
<template> |
||||||
|
<div class="result-board"> |
||||||
|
<div class="result-text" v-if="$store.state.pk.loser === 'all'"> |
||||||
|
Draw |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="result-text" v-else-if="$store.state.pk.loser === 'A' && $store.state.pk.a_id === parseInt($store.state.user.id)"> |
||||||
|
Lose |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="result-text" v-else-if="$store.state.pk.loser === 'B' && $store.state.pk.b_id == parseInt($store.state.user.id)"> |
||||||
|
Lose |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="result-text" v-else> |
||||||
|
You Win |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="reset-btn"> |
||||||
|
<button @click="reset" class="btn btn-warning lg">再来一次!</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
import { useStore } from 'vuex'; |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
|
||||||
|
const reset = () => { |
||||||
|
store.commit("updateStatus","matching"); |
||||||
|
store.commit("updateLoser","none"); |
||||||
|
store.commit("updateOpponent", { |
||||||
|
username: "我的对手", |
||||||
|
photo: |
||||||
|
"https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png", |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
reset, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.result-board { |
||||||
|
width: 30vw; |
||||||
|
height: 30vh; |
||||||
|
background-color: rgba(34, 20, 77, 0.5); |
||||||
|
position: absolute; |
||||||
|
top: 30vh; |
||||||
|
left: 35vw; |
||||||
|
} |
||||||
|
|
||||||
|
.result-text { |
||||||
|
font-size: 50px; |
||||||
|
color:yellowgreen; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
font-style: italic; |
||||||
|
padding-top: 4vh; |
||||||
|
padding-bottom: 4vh; |
||||||
|
} |
||||||
|
|
||||||
|
.reset-btn { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
|
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,22 @@ |
|||||||
|
<template> |
||||||
|
<div class="show"> |
||||||
|
<div class="snakeInfo" style="background-color: #4876ec;" v-if="$store.state.user.id == $store.state.pk.a_id">蓝方</div> |
||||||
|
<div class="snakeInfo" style="background-color: #f94848;" v-if="$store.state.user.id == $store.state.pk.b_id">红方</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.snakeInfo { |
||||||
|
width: 50px; |
||||||
|
height: 50px; |
||||||
|
border-radius: 50%; |
||||||
|
position: absolute; |
||||||
|
top: 30vh; |
||||||
|
left: 8vw; |
||||||
|
text-align: center; |
||||||
|
line-height: 50px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,52 @@ |
|||||||
|
export default { |
||||||
|
state: { // 全局变量
|
||||||
|
status: "matching", // matching表示匹配界面,playing表示对战界面
|
||||||
|
socket: null, |
||||||
|
opponent_username: "", //对手的用户名
|
||||||
|
opponent_photo: "", // 对手的头像
|
||||||
|
gamemap: null, // 对战的地图
|
||||||
|
a_id: 0, |
||||||
|
a_sx: 0, |
||||||
|
a_sy: 0, |
||||||
|
b_id: 0, |
||||||
|
b_sx: 0, |
||||||
|
b_sy: 0, |
||||||
|
gameObject: null, |
||||||
|
loser: "none" //"none","all","A","B" none表示还没有结果
|
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { // 用于修改全局数据
|
||||||
|
updateSocket(state, socket) { |
||||||
|
state.socket = socket; |
||||||
|
}, |
||||||
|
updateOpponent(state, opponent) { |
||||||
|
state.opponent_username = opponent.username; |
||||||
|
state.opponent_photo = opponent.photo; |
||||||
|
}, |
||||||
|
updateStatus(state, status) { |
||||||
|
state.status = status; |
||||||
|
}, |
||||||
|
updateGame(state, game) { |
||||||
|
state.gamemap = game.map; |
||||||
|
state.a_id = game.a_id; |
||||||
|
state.a_sx = game.a_sx; |
||||||
|
state.a_sy = game.a_sy; |
||||||
|
state.b_id = game.b_id; |
||||||
|
state.b_sx = game.b_sx; |
||||||
|
state.b_sy = game.b_sy; |
||||||
|
|
||||||
|
}, |
||||||
|
updateGameObject(state, gameObject) { |
||||||
|
state.gameObject = gameObject; |
||||||
|
}, |
||||||
|
updateLoser(state, loser) { |
||||||
|
state.loser = loser; |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { // 在actions中调用修改全局变量的函数
|
||||||
|
|
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
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,record_loser) { |
||||||
|
state.record_loser = record_loser; |
||||||
|
} |
||||||
|
|
||||||
|
}, |
||||||
|
actions: {
|
||||||
|
|
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
export default { |
||||||
|
state: { |
||||||
|
router_name: "menu", // menu,pk,record,record_content,ranklist,user_bot
|
||||||
|
}, |
||||||
|
getters: { |
||||||
|
|
||||||
|
}, |
||||||
|
mutations: {
|
||||||
|
updateRouterName(state, router_name) { |
||||||
|
state.router_name = router_name; |
||||||
|
}, |
||||||
|
}, |
||||||
|
actions: {
|
||||||
|
|
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
import $ from 'jquery' |
||||||
|
|
||||||
|
export default { |
||||||
|
state: { // 全局变量
|
||||||
|
id: "", |
||||||
|
username: "", |
||||||
|
photo: "", |
||||||
|
token: "", |
||||||
|
is_login: false, // 默认未登录状态
|
||||||
|
pulling_info: true, // 是否正在拉取信息
|
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { // 用于修改全局数据
|
||||||
|
updateUser(state,user) { // 更新用户信息
|
||||||
|
state.id = user.id; |
||||||
|
state.username = user.username; |
||||||
|
state.photo = user.photo; |
||||||
|
state.is_login = user.is_login; |
||||||
|
}, |
||||||
|
updateToken(state,token) { // 更新token
|
||||||
|
state.token = token; |
||||||
|
}, |
||||||
|
logout(state) { |
||||||
|
state.id = ""; |
||||||
|
state.username = ""; |
||||||
|
state.photo = ""; |
||||||
|
state.token = ""; |
||||||
|
state.is_login = false; |
||||||
|
}, |
||||||
|
updatePullingInfo(state,pulling_info) { |
||||||
|
state.pulling_info = pulling_info; |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { // 在actions中调用修改全局变量的函数
|
||||||
|
login(context,data) { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/account/token/", |
||||||
|
type : "POST", |
||||||
|
data: { |
||||||
|
username: data.username, |
||||||
|
password: data.password, |
||||||
|
}, |
||||||
|
success(resp) {
|
||||||
|
// resp对应的就是后端的map的json格式,每个字段对应键值对中的key
|
||||||
|
// 登陆成功
|
||||||
|
if(resp.error_msg === "success"){ |
||||||
|
// 调用updateToken函数,形参中的data为resp.token
|
||||||
|
// 将登陆信息的token存入localStorage实现持久化(浏览器刷新退出登陆)
|
||||||
|
localStorage.setItem("jwt_token",resp.token); |
||||||
|
context.commit("updateToken",resp.token); |
||||||
|
data.success(resp); |
||||||
|
}else { |
||||||
|
data.error(resp); |
||||||
|
} |
||||||
|
}, |
||||||
|
error(resp) { |
||||||
|
data.error(resp); |
||||||
|
}, |
||||||
|
}); |
||||||
|
}, |
||||||
|
getInfo(context,data) { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/account/info/", |
||||||
|
type: "GET", |
||||||
|
headers: { |
||||||
|
Authorization: "Bearer " + context.state.token, |
||||||
|
}, |
||||||
|
success(resp) {
|
||||||
|
if (resp.error_msg === "success"){ |
||||||
|
// 登陆成功后,更新当前用户信息
|
||||||
|
context.commit("updateUser",{ |
||||||
|
...resp, |
||||||
|
is_login: true, |
||||||
|
}); |
||||||
|
data.success(resp); // 调用UserLoginView中的success()回调函数
|
||||||
|
}else { |
||||||
|
data.error(resp); // 调用UserLoginView中的error()回调函数
|
||||||
|
} |
||||||
|
}, |
||||||
|
error(resp) { |
||||||
|
data.error(resp); |
||||||
|
}, |
||||||
|
}); |
||||||
|
}, |
||||||
|
logout(context) { |
||||||
|
// 退出时删除localStorage保存的token
|
||||||
|
localStorage.removeItem("jwt_token"); |
||||||
|
context.commit("logout"); // 退出登录
|
||||||
|
} |
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
<template> |
||||||
|
<div class="menu-field"> |
||||||
|
<div class="menu"> |
||||||
|
<div class="menu-item" @click="handlePkClick">对战</div> |
||||||
|
<div class="menu-item" @click="handleRecordClick">对局列表</div> |
||||||
|
<div class="menu-item" @click="handleRankListClick">排行榜</div> |
||||||
|
<div class="menu-item" @click="handleUserBotClick">我的bot</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { useStore } from "vuex"; |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
const handlePkClick = () => { |
||||||
|
store.commit("updateRouterName","pk"); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleRecordClick = () => { |
||||||
|
store.commit("updateRouterName","record"); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleRankListClick = () => { |
||||||
|
store.commit("updateRouterName","ranklist"); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleUserBotClick = () => { |
||||||
|
store.commit("updateRouterName","user_bot"); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
handlePkClick, |
||||||
|
handleRecordClick, |
||||||
|
handleRankListClick, |
||||||
|
handleUserBotClick, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.menu-field { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
div.menu { |
||||||
|
width: 25vw; |
||||||
|
height: 30vh; |
||||||
|
background-color: rgba(0, 0, 0, 0.3); |
||||||
|
} |
||||||
|
|
||||||
|
div.menu-item { |
||||||
|
width: 100%; |
||||||
|
height: 7.5vh; |
||||||
|
line-height: 7.5vh; |
||||||
|
font-size: 24px; |
||||||
|
color: white; |
||||||
|
text-align: center; |
||||||
|
font-style: italic; |
||||||
|
font-weight: bold; |
||||||
|
cursor: pointer; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
div.menu-item:hover { |
||||||
|
scale: 1.2; |
||||||
|
transition: 400ms; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,99 @@ |
|||||||
|
<template> |
||||||
|
<ContentField> |
||||||
|
<PlayGround v-if="$store.state.pk.status === 'playing'" /> |
||||||
|
<MatchGround v-if="$store.state.pk.status === 'matching'" /> |
||||||
|
<ResultBoard v-if="$store.state.pk.loser !== 'none'" /> |
||||||
|
<UserInfo v-if="$store.state.pk.status === 'playing'" /> |
||||||
|
</ContentField> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import PlayGround from "../../components/PlayGround.vue"; |
||||||
|
import MatchGround from "../../components/MatchGround.vue"; |
||||||
|
import ResultBoard from "@/components/ResultBoard.vue"; |
||||||
|
import UserInfo from "@/components/UserInfo.vue"; |
||||||
|
import { onMounted, onUnmounted } from "vue"; // 加载组件和卸载组件 |
||||||
|
import { useStore } from "vuex"; |
||||||
|
import ContentField from "@/components/ContentField.vue"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
PlayGround, |
||||||
|
MatchGround, |
||||||
|
ResultBoard, |
||||||
|
UserInfo, |
||||||
|
ContentField, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
const socket_url = `wss://kob.bnblogs.cc/websocket/${store.state.user.token}/`; |
||||||
|
|
||||||
|
store.commit("updateLoser", "none"); |
||||||
|
store.commit("updateIsRecord", false); |
||||||
|
|
||||||
|
let socket = null; |
||||||
|
// 当前组件被加载(点击pk页面)时执行该函数 |
||||||
|
// onMounted和setup作用相似 |
||||||
|
onMounted(() => { |
||||||
|
store.commit("updateOpponent", { |
||||||
|
username: "我的对手", |
||||||
|
photo: |
||||||
|
"https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png", |
||||||
|
}); |
||||||
|
// 建立一个socket链接 |
||||||
|
socket = new WebSocket(socket_url); |
||||||
|
|
||||||
|
socket.onopen = () => { |
||||||
|
store.commit("updateSocket", socket); |
||||||
|
console.log("connnected!"); |
||||||
|
}; |
||||||
|
|
||||||
|
// 接收到后端发来的信息 |
||||||
|
socket.onmessage = (msg) => { |
||||||
|
const data = JSON.parse(msg.data); |
||||||
|
|
||||||
|
// 匹配成功 |
||||||
|
if (data.event === "start-matching") { |
||||||
|
store.commit("updateOpponent", { |
||||||
|
username: data.opponent_username, |
||||||
|
photo: data.opponent_photo, |
||||||
|
}); |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
store.commit("updateStatus", "playing"); |
||||||
|
}, 200); // 0.2秒后更新地图(修复穿墙bug) |
||||||
|
|
||||||
|
// 更新游戏信息(包括地图、玩家坐标和id) |
||||||
|
store.commit("updateGame", data.game); |
||||||
|
} else if (data.event === "move") { |
||||||
|
const game = store.state.pk.gameObject; |
||||||
|
const [snake0, snake1] = game.snakes; |
||||||
|
snake0.set_direction(data.a_direction); |
||||||
|
snake1.set_direction(data.b_direction); |
||||||
|
} else if (data.event === "result") { |
||||||
|
const game = store.state.pk.gameObject; |
||||||
|
const [snake0, snake1] = game.snakes; |
||||||
|
if (data.loser == "all" || data.loser == "A") { |
||||||
|
snake0.status = "die"; |
||||||
|
} |
||||||
|
if (data.loser == "all" || data.loser == "B") { |
||||||
|
snake1.status = "die"; |
||||||
|
} |
||||||
|
store.commit("updateLoser", data.loser); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
socket.onclose = () => { |
||||||
|
console.log("disconnected!"); |
||||||
|
store.commit("updateStatus", "matching"); |
||||||
|
}; |
||||||
|
}); |
||||||
|
// 切换其他页面或者关闭pk页面时调用 |
||||||
|
onUnmounted(() => { |
||||||
|
socket.close(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped></style> |
@ -0,0 +1,133 @@ |
|||||||
|
<template> |
||||||
|
<ContentField> |
||||||
|
<table class="table table-striped table-hover table-sm" style="text-align: center;"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>玩家</th> |
||||||
|
<th>积分</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr v-for="user in users" :key="user.id" class=align-middle> |
||||||
|
<td> |
||||||
|
<img :src="user.photo" alt="" class="user_photo"> |
||||||
|
<div class="username userA">{{user.username}}</div> |
||||||
|
</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 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> |
||||||
|
import ContentField from '../../components/ContentField.vue'; |
||||||
|
import { useStore } from 'vuex'; |
||||||
|
import $ from 'jquery'; |
||||||
|
import { ref } from 'vue'; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
ContentField |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let currentPage = 1; |
||||||
|
let total_users = 0; // 保存总对局数 |
||||||
|
let users = ref([]); // 保存对战记录 |
||||||
|
let pages = ref([]); // 展示的多个对战页面 |
||||||
|
|
||||||
|
const updatePages = () => { |
||||||
|
let max_pages = parseInt(Math.ceil(total_users / 8)); |
||||||
|
let new_pages = []; |
||||||
|
for (let i = currentPage - 2; i <= currentPage + 2; i++) { |
||||||
|
if (i >= 1 && i <= max_pages) { |
||||||
|
new_pages.push({ |
||||||
|
number: i, |
||||||
|
is_active: i === currentPage ? "active" : "", |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
pages.value = new_pages; |
||||||
|
} |
||||||
|
|
||||||
|
const click_page = page => { |
||||||
|
if (page === -2) page = currentPage - 1; |
||||||
|
else if (page === -1) page = currentPage + 1; |
||||||
|
let max_pages = parseInt(Math.ceil(total_users / 8)); |
||||||
|
|
||||||
|
if (page >= 1 && page <= max_pages) { |
||||||
|
pull_page(page); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const pull_page = page => { |
||||||
|
currentPage = page; |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/ranklist/getlist/", |
||||||
|
type: "GET", |
||||||
|
data: { |
||||||
|
page |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, // 任何需要登录才能显示的都要加这个验证 |
||||||
|
}, |
||||||
|
success(resp){ |
||||||
|
users.value = resp.users; |
||||||
|
total_users = resp.users_count; |
||||||
|
updatePages(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pull_page(currentPage); |
||||||
|
|
||||||
|
return { |
||||||
|
click_page, |
||||||
|
users, |
||||||
|
pages, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.user_photo{ |
||||||
|
width: 4vh; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
.username { |
||||||
|
font-weight: bold; |
||||||
|
font-size: 1vw; |
||||||
|
} |
||||||
|
|
||||||
|
.userA { |
||||||
|
color: blue; |
||||||
|
} |
||||||
|
|
||||||
|
.userB { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,19 @@ |
|||||||
|
<template> |
||||||
|
<PlayGround /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import PlayGround from "../../components/PlayGround.vue"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
PlayGround, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped></style> |
@ -0,0 +1,197 @@ |
|||||||
|
<template> |
||||||
|
<ContentField> |
||||||
|
<table class="table table-striped table-hover table-sm align-middle" style="text-align: center;"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>player A</th> |
||||||
|
<th>player B</th> |
||||||
|
<th>对战结果</th> |
||||||
|
<th>对战时间</th> |
||||||
|
<th>操作</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr v-for="record in records" :key="record.record.id" class=align-middle> |
||||||
|
<td > |
||||||
|
<img :src="record.a_photo" alt="" class="user_photo"> |
||||||
|
<div class="username userA">{{record.a_username}}</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<img :src="record.b_photo" alt="" class="user_photo"> |
||||||
|
<div class="username userB">{{record.b_username}}</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<div class="info"> |
||||||
|
{{record.result}} |
||||||
|
</div> |
||||||
|
|
||||||
|
</td> |
||||||
|
|
||||||
|
<td> |
||||||
|
<div class="info"> |
||||||
|
{{record.record.createTime}} |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<button class="btn btn-info sm" @click="open_record_content(record.record.id)">查看录像</button> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
|
||||||
|
<nav aria-label="..." style="float: right;"> |
||||||
|
<ul class="pagination"> |
||||||
|
<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 $ from 'jquery'; |
||||||
|
import { ref } from 'vue'; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
ContentField |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let currentPage = 1; |
||||||
|
let total_records = 0; // 保存总对局数 |
||||||
|
let records = ref([]); // 保存对战记录 |
||||||
|
let pages = ref([]); // 展示的多个对战页面 |
||||||
|
|
||||||
|
const updatePages = () => { |
||||||
|
let max_pages = parseInt(Math.ceil(total_records / 8)); |
||||||
|
let new_pages = []; |
||||||
|
for (let i = currentPage - 2; i <= currentPage + 2; i++) { |
||||||
|
if (i >= 1 && i <= max_pages) { |
||||||
|
new_pages.push({ |
||||||
|
number: i, |
||||||
|
is_active: i === currentPage ? "active" : "", |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
pages.value = new_pages; |
||||||
|
} |
||||||
|
|
||||||
|
const click_page = page => { |
||||||
|
if (page === -2) page = currentPage - 1; |
||||||
|
else if (page === -1) page = currentPage + 1; |
||||||
|
let max_pages = parseInt(Math.ceil(total_records / 8)); |
||||||
|
|
||||||
|
if (page >= 1 && page <= max_pages) { |
||||||
|
pull_page(page); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const pull_page = page => { |
||||||
|
currentPage = page; |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/record/list/", |
||||||
|
type: "GET", |
||||||
|
data: { |
||||||
|
page |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, // 任何需要登录才能显示的都要加这个验证 |
||||||
|
}, |
||||||
|
success(resp){ |
||||||
|
records.value = resp.records; |
||||||
|
total_records = resp.records_count; |
||||||
|
updatePages(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pull_page(currentPage); |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
return g; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 打开对应recordId保存的录像 |
||||||
|
const 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); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
open_record_content, |
||||||
|
click_page, |
||||||
|
records, |
||||||
|
pages, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.user_photo{ |
||||||
|
width: 4vh; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
.username { |
||||||
|
font-weight: bold; |
||||||
|
font-size: 1vw; |
||||||
|
} |
||||||
|
|
||||||
|
.userA { |
||||||
|
color: blue; |
||||||
|
} |
||||||
|
|
||||||
|
.userB { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,279 @@ |
|||||||
|
<template> |
||||||
|
<ContentField> |
||||||
|
<div class="container"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-3"> |
||||||
|
<div class="card" style="margin-top: 30px"> |
||||||
|
<div class="card-body"> |
||||||
|
<img :src="$store.state.user.photo" alt=""> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="col-9"> |
||||||
|
<div class="card" style="margin-top: 30px"> |
||||||
|
<div class="card-header" > |
||||||
|
<span style="font-size: 20px; font-weight: bold ">我的bots</span> |
||||||
|
<button type="button" class="btn btn-primary float-end" btn-sm data-bs-toggle="modal" data-bs-target="#add_bot_btn">创建bot</button> |
||||||
|
<!-- Modal --> |
||||||
|
<div class="modal fade" id="add_bot_btn" tabindex="-1"> |
||||||
|
<div class="modal-dialog modal-lg"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<h5 class="modal-title" id="exampleModalLabel">创建新的bot</h5> |
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<form> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_title" class="form-label">bot名称</label> |
||||||
|
<input type="text" v-model="new_bot.title" class="form-control" id="add_bot_title" placeholder="请输入新bot的名称"> |
||||||
|
</div> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_description" class="form-label">bot简介</label> |
||||||
|
<textarea class="form-control" v-model="new_bot.description" id="add_bot_description" rows= "3" placeholder="请输入新bot的简介"></textarea> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_code" class="form-label">bot的代码</label> |
||||||
|
<VAceEditor |
||||||
|
v-model:value="new_bot.content" |
||||||
|
@init="editorInit" |
||||||
|
lang="c_cpp" |
||||||
|
theme="textmate" |
||||||
|
:options="{fontSize: 16}" |
||||||
|
style="height: 300px" /> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
<div class="modal-footer"> |
||||||
|
<div class="error_msg">{{ new_bot.error_msg }}</div> |
||||||
|
<button type="button" class="btn btn-success" @click="add_bot" btn-sm>创建</button> |
||||||
|
<button type="button" class="btn btn-danger" btn-sm @click="cancel_add">取消</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card-body"> |
||||||
|
<table class="table table-striped table-hover"> |
||||||
|
<!--表头--> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>名称</th> |
||||||
|
<th>创建时间</th> |
||||||
|
<th>操作</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<!--内容--> |
||||||
|
<tbody> |
||||||
|
<tr v-for="bot in bots" :key="bot.id"> |
||||||
|
<td>{{bot.title}}</td> |
||||||
|
<td>{{bot.createTime}}</td> |
||||||
|
<td> |
||||||
|
<button type="button" class="btn btn-success" btn-sm style="margin-right: 10px" data-bs-toggle="modal" :data-bs-target="'#update-bot-btn-'+bot.id" >修改</button> |
||||||
|
<button type="button" class="btn btn-danger" btn-sm @click="remove_bot(bot)">删除</button> |
||||||
|
|
||||||
|
<div class="modal fade" :id="'update-bot-btn-'+bot.id" tabindex="-1"> |
||||||
|
<div class="modal-dialog modal-lg"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<h5 class="modal-title">修改bot信息</h5> |
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<form> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_title" class="form-label">bot名称</label> |
||||||
|
<input type="text" v-model="bot.title" class="form-control" id="add_bot_title" > |
||||||
|
</div> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_description" class="form-label">bot简介</label> |
||||||
|
<textarea class="form-control" v-model="bot.description" id="add_bot_description" rows= "3" ></textarea> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="mb-3"> |
||||||
|
<label for="add_bot_code" class="form-label">bot的代码</label> |
||||||
|
<VAceEditor |
||||||
|
v-model:value="bot.content" |
||||||
|
@init="editorInit" |
||||||
|
lang="c_cpp" |
||||||
|
theme="textmate" |
||||||
|
:options="{fontSize: 16}" |
||||||
|
style="height: 300px" /> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
<div class="modal-footer"> |
||||||
|
<div class="error_msg">{{ bot.error_msg }}</div> |
||||||
|
<button type="button" class="btn btn-success" @click="update_bot(bot)" btn-sm>保存修改</button> |
||||||
|
<button type="button" class="btn btn-danger" btn-sm @click="cancel_update(bot)">取消</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</ContentField> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref,reactive } from "vue" |
||||||
|
import $ from 'jquery' |
||||||
|
import { useStore } from "vuex" |
||||||
|
import {Modal} from "bootstrap/dist/js/bootstrap" |
||||||
|
import ContentField from "@/components/ContentField.vue" |
||||||
|
import { VAceEditor } from 'vue3-ace-editor'; |
||||||
|
import ace from 'ace-builds'; |
||||||
|
|
||||||
|
export default { |
||||||
|
components:{ |
||||||
|
VAceEditor, |
||||||
|
ContentField, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
ace.config.set( |
||||||
|
"basePath", |
||||||
|
"https://cdn.jsdelivr.net/npm/ace-builds@" + require('ace-builds').version + "/src-noconflict/"); |
||||||
|
const store = useStore(); |
||||||
|
let bots = ref([]) // 定义bot列表 |
||||||
|
const new_bot = reactive({ |
||||||
|
title: "", |
||||||
|
description: "", |
||||||
|
content: "", |
||||||
|
error_msg: "", |
||||||
|
}) |
||||||
|
|
||||||
|
const cancel_update = (bot)=> { |
||||||
|
Modal.getInstance("#update-bot-btn-"+bot.id).hide(); |
||||||
|
refresh_bots(); |
||||||
|
} |
||||||
|
|
||||||
|
const cancel_add = ()=> { |
||||||
|
Modal.getInstance("#add_bot_btn").hide(); |
||||||
|
refresh_bots(); |
||||||
|
} |
||||||
|
|
||||||
|
const refresh_bots = () => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/bot/getlist/", |
||||||
|
type: "GET", |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, // 任何需要登录才能显示的都要加这个验证 |
||||||
|
}, |
||||||
|
success(resp){ |
||||||
|
// 将后端得到的数据传给bot |
||||||
|
bots.value = resp; |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
refresh_bots(); |
||||||
|
|
||||||
|
// 点击创建bot按钮触发的事件 |
||||||
|
const add_bot = () => { |
||||||
|
new_bot.error_msg = ""; |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/bot/add/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
title: new_bot.title, |
||||||
|
description: new_bot.description, |
||||||
|
content: new_bot.content, |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
if (resp.error_msg === "success") { // 添加bot成功 |
||||||
|
new_bot.title = "", |
||||||
|
new_bot.description="", |
||||||
|
new_bot.content="", |
||||||
|
Modal.getInstance("#add_bot_btn").hide(); |
||||||
|
refresh_bots(); |
||||||
|
} |
||||||
|
else { |
||||||
|
new_bot.error_msg = resp.error_msg; // 返回错误信息 |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// 点击更新bot按钮触发的事件 |
||||||
|
const update_bot = (bot) => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/bot/update/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
bot_id: bot.id, |
||||||
|
title: bot.title, |
||||||
|
description: bot.description, |
||||||
|
content: bot.content, |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
if (resp.error_msg === "success") { // 更新bot成功 |
||||||
|
Modal.getInstance("#update-bot-btn-"+bot.id).hide(); |
||||||
|
refresh_bots(); |
||||||
|
} |
||||||
|
else { |
||||||
|
new_bot.error_msg = resp.error_msg; // 返回错误信息 |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 点击删除bot按钮触发的事件 |
||||||
|
const remove_bot = (bot) => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://kob.bnblogs.cc/api/user/bot/remove/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
bot_id: bot.id |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
"Authorization": "Bearer " + store.state.user.token, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
console.log(resp) |
||||||
|
if (resp.error_msg === "success") { // 添加bot成功 |
||||||
|
refresh_bots(); |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
bots, |
||||||
|
new_bot, |
||||||
|
add_bot, |
||||||
|
remove_bot, |
||||||
|
update_bot, |
||||||
|
cancel_update, |
||||||
|
cancel_add, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
img{ |
||||||
|
width: 100% |
||||||
|
} |
||||||
|
.error_msg{ |
||||||
|
font-size: 20px; |
||||||
|
font-weight: bold; |
||||||
|
color: red; |
||||||
|
} |
||||||
|
</style> |
@ -1,4 +1,9 @@ |
|||||||
const { defineConfig } = require('@vue/cli-service') |
const { defineConfig } = require('@vue/cli-service') |
||||||
module.exports = defineConfig({ |
module.exports = defineConfig({ |
||||||
transpileDependencies: true |
transpileDependencies: true, |
||||||
|
configureWebpack: { |
||||||
|
optimization: { |
||||||
|
splitChunks: false |
||||||
|
} |
||||||
|
} |
||||||
}) |
}) |
Loading…
Reference in new issue