Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
fc-minigame
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陈冲
fc-minigame
Commits
8a5923a5
Commit
8a5923a5
authored
Jun 02, 2026
by
陈冲
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
更新h5代码
parent
948fdad3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
438 additions
and
479 deletions
+438
-479
Cargo.lock
backend/Cargo.lock
+26
-26
Cargo.toml
backend/Cargo.toml
+2
-2
game.sql
backend/deploy/game.sql
+4
-6
game_mysql.sql
backend/deploy/game_mysql.sql
+2
-4
game.rs
backend/src/db/models/game.rs
+7
-11
socket_io.rs
backend/src/protocols/socket_io.rs
+65
-53
utils.ts
frontend-h5/src/commons/utils.ts
+26
-32
ws.ts
frontend-h5/src/commons/ws.ts
+21
-1
MobileStage.vue
frontend-h5/src/components/MobileStage.vue
+4
-4
useGameSocket.ts
frontend-h5/src/composables/useGameSocket.ts
+87
-72
Login.vue
frontend-h5/src/pages/Login.vue
+0
-123
Game1.vue
frontend-h5/src/pages/game1/Game1.vue
+71
-35
LoadingView.vue
frontend-h5/src/pages/game1/views/LoadingView.vue
+16
-72
ScoreView.vue
frontend-h5/src/pages/game1/views/ScoreView.vue
+2
-2
index.ts
frontend-h5/src/router/index.ts
+8
-14
vite.config.ts
frontend-h5/vite.config.ts
+1
-1
game1.mp3
frontend-large/src/assets/images/game1.mp3
+0
-0
qrcode.png
frontend-large/src/assets/images/game1/qrcode.png
+0
-0
music.ts
frontend-large/src/commons/music.ts
+34
-0
utils.ts
frontend-large/src/commons/utils.ts
+12
-2
ws.ts
frontend-large/src/commons/ws.ts
+4
-2
useAdminGameSocket.ts
frontend-large/src/composables/useAdminGameSocket.ts
+4
-4
Login.vue
frontend-large/src/pages/Login.vue
+8
-2
Game1.vue
frontend-large/src/pages/game1/Game1.vue
+15
-3
LoadingView.vue
frontend-large/src/pages/game1/views/LoadingView.vue
+2
-2
Rank2View.vue
frontend-large/src/pages/game1/views/Rank2View.vue
+14
-4
vite.config.ts
frontend-large/vite.config.ts
+3
-2
No files found.
backend/Cargo.lock
View file @
8a5923a5
...
@@ -1031,6 +1031,32 @@ dependencies = [
...
@@ -1031,6 +1031,32 @@ dependencies = [
]
]
[[package]]
[[package]]
name = "hddpServer"
version = "0.1.0"
dependencies = [
"aes",
"async-trait",
"base64",
"bytes",
"cbc",
"chrono",
"dashmap",
"lazy_static",
"md-5",
"rand 0.10.0",
"rmp-serde",
"rust-embed",
"salvo",
"serde",
"serde_json",
"socketioxide",
"sqlx",
"tokio",
"tower",
"tower-http",
]
[[package]]
name = "headers"
name = "headers"
version = "0.4.1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "registry+https://github.com/rust-lang/crates.io-index"
...
@@ -1611,32 +1637,6 @@ dependencies = [
...
@@ -1611,32 +1637,6 @@ dependencies = [
]
]
[[package]]
[[package]]
name = "minigame"
version = "0.1.0"
dependencies = [
"aes",
"async-trait",
"base64",
"bytes",
"cbc",
"chrono",
"dashmap",
"lazy_static",
"md-5",
"rand 0.10.0",
"rmp-serde",
"rust-embed",
"salvo",
"serde",
"serde_json",
"socketioxide",
"sqlx",
"tokio",
"tower",
"tower-http",
]
[[package]]
name = "miniz_oxide"
name = "miniz_oxide"
version = "0.8.9"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
source = "registry+https://github.com/rust-lang/crates.io-index"
...
...
backend/Cargo.toml
View file @
8a5923a5
[package]
[package]
name
=
"
minigame
"
name
=
"
hddpServer
"
version
=
"0.1.0"
version
=
"0.1.0"
edition
=
"2024"
edition
=
"2024"
...
@@ -27,7 +27,7 @@ cbc = { version = "0.1.2", features = ["alloc"] }
...
@@ -27,7 +27,7 @@ cbc = { version = "0.1.2", features = ["alloc"] }
md5
=
{
version
=
"0.10.6"
,
package
=
"md-5"
}
md5
=
{
version
=
"0.10.6"
,
package
=
"md-5"
}
[[bin]]
[[bin]]
name
=
"
minigame
"
name
=
"
hddpServer
"
[target.x86_64-unknown-linux-musl]
[target.x86_64-unknown-linux-musl]
linker
=
"rust-lld"
linker
=
"rust-lld"
...
...
backend/deploy/game.sql
View file @
8a5923a5
drop
table
if
exists
tb_game
;
drop
table
if
exists
tb_game
;
create
table
tb_game
create
table
tb_game
(
(
event_date
date
,
-- 活动日期
wechat_id
text
,
-- 微信id
wechat_id
varchar
(
50
),
-- 微信id
nickname
varchar
(
50
),
-- 昵称
nickname
varchar
(
50
),
-- 昵称
avatar
varchar
(
200
),
-- 头像
avatar
text
,
-- 头像
round_num
int
NOT
NULL
CHECK
(
round_num
between
1
and
3
),
-- 比赛场次
game_rank
int
,
-- 排名
item_num
int
not
null
check
(
item_num
between
1
and
6
),
-- 游戏项目(1到6)
item_num
int
not
null
check
(
item_num
between
1
and
6
),
-- 游戏项目(1到6)
score
int
not
null
default
0
check
(
score
>=
0
),
-- 得分
score
int
not
null
default
0
check
(
score
>=
0
),
-- 得分
create_date
timestamptz
,
-- 创建时间
create_date
timestamptz
-- 创建时间
unique
(
event_date
,
wechat_id
,
round_num
,
item_num
)
);
);
SELECT
*
FROM
tb_game
SELECT
*
FROM
tb_game
...
...
backend/deploy/game_mysql.sql
View file @
8a5923a5
DROP
TABLE
IF
EXISTS
tb_game
;
DROP
TABLE
IF
EXISTS
tb_game
;
CREATE
TABLE
tb_game
CREATE
TABLE
tb_game
(
(
event_date
DATE
,
wechat_id
VARCHAR
(
50
),
wechat_id
VARCHAR
(
50
),
nickname
VARCHAR
(
50
),
nickname
VARCHAR
(
50
),
avatar
VARCHAR
(
200
),
avatar
VARCHAR
(
200
),
round_num
INT
NOT
NULL
,
game_rank
INT
NOT
NULL
,
item_num
INT
NOT
NULL
,
item_num
INT
NOT
NULL
,
score
INT
NOT
NULL
DEFAULT
0
,
score
INT
NOT
NULL
DEFAULT
0
,
create_date
DATETIME
,
create_date
DATETIME
UNIQUE
KEY
uk_tb_game_player_item
(
event_date
,
wechat_id
,
round_num
,
item_num
)
);
);
DROP
TABLE
IF
EXISTS
tb_log
;
DROP
TABLE
IF
EXISTS
tb_log
;
...
...
backend/src/db/models/game.rs
View file @
8a5923a5
...
@@ -4,15 +4,13 @@ use serde_json::json;
...
@@ -4,15 +4,13 @@ use serde_json::json;
use
crate
::
db
::
models
::
log
::
Log
;
use
crate
::
db
::
models
::
log
::
Log
;
pub
struct
Game
{
pub
struct
Game
{
/// 活动日期
pub
event_date
:
NaiveDate
,
pub
wechat_id
:
String
,
pub
wechat_id
:
String
,
pub
nickname
:
Option
<
String
>
,
pub
nickname
:
String
,
pub
avatar
:
Option
<
String
>
,
pub
avatar
:
String
,
/// 比赛场次
pub
round_num
:
i32
,
/// 游戏项目(1到6)
/// 游戏项目(1到6)
pub
item_num
:
i32
,
pub
item_num
:
i32
,
/// 排序
pub
game_rank
:
i32
,
/// 得分
/// 得分
pub
score
:
i32
,
pub
score
:
i32
,
/// 创建时间
/// 创建时间
...
@@ -22,14 +20,13 @@ pub struct Game {
...
@@ -22,14 +20,13 @@ pub struct Game {
impl
Game
{
impl
Game
{
pub
async
fn
insert
(
&
self
)
->
bool
{
pub
async
fn
insert
(
&
self
)
->
bool
{
let
res
=
crate
::
db
::
db_query
(
let
res
=
crate
::
db
::
db_query
(
r
#
"insert into tb_game(
event_date,wechat_id,nickname,avatar,round_num,item_num,score,create_date) values(?::date,
?,?,?,?,?,?,now());"
#
,
r
#
"insert into tb_game(
wechat_id,nickname,avatar,item_num,game_rank,score,create_date) values(
?,?,?,?,?,?,now());"
#
,
vec!
[
vec!
[
json!
(
&
self
.event_date
),
json!
(
&
self
.wechat_id
),
json!
(
&
self
.wechat_id
),
json!
(
&
self
.nickname
),
json!
(
&
self
.nickname
),
json!
(
&
self
.avatar
),
json!
(
&
self
.avatar
),
json!
(
&
self
.round_num
),
json!
(
&
self
.item_num
),
json!
(
&
self
.item_num
),
json!
(
&
self
.game_rank
),
json!
(
&
self
.score
),
json!
(
&
self
.score
),
],
],
)
)
...
@@ -46,12 +43,11 @@ impl Game {
...
@@ -46,12 +43,11 @@ impl Game {
id
:
0
,
id
:
0
,
msg
:
err
,
msg
:
err
,
params
:
json!
({
params
:
json!
({
"event_date"
:
self
.event_date
,
"wechat_id"
:
self
.wechat_id
,
"wechat_id"
:
self
.wechat_id
,
"nickname"
:
self
.nickname
,
"nickname"
:
self
.nickname
,
"avatar"
:
self
.avatar
,
"avatar"
:
self
.avatar
,
"round_num"
:
self
.round_num
,
"item_num"
:
self
.item_num
,
"item_num"
:
self
.item_num
,
"game_rank"
:
self
.game_rank
,
"score"
:
self
.score
,
"score"
:
self
.score
,
}),
}),
create_date
:
None
,
create_date
:
None
,
...
...
backend/src/protocols/socket_io.rs
View file @
8a5923a5
This diff is collapsed.
Click to expand it.
frontend-h5/src/commons/utils.ts
View file @
8a5923a5
import
SHA256
from
'crypto-js/sha256'
type
StorageRecord
=
Record
<
string
,
unknown
>
;
type
StorageRecord
=
Record
<
string
,
unknown
>
;
function
isStorageRecord
(
value
:
unknown
):
value
is
StorageRecord
{
function
isStorageRecord
(
value
:
unknown
):
value
is
StorageRecord
{
...
@@ -54,37 +56,6 @@ export function $read<T>(key: string | StorageRecord, defaultValue?: T): T | Sto
...
@@ -54,37 +56,6 @@ export function $read<T>(key: string | StorageRecord, defaultValue?: T): T | Sto
}
}
}
}
export
function
$goto_login
(
evt
:
string
,
router
:
any
,
path
:
string
):
boolean
{
if
(
evt
==
"login_result"
)
{
$remove_socket_storage
();
router
.
replace
(
path
).
then
(()
=>
{
});
return
true
;
}
return
false
;
}
export
function
$remove_socket_storage
():
void
{
localStorage
.
removeItem
(
'socket_path'
);
localStorage
.
removeItem
(
'client_ns'
);
localStorage
.
removeItem
(
'wechat_id'
);
localStorage
.
removeItem
(
'nickname'
);
localStorage
.
removeItem
(
'telephone'
);
localStorage
.
removeItem
(
'avatar'
);
}
export
function
$read_socket_storge
():
any
{
const
domain
=
$read
(
'domain'
,
''
);
const
socket_path
=
$read
(
'socket_path'
,
''
);
const
client_ns
=
$read
(
'client_ns'
,
''
)
const
wechat_id
=
$read
(
'wechat_id'
,
''
);
const
nickname
=
$read
(
'nickname'
,
''
);
const
telephone
=
$read
(
'telephone'
,
''
);
const
avatar
=
$read
(
'avatar'
,
''
);
return
{
domain
,
socket_path
,
client_ns
,
wechat_id
,
nickname
,
telephone
,
avatar
}
}
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
-
2
)
{
return
str
;
return
str
;
...
@@ -93,8 +64,30 @@ export function $format_str(str: string, num: number) {
...
@@ -93,8 +64,30 @@ export function $format_str(str: string, num: number) {
}
}
export
function
$is_run_local
():
boolean
{
export
function
$is_run_local
():
boolean
{
if
(
/localhost|192.168/gim
.
test
(
location
.
href
))
{
if
(
/
https
?
:
\/\/
localhost|192.168/gim
.
test
(
location
.
href
))
{
return
true
;
return
true
;
}
}
return
false
;
return
false
;
}
}
function
$query
<
T
extends
string
|
string
[]
>
(
q
:
T
):
T
{
const
query
=
location
.
href
.
match
(
/
\?([^
#
]
*
)
/
)?.[
1
]?.
replace
(
/
\/
$/
,
''
);
const
params
=
new
URLSearchParams
(
query
);
if
(
Array
.
isArray
(
q
))
{
return
q
.
map
((
key
)
=>
params
.
get
(
key
)).
filter
((
v
):
v
is
string
=>
!!
v
)
as
T
;
}
return
params
.
get
(
q
)
as
T
;
}
export
function
$getWechat
():
any
|
null
{
const
q
=
$query
([
'token'
,
'nickname'
,
'avatar'
]);
if
(
q
&&
q
.
length
==
3
)
{
return
{
token
:
SHA256
(
q
[
0
]).
toString
(),
//原token很长,这里压缩下,压缩后不能还原,最后提交成绩时要提交原始token
token_origin
:
q
[
0
],
nickname
:
q
[
1
],
avatar
:
q
[
2
],
};
}
return
null
;
}
\ No newline at end of file
frontend-h5/src/commons/ws.ts
View file @
8a5923a5
...
@@ -35,8 +35,19 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -35,8 +35,19 @@ export function initSocket(args: SocketInitArgs): Socket | null {
if
(
socket
)
{
if
(
socket
)
{
if
(
socket
.
connected
)
{
if
(
socket
.
connected
)
{
args
.
onConnect
?.(
socket
);
args
.
onConnect
?.(
socket
);
return
socket
;
}
}
return
socket
;
socket
.
removeAllListeners
();
socket
.
disconnect
();
socket
=
null
;
}
if
(
$is_run_local
())
{
console
.
log
(
'socket.io init:'
,
{
namespace
:
args
.
client_ns
,
path
:
args
.
socket_path
,
auth
:
args
.
auth
,
});
}
}
socket
=
io
(
args
.
client_ns
,
{
socket
=
io
(
args
.
client_ns
,
{
...
@@ -49,6 +60,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -49,6 +60,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
});
});
socket
.
on
(
'connect'
,
()
=>
{
socket
.
on
(
'connect'
,
()
=>
{
if
(
$is_run_local
())
{
console
.
log
(
'socket.io connected:'
,
socket
?.
id
);
}
args
.
onConnect
?.(
socket
!
);
args
.
onConnect
?.(
socket
!
);
});
});
socket
.
onAny
((
evt
,
payload
)
=>
{
socket
.
onAny
((
evt
,
payload
)
=>
{
...
@@ -60,6 +74,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -60,6 +74,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
});
});
socket
.
on
(
'disconnect'
,
(
reason
)
=>
{
socket
.
on
(
'disconnect'
,
(
reason
)
=>
{
if
(
$is_run_local
())
{
console
.
log
(
'socket.io disconnect:'
,
reason
);
}
args
.
onDisconnect
?.(
reason
);
args
.
onDisconnect
?.(
reason
);
});
});
...
@@ -72,6 +89,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -72,6 +89,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
export
function
sendSocketMessage
(
cmd
:
string
,
data
:
any
=
{}):
boolean
{
export
function
sendSocketMessage
(
cmd
:
string
,
data
:
any
=
{}):
boolean
{
if
(
!
socket
?.
connected
)
{
if
(
!
socket
?.
connected
)
{
if
(
$is_run_local
())
{
console
.
log
(
'socket.io send blocked:'
,
cmd
,
data
);
}
return
false
;
return
false
;
}
}
...
...
frontend-h5/src/components/MobileStage.vue
View file @
8a5923a5
...
@@ -144,11 +144,11 @@ defineExpose({
...
@@ -144,11 +144,11 @@ defineExpose({
<main
class=
"mobile-stage-viewport"
>
<main
class=
"mobile-stage-viewport"
>
<div
class=
"mobile-stage-background"
:style=
"backgroundStyle"
/>
<div
class=
"mobile-stage-background"
:style=
"backgroundStyle"
/>
<div
class=
"mobile-stage"
:style=
"stageStyle"
>
<div
class=
"mobile-stage"
:style=
"stageStyle"
>
<div
v-if=
"countdownVisible"
class=
"countdown-modal"
>
<div
:key=
"countdownKey"
class=
"countdown-number"
>
{{
countdown
}}
</div>
</div>
<slot
/>
<slot
/>
</div>
</div>
<div
v-if=
"countdownVisible"
class=
"countdown-modal"
>
<div
:key=
"countdownKey"
class=
"countdown-number"
>
{{
countdown
}}
</div>
</div>
</main>
</main>
</
template
>
</
template
>
...
@@ -187,7 +187,7 @@ defineExpose({
...
@@ -187,7 +187,7 @@ defineExpose({
.countdown-number
{
.countdown-number
{
color
:
white
;
color
:
white
;
font-size
:
3
00pt
;
font-size
:
2
00pt
;
font-weight
:
800
;
font-weight
:
800
;
line-height
:
1
;
line-height
:
1
;
text-shadow
:
0
18px
38px
rgba
(
0
,
0
,
0
,
0.25
);
text-shadow
:
0
18px
38px
rgba
(
0
,
0
,
0
,
0.25
);
...
...
frontend-h5/src/composables/useGameSocket.ts
View file @
8a5923a5
import
{
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
import
{
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
useRouter
}
from
'vue-router'
import
type
{
Socket
}
from
'socket.io-client'
import
type
{
Socket
}
from
'socket.io-client'
import
{
$read_socket_storge
}
from
'@/commons/utils'
import
{
initSocket
,
onSocketMessage
,
sendSocketMessage
}
from
'@/commons/ws'
import
{
initSocket
,
onSocketMessage
,
sendSocketMessage
}
from
'@/commons/ws'
import
{
$read
}
from
'@/commons/utils'
type
SocketPayload
=
{
type
SocketPayload
=
{
msg
?:
string
msg
?:
string
...
@@ -11,9 +11,12 @@ type SocketPayload = {
...
@@ -11,9 +11,12 @@ type SocketPayload = {
}
}
type
GameSocketOptions
=
{
type
GameSocketOptions
=
{
gameId
:
string
gameId
:
string
,
auth
:
any
,
onConnect
?:
()
=>
void
,
onGameStart
?:
(
data
:
any
)
=>
void
onGameStart
?:
(
data
:
any
)
=>
void
onScoreSubmitted
?:
(
data
:
any
)
=>
void
onScoreSubmitted
?:
(
is_save
:
boolean
,
data
:
any
)
=>
void
onRescoreSubmitted
?:
(
recount
:
number
)
=>
void
onRoomClosed
?:
(
data
:
any
)
=>
void
onRoomClosed
?:
(
data
:
any
)
=>
void
onEvent
?:
(
evt
:
string
,
payload
:
SocketPayload
)
=>
void
onEvent
?:
(
evt
:
string
,
payload
:
SocketPayload
)
=>
void
}
}
...
@@ -36,94 +39,106 @@ function getGameHomePath(data: any) {
...
@@ -36,94 +39,106 @@ function getGameHomePath(data: any) {
return
`/game
${
gameIndex
}
`
return
`/game
${
gameIndex
}
`
}
}
export
function
joinSharedRoom
(
nickname
:
string
,
token
:
string
,
avatar
:
string
)
{
return
sendSocketMessage
(
'room_join'
,
{
nickname
:
nickname
,
token
,
avatar
})
}
export
function
sendGameMessage
(
cmd
:
string
,
data
:
any
=
{})
{
return
sendSocketMessage
(
cmd
,
data
)
}
export
const
userJoinStatus
=
ref
(
false
)
export
function
useGameSocket
(
options
:
GameSocketOptions
)
{
export
function
useGameSocket
(
options
:
GameSocketOptions
)
{
const
router
=
useRouter
()
const
router
=
useRouter
()
const
userJoinStatus
=
ref
(
false
)
let
socket
:
Socket
|
null
=
null
let
socket
:
Socket
|
null
=
null
let
offSocketMessage
:
(()
=>
void
)
|
undefined
let
offSocketMessage
:
(()
=>
void
)
|
undefined
const
domain
=
$read
(
'domain'
,
''
);
let
resubmitcount
=
0
;
socket
=
initSocket
({
socket_path
:
'/socket.io'
,
client_ns
:
`
${
domain
}
/ws`
,
auth
:
options
.
auth
,
onConnect
:
(
socket
)
=>
{
options
.
onConnect
?.();
},
onDisconnect
:
(
_reason
:
string
)
=>
{
},
onError
:
(
error
:
any
)
=>
{
console
.
error
(
'socket.io connect_error:'
,
error
)
},
})
function
joinSharedRoom
()
{
offSocketMessage
=
onSocketMessage
((
evt
,
payload
:
SocketPayload
)
=>
{
const
{
nickname
,
telephone
,
avatar
}
=
$read_socket_storge
()
const
{
msg
,
state
,
data
}
=
payload
return
sendSocketMessage
(
'room_join'
,
{
nickname
,
telephone
,
avatar
})
}
function
sendGameMessage
(
cmd
:
string
,
data
:
any
=
{})
{
return
sendSocketMessage
(
cmd
,
data
)
}
onMounted
(()
=>
{
if
(
state
===
0
)
{
const
{
domain
,
socket_path
,
client_ns
,
wechat_id
}
=
$read_socket_storge
()
if
(
evt
===
'room_join_result'
)
{
switch
(
data
)
{
socket
=
initSocket
({
case
1
:
userJoinStatus
.
value
=
true
;
break
;
socket_path
:
`
${
domain
}${
socket_path
}
`
,
case
3
:
alert
(
msg
);
break
;
client_ns
,
auth
:
{
userid
:
wechat_id
,
},
onConnect
:
()
=>
{
window
.
setTimeout
(
joinSharedRoom
,
200
)
},
onDisconnect
:
(
_reason
:
string
)
=>
{
},
onError
:
(
_error
:
any
)
=>
{
},
})
offSocketMessage
=
onSocketMessage
((
evt
,
payload
:
SocketPayload
)
=>
{
const
{
msg
,
state
,
data
}
=
payload
if
(
state
===
0
&&
evt
===
'room_join_result'
)
{
if
(
data
===
1
)
{
userJoinStatus
.
value
=
true
}
}
return
}
else
if
(
evt
==
'submit_score_save'
)
{
//提交分数的情况下有问题的情况下,只重试3次
if
(
resubmitcount
>=
3
)
{
return
;
}
setTimeout
(()
=>
{
resubmitcount
++
;
options
.
onRescoreSubmitted
?.(
resubmitcount
);
},
200
);
}
}
return
;
}
if
(
state
===
0
)
{
if
(
state
===
0
)
{
alert
(
msg
)
alert
(
msg
)
return
return
}
}
switch
(
evt
)
{
switch
(
evt
)
{
case
'room_create_result'
:
{
case
'room_create_result'
:
{
const
nextPath
=
getGameHomePath
(
data
)
const
nextPath
=
getGameHomePath
(
data
)
if
(
nextPath
&&
router
.
currentRoute
.
value
.
path
!==
nextPath
)
{
if
(
nextPath
&&
router
.
currentRoute
.
value
.
path
!==
nextPath
)
{
router
.
replace
(
nextPath
)
router
.
replace
(
nextPath
)
}
break
}
}
case
'room_join_result'
:
{
break
userJoinStatus
.
value
=
true
}
break
case
'room_join_result'
:
{
}
userJoinStatus
.
value
=
true
case
'game_start_result'
:
{
break
if
(
data
===
options
.
gameId
)
{
}
options
.
onGameStart
?.(
data
)
case
'game_start_result'
:
{
}
if
(
data
===
options
.
gameId
)
{
break
options
.
onGameStart
?.(
data
)
}
case
'submit_score_result'
:
{
options
.
onScoreSubmitted
?.(
data
)
break
}
case
'room_close'
:
{
userJoinStatus
.
value
=
false
options
.
onRoomClosed
?.(
data
)
break
}
}
break
}
}
case
'submit_score_result'
:
{
options
.
onScoreSubmitted
?.(
false
,
data
)
break
}
case
'submit_score_save_result'
:
{
options
.
onScoreSubmitted
?.(
true
,
data
)
break
}
case
'room_close'
:
{
userJoinStatus
.
value
=
false
options
.
onRoomClosed
?.(
data
)
break
}
}
options
.
onEvent
?.(
evt
,
payload
)
options
.
onEvent
?.(
evt
,
payload
)
})
})
})
onBeforeUnmount
(()
=>
{
//
onBeforeUnmount(() => {
offSocketMessage
?.()
//
offSocketMessage?.()
})
//
})
return
{
return
{
socket
,
socket
,
userJoinStatus
,
userJoinStatus
,
joinSharedRoom
,
offSocketMessage
sendGameMessage
,
}
}
}
}
frontend-h5/src/pages/Login.vue
deleted
100644 → 0
View file @
948fdad3
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
MobileStage
from
'@/components/MobileStage.vue'
import
{
$save
}
from
'@/commons/utils'
const
router
=
useRouter
()
const
nickname
=
ref
(
''
)
async
function
getConn
()
{
let
domain
=
localStorage
.
getItem
(
'domain'
);
const
res
=
await
fetch
(
`
${
domain
}
/manager/getConn`
,
{
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
,
},
body
:
JSON
.
stringify
({}),
})
const
data
=
await
res
.
json
()
if
(
!
res
.
ok
||
data
.
state
!==
1
)
{
alert
(
data
.
msg
||
'Login failed'
)
throw
new
Error
(
data
.
msg
||
'Login failed'
)
}
return
data
}
const
loginHandler
=
async
()
=>
{
const
{
state
,
msg
,
data
}
=
await
getConn
()
if
(
state
===
0
)
{
alert
(
msg
)
return
}
for
(
let
i
in
data
)
{
$save
(
i
,
data
[
i
]);
}
$save
({
wechat_id
:
nickname
.
value
,
nickname
:
nickname
.
value
,
telephone
:
'13111223344'
,
avatar
:
'13111223344'
,
});
await
router
.
replace
(
'/game1'
)
}
</
script
>
<
template
>
<MobileStage>
<section
class=
"h5-page login-page"
>
<div
class=
"brand"
>
<span
class=
"brand-mark"
>
H5
</span>
<div>
<h1
class=
"h5-title"
>
小游戏
</h1>
<p
class=
"h5-subtitle"
>
小屏互动端
</p>
</div>
</div>
<form
class=
"login-form h5-panel"
>
<label
class=
"field"
>
<span>
昵称
</span>
<input
v-model=
"nickname"
maxlength=
"16"
placeholder=
"请输入昵称"
autocomplete=
"nickname"
>
</label>
<input
class=
"h5-button"
@
click=
"loginHandler"
type=
"button"
value=
"进入游戏"
>
</form>
</section>
</MobileStage>
</
template
>
<
style
scoped
>
.login-page
{
display
:
flex
;
flex-direction
:
column
;
justify-content
:
space-between
;
gap
:
32px
;
}
.brand
{
display
:
flex
;
align-items
:
center
;
gap
:
14px
;
padding-top
:
18px
;
}
.brand-mark
{
display
:
grid
;
width
:
58px
;
height
:
58px
;
place-items
:
center
;
border-radius
:
8px
;
background
:
var
(
--color-primary
);
color
:
white
;
font-size
:
20px
;
font-weight
:
900
;
}
.login-form
{
display
:
grid
;
gap
:
20px
;
padding
:
18px
;
}
.field
{
display
:
grid
;
gap
:
8px
;
color
:
var
(
--color-muted
);
font-size
:
14px
;
}
.field
input
{
width
:
100%
;
height
:
48px
;
border
:
1px
solid
var
(
--color-border
);
border-radius
:
8px
;
padding
:
0
14px
;
background
:
#fff
;
color
:
var
(
--color-text
);
outline
:
none
;
}
.field
input
:focus
{
border-color
:
var
(
--color-primary
);
}
</
style
>
frontend-h5/src/pages/game1/Game1.vue
View file @
8a5923a5
...
@@ -2,10 +2,11 @@
...
@@ -2,10 +2,11 @@
import
{
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
import
{
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
import
{
cssAssetUrl
}
from
'@/commons/assets.ts'
import
{
cssAssetUrl
}
from
'@/commons/assets.ts'
import
MobileStage
from
'@/components/MobileStage.vue'
import
MobileStage
from
'@/components/MobileStage.vue'
import
{
useGameSocket
}
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
ScoreView
from
'./views/ScoreView.vue'
import
ScoreView
from
'./views/ScoreView.vue'
import
{
$getWechat
}
from
'@/commons/utils.ts'
type
GameView
=
'loading'
|
'playing'
|
'score'
type
GameView
=
'loading'
|
'playing'
|
'score'
...
@@ -25,16 +26,20 @@ const imageUrls = {
...
@@ -25,16 +26,20 @@ const imageUrls = {
replay
:
cssAssetUrl
(
'game1/replay.png'
),
replay
:
cssAssetUrl
(
'game1/replay.png'
),
}
}
const
wechat
=
$getWechat
()
const
currentView
=
ref
<
GameView
>
(
'loading'
)
const
currentView
=
ref
<
GameView
>
(
'loading'
)
const
countdownInterval
=
ref
(
60
)
const
countdownInterval
=
ref
(
60
)
const
isDivDescVisible
=
ref
(
false
)
const
isDivDescVisible
=
ref
(
false
)
const
tick
=
ref
(
0
)
const
tick
=
ref
(
0
)
//点击次数
const
rank
=
ref
(
8
)
const
rank
=
ref
(
0
)
//排名
const
level
=
ref
(
5
)
const
guFrames
:
[
string
,
string
,
string
]
=
[
imageUrls
.
gu01
,
imageUrls
.
gu02
,
imageUrls
.
gu03
]
const
guFrames
:
[
string
,
string
,
string
]
=
[
imageUrls
.
gu01
,
imageUrls
.
gu02
,
imageUrls
.
gu03
]
const
currentGu
=
ref
(
imageUrls
.
gu01
)
const
currentGu
=
ref
(
imageUrls
.
gu01
)
const
isGuPlaying
=
ref
(
false
)
const
isGuPlaying
=
ref
(
false
)
const
token
=
ref
(
wechat
?.
token
??
''
)
const
nickname
=
ref
(
wechat
?.
nickname
??
''
)
const
avatar
=
ref
(
wechat
?.
avatar
??
''
)
const
mobileStageRef
=
ref
<
InstanceType
<
typeof
MobileStage
>
|
null
>
(
null
)
const
mobileStageRef
=
ref
<
InstanceType
<
typeof
MobileStage
>
|
null
>
(
null
)
const
_offSocketMessage
=
ref
<
(()
=>
void
)
|
undefined
>
()
let
guFrameTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
let
guFrameTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
let
gameCountdownTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
let
gameCountdownTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
...
@@ -55,8 +60,22 @@ function stopGameCountdown() {
...
@@ -55,8 +60,22 @@ function stopGameCountdown() {
}
}
}
}
function
submitScore
()
{
function
submitScore
(
save
:
boolean
=
false
)
{
sendGameMessage
(
'submit_score'
,
{
score
:
tick
.
value
})
//最后提交保存在数据库中
if
(
save
)
{
//item_num 游戏项目(1到6)
//wechat 原始token
sendGameMessage
(
'submit_score_save'
,
{
score
:
tick
.
value
,
wechat
:
wechat
.
token_origin
,
item_num
:
1
,
nickname
:
wechat
.
nickname
,
avatar
:
wechat
.
avatar
,
rank
:
rank
.
value
,
})
}
else
{
sendGameMessage
(
'submit_score'
,
{
score
:
tick
.
value
})
}
}
}
function
startGameCountdown
(
seconds
=
60
)
{
function
startGameCountdown
(
seconds
=
60
)
{
...
@@ -71,7 +90,7 @@ function startGameCountdown(seconds = 60) {
...
@@ -71,7 +90,7 @@ function startGameCountdown(seconds = 60) {
countdownInterval
.
value
=
0
countdownInterval
.
value
=
0
gameCountdownTimer
=
undefined
gameCountdownTimer
=
undefined
setDivDescVisible
(
false
)
setDivDescVisible
(
false
)
submitScore
()
submitScore
(
true
)
showScoreView
()
showScoreView
()
return
return
}
}
...
@@ -93,46 +112,24 @@ function startGameView() {
...
@@ -93,46 +112,24 @@ function startGameView() {
tick
.
value
=
0
tick
.
value
=
0
currentGu
.
value
=
guFrames
[
0
]
currentGu
.
value
=
guFrames
[
0
]
currentView
.
value
=
'playing'
currentView
.
value
=
'playing'
startGameCountdown
(
60
)
startGameCountdown
()
}
async
function
startGameWithCountdown
()
{
await
mobileStageRef
.
value
?.
startCountdown
(
3
)
startGameView
()
}
}
function
showScoreView
()
{
function
showScoreView
()
{
stopGameCountdown
()
stopGameCountdown
()
setDivDescVisible
(
false
)
setDivDescVisible
(
false
)
level
.
value
=
rank
.
value
currentView
.
value
=
'score'
currentView
.
value
=
'score'
}
}
function
updateSubmittedRank
(
data
:
any
)
{
const
nextRank
=
Number
(
typeof
data
===
'object'
?
data
?.
rank
:
data
)
if
(
Number
.
isFinite
(
nextRank
)
&&
nextRank
>
0
)
{
rank
.
value
=
nextRank
level
.
value
=
nextRank
}
}
function
replayGame
()
{
function
replayGame
()
{
startGameView
()
startGameView
()
}
}
function
backToWaiting
()
{
function
backToWaiting
()
{
showLoadingView
()
showLoadingView
()
joinSharedRoom
()
joinSharedRoom
(
nickname
.
value
,
token
.
value
,
avatar
.
value
)
}
}
const
{
userJoinStatus
,
joinSharedRoom
,
sendGameMessage
}
=
useGameSocket
({
gameId
:
'game1'
,
onGameStart
:
startGameWithCountdown
,
onScoreSubmitted
:
updateSubmittedRank
,
onRoomClosed
:
showLoadingView
,
})
const
touchHandler
=
()
=>
{
const
touchHandler
=
()
=>
{
if
(
isGuPlaying
.
value
)
{
if
(
isGuPlaying
.
value
)
{
return
return
...
@@ -162,26 +159,65 @@ const touchHandler = () => {
...
@@ -162,26 +159,65 @@ const touchHandler = () => {
playNextFrame
()
playNextFrame
()
}
}
if
(
wechat
)
{
const
{
offSocketMessage
}
=
useGameSocket
({
gameId
:
'game1'
,
auth
:
{
userid
:
wechat
.
token
,
},
onConnect
:
()
=>
{
window
.
setTimeout
(()
=>
{
const
joined
=
joinSharedRoom
(
nickname
.
value
,
token
.
value
,
avatar
.
value
)
},
200
)
},
onGameStart
:
async
()
=>
{
await
mobileStageRef
.
value
?.
startCountdown
()
startGameView
()
},
onScoreSubmitted
:
(
is_save
,
data
)
=>
{
if
(
!
is_save
)
{
//非保存状态下
const
nextRank
=
Number
(
typeof
data
===
'object'
?
data
?.
rank
:
data
)
if
(
Number
.
isFinite
(
nextRank
)
&&
nextRank
>
0
)
{
rank
.
value
=
nextRank
}
}
},
onRescoreSubmitted
:
(
_recount
)
=>
{
submitScore
(
true
);
},
onRoomClosed
:
showLoadingView
,
})
_offSocketMessage
.
value
=
offSocketMessage
}
onMounted
(()
=>
{
onMounted
(()
=>
{
window
.
addEventListener
(
'beforeunload'
,
confirmRefresh
)
if
(
token
.
value
)
{
window
.
addEventListener
(
'beforeunload'
,
confirmRefresh
)
}
})
})
onBeforeUnmount
(()
=>
{
onBeforeUnmount
(()
=>
{
_offSocketMessage
.
value
?.()
if
(
guFrameTimer
)
{
if
(
guFrameTimer
)
{
window
.
clearTimeout
(
guFrameTimer
)
window
.
clearTimeout
(
guFrameTimer
)
}
}
stopGameCountdown
()
stopGameCountdown
()
window
.
removeEventListener
(
'beforeunload'
,
confirmRefresh
)
if
(
token
.
value
)
{
window
.
removeEventListener
(
'beforeunload'
,
confirmRefresh
)
}
})
})
</
script
>
</
script
>
<
template
>
<
template
>
<MobileStage
ref=
"mobileStageRef"
:background=
"`$
{imageUrls.bg} center/cover`">
<MobileStage
v-if=
"token"
ref=
"mobileStageRef"
:background=
"`$
{imageUrls.bg} center/cover`">
<LoadingView
v-if=
"currentView === 'loading'"
:image-urls=
"imageUrls"
:user-join-status=
"userJoinStatus"
/>
<LoadingView
v-if=
"currentView === 'loading'"
:image-urls=
"imageUrls"
:user-join-status=
"userJoinStatus"
/>
<PlayingView
v-else-if=
"currentView === 'playing'"
:image-urls=
"imageUrls"
:tick=
"tick"
:rank=
"rank"
<PlayingView
v-else-if=
"currentView === 'playing'"
:image-urls=
"imageUrls"
:tick=
"tick"
:rank=
"rank"
:current-gu=
"currentGu"
:countdown-interval=
"countdownInterval"
:is-div-desc-visible=
"isDivDescVisible"
:current-gu=
"currentGu"
:countdown-interval=
"countdownInterval"
:is-div-desc-visible=
"isDivDescVisible"
@
touch=
"touchHandler"
/>
@
touch=
"touchHandler"
/>
<ScoreView
v-else
:image-urls=
"imageUrls"
:
tick=
"tick"
:level=
"level
"
@
replay=
"replayGame"
<ScoreView
v-else
:image-urls=
"imageUrls"
:
rank=
"rank"
:tick=
"tick
"
@
replay=
"replayGame"
@
back=
"backToWaiting"
/>
@
back=
"backToWaiting"
/>
</MobileStage>
</MobileStage>
<div
v-else
style=
"text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;"
>
请使用微信扫码进入游戏
</div>
</
template
>
</
template
>
frontend-h5/src/pages/game1/views/LoadingView.vue
View file @
8a5923a5
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
$getWechat
}
from
'@/commons/utils.ts'
import
{
onMounted
,
ref
}
from
'vue'
;
defineProps
<
{
defineProps
<
{
imageUrls
:
Record
<
string
,
string
>
imageUrls
:
Record
<
string
,
string
>
// countdownInterval: number
// isDivDescVisible: boolean
userJoinStatus
:
boolean
userJoinStatus
:
boolean
}
>
()
}
>
();
const
nickname
=
ref
(
''
);
const
avatar
=
ref
(
''
);
onMounted
(()
=>
{
const
wechat
=
$getWechat
();
nickname
.
value
=
wechat
.
nickname
;
avatar
.
value
=
wechat
.
avatar
;
});
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"h5-page game-stage"
>
<div
class=
"h5-page game-stage"
>
<!--
<Transition
name=
"div-desc-slide"
appear
>
<div
v-if=
"isDivDescVisible"
class=
"div-desc"
>
<span
class=
"desc-clock"
aria-hidden=
"true"
></span>
<span
class=
"desc-time"
>
{{
countdownInterval
}}
</span>
</div>
</Transition>
-->
<div
class=
"game-desc"
>
游戏规则
</div>
<div
class=
"game-desc"
>
游戏规则
</div>
<div
class=
"img-logo"
></div>
<div
class=
"img-logo"
></div>
<div
class=
"img-title"
></div>
<div
class=
"img-title"
></div>
<div
class=
"img-avatar"
></div>
<div
class=
"img-avatar"
:style=
"`background: url($
{avatar});border-radius:66px;width:132px;height:132px;`"
>
</div>
<div
class=
"txt-nickname"
>
张小明
</div>
<div
class=
"txt-nickname"
>
{{
nickname
}}
</div>
<div
class=
"txt-loading"
>
<div
class=
"txt-loading"
>
<label
v-if=
"userJoinStatus"
>
您已成功加入游戏等待主持人开始
</label>
<label
v-if=
"userJoinStatus"
>
您已成功加入游戏等待主持人开始
</label>
<label
v-else
>
正在加入游戏...
</label>
<label
v-else
>
正在加入游戏...
</label>
...
@@ -37,64 +39,6 @@ defineProps<{
...
@@ -37,64 +39,6 @@ defineProps<{
padding
:
0
;
padding
:
0
;
}
}
/* .div-desc {
position: absolute;
top: 150px;
left: var(--stage-viewport-left, 0);
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
width: 180px;
height: 80px;
border-radius: 0 56px 56px 0;
background: rgba(40, 12, 8, 0.48);
color: #fff;
.desc-clock {
position: absolute;
left: 20px;
width: 38px;
height: 44px;
border-radius: 50%;
background: v-bind('imageUrls.clock') center/cover;
}
.desc-time {
position: absolute;
right: 40px;
color: white;
font-size: 35pt;
line-height: 1;
}
} */
/*
.div-desc-slide-enter-active {
transition:
transform 360ms ease-out 220ms,
opacity 360ms ease-out 220ms;
}
.div-desc-slide-leave-active {
transition:
transform 260ms ease-in,
opacity 260ms ease-in;
}
.div-desc-slide-enter-from,
.div-desc-slide-leave-to {
opacity: 0;
transform: translateX(-110%);
}
.div-desc-slide-enter-to,
.div-desc-slide-leave-from {
opacity: 1;
transform: translateX(0);
} */
.game-desc
{
.game-desc
{
position
:
absolute
;
position
:
absolute
;
top
:
50%
;
top
:
50%
;
...
@@ -150,15 +94,15 @@ defineProps<{
...
@@ -150,15 +94,15 @@ defineProps<{
position
:
absolute
;
position
:
absolute
;
top
:
480px
;
top
:
480px
;
left
:
50%
;
left
:
50%
;
width
:
148px
;
/*
width: 148px;
height: 148px;
height: 148px;
background
:
v-bind
(
'
imageUrls.avatar'
)
center
/
cover
;
background: v-bind('
avatar') center/cover; */
transform
:
translateX
(
-50%
);
transform
:
translateX
(
-50%
);
}
}
.txt-nickname
{
.txt-nickname
{
position
:
absolute
;
position
:
absolute
;
top
:
6
5
0px
;
top
:
6
4
0px
;
left
:
50%
;
left
:
50%
;
color
:
#E00000
;
color
:
#E00000
;
font-size
:
25pt
;
font-size
:
25pt
;
...
...
frontend-h5/src/pages/game1/views/ScoreView.vue
View file @
8a5923a5
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
defineProps
<
{
defineProps
<
{
imageUrls
:
Record
<
string
,
string
>
imageUrls
:
Record
<
string
,
string
>
tick
:
number
tick
:
number
level
:
number
rank
:
number
}
>
()
}
>
()
defineEmits
<
{
defineEmits
<
{
...
@@ -23,7 +23,7 @@ defineEmits<{
...
@@ -23,7 +23,7 @@ defineEmits<{
<div>
击鼓次数(次)
</div>
<div>
击鼓次数(次)
</div>
<div>
{{
tick
}}
</div>
<div>
{{
tick
}}
</div>
<div>
最终排名
</div>
<div>
最终排名
</div>
<div>
第
<label>
{{
level
}}
</label>
名
</div>
<div>
第
<label>
{{
rank
}}
</label>
名
</div>
</div>
</div>
</div>
</div>
<div
class=
"btn-group"
>
<div
class=
"btn-group"
>
...
...
frontend-h5/src/router/index.ts
View file @
8a5923a5
import
{
createRouter
,
createWebHashHistory
}
from
'vue-router'
import
{
createRouter
,
createWebHashHistory
}
from
'vue-router'
import
{
$read
}
from
'@/commons/utils'
import
{
$read
}
from
'@/commons/utils'
import
Login
from
'@/pages/Login.vue'
import
Game1
from
'@/pages/game1/Game1.vue'
import
Game1
from
'@/pages/game1/Game1.vue'
const
getWechatId
=
()
=>
{
const
getWechatId
=
()
=>
{
return
$read
(
'
wechat_id
'
,
''
)
return
$read
(
'
token
'
,
''
)
}
}
const
router
=
createRouter
({
const
router
=
createRouter
({
history
:
createWebHashHistory
(),
history
:
createWebHashHistory
(),
routes
:
[
routes
:
[
{
// {
path
:
'/'
,
// path: '/',
redirect
:
()
=>
(
getWechatId
()
?
'/game1'
:
'/login'
),
// redirect: () => (getWechatId() ? '/game1' : '/login'),
},
// },
{
path
:
'/login'
,
name
:
'Login'
,
component
:
Login
,
},
{
{
path
:
'/game1'
,
path
:
'/game1'
,
name
:
'Game1'
,
name
:
'Game1'
,
component
:
Game1
,
component
:
Game1
,
meta
:
{
//
meta: {
requiresAuth
:
true
,
//
requiresAuth: true,
},
//
},
},
},
// {
// {
// path: '/:pathMatch(.*)*',
// path: '/:pathMatch(.*)*',
...
...
frontend-h5/vite.config.ts
View file @
8a5923a5
...
@@ -6,7 +6,7 @@ import { defineConfig } from 'vite'
...
@@ -6,7 +6,7 @@ import { defineConfig } from 'vite'
import
vue
from
'@vitejs/plugin-vue'
import
vue
from
'@vitejs/plugin-vue'
import
vueDevTools
from
'vite-plugin-vue-devtools'
import
vueDevTools
from
'vite-plugin-vue-devtools'
const
buildDomain
=
'https://hddp
server
.guocai365.org.cn'
const
buildDomain
=
'https://hddp.guocai365.org.cn'
function
copyImageAssets
()
{
function
copyImageAssets
()
{
return
{
return
{
...
...
frontend-large/src/assets/images/game1.mp3
0 → 100644
View file @
8a5923a5
File added
frontend-large/src/assets/images/game1/qrcode.png
View replaced file @
948fdad3
View file @
8a5923a5
31.3 KB
|
W:
|
H:
695 Bytes
|
W:
|
H:
2-up
Swipe
Onion skin
frontend-large/src/commons/music.ts
0 → 100644
View file @
8a5923a5
import
game1MusicUrl
from
'@/assets/images/game1.mp3'
let
backgroundMusic
:
HTMLAudioElement
|
null
=
null
function
getBackgroundMusic
()
{
if
(
!
backgroundMusic
)
{
backgroundMusic
=
new
Audio
(
game1MusicUrl
)
backgroundMusic
.
loop
=
true
backgroundMusic
.
preload
=
'auto'
backgroundMusic
.
volume
=
0.6
backgroundMusic
.
load
()
}
return
backgroundMusic
}
export
async
function
playGame1Music
()
{
const
audio
=
getBackgroundMusic
()
try
{
await
audio
.
play
()
window
.
removeEventListener
(
'pointerdown'
,
playGame1Music
)
window
.
removeEventListener
(
'keydown'
,
playGame1Music
)
}
catch
{
window
.
addEventListener
(
'pointerdown'
,
playGame1Music
,
{
once
:
true
})
window
.
addEventListener
(
'keydown'
,
playGame1Music
,
{
once
:
true
})
}
}
export
function
stopGame1Music
()
{
window
.
removeEventListener
(
'pointerdown'
,
playGame1Music
)
window
.
removeEventListener
(
'keydown'
,
playGame1Music
)
backgroundMusic
?.
pause
()
}
frontend-large/src/commons/utils.ts
View file @
8a5923a5
...
@@ -74,7 +74,7 @@ export function $read_socket_storge(): any {
...
@@ -74,7 +74,7 @@ export function $read_socket_storge(): any {
let
token
=
$read
(
'token'
,
''
);
let
token
=
$read
(
'token'
,
''
);
const
socket_path
=
$read
(
'socket_path'
,
''
);
const
socket_path
=
$read
(
'socket_path'
,
''
);
const
client_ns
=
$read
(
'client_ns'
,
''
)
const
client_ns
=
$read
(
'client_ns'
,
''
)
const
domain
=
localStorage
.
getItem
(
'domain'
)
||
''
const
domain
=
$read
(
'domain'
,
''
);
return
{
return
{
passphrase
,
token
,
socket_path
,
client_ns
,
domain
passphrase
,
token
,
socket_path
,
client_ns
,
domain
}
}
...
@@ -88,8 +88,17 @@ export function $format_str(str: string, num: number) {
...
@@ -88,8 +88,17 @@ export function $format_str(str: string, num: number) {
}
}
export
function
$is_run_local
():
boolean
{
export
function
$is_run_local
():
boolean
{
if
(
/localhost|192.168/gim
.
test
(
location
.
href
))
{
if
(
/
https
?
:
\/\/
localhost|192.168/gim
.
test
(
location
.
href
))
{
return
true
;
return
true
;
}
}
return
false
;
return
false
;
}
}
export
function
$query
(
q
:
string
|
string
[]):
string
|
string
[]
{
const
query
=
location
.
href
.
match
(
/
\?([^
#
]
*
)
/
)?.[
1
]?.
replace
(
/
\/
$/
,
''
);
const
params
=
new
URLSearchParams
(
query
);
if
(
Array
.
isArray
(
q
))
{
return
q
.
map
((
key
)
=>
params
.
get
(
key
)).
filter
((
v
):
v
is
string
=>
!!
v
);
}
return
params
.
get
(
q
)
||
''
;
}
\ No newline at end of file
frontend-large/src/commons/ws.ts
View file @
8a5923a5
import
{
io
,
type
Socket
}
from
"socket.io-client"
;
import
{
io
,
type
Socket
}
from
"socket.io-client"
;
import
{
decode
,
encode
}
from
"@msgpack/msgpack"
;
import
{
decode
,
encode
}
from
"@msgpack/msgpack"
;
import
{
$is_run_local
}
from
"./utils"
;
export
type
SocketIOClientOptions
=
{
export
type
SocketIOClientOptions
=
{
url
?:
string
;
url
?:
string
;
...
@@ -36,7 +37,6 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -36,7 +37,6 @@ export function initSocket(args: SocketInitArgs): Socket | null {
args
.
onConnect
?.(
socket
);
args
.
onConnect
?.(
socket
);
return
socket
;
return
socket
;
}
}
socket
.
removeAllListeners
();
socket
.
removeAllListeners
();
socket
.
disconnect
();
socket
.
disconnect
();
socket
=
null
;
socket
=
null
;
...
@@ -56,7 +56,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
...
@@ -56,7 +56,9 @@ export function initSocket(args: SocketInitArgs): Socket | null {
});
});
socket
.
onAny
((
evt
,
payload
)
=>
{
socket
.
onAny
((
evt
,
payload
)
=>
{
const
data
=
decode
(
payload
);
const
data
=
decode
(
payload
);
console
.
log
(
evt
,
data
);
if
(
$is_run_local
())
{
console
.
log
(
evt
,
data
);
}
messageHandlers
.
forEach
((
handler
)
=>
handler
(
evt
,
data
));
messageHandlers
.
forEach
((
handler
)
=>
handler
(
evt
,
data
));
});
});
...
...
frontend-large/src/composables/useAdminGameSocket.ts
View file @
8a5923a5
...
@@ -45,7 +45,7 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
...
@@ -45,7 +45,7 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
const
{
passphrase
,
token
,
socket_path
,
client_ns
,
domain
}
=
$read_socket_storge
()
const
{
passphrase
,
token
,
socket_path
,
client_ns
,
domain
}
=
$read_socket_storge
()
initSocket
({
initSocket
({
socket_path
:
`
${
domain
}${
socket_path
}
`
,
socket_path
:
`
${
domain
}${
socket_path
}
`
,
client_ns
,
client_ns
:
"/ws"
,
auth
:
{
auth
:
{
userid
:
token
,
userid
:
token
,
validuser
:
CryptoJS
.
AES
.
encrypt
(
token
,
passphrase
).
toString
(),
validuser
:
CryptoJS
.
AES
.
encrypt
(
token
,
passphrase
).
toString
(),
...
@@ -58,9 +58,9 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
...
@@ -58,9 +58,9 @@ export function useAdminGameSocket(options: AdminGameSocketOptions) {
offSocketMessage
=
onSocketMessage
(
async
(
evt
,
payload
:
SocketPayload
)
=>
{
offSocketMessage
=
onSocketMessage
(
async
(
evt
,
payload
:
SocketPayload
)
=>
{
const
{
msg
,
state
,
data
}
=
payload
const
{
msg
,
state
,
data
}
=
payload
if
(
$goto_login
(
evt
,
router
,
options
.
loginRedirectPath
??
'/game1'
))
{
//
if ($goto_login(evt, router, options.loginRedirectPath ?? '/game1')) {
return
//
return
}
//
}
if
(
state
===
0
)
{
if
(
state
===
0
)
{
if
(
evt
===
'game_start_result'
&&
data
===
1
)
{
if
(
evt
===
'game_start_result'
&&
data
===
1
)
{
...
...
frontend-large/src/pages/Login.vue
View file @
8a5923a5
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
$save
}
from
'@/commons/utils.ts'
;
import
{
$save
}
from
'@/commons/utils.ts'
;
import
{
ref
}
from
'vue'
import
{
onMounted
,
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
useRouter
}
from
'vue-router'
import
DesignStage
from
'@/components/DesignStage.vue'
import
DesignStage
from
'@/components/DesignStage.vue'
import
{
playGame1Music
}
from
'@/commons/music'
const
router
=
useRouter
()
const
router
=
useRouter
()
const
username
=
ref
(
''
)
const
username
=
ref
(
''
)
const
password
=
ref
(
''
)
const
password
=
ref
(
''
)
...
@@ -16,8 +17,8 @@ async function login() {
...
@@ -16,8 +17,8 @@ async function login() {
},
},
body
:
JSON
.
stringify
({
username
:
username
.
value
,
password
:
password
.
value
}),
body
:
JSON
.
stringify
({
username
:
username
.
value
,
password
:
password
.
value
}),
})
})
const
data
=
await
res
.
json
()
const
data
=
await
res
.
json
()
if
(
!
res
.
ok
||
data
.
state
!==
1
)
{
if
(
!
res
.
ok
||
data
.
state
!==
1
)
{
alert
(
data
.
msg
||
'Login failed'
)
alert
(
data
.
msg
||
'Login failed'
)
throw
new
Error
(
data
.
msg
||
'Login failed'
)
throw
new
Error
(
data
.
msg
||
'Login failed'
)
...
@@ -27,6 +28,7 @@ async function login() {
...
@@ -27,6 +28,7 @@ async function login() {
}
}
const
loginHandler
=
async
()
=>
{
const
loginHandler
=
async
()
=>
{
playGame1Music
()
const
{
state
,
msg
,
data
}
=
await
login
()
const
{
state
,
msg
,
data
}
=
await
login
()
if
(
state
===
0
)
{
if
(
state
===
0
)
{
...
@@ -38,6 +40,10 @@ const loginHandler = async () => {
...
@@ -38,6 +40,10 @@ const loginHandler = async () => {
}
}
await
router
.
replace
(
'/game1'
)
await
router
.
replace
(
'/game1'
)
}
}
onMounted
(()
=>
{
playGame1Music
()
})
</
script
>
</
script
>
<
template
>
<
template
>
...
...
frontend-large/src/pages/game1/Game1.vue
View file @
8a5923a5
...
@@ -5,6 +5,7 @@ import Rank1 from './views/Rank1View.vue'
...
@@ -5,6 +5,7 @@ import Rank1 from './views/Rank1View.vue'
import
Rank2
from
'./views/Rank2View.vue'
import
Rank2
from
'./views/Rank2View.vue'
import
{
useAdminGameSocket
}
from
'@/composables/useAdminGameSocket'
import
{
useAdminGameSocket
}
from
'@/composables/useAdminGameSocket'
import
type
Player
from
'@/commons/player.ts'
import
type
Player
from
'@/commons/player.ts'
import
{
playGame1Music
}
from
'@/commons/music'
type
GameScreen
=
'loading'
|
'rank1'
|
'rank2'
type
GameScreen
=
'loading'
|
'rank1'
|
'rank2'
...
@@ -108,6 +109,11 @@ const { startGame, createRoom, requestRoomState } = useAdminGameSocket({
...
@@ -108,6 +109,11 @@ const { startGame, createRoom, requestRoomState } = useAdminGameSocket({
onRoomRank
:
updateRankPlayers
,
onRoomRank
:
updateRankPlayers
,
})
})
const
startGameWithMusic
=
()
=>
{
playGame1Music
()
startGame
()
}
const
nextRound
=
()
=>
{
const
nextRound
=
()
=>
{
gotoLoading
()
gotoLoading
()
createRoom
(
1
)
createRoom
(
1
)
...
@@ -116,8 +122,14 @@ const nextRound = () => {
...
@@ -116,8 +122,14 @@ const nextRound = () => {
},
200
)
},
200
)
}
}
const
nextRoundWithMusic
=
()
=>
{
playGame1Music
()
nextRound
()
}
onMounted
(()
=>
{
onMounted
(()
=>
{
window
.
addEventListener
(
'beforeunload'
,
confirmRefresh
)
window
.
addEventListener
(
'beforeunload'
,
confirmRefresh
)
playGame1Music
()
})
})
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
...
@@ -126,7 +138,7 @@ onUnmounted(() => {
...
@@ -126,7 +138,7 @@ onUnmounted(() => {
</
script
>
</
script
>
<
template
>
<
template
>
<Loading
v-if=
"screen === 'loading'"
:players=
"loadingPlayers"
:player-count=
"playerCount"
@
start=
"startGame"
/>
<Loading
v-if=
"screen === 'loading'"
:players=
"loadingPlayers"
:player-count=
"playerCount"
@
start=
"startGame
WithMusic
"
/>
<Rank1
v-else-if=
"screen === 'rank1'"
ref=
"rank1Ref"
:rank-list=
"rankPlayers"
@
start=
"startGame"
/>
<Rank1
v-else-if=
"screen === 'rank1'"
ref=
"rank1Ref"
:rank-list=
"rankPlayers"
@
start=
"startGame
WithMusic
"
/>
<Rank2
v-else
:rank-list=
"rankPlayers"
@
next=
"nextRound"
/>
<Rank2
v-else
:rank-list=
"rankPlayers"
@
next=
"nextRound
WithMusic
"
/>
</
template
>
</
template
>
frontend-large/src/pages/game1/views/LoadingView.vue
View file @
8a5923a5
...
@@ -57,7 +57,7 @@ const releaseStartButton = () => {
...
@@ -57,7 +57,7 @@ const releaseStartButton = () => {
<div
v-for=
"(item, index) in players"
:key=
"item.userid || item.wechat_id || `empty-$
{index}`">
<div
v-for=
"(item, index) in players"
:key=
"item.userid || item.wechat_id || `empty-$
{index}`">
<div>
<div>
<div
class=
"img-avatar-container"
>
<div
class=
"img-avatar-container"
>
<div
class=
"img-avatar"
></div>
<div
class=
"img-avatar"
:style=
"`background:url('$
{item.avatar}') center / cover no-repeat;`"
>
</div>
</div>
</div>
<div
class=
"txt-nickname"
>
{{
$format_str
(
item
.
nickname
,
12
)
}}
</div>
<div
class=
"txt-nickname"
>
{{
$format_str
(
item
.
nickname
,
12
)
}}
</div>
</div>
</div>
...
@@ -205,7 +205,7 @@ const releaseStartButton = () => {
...
@@ -205,7 +205,7 @@ const releaseStartButton = () => {
width
:
60px
;
width
:
60px
;
height
:
60px
;
height
:
60px
;
border-radius
:
30px
;
border-radius
:
30px
;
background
:
v-bind
(
'imageUrls.avatar'
);
/* background: v-bind('imageUrls.avatar'); */
}
}
.txt-nickname
{
.txt-nickname
{
...
...
frontend-large/src/pages/game1/views/Rank2View.vue
View file @
8a5923a5
...
@@ -54,6 +54,13 @@ watch(
...
@@ -54,6 +54,13 @@ watch(
(
rankList
)
=>
updateFinalRank
(
rankList
),
(
rankList
)
=>
updateFinalRank
(
rankList
),
{
immediate
:
true
,
deep
:
true
},
{
immediate
:
true
,
deep
:
true
},
);
);
const
renderAvatar
=
(
item
:
any
|
null
)
=>
{
if
(
item
&&
item
.
avatar
)
{
return
`background: url('
${
item
.
avatar
}
') center / cover no-repeat;`
}
return
`background: v-bind('imageUrls.avatar') center / cover no-repeat;`
}
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -66,21 +73,24 @@ watch(
...
@@ -66,21 +73,24 @@ watch(
<div
class=
"podium"
>
<div
class=
"podium"
>
<div
class=
"rank-no1"
>
<div
class=
"rank-no1"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"`$
{renderAvatar(list[0])}`">
</div>
<div
class=
"nickname"
>
{{
list
[
0
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
0
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
</div>
</div>
<div
class=
"rank-no2"
>
<div
class=
"rank-no2"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"`$
{renderAvatar(list[1])}`">
</div>
<div
class=
"nickname"
>
{{
list
[
1
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
1
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
</div>
</div>
<div
class=
"rank-no3"
>
<div
class=
"rank-no3"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"`$
{renderAvatar(list[2])}`">
</div>
<div
class=
"nickname"
>
{{
list
[
2
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
2
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
...
@@ -172,7 +182,7 @@ watch(
...
@@ -172,7 +182,7 @@ watch(
}
}
.avatar
{
.avatar
{
background
:
v-bind
(
'imageUrls.avatar'
)
center
/
cover
no-repeat
;
/* background: v-bind('imageUrls.avatar') center / cover no-repeat; */
width
:
120px
;
width
:
120px
;
height
:
120px
;
height
:
120px
;
border-radius
:
60px
;
border-radius
:
60px
;
...
...
frontend-large/vite.config.ts
View file @
8a5923a5
...
@@ -6,7 +6,7 @@ import { defineConfig } from 'vite'
...
@@ -6,7 +6,7 @@ import { defineConfig } from 'vite'
import
vue
from
'@vitejs/plugin-vue'
import
vue
from
'@vitejs/plugin-vue'
import
vueDevTools
from
'vite-plugin-vue-devtools'
import
vueDevTools
from
'vite-plugin-vue-devtools'
const
buildDomain
=
'https://hddp
server
.guocai365.org.cn'
const
buildDomain
=
'https://hddp.guocai365.org.cn'
function
copyImageAssets
()
{
function
copyImageAssets
()
{
return
{
return
{
...
@@ -28,7 +28,8 @@ function copyImageAssets() {
...
@@ -28,7 +28,8 @@ function copyImageAssets() {
export
default
defineConfig
(({
command
})
=>
({
export
default
defineConfig
(({
command
})
=>
({
base
:
command
===
'build'
?
'/cc/pc/'
:
'/'
,
base
:
command
===
'build'
?
'/cc/pc/'
:
'/'
,
define
:
{
define
:
{
__APP_DOMAIN__
:
JSON
.
stringify
(
command
===
'build'
?
buildDomain
:
''
),
// __APP_DOMAIN__: JSON.stringify(command === 'build' ? buildDomain : ''),
__APP_DOMAIN__
:
JSON
.
stringify
(
buildDomain
),
},
},
plugins
:
[
plugins
:
[
vue
(),
vue
(),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment