Commit 8f4557d9 authored by 陈冲's avatar 陈冲

feat: 提交

parent 40a1a7b0
...@@ -6,38 +6,22 @@ mod router; ...@@ -6,38 +6,22 @@ mod router;
use commons::utils::CONFIG as config; use commons::utils::CONFIG as config;
use db::init_db; use db::init_db;
// #[derive(rust_embed::RustEmbed)] // 后台测试页,正式线上时不要代理该路径
// #[folder = "src/web"] #[derive(rust_embed::RustEmbed)]
// struct AssetsWeb; #[folder = "src/web"]
struct AssetsWeb;
// #[derive(rust_embed::RustEmbed)]
// #[folder = "src/large"]
// struct AssetsLarge;
// #[derive(rust_embed::RustEmbed)]
// #[folder = "src/wechat"]
// struct AssetsWechat;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
if let Err(err) = init_db().await { if let Err(err) = init_db().await {
panic!("init db failed: {err}"); panic!("init db failed: {err}");
} }
let router = Router::new() let router = Router::new().push(router::config_router())
.push(router::config_router()) .push(
// .push( Router::with_path("/web/{**path}")
// Router::with_path("/web/{**path}") .get(salvo::serve_static::static_embed::<AssetsWeb>().fallback("index.html")),
// .get(salvo::serve_static::static_embed::<AssetsWeb>().fallback("index.html")), )
// ) ;
// .push(
// Router::with_path("/wechat/{**path}")
// .get(salvo::serve_static::static_embed::<AssetsWechat>().fallback("index.html")),
// )
// .push(
// Router::with_path("/large/{**path}")
// .get(salvo::serve_static::static_embed::<AssetsLarge>().fallback("index.html")),
// )
;
let host = config.host.clone(); let host = config.host.clone();
dbg!(&config); dbg!(&config);
println!("http://{}", host); println!("http://{}", host);
......
...@@ -39,17 +39,17 @@ const GAME_RUNNING_SECS: u64 = 60; ...@@ -39,17 +39,17 @@ const GAME_RUNNING_SECS: u64 = 60;
//管理员退出5分钟后,关闭所有玩家的连接 //管理员退出5分钟后,关闭所有玩家的连接
const ADMIN_ABSENT_DEACTIVATE_PLAYER_SECS: u64 = 5 * 60; const ADMIN_ABSENT_DEACTIVATE_PLAYER_SECS: u64 = 5 * 60;
fn get_game_tilte(key: &str) -> String { // fn get_game_tilte(key: &str) -> String {
let mut GAME_MAP = HashMap::new(); // let mut GAME_MAP = HashMap::new();
GAME_MAP.insert("game1", "击鼓齐福"); // GAME_MAP.insert("game1", "击鼓齐福");
GAME_MAP.insert("game2", "聚宝接福"); // GAME_MAP.insert("game2", "聚宝接福");
GAME_MAP.insert("game3", "马上有福"); // GAME_MAP.insert("game3", "马上有福");
GAME_MAP.insert("game4", "策马迎福"); // GAME_MAP.insert("game4", "策马迎福");
GAME_MAP.insert("game5", "圈彩纳福"); // GAME_MAP.insert("game5", "圈彩纳福");
GAME_MAP.insert("game6", "福运当头"); // GAME_MAP.insert("game6", "福运当头");
let str = GAME_MAP.get(key).map_or("", |v| v); // let str = GAME_MAP.get(key).map_or("", |v| v);
str.to_string() // str.to_string()
} // }
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct RoomPlayer { struct RoomPlayer {
...@@ -102,6 +102,28 @@ impl RoomState { ...@@ -102,6 +102,28 @@ impl RoomState {
}) })
} }
pub fn get_game_tilte(&self, key: Option<&str>) -> String {
let key = if key.is_none() {
&self.current_game_id
} else {
&Some("".to_string())
};
if let Some(key) = key {
let mut GAME_MAP = HashMap::new();
GAME_MAP.insert("game1", "击鼓齐福");
GAME_MAP.insert("game2", "聚宝接福");
GAME_MAP.insert("game3", "马上有福");
GAME_MAP.insert("game4", "策马迎福");
GAME_MAP.insert("game5", "圈彩纳福");
GAME_MAP.insert("game6", "福运当头");
let str = GAME_MAP.get(key.as_str()).map_or("", |v| v);
str.to_string()
} else {
"".to_string()
}
}
fn ranking_from_player(wechat: &str, player: &RoomPlayer) -> RankingPlayer { fn ranking_from_player(wechat: &str, player: &RoomPlayer) -> RankingPlayer {
RankingPlayer { RankingPlayer {
wechat: wechat.to_string(), wechat: wechat.to_string(),
...@@ -563,17 +585,12 @@ async fn handle_room_command( ...@@ -563,17 +585,12 @@ async fn handle_room_command(
let game_id = format!("game{index}"); let game_id = format!("game{index}");
if cmd == "room_create" { if cmd == "room_create" {
if state.status != RoomStatus::Closed { if state.status != RoomStatus::Closed {
let title = state.get_game_tilte(Some(&game_id));
drop(state); drop(state);
emit_msgpack( emit_msgpack(
&socket, &socket,
evt, evt,
&err_resp( &err_resp(&format!("不能重复创建游戏 - {} 房间", title), None),
&format!(
"不能重复创建游戏 - {} 房间",
get_game_tilte(&game_id)
),
None,
),
); );
return; return;
} }
...@@ -594,6 +611,7 @@ async fn handle_room_command( ...@@ -594,6 +611,7 @@ async fn handle_room_command(
if is_admin { if is_admin {
return; return;
} }
// dbg!(&data);
//超过300人的情况 //超过300人的情况
if get_player_count() > 300 { if get_player_count() > 300 {
emit_msgpack(&socket, evt, &err_resp("本场游戏人员已满", Some(json!(3)))); emit_msgpack(&socket, evt, &err_resp("本场游戏人员已满", Some(json!(3))));
...@@ -616,31 +634,28 @@ async fn handle_room_command( ...@@ -616,31 +634,28 @@ async fn handle_room_command(
.to_string(); .to_string();
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
// 当前h5进入的不是大屏开启的游戏房间
if gameId != state.current_game_id.clone().unwrap_or_default() {
let title = state.get_game_tilte(None);
drop(state);
emit_msgpack(
&socket,
evt,
&err_resp(&format!("当前游戏 - {} 房间还未开启", title), None),
);
return;
}
if matches!(state.status, RoomStatus::Closed) { if matches!(state.status, RoomStatus::Closed) {
let title = state.get_game_tilte(None);
drop(state); drop(state);
emit_msgpack( emit_msgpack(
&socket, &socket,
evt, evt,
&err_resp( &err_resp(&format!("当前游戏 - {} 房间已关闭", title), None),
&format!("当前游戏 - {} 房间还未开启", get_game_tilte(&gameId)),
None,
),
); );
return; return;
} else if matches!(state.status, RoomStatus::Running) { } else if matches!(state.status, RoomStatus::Running) {
// 当前h5进入的不是大屏开启的游戏房间
if gameId != state.current_game_id.clone().unwrap_or_default() {
drop(state);
emit_msgpack(
&socket,
evt,
&err_resp(
&format!("当前游戏 - {} 房间还未开启", get_game_tilte(&gameId)),
None,
),
);
return;
}
//如果是正在游戏中 //如果是正在游戏中
let already_joined = state.players.contains_key(userid); let already_joined = state.players.contains_key(userid);
//玩家游戏过程中掉线重连 //玩家游戏过程中掉线重连
...@@ -657,12 +672,36 @@ async fn handle_room_command( ...@@ -657,12 +672,36 @@ async fn handle_room_command(
} }
emit_msgpack(&socket, "room_recover_result", &ok_resp(reconnect_payload)); emit_msgpack(&socket, "room_recover_result", &ok_resp(reconnect_payload));
return; return;
} else {
emit_msgpack(
&socket,
evt,
&err_resp(
&format!(
"游戏 - {} 已经开始,您暂时无法进入",
state.get_game_tilte(None)
),
Some(json!(gameId)),
),
);
return;
} }
} else if matches!(state.status, RoomStatus::Submit | RoomStatus::Ended) { } else if matches!(state.status, RoomStatus::Submit | RoomStatus::Ended) {
//如果是在提交或游戏结束阶段断线重连 //如果是在提交或游戏结束阶段断线重连
if let Some(mut player) = CLIENT_PLAYER_MAP.get_mut(userid) { // if let Some(mut player) = CLIENT_PLAYER_MAP.get_mut(userid) {
player.0 = 1; // player.0 = 1;
} // }
emit_msgpack(
&socket,
evt,
&err_resp(
&format!(
"游戏 - {} 正在结算,您暂时无法进入",
state.get_game_tilte(None)
),
Some(json!(gameId)),
),
);
return; return;
} }
let entry_order = state.next_entry_order; let entry_order = state.next_entry_order;
...@@ -746,21 +785,18 @@ async fn handle_room_command( ...@@ -746,21 +785,18 @@ async fn handle_room_command(
drop(state); drop(state);
emit_msgpack(&socket, evt, &err_resp("您不在房间中", None)); emit_msgpack(&socket, evt, &err_resp("您不在房间中", None));
return; return;
} else if !matches!(state.status, RoomStatus::Running) { } else if !matches!(state.status, RoomStatus::Running | RoomStatus::Submit) {
// dbg!(&state.status.clone().as_str());
// let title = state.get_game_tilte(None);
let status = state.status.clone().as_str();
drop(state); drop(state);
emit_msgpack(&socket, evt, &err_resp("游戏还未开始", None)); emit_msgpack(
&socket,
evt,
&err_resp(&format!("当前游戏状态{status}不能提交分数"), None),
);
return; return;
} } else if state.players.contains_key(userid) {
/* else if !state
.players
.get(userid)
.map(|player| player.online)
.unwrap_or(false)
{
drop(state);
emit_msgpack(&socket, evt, &err_resp("当前玩家不在游戏线上", None));
}*/
else if state.players.contains_key(userid) {
let player = state.players.get_mut(userid).unwrap(); let player = state.players.get_mut(userid).unwrap();
player.score = score; player.score = score;
// player.wechat = wechat.to_string(); // player.wechat = wechat.to_string();
...@@ -889,17 +925,11 @@ async fn handle_room_command( ...@@ -889,17 +925,11 @@ async fn handle_room_command(
if let Ok(mut state) = ROOM_STATE.lock() { if let Ok(mut state) = ROOM_STATE.lock() {
status = state.current_game_id.clone().unwrap_or_default(); status = state.current_game_id.clone().unwrap_or_default();
title = get_game_tilte(&status); title = state.get_game_tilte(None);
state.reset(); state.reset();
drop(state); drop(state);
} }
broadcast_msgpack_all_player( broadcast_msgpack_all_player(evt, &ok_resp(Some(json!(status))));
evt,
&err_resp(
&format!("管理员关闭了 {} 该房间", title),
Some(json!(status)),
),
);
broadcast_room_rank(Vec::new()); broadcast_room_rank(Vec::new());
emit_msgpack(&socket, evt, &ok_resp(None)); emit_msgpack(&socket, evt, &ok_resp(None));
} }
...@@ -1090,10 +1120,10 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value ...@@ -1090,10 +1120,10 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
let count = state.players.len(); let count = state.players.len();
let ranking = state.current_ranking_payload(); let ranking = state.current_ranking_payload();
drop(state); drop(state);
// broadcast_msgpack_all_admin( broadcast_msgpack_all_admin(
// "room_join_result", "room_join_result",
// &ok_resp(Some(json!({"list":ranking,"count":count}))), &ok_resp(Some(json!({"list":ranking,"count":count}))),
// ); );
} }
} }
} }
...@@ -1154,17 +1184,14 @@ pub async fn login(req: &mut Request, res: &mut Response) { ...@@ -1154,17 +1184,14 @@ pub async fn login(req: &mut Request, res: &mut Response) {
} }
if get_admin_count() > 0 { if get_admin_count() > 0 {
if let Ok(state) = ROOM_STATE.lock() { if let Ok(state) = ROOM_STATE.lock() {
let game_id = state.current_game_id.clone().unwrap_or("".to_string());
if matches!( if matches!(
state.status, state.status,
RoomStatus::Loading | RoomStatus::Running | RoomStatus::Submit RoomStatus::Loading | RoomStatus::Running | RoomStatus::Submit
) { ) {
let title = state.get_game_tilte(None);
drop(state); drop(state);
res.render(Json(err_resp( res.render(Json(err_resp(
&format!( &format!("当前管理员已开启游戏房间 - {},请稍后再登录", title),
"当前管理员已开启游戏房间 - {},请稍后再登录",
get_game_tilte(&game_id)
),
None, None,
))); )));
return; return;
......
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Socket.IO Test</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #f4f6f8;
color: #1f2937;
font-family: Arial, "Microsoft YaHei", sans-serif;
}
main {
width: min(680px, calc(100vw - 32px));
padding: 24px;
background: #fff;
border: 1px solid #d8dee6;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
}
h1 {
margin: 0 0 18px;
font-size: 22px;
}
label {
display: block;
margin: 12px 0 6px;
font-size: 14px;
font-weight: 700;
}
input,
textarea {
width: 100%;
padding: 10px;
border: 1px solid #c7d0dc;
border-radius: 6px;
font-size: 14px;
font-family: inherit;
}
input {
height: 38px;
padding-top: 0;
padding-bottom: 0;
}
textarea {
min-height: 110px;
resize: vertical;
font-family: Consolas, Monaco, monospace;
}
.buttons {
display: contents;
gap: 10px;
margin-top: 16px;
flex-wrap: wrap;
}
button {
height: 38px;
padding: 0 14px;
border: 0;
border-radius: 6px;
background: #2563eb;
color: #fff;
font-size: 14px;
cursor: pointer;
}
button[disabled] {
background: #94a3b8;
cursor: not-allowed;
}
button.secondary {
background: #475569;
}
#status {
margin-top: 16px;
padding: 10px;
border-radius: 6px;
background: #eef2ff;
color: #1e3a8a;
font-size: 14px;
}
pre {
min-height: 160px;
margin: 16px 0 0;
padding: 12px;
overflow: auto;
border-radius: 6px;
background: #111827;
color: #d1fae5;
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
height: 300px;
}
input[type=checkbox] {
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<main id="app">
<h1>Socket.IO 多客户端测试</h1>
<pre id="log"></pre>
<div style="margin-top:10px">
<button @click="create1">创建客户端*1</button>
<button @click="create10">创建客户端*10</button>
<button @click="create100">创建客户端*100</button>
<button @click="create254">创建客户端*254</button>
<button @click="create45">创建客户端*45</button>
<hr />
<div class="btn-ctrls">
onAny:<input type="checkbox" v-model="anyLog" />
<select v-model="room">
<option v-for="item,index in 6" :value="item">房间{{index+1}}</option>
</select>
<button @click="joinRoom">进入房间</button>
<button @click="submitScore">随机添加分数</button>
</div>
<hr />
当前玩家数量:{{dic.size}}
<hr />
<div>{{message}}</div>
</div>
</main>
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@msgpack/msgpack@3.1.2/dist.umd/msgpack.min.js"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.min.js"></script>
<script>
function generateRandom({
minDigits = 8,
maxDigits = 12,
pool = 'num+upper+lower'
} = {}) {
// 计算实际位数
const digits = Math.floor(Math.random() * (maxDigits - minDigits + 1)) + minDigits;
// 根据pool配置字符池
let charDictionary = "";
if (pool.includes('num')) charDictionary += "23456789";
if (pool.includes('upper')) charDictionary += "ABCDEFGHJKLMNPQRSTUVWXYZ";
if (pool.includes('lower')) charDictionary += "abcdefghjkmnpqrstuvwxyz";
if (!charDictionary) charDictionary = "1234567890abcdef"; // 默认值
// 字符池安全清洗
if (charDictionary.length < 8)
throw "字符池需至少包含8种字符";
let output = "";
const cryptoBuffer = new Uint8Array(digits);
window.crypto.getRandomValues(cryptoBuffer);
// 从字典随机选择
for (let i = 0; i < digits; i++) {
const pos = cryptoBuffer[i] % charDictionary.length;
output += charDictionary.charAt(pos);
}
return output;
}
function decodeMsgpackPayload(payload) {
if (payload instanceof ArrayBuffer) {
return MessagePack.decode(new Uint8Array(payload));
}
if (payload instanceof Uint8Array) {
return MessagePack.decode(payload);
}
return payload;
}
const { createApp, ref } = Vue
createApp({
methods: {
setStatus: function (msg) {
this.message = msg;
},
create_sio: function (args) {
let socket = io(`ws://127.0.0.1:8082/ws`, {
path: "/socket.io",
auth: args.auth,
transports: ["websocket"],
upgrade: false,
forceNew: true,
timeout: 10000
});
socket.on("connect", () => {
//this.setStatus(`已连接: ${socket.id}`);
// log("connect", { id: socket.id });
args.connect?.();
});
socket.on("disconnect", (reason) => {
// this.setStatus(`已断开: ${reason}`);
// log("disconnect", { reason });
args.disconnect?.();
});
socket.on("connect_error", (err) => {
// this.setStatus(`连接失败: ${err.message}`);
// log("connect_error", { message: err.message });
});
socket.onAny((evt, payload) => {
const data = decodeMsgpackPayload(payload);
args.onAny?.(evt, data);
});
return socket;
},
joinRoom: function () {
this.sendMsg({ cmd: "room_join", data: {} });
},
submitScore: function () {
this.score += 5;
this.randomSendMsg({ cmd: "submit_score", data: { score: this.score } });
},
randomSendMsg({ cmd, data }, gameId) {
if (!gameId) {
gameId = `game${this.room}`;
}
const clients = Array.from(this.dic.entries())
.filter(([, socket]) => socket.connected);
if (clients.length === 0) {
console.warn("没有已连接的客户端可发送消息");
return;
}
const sendCount = Math.min(
clients.length,
Math.floor(Math.random() * 9) + 4
);
const selectedClients = clients
.sort(() => Math.random() - 0.5)
.slice(0, sendCount);
console.log("send<=========", cmd, `随机发送给 ${sendCount} 个客户端`);
for (const [key, socket] of selectedClients) {
const messageData = {
...data,
nickname: key,
token: key,
gameId,
avatar: 'https://media.gettyimages.com/id/683773306/vector/male-thief-icon.jpg?s=612x612&w=0&k=20&c=6sNjY_MvNNmpHW6r7CAu3ncWTTur12kK7VC4ydTFiS4='
};
socket.emit("message", MessagePack.encode({ cmd, data: messageData }));
}
},
sendMsg({ cmd, data }, gameId) {
if (!gameId) {
gameId = `game${this.room}`;
}
console.log("send<=========", cmd, data);
for (const [key, socket] of this.dic) {
data.nickname = key;
data.token = key;
data.gameId = gameId;
data.avatar = 'https://media.gettyimages.com/id/683773306/vector/male-thief-icon.jpg?s=612x612&w=0&k=20&c=6sNjY_MvNNmpHW6r7CAu3ncWTTur12kK7VC4ydTFiS4='
socket.emit("message", MessagePack.encode({ cmd, data }));
}
},
create1: function () {
let auth = {
userid: generateRandom({
pool: 'num+upper',
minDigits: 10
}),
};
let socket = this.create_sio({
auth,
connect: () => {
this.dic.set(auth.userid, socket);
},
disconnect: () => {
this.dic.delete(auth.userid);
},
onAny: (evt, args) => {
let { data, msg, state } = args;
if (this.anyLog) {
console.log("=========>recv", evt, data, msg, state);
}
if (state == 1) {
if (evt == 'room_back_result') {
// this.joinRoom();
} else if (evt == 'game_start_result') {
// this.roomType = 1;
} else if (evt == 'submit_score_result') {
// debugger
} else if (evt == 'room_join_result') {
// this.joinRoom(data);
}
}
}
});
},
create10: function () {
for (let i = 0; i < 10; i++) {
this.create1();
}
},
create100: function () {
for (let i = 0; i < 100; i++) {
this.create1();
}
},
create254: function () {
for (let i = 0; i < 254; i++) {
this.create1();
}
},
create45: function () {
for (let i = 0; i < 45; i++) {
this.create1();
}
}
},
data() {
return {
dic: new Map(),
message: '',
room: 1,
roomType: 0,//1:进入游戏中
score: 0,
anyLog: true,
}
},
setup() {
}
}).mount('#app')
</script>
<!-- <script>
const $$ = (id) => Array.from(document.querySelectorAll(id) || []);
const list = [];
const create_sio = (auth) => {
let socket = io(`/ws`, {
path: "/socket.io",
auth,
transports: ["websocket"],
upgrade: false,
forceNew: true,
timeout: 10000
});
socket.on("connect", () => {
setStatus(`已连接: ${socket.id}`);
log("connect", { id: socket.id });
});
socket.on("disconnect", (reason) => {
setStatus(`已断开: ${reason}`);
log("disconnect", { reason });
if (stopReconnectAfterRoomClose) {
socket = null;
}
});
socket.on("connect_error", (err) => {
setStatus(`连接失败: ${err.message}`);
log("connect_error", { message: err.message });
});
socket.onAny((event, payload) => {
const data = decodeMsgpackPayload(payload);
log(`========>recv`, event, data);
});
return socket;
}
$$(".btnCreate1")[0].onclick = () => {
let socket = create_sio({});
}
function log(message, data) {
const line = `[${new Date().toLocaleTimeString()}] ${message}`;
const extra = data === undefined ? "" : `\n${JSON.stringify(data, null, 2)}`;
document.getElementById("log").textContent += `${line}${extra}\n`;
console.log(message, data);
}
function clearLog() {
document.getElementById("log").textContent = "";
}
</script> -->
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
:root{--color-background:#fbf7ef;--color-surface:#fff;--color-text:#231f20;--color-muted:#7c726b;--color-primary:#fa6047;--color-primary-dark:#d94835;--color-border:#231f201f}*,:before,:after{box-sizing:border-box;-webkit-tap-highlight-color:transparent;margin:0;font-weight:400}html,body{width:100%;height:100%}body{color:var(--color-text);background:var(--color-background);text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,PingFang SC,Microsoft YaHei,sans-serif;font-size:16px;line-height:1.5}button,input{font:inherit}button{cursor:pointer;border:0}img{max-width:100%;display:block}#app{-webkit-user-select:none;user-select:none;width:100vw;height:100vh;overflow:hidden}.h5-page{width:100%;min-height:100%;padding:max(18px, env(safe-area-inset-top)) 18px max(18px, env(safe-area-inset-bottom))}.h5-title{letter-spacing:0;font-size:26px;font-weight:800;line-height:1.15}.h5-subtitle{color:var(--color-muted);margin-top:8px;font-size:14px}.h5-panel{border:1px solid var(--color-border);background:#ffffffe0;border-radius:8px;box-shadow:0 16px 36px #5d36231f}.h5-button{background:var(--color-primary);color:#fff;border-radius:8px;width:100%;min-height:48px;font-size:17px;font-weight:800;line-height:1}.h5-button:active{background:var(--color-primary-dark)}.mobile-stage-viewport[data-v-3129d68a]{overscroll-behavior:none;width:100%;height:100%;position:relative;overflow:hidden}.mobile-stage-background[data-v-3129d68a]{transform-origin:0 0;z-index:0;position:absolute}.mobile-stage[data-v-3129d68a]{transform-origin:0 0;z-index:1;position:absolute;overflow:visible}.countdown-modal[data-v-3129d68a]{z-index:10;pointer-events:none;background-color:#0003;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.countdown-number[data-v-3129d68a]{color:#fff;text-shadow:0 18px 38px #00000040;font-size:300pt;font-weight:800;line-height:1;animation:1s both countdown-pop-3129d68a}@keyframes countdown-pop-3129d68a{0%{opacity:0;transform:scale(.45)}18%{opacity:1;transform:scale(1.14)}58%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.84)}}.login-page[data-v-d76172b7]{flex-direction:column;justify-content:space-between;gap:32px;display:flex}.brand[data-v-d76172b7]{align-items:center;gap:14px;padding-top:18px;display:flex}.brand-mark[data-v-d76172b7]{background:var(--color-primary);color:#fff;border-radius:8px;place-items:center;width:58px;height:58px;font-size:20px;font-weight:900;display:grid}.login-form[data-v-d76172b7]{gap:20px;padding:18px;display:grid}.field[data-v-d76172b7]{color:var(--color-muted);gap:8px;font-size:14px;display:grid}.field input[data-v-d76172b7]{border:1px solid var(--color-border);width:100%;height:48px;color:var(--color-text);background:#fff;border-radius:8px;outline:none;padding:0 14px}.field input[data-v-d76172b7]:focus{border-color:var(--color-primary)}.game-stage[data-v-53db301a]{width:750px;height:1624px;padding:0;position:absolute}.game-desc[data-v-53db301a]{top:50%;left:var(--stage-viewport-left,0);color:#fff;letter-spacing:10px;text-align:center;text-orientation:upright;writing-mode:vertical-rl;background:#280c0885;border-radius:0 12px 12px 0;justify-content:center;align-items:center;width:86px;height:256px;font-size:32pt;line-height:1.25;display:flex;position:absolute;transform:translateY(-50%)}.bg-bottom[data-v-53db301a]{bottom:0;left:var(--stage-viewport-left,0);width:var(--stage-viewport-width,750px);background:var(--c3e37f42) center/cover;opacity:.6;height:479px;position:absolute}.img-logo[data-v-53db301a]{background:var(--v23be7c19) center/cover;width:428px;height:77px;position:absolute;top:66px;left:50%;transform:translate(-50%)}.img-title[data-v-53db301a]{background:var(--v547f39aa) center/cover;width:609px;height:243px;position:absolute;top:200px;left:50%;transform:translate(-50%)}.img-avatar[data-v-53db301a]{background:var(--v1baa9447) center/cover;width:148px;height:148px;position:absolute;top:480px;left:50%;transform:translate(-50%)}.txt-nickname[data-v-53db301a]{color:#e00000;font-size:25pt;font-weight:700;position:absolute;top:650px;left:50%;transform:translate(-50%)}.txt-loading[data-v-53db301a]{color:#e00000;text-align:center;width:288px;font-size:25pt;font-weight:700;position:absolute;top:710px;left:50%;transform:translate(-50%)}.txt-loading label[data-v-53db301a]{transform-origin:50%;animation:1.2s ease-in-out infinite loading-text-scale-53db301a;display:inline-block}@keyframes loading-text-scale-53db301a{0%,to{transform:scale(1)}50%{transform:scale(1.08)}}.loading-gu[data-v-53db301a]{background:var(--ab7af248) center/cover;width:600px;height:534px;position:absolute;bottom:100px;left:50%;transform:translate(-50%)}.game-stage[data-v-a126c659]{width:750px;height:1624px;padding:0;position:absolute}.div-desc[data-v-a126c659]{top:150px;left:var(--stage-viewport-left,0);z-index:2;color:#fff;background:#280c087a;border-radius:0 56px 56px 0;justify-content:center;align-items:center;gap:24px;width:180px;height:80px;display:flex;position:absolute}.div-desc .desc-clock[data-v-a126c659]{background:var(--v1ed51044) center/cover;border-radius:50%;width:38px;height:44px;position:absolute;left:20px}.div-desc .desc-time[data-v-a126c659]{color:#fff;font-size:35pt;line-height:1;position:absolute;right:40px}.div-desc-slide-enter-active[data-v-a126c659]{transition:transform .36s ease-out .22s,opacity .36s ease-out .22s}.div-desc-slide-leave-active[data-v-a126c659]{transition:transform .26s ease-in,opacity .26s ease-in}.div-desc-slide-enter-from[data-v-a126c659],.div-desc-slide-leave-to[data-v-a126c659]{opacity:0;transform:translate(-110%)}.div-desc-slide-enter-to[data-v-a126c659],.div-desc-slide-leave-from[data-v-a126c659]{opacity:1;transform:translate(0)}.bg-bottom[data-v-a126c659]{bottom:0;left:var(--stage-viewport-left,0);width:var(--stage-viewport-width,750px);background:var(--ea746746) center/cover;opacity:.6;height:479px;position:absolute}.img-logo[data-v-a126c659]{background:var(--v640f204a) center/cover;width:428px;height:77px;position:absolute;top:66px;left:50%;transform:translate(-50%)}.play-content[data-v-a126c659]{color:#fff;text-align:center;background:#280c0885;border-radius:25px;margin-top:240px;padding:30px 0;font-size:28pt}.play-content>div[data-v-a126c659]:nth-child(2){color:#0000;transform-origin:50%;background:linear-gradient(#ffe68d 0%,#fad500 100%);-webkit-background-clip:text;background-clip:text;margin:10px 0;font-size:48pt;font-weight:700;display:inline-block;transform:scaleY(1.2)}.play-content>div[data-v-a126c659]:nth-child(3){position:relative;top:-10px}.play-content>div:nth-child(3) label[data-v-a126c659]{color:#fff;font-size:42pt}.play-gu[data-v-a126c659]{width:400px;height:400px;position:absolute;bottom:350px;left:50%;transform:translate(-50%)scale(2.4)}.game-stage[data-v-ae12dba1]{width:750px;height:1624px;padding:0;position:absolute}.bg-bottom[data-v-ae12dba1]{bottom:0;left:var(--stage-viewport-left,0);width:var(--stage-viewport-width,750px);background:var(--v303763f5) center/cover;opacity:.6;height:479px;position:absolute}.img-logo[data-v-ae12dba1]{background:var(--v528c697a) center/cover;width:428px;height:77px;position:absolute;top:66px;left:50%;transform:translate(-50%)}.img-score[data-v-ae12dba1]{background:var(--v3d51dddf) center/cover;width:465px;height:731px;position:absolute;top:500px;left:50%;transform:translate(-50%)scale(1.2)}.score-avatar[data-v-ae12dba1]{background:var(--v05e27e1e) center/cover;width:60px;height:60px;position:absolute;top:-20px;left:50%;transform:translate(-50%)scale(2)}.score-content[data-v-ae12dba1]{text-align:center;width:100%;height:calc(100% - 100px);font-size:28pt;position:absolute;bottom:0;left:0}.score-content>div[data-v-ae12dba1]:first-child{color:#e00000;font-size:32pt;font-weight:700}.score-content>div[data-v-ae12dba1]:nth-child(2){margin-top:20px;font-size:32pt}.score-content>div[data-v-ae12dba1]:nth-child(3){margin-top:40px}.score-content>div[data-v-ae12dba1]:nth-child(4){color:#e00000;margin-top:30px;font-size:32pt;font-weight:700;transform:scaleY(2.4)}.score-content>div[data-v-ae12dba1]:nth-child(5){margin-top:40px}.score-content>div[data-v-ae12dba1]:nth-child(6){color:#e00000;margin-top:20px;font-size:32pt;font-weight:700}.score-content>div:nth-child(6) label[data-v-ae12dba1]{color:#e00000;margin:0 10px;font-size:42pt}.btn-group[data-v-ae12dba1]{gap:40px;display:flex;position:absolute;top:1354px;left:50%;transform:translate(-50%)}.btn-replay[data-v-ae12dba1],.btn-back[data-v-ae12dba1]{width:315px;height:100px}.btn-replay[data-v-ae12dba1]{background:var(--v1928477f) center/cover}.btn-back[data-v-ae12dba1]{background:var(--v5295eb02) center/cover}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>小游戏-H5</title>
<style>body{background:#fbf7ef;}</style>
<script type="module" crossorigin src="/assets/index-BsTKH5UX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DWVl7jhI.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
...@@ -74,20 +74,16 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -74,20 +74,16 @@ export function useGameSocket(options: GameSocketOptions) {
const { msg, state, data } = payload const { msg, state, data } = payload
if (state === 0) { if (state === 0) {
if (evt == 'connect_result') {
router.replace('/loading').then(() => { });
return;
}
if (evt === 'room_join_result') { if (evt === 'room_join_result') {
switch (data) { $toast(msg);
case 1: userJoinStatus.value = true; return; router.replace('/loading').then(() => { });
case 2: { return;
// $toast(msg) }
alert(msg) if (evt == 'submit_score_save') {
return;
}
case 3:
// alert(msg);
$toast(msg)
return;
}
} else if (evt == 'submit_score_save') {
//提交分数的情况下有问题的情况下,只重试3次 //提交分数的情况下有问题的情况下,只重试3次
if (resubmitcount >= 3) { if (resubmitcount >= 3) {
return; return;
...@@ -96,13 +92,9 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -96,13 +92,9 @@ export function useGameSocket(options: GameSocketOptions) {
resubmitcount++; resubmitcount++;
options.onRescoreSubmitted?.(resubmitcount); options.onRescoreSubmitted?.(resubmitcount);
}, 200); }, 200);
} else if (evt == 'room_close_result') {
userJoinStatus.value = false
if (data == options.gameId) {
options.onRoomClosed?.(data)
}
return; return;
} }
debugger
return; return;
} }
...@@ -113,10 +105,6 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -113,10 +105,6 @@ export function useGameSocket(options: GameSocketOptions) {
} }
switch (evt) { switch (evt) {
case 'connect_result': {
debugger
break;
}
case 'room_create_result': { case 'room_create_result': {
if (`game${data}` == options.gameId) { if (`game${data}` == options.gameId) {
// location.reload(); // location.reload();
...@@ -170,6 +158,10 @@ export function useGameSocket(options: GameSocketOptions) { ...@@ -170,6 +158,10 @@ export function useGameSocket(options: GameSocketOptions) {
options.onRoomBack?.(data); options.onRoomBack?.(data);
break; break;
} }
case 'room_close_result': {
$toast('管理员关闭了该房间')
break;
}
default: { default: {
debugger debugger
} }
......
<script setup lang="ts">
import { cssAssetUrl } from '@/commons/assets'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const imageUrls = {
bg: cssAssetUrl('loading-bg.png'),
loading: cssAssetUrl('loading.png'),
icon: cssAssetUrl('loading-icon.png')
// bg2: cssAssetUrl('game1/bg2.png'),
// logo: cssAssetUrl('logo.png'),
// title: cssAssetUrl('game1/title.png'),
// avatar: cssAssetUrl('avatar-yellow.png'),
// gu: cssAssetUrl('game1/gu.png'),
// clock: cssAssetUrl('game1/clock.png'),
// gu01: cssAssetUrl('game1/gu01.png'),
// gu02: cssAssetUrl('game1/gu02.png'),
// gu03: cssAssetUrl('game1/gu03.png'),
// scoreBg: cssAssetUrl('game1/score-bg.png'),
// back: cssAssetUrl('game1/back.png'),
// replay: cssAssetUrl('game1/replay.png'),
// close: cssAssetUrl('game1/close.png'),
// rule: cssAssetUrl('game1/rule.png')
}
onMounted(() => {
document.title = '小游戏'
})
onBeforeUnmount(() => {
})
</script>
<template>
<div class="bg">
<div class="loading">
<div class="icon"></div>
</div>
</div>
</template>
<style scoped>
.bg {
width: 100vw;
height: 100vh;
background: v-bind('imageUrls.bg') center no-repeat;
position: relative;
.loading {
width: 473px;
height: 733px;
background: v-bind('imageUrls.loading') center / cover no-repeat;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0.72);
.icon {
width: 190px;
height: 232px;
background: v-bind('imageUrls.icon') center / cover no-repeat;
position: absolute;
left: 50%;
top: 50%;
transform-origin: center center;
animation: swing 0.8s ease-in-out infinite alternate;
}
}
}
@keyframes swing {
0% {
transform: translateX(-50%) scale(0.72) rotate(-30deg);
}
100% {
transform: translateX(-50%) scale(0.72) rotate(15deg);
}
}
</style>
...@@ -120,8 +120,22 @@ onMounted(() => { ...@@ -120,8 +120,22 @@ onMounted(() => {
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
transform: translateX(-50%); transform: translateX(-50%);
animation: loading-text-scale 1.2s ease-in-out infinite;
} }
@keyframes loading-text-scale {
0%,
100% {
transform: translateX(-50%) scale(1);
}
50% {
transform: translateX(-50%) scale(1.08);
}
}
.txt-loading2 { .txt-loading2 {
width: 500px; width: 500px;
height: 90px; height: 90px;
......
...@@ -5,7 +5,7 @@ import MobileStage from '@/components/MobileStage.vue' ...@@ -5,7 +5,7 @@ import MobileStage from '@/components/MobileStage.vue'
import { useGameSocket, joinSharedRoom, sendGameMessage, userJoinStatus } from '@/composables/useGameSocket' import { useGameSocket, joinSharedRoom, sendGameMessage, userJoinStatus } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue' import LoadingView from './views/LoadingView.vue'
import PlayingView from './views/PlayingView.vue' import PlayingView from './views/PlayingView.vue'
import { $getWechat, $toast } from '@/commons/utils.ts' import { $format_str, $getWechat, $toast } from '@/commons/utils.ts'
import { playGame6Music, stopGame6Music } from '@/commons/music'; import { playGame6Music, stopGame6Music } from '@/commons/music';
type GameView = 'loading' | 'playing' type GameView = 'loading' | 'playing'
...@@ -326,7 +326,7 @@ const rankCloseHandler = () => { ...@@ -326,7 +326,7 @@ const rankCloseHandler = () => {
<div class="col-2"> <div class="col-2">
<div class="avatar" <div class="avatar"
:style="item.avatar ? { backgroundImage: `url(${item.avatar})` } : {}"></div> :style="item.avatar ? { backgroundImage: `url(${item.avatar})` } : {}"></div>
<div>{{ item.nickname }}</div> <div>{{ $format_str(item.nickname,12) }}</div>
</div> </div>
<div class="col-3">{{ item.score }}</div> <div class="col-3">{{ item.score }}</div>
</div> </div>
......
...@@ -56,8 +56,10 @@ onMounted(() => { ...@@ -56,8 +56,10 @@ onMounted(() => {
color: white; color: white;
letter-spacing: 2pt; letter-spacing: 2pt;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.txt-bottom { .txt-bottom {
position: absolute; position: absolute;
bottom: 130px; bottom: 130px;
...@@ -66,6 +68,20 @@ onMounted(() => { ...@@ -66,6 +68,20 @@ onMounted(() => {
color: white; color: white;
font-size: 28px; font-size: 28px;
text-align: center; text-align: center;
animation: loading-text-scale 1.2s ease-in-out infinite;
}
@keyframes loading-text-scale {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.08);
}
} }
.game-stage { .game-stage {
...@@ -105,8 +121,10 @@ onMounted(() => { ...@@ -105,8 +121,10 @@ onMounted(() => {
height: 479px; height: 479px;
background: v-bind('imageUrls.bg2') center/cover; background: v-bind('imageUrls.bg2') center/cover;
opacity: 0.6; opacity: 0.6;
} }
.img-logo { .img-logo {
position: absolute; position: absolute;
top: 66px; top: 66px;
......
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import { $read } from '@/commons/utils' import { $read } from '@/commons/utils'
import Loading from '@/pages/Loading.vue'
import Game1 from '@/pages/game1/Game.vue' import Game1 from '@/pages/game1/Game.vue'
import Game4 from '@/pages/game4/Game4.vue' import Game4 from '@/pages/game4/Game4.vue'
import Game5 from '@/pages/game5/Game5.vue' import Game5 from '@/pages/game5/Game5.vue'
...@@ -17,6 +18,11 @@ const router = createRouter({ ...@@ -17,6 +18,11 @@ const router = createRouter({
// redirect: () => (getWechatId() ? '/game1' : '/login'), // redirect: () => (getWechatId() ? '/game1' : '/login'),
// }, // },
{ {
path: '/loading',
name: 'Loading',
component: Loading,
},
{
path: '/game1', path: '/game1',
name: 'Game1', name: 'Game1',
component: Game1, component: Game1,
......
...@@ -23,24 +23,6 @@ export function stopAllMusic() { ...@@ -23,24 +23,6 @@ export function stopAllMusic() {
backgroundMusic = null; backgroundMusic = null;
} }
function playAudioTimes(audio: HTMLAudioElement, times: number) {
let count = 1
audio.currentTime = 0
audio.play()
audio.onended = () => {
if (count >= times) {
audio.onended = null
return
}
count++
audio.currentTime = 0
audio.play()
}
}
function getBackgroundMusic(music: string, force: boolean = true) { function getBackgroundMusic(music: string, force: boolean = true) {
if (force) { // if (force) { //
if (backgroundMusic) { if (backgroundMusic) {
...@@ -63,10 +45,12 @@ export async function playApplauseMusic() { ...@@ -63,10 +45,12 @@ export async function playApplauseMusic() {
const audio = getBackgroundMusic(applause) const audio = getBackgroundMusic(applause)
try { try {
playAudioTimes(audio, 2); //播放两次停止 await audio.play()
window.removeEventListener('pointerdown', playApplauseMusic)
window.removeEventListener('keydown', playApplauseMusic)
} catch { } catch {
window.addEventListener('pointerdown', playApplauseMusic, { once: false }) window.addEventListener('pointerdown', playApplauseMusic, { once: true })
window.addEventListener('keydown', playApplauseMusic, { once: false }) window.addEventListener('keydown', playApplauseMusic, { once: true })
} }
} }
......
...@@ -73,7 +73,7 @@ export function $read_socket_storge(): any { ...@@ -73,7 +73,7 @@ export function $read_socket_storge(): any {
} }
export function $format_str(str: string, num: number) { export function $format_str(str: string, num: number) {
if (str.length <= num - 2) { if (str.length <= num) {
return str; return str;
} }
return str.substring(0, num) + '...'; return str.substring(0, num) + '...';
......
...@@ -70,7 +70,7 @@ const renderAvatar = (item: any | null) => { ...@@ -70,7 +70,7 @@ const renderAvatar = (item: any | null) => {
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 8) }}</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -61,63 +61,34 @@ const emptyPlayer = (index: number): Player => ({ ...@@ -61,63 +61,34 @@ const emptyPlayer = (index: number): Player => ({
}); });
const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index))); const list = ref<Player[]>(Array.from({ length: RANK_LIMIT }, (_, index) => emptyPlayer(index)));
const playerKey = (player: Player, index = 0) => player.userid || player.wechat_id || `empty-${index}`; const playerKey = (player: Player, index = 0) => {
const mockRankStep = ref(0); if (player.userid || player.wechat_id) {
return player.userid || player.wechat_id || `empty-${index}`;
}
if (player.nickname !== '-') {
return `player-${player.nickname}-${player.avatar || ''}`;
}
return `empty-${index}`;
};
const makeMockPlayer = (id: number, score: number): Player => ({ const playRankAnimation = (rankList: Player[]) => {
wechat_id: `mock-${id}`, const oldRanks = new Map(list.value.map((player, index) => [playerKey(player, index), index]));
nickname: `Player ${id}`, const next = Array.from(
avatar: '', { length: RANK_LIMIT },
score, (_, index) => rankList[index] ? { ...rankList[index] } : emptyPlayer(index),
}); );
const changed = next.some((player, index) => {
const current = list.value[index] ?? emptyPlayer(index);
return playerKey(player, index) !== playerKey(current, index)
|| player.score !== current.score;
});
const mockRankRounds: Player[][] = [ if (!changed) return;
[
makeMockPlayer(1, 980),
makeMockPlayer(2, 900),
makeMockPlayer(3, 860),
makeMockPlayer(4, 820),
makeMockPlayer(5, 780),
makeMockPlayer(6, 720),
makeMockPlayer(7, 680),
makeMockPlayer(8, 640),
makeMockPlayer(9, 600),
makeMockPlayer(10, 560),
],
[
makeMockPlayer(4, 1040),
makeMockPlayer(1, 1010),
makeMockPlayer(7, 960),
makeMockPlayer(2, 940),
makeMockPlayer(11, 880),
makeMockPlayer(3, 850),
makeMockPlayer(5, 830),
makeMockPlayer(8, 790),
makeMockPlayer(6, 760),
makeMockPlayer(10, 700),
],
[
makeMockPlayer(7, 1180),
makeMockPlayer(11, 1090),
makeMockPlayer(4, 1060),
makeMockPlayer(12, 1000),
makeMockPlayer(1, 980),
makeMockPlayer(8, 930),
makeMockPlayer(2, 890),
makeMockPlayer(5, 850),
makeMockPlayer(13, 810),
makeMockPlayer(3, 780),
],
];
const updateRankList = (rankList: Player[]) => {
const oldRanks = new Map(list.value.map((player, index) => [playerKey(player, index), index]));
const next = Array.from({ length: RANK_LIMIT }, (_, index) => rankList[index] ?? emptyPlayer(index));
const effects: Record<string, "up" | "down" | "new"> = {}; const effects: Record<string, "up" | "down" | "new"> = {};
next.forEach((player, index) => { next.forEach((player, index) => {
const key = playerKey(player, index); const key = playerKey(player, index);
if (!key || key.startsWith("empty-")) return; if (key.startsWith('empty-')) return;
const oldIndex = oldRanks.get(key); const oldIndex = oldRanks.get(key);
if (oldIndex === undefined) effects[key] = "new"; if (oldIndex === undefined) effects[key] = "new";
...@@ -134,17 +105,10 @@ const updateRankList = (rankList: Player[]) => { ...@@ -134,17 +105,10 @@ const updateRankList = (rankList: Player[]) => {
watch( watch(
() => props.rankList, () => props.rankList,
(rankList) => updateRankList(rankList), (rankList) => playRankAnimation(rankList),
{ immediate: true, deep: true }, { immediate: true, deep: true, flush: 'post' },
); );
const playMockRankAnimation = () => {
if ($is_run_local()) {
updateRankList(mockRankRounds[mockRankStep.value] ?? mockRankRounds[0] ?? []);
mockRankStep.value = (mockRankStep.value + 1) % mockRankRounds.length;
}
};
const startGame = () => { const startGame = () => {
if ($is_run_local()) { if ($is_run_local()) {
emit('start'); emit('start');
...@@ -263,7 +227,7 @@ const avatarStyle = (item: Player) => { ...@@ -263,7 +227,7 @@ const avatarStyle = (item: Player) => {
</div> </div>
</div> </div>
<div class="rank-container"> <div class="rank-container">
<div class="rank-txt" @click="startGame()" @contextmenu.prevent="playMockRankAnimation()"></div> <div class="rank-txt" @click="startGame()"></div>
<TransitionGroup name="rank" tag="div" class="rank-list"> <TransitionGroup name="rank" tag="div" class="rank-list">
<div v-for="(item, index) in list" :key="playerKey(item, index)" class="rank-item" <div v-for="(item, index) in list" :key="playerKey(item, index)" class="rank-item"
:class="[rankEffects[playerKey(item, index)], index < 3 ? `rank-top-${index + 1}` : '']"> :class="[rankEffects[playerKey(item, index)], index < 3 ? `rank-top-${index + 1}` : '']">
...@@ -461,12 +425,13 @@ const avatarStyle = (item: Player) => { ...@@ -461,12 +425,13 @@ const avatarStyle = (item: Player) => {
} }
.rank-move { .rank-move {
transition: transform 0.45s ease; z-index: 1;
transition: transform 0.65s cubic-bezier(0.22, 1, 0.36, 1);
} }
.rank-enter-active, .rank-enter-active,
.rank-leave-active { .rank-leave-active {
transition: opacity 0.25s ease, transform 0.25s ease; transition: opacity 0.35s ease, transform 0.35s ease;
} }
.rank-leave-active { .rank-leave-active {
...@@ -477,7 +442,7 @@ const avatarStyle = (item: Player) => { ...@@ -477,7 +442,7 @@ const avatarStyle = (item: Player) => {
.rank-enter-from, .rank-enter-from,
.rank-leave-to { .rank-leave-to {
opacity: 0; opacity: 0;
transform: translateY(90px); transform: translateY(70px);
} }
.rank-item.up, .rank-item.up,
......
...@@ -76,7 +76,7 @@ const releaseStartButton = () => { ...@@ -76,7 +76,7 @@ const releaseStartButton = () => {
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname"> <div class="txt-nickname">
{{ $format_str(item.nickname, 12) }} {{ $format_str(item.nickname, 8) }}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -71,7 +71,7 @@ const renderAvatar = (item: any | null) => { ...@@ -71,7 +71,7 @@ const renderAvatar = (item: any | null) => {
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 8) }}</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -301,7 +301,7 @@ const rankIcon = (index: number) => { ...@@ -301,7 +301,7 @@ const rankIcon = (index: number) => {
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 8) }}</div>
<div class="txt-score">{{ item.score }}</div> <div class="txt-score">{{ item.score }}</div>
</div> </div>
</div> </div>
......
...@@ -82,7 +82,7 @@ const renderAvatar = (item: any | null) => { ...@@ -82,7 +82,7 @@ const renderAvatar = (item: any | null) => {
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 8) }}</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -370,7 +370,7 @@ const rankIcon = (index: number) => { ...@@ -370,7 +370,7 @@ const rankIcon = (index: number) => {
<div class="img-avatar-container"> <div class="img-avatar-container">
<div class="img-avatar" :style="`${renderAvatar(item)}`"></div> <div class="img-avatar" :style="`${renderAvatar(item)}`"></div>
</div> </div>
<div class="txt-nickname">{{ $format_str(item.nickname, 12) }}</div> <div class="txt-nickname">{{ $format_str(item.nickname, 8) }}</div>
<div class="txt-score">{{ item.score }}</div> <div class="txt-score">{{ item.score }}</div>
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment