mirror of
https://ghproxy.com/https://github.com/StreakingMan/solvable-sheep-game
synced 2025-05-23 22:06:08 +08:00
feat: 排行榜
This commit is contained in:
parent
01d85610eb
commit
9682d31c49
|
@ -5,6 +5,7 @@ import {
|
||||||
LAST_LEVEL_STORAGE_KEY,
|
LAST_LEVEL_STORAGE_KEY,
|
||||||
LAST_SCORE_STORAGE_KEY,
|
LAST_SCORE_STORAGE_KEY,
|
||||||
LAST_TIME_STORAGE_KEY,
|
LAST_TIME_STORAGE_KEY,
|
||||||
|
PLAYING_THEME_ID_STORAGE_KEY,
|
||||||
wrapThemeDefaultSounds,
|
wrapThemeDefaultSounds,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { Theme } from './themes/interface';
|
import { Theme } from './themes/interface';
|
||||||
|
@ -30,6 +31,7 @@ const App: FC<{ theme: Theme<any> }> = ({ theme: initTheme }) => {
|
||||||
const [diyDialogShow, setDiyDialogShow] = useState<boolean>(false);
|
const [diyDialogShow, setDiyDialogShow] = useState<boolean>(false);
|
||||||
|
|
||||||
const changeTheme = (theme: Theme<any>) => {
|
const changeTheme = (theme: Theme<any>) => {
|
||||||
|
sessionStorage.setItem(PLAYING_THEME_ID_STORAGE_KEY, theme.title);
|
||||||
wrapThemeDefaultSounds(theme);
|
wrapThemeDefaultSounds(theme);
|
||||||
domRelatedOptForTheme(theme);
|
domRelatedOptForTheme(theme);
|
||||||
setTheme({ ...theme });
|
setTheme({ ...theme });
|
||||||
|
|
133
src/components/Fireworks.module.scss
Normal file
133
src/components/Fireworks.module.scss
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
@use 'sass:math';
|
||||||
|
|
||||||
|
// 烟花动画,来源 https://codepen.io/yshlin/pen/WNMmQX
|
||||||
|
|
||||||
|
$particles: 50;
|
||||||
|
$width: 500;
|
||||||
|
$height: 500;
|
||||||
|
|
||||||
|
// Create the explosion...
|
||||||
|
$box-shadow: ();
|
||||||
|
$box-shadow2: ();
|
||||||
|
@for $i from 0 through $particles {
|
||||||
|
$box-shadow: $box-shadow,
|
||||||
|
math.random($width) -
|
||||||
|
math.div($width, 2) +
|
||||||
|
px
|
||||||
|
math.random($height) -
|
||||||
|
math.div($height, 1.2) +
|
||||||
|
px
|
||||||
|
hsl(math.random(360) 100% 50%);
|
||||||
|
$box-shadow2: $box-shadow2, 0 0 #fff;
|
||||||
|
}
|
||||||
|
@mixin keyframes($animationName) {
|
||||||
|
@keyframes #{$animationName} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes #{$animationName} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes #{$animationName} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes #{$animationName} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes #{$animationName} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin animation-delay($settings) {
|
||||||
|
animation-delay: $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin animation-duration($settings) {
|
||||||
|
animation-duration: $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin animation($settings) {
|
||||||
|
animation: $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin transform($settings) {
|
||||||
|
transform: $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pyro {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pyro > .before,
|
||||||
|
.pyro > .after {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: $box-shadow2;
|
||||||
|
@include animation(
|
||||||
|
(
|
||||||
|
1s bang ease-out infinite backwards,
|
||||||
|
1s gravity ease-in infinite backwards,
|
||||||
|
5s position linear infinite backwards
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pyro > .after {
|
||||||
|
@include animation-delay((1.25s, 1.25s, 1.25s));
|
||||||
|
@include animation-duration((1.25s, 1.25s, 6.25s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@include keyframes(bang) {
|
||||||
|
to {
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include keyframes(gravity) {
|
||||||
|
to {
|
||||||
|
@include transform(translateY(200px));
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include keyframes(position) {
|
||||||
|
0%,
|
||||||
|
19.9% {
|
||||||
|
margin-top: 10%;
|
||||||
|
margin-left: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
20%,
|
||||||
|
39.9% {
|
||||||
|
margin-top: 40%;
|
||||||
|
margin-left: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
40%,
|
||||||
|
59.9% {
|
||||||
|
margin-top: 20%;
|
||||||
|
margin-left: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
60%,
|
||||||
|
79.9% {
|
||||||
|
margin-top: 30%;
|
||||||
|
margin-left: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
80%,
|
||||||
|
99.9% {
|
||||||
|
margin-top: 30%;
|
||||||
|
margin-left: 80%;
|
||||||
|
}
|
||||||
|
}
|
12
src/components/Fireworks.tsx
Normal file
12
src/components/Fireworks.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import style from './Fireworks.module.scss';
|
||||||
|
const Fireworks: FC = () => {
|
||||||
|
return (
|
||||||
|
<div className={style.pyro}>
|
||||||
|
<div className={style.before} />
|
||||||
|
<div className={style.after} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Fireworks;
|
|
@ -74,21 +74,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
|
||||||
position: fixed;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background-color: rgb(0 0 0 / 10%);
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bgm-button {
|
.bgm-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
|
Suspense,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import './Game.scss';
|
import './Game.scss';
|
||||||
import {
|
import {
|
||||||
|
@ -11,9 +12,11 @@ import {
|
||||||
LAST_SCORE_STORAGE_KEY,
|
LAST_SCORE_STORAGE_KEY,
|
||||||
LAST_TIME_STORAGE_KEY,
|
LAST_TIME_STORAGE_KEY,
|
||||||
randomString,
|
randomString,
|
||||||
|
timestampToUsedTimeString,
|
||||||
waitTimeout,
|
waitTimeout,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { Icon, Theme } from '../themes/interface';
|
import { Icon, Theme } from '../themes/interface';
|
||||||
|
import Score from './Score';
|
||||||
|
|
||||||
interface MySymbol {
|
interface MySymbol {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -488,17 +491,20 @@ const Game: FC<{
|
||||||
<br />
|
<br />
|
||||||
得分{score}
|
得分{score}
|
||||||
<br />
|
<br />
|
||||||
用时{(usedTime / 1000).toFixed(3)}秒
|
用时{timestampToUsedTimeString(usedTime)}
|
||||||
</div>
|
</div>
|
||||||
{/*提示弹窗*/}
|
{/*积分、排行榜*/}
|
||||||
{finished && (
|
<Suspense fallback={<span>rank list</span>}>
|
||||||
<div className="modal">
|
{finished && (
|
||||||
<h1>{tipText}</h1>
|
<Score
|
||||||
<h1>得分{score}</h1>
|
level={level}
|
||||||
<h1>用时{(usedTime / 1000).toFixed(3)}秒</h1>
|
time={usedTime}
|
||||||
<button onClick={restart}>再来一次</button>
|
score={score}
|
||||||
</div>
|
success={level === maxLevel}
|
||||||
)}
|
restartMethod={restart}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Suspense>
|
||||||
{/*bgm*/}
|
{/*bgm*/}
|
||||||
{theme.bgm && (
|
{theme.bgm && (
|
||||||
<button className="bgm-button" onClick={() => setBgmOn(!bgmOn)}>
|
<button className="bgm-button" onClick={() => setBgmOn(!bgmOn)}>
|
||||||
|
|
85
src/components/Score.module.scss
Normal file
85
src/components/Score.module.scss
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
background-color: rgb(0 0 0 / 20%);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10 !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
max-width: 450px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 auto;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameInput {
|
||||||
|
background-color: rgb(0 0 0 / 20%);
|
||||||
|
color: currentcolor;
|
||||||
|
border: 1px currentcolor dashed;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: currentcolor;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rankContainer {
|
||||||
|
border: 1px solid currentcolor;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 200px;
|
||||||
|
background-color: rgb(0 0 0 / 20%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 900;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 30vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: crimson;
|
||||||
|
}
|
||||||
|
}
|
266
src/components/Score.tsx
Normal file
266
src/components/Score.tsx
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
import React, { FC, Suspense, useEffect, useRef, useState } from 'react';
|
||||||
|
import style from './Score.module.scss';
|
||||||
|
import Bmob from 'hydrogen-js-sdk';
|
||||||
|
import {
|
||||||
|
PLAYING_THEME_ID_STORAGE_KEY,
|
||||||
|
randomString,
|
||||||
|
timestampToUsedTimeString,
|
||||||
|
USER_ID_STORAGE_KEY,
|
||||||
|
USER_NAME_STORAGE_KEY,
|
||||||
|
} from '../utils';
|
||||||
|
import WxQrCode from './WxQrCode';
|
||||||
|
|
||||||
|
const Fireworks = React.lazy(() => import('./Fireworks'));
|
||||||
|
|
||||||
|
interface RankInfo {
|
||||||
|
// id
|
||||||
|
objectId?: string;
|
||||||
|
// 综合评分
|
||||||
|
rating: number;
|
||||||
|
// 通关数
|
||||||
|
level: number;
|
||||||
|
// 游戏得分
|
||||||
|
score: number;
|
||||||
|
// 主题id
|
||||||
|
themeId: string;
|
||||||
|
// 耗时
|
||||||
|
time: number;
|
||||||
|
// 用户昵称
|
||||||
|
username: string;
|
||||||
|
// 用户id
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 该组件条件渲染
|
||||||
|
const Score: FC<{
|
||||||
|
level: number;
|
||||||
|
score: number;
|
||||||
|
time: number;
|
||||||
|
success: boolean;
|
||||||
|
restartMethod: () => void;
|
||||||
|
}> = ({ level, score, time, success, restartMethod }) => {
|
||||||
|
const [rankList, setRankList] = useState<RankInfo[]>([]);
|
||||||
|
const [username, setUsername] = useState<string>(
|
||||||
|
localStorage.getItem(USER_NAME_STORAGE_KEY) || ''
|
||||||
|
);
|
||||||
|
const [userId, setUserId] = useState<string>(
|
||||||
|
localStorage.getItem(USER_ID_STORAGE_KEY) || ''
|
||||||
|
);
|
||||||
|
const usernameInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [tip, setTip] = useState<string>('');
|
||||||
|
|
||||||
|
// 综合评分
|
||||||
|
const rating = Math.max(0, score) * 100 - Math.round(time / 1000);
|
||||||
|
// 分主题排行
|
||||||
|
const themeId = sessionStorage.getItem(PLAYING_THEME_ID_STORAGE_KEY);
|
||||||
|
|
||||||
|
const uploadRankInfo = (id?: string) => {
|
||||||
|
const _userId = localStorage.getItem(USER_ID_STORAGE_KEY);
|
||||||
|
const _username = localStorage.getItem(USER_NAME_STORAGE_KEY);
|
||||||
|
if (!themeId || !_userId || !_username) return;
|
||||||
|
const rankInfo: RankInfo = {
|
||||||
|
rating,
|
||||||
|
themeId,
|
||||||
|
level,
|
||||||
|
score,
|
||||||
|
time,
|
||||||
|
username: _username,
|
||||||
|
userId: _userId,
|
||||||
|
};
|
||||||
|
const query = Bmob.Query('rank');
|
||||||
|
id && query.set('id', id);
|
||||||
|
for (const [key, val] of Object.entries(rankInfo)) {
|
||||||
|
query.set(key, val);
|
||||||
|
}
|
||||||
|
query
|
||||||
|
.save()
|
||||||
|
.then(() => {
|
||||||
|
getRankList();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRankList = (cb?: (rankList: RankInfo[]) => void) => {
|
||||||
|
if (!themeId) return;
|
||||||
|
const query = Bmob.Query('rank');
|
||||||
|
query.equalTo('themeId', '==', themeId);
|
||||||
|
query.order('-rating');
|
||||||
|
query.limit(50);
|
||||||
|
query
|
||||||
|
.find()
|
||||||
|
.then((res) => {
|
||||||
|
setRankList(res as any);
|
||||||
|
cb && cb(res as any);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConfirmNameClick = () => {
|
||||||
|
const inputUsername = usernameInputRef.current?.value;
|
||||||
|
if (!inputUsername) return;
|
||||||
|
const newUserId = randomString(8);
|
||||||
|
setUsername(inputUsername);
|
||||||
|
setUserId(newUserId);
|
||||||
|
localStorage.setItem(USER_NAME_STORAGE_KEY, inputUsername);
|
||||||
|
localStorage.setItem(USER_ID_STORAGE_KEY, newUserId);
|
||||||
|
judgeAndUpload(rankList, newUserId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否需要上传记录
|
||||||
|
const judgeAndUpload = (_rankList: RankInfo[], _userId: string) => {
|
||||||
|
if (!_userId) return;
|
||||||
|
if (
|
||||||
|
_rankList.length < 50 ||
|
||||||
|
rating > _rankList[_rankList.length - 1].rating
|
||||||
|
) {
|
||||||
|
// 榜未满或者分数高于榜上最后一名
|
||||||
|
// 本次排名
|
||||||
|
let thisRank = _rankList.findIndex((rank) => rank.rating < rating);
|
||||||
|
if (thisRank === -1) {
|
||||||
|
thisRank = _rankList.length + 1;
|
||||||
|
} else {
|
||||||
|
thisRank++;
|
||||||
|
}
|
||||||
|
// 查找是否曾上榜
|
||||||
|
const findSelf = _rankList.findIndex(
|
||||||
|
(rank) => rank.userId === _userId
|
||||||
|
);
|
||||||
|
if (findSelf === -1) {
|
||||||
|
// 新上榜
|
||||||
|
uploadRankInfo();
|
||||||
|
setTip(`恭喜上榜!本次排名${thisRank}`);
|
||||||
|
} else {
|
||||||
|
if (_rankList[findSelf].rating < rating) {
|
||||||
|
// 破自己记录
|
||||||
|
uploadRankInfo(_rankList[findSelf].objectId);
|
||||||
|
setTip(`个人新高!本次排名${thisRank}`);
|
||||||
|
} else if (_rankList[findSelf].rating > rating) {
|
||||||
|
// 没破自己记录
|
||||||
|
setTip(
|
||||||
|
`距离你的最高记录${_rankList[findSelf].rating}还差一点~`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setTip(`与你的最高记录${_rankList[findSelf].rating}持平~`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未上榜
|
||||||
|
setTip('本次未上榜');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!__DIY__) {
|
||||||
|
// 排行榜
|
||||||
|
getRankList((rankList) =>
|
||||||
|
judgeAndUpload(
|
||||||
|
rankList,
|
||||||
|
localStorage.getItem(USER_ID_STORAGE_KEY) || ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.modal}>
|
||||||
|
<Suspense fallback={<span>fireworks</span>}>
|
||||||
|
{success && <Fireworks />}
|
||||||
|
</Suspense>
|
||||||
|
<div className={style.inner}>
|
||||||
|
{success ? <h1>🎉恭喜通关!</h1> : <h1>😫就差一点!</h1>}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>通关数</th>
|
||||||
|
<th>用时</th>
|
||||||
|
<th>得分</th>
|
||||||
|
<th>综合评分</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{level}</td>
|
||||||
|
<td>{timestampToUsedTimeString(time)}</td>
|
||||||
|
<td>{score}</td>
|
||||||
|
<td>{rating}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{!__DIY__ && !username && (
|
||||||
|
<div className={'flex-container flex-center'}>
|
||||||
|
<input
|
||||||
|
className={style.nameInput}
|
||||||
|
ref={usernameInputRef}
|
||||||
|
placeholder={'留下大名进行排行榜pk!'}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={'primary'}
|
||||||
|
onClick={onConfirmNameClick}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>{tip}</div>
|
||||||
|
|
||||||
|
{!__DIY__ && (
|
||||||
|
<div className={style.rankContainer}>
|
||||||
|
<h1 className={style.title}>TOP 50</h1>
|
||||||
|
{rankList.length ? (
|
||||||
|
<div className={style.list}>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>名次</th>
|
||||||
|
<th>名称</th>
|
||||||
|
{/*<th>通关数</th>*/}
|
||||||
|
{/*<th>用时</th>*/}
|
||||||
|
{/*<th>得分</th>*/}
|
||||||
|
<th>综合评分</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rankList.map((rank, idx) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td>{idx + 1}</td>
|
||||||
|
<td>
|
||||||
|
{rank.username}
|
||||||
|
{rank.userId === userId &&
|
||||||
|
'(你)'}
|
||||||
|
</td>
|
||||||
|
{/*<td>{rank.level}</td>*/}
|
||||||
|
{/*<td>*/}
|
||||||
|
{/* {timestampToUsedTimeString(*/}
|
||||||
|
{/* rank.time*/}
|
||||||
|
{/* )}*/}
|
||||||
|
{/*</td>*/}
|
||||||
|
{/*<td>{rank.score}</td>*/}
|
||||||
|
<td>{rank.rating}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={style.tip}>
|
||||||
|
暂无排行,速速霸榜!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button className={'primary'} onClick={restartMethod}>
|
||||||
|
再来一次
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<WxQrCode />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Score;
|
12
src/main.tsx
12
src/main.tsx
|
@ -8,6 +8,7 @@ import {
|
||||||
DEFAULT_BGM_STORAGE_KEY,
|
DEFAULT_BGM_STORAGE_KEY,
|
||||||
domRelatedOptForTheme,
|
domRelatedOptForTheme,
|
||||||
parsePathCustomThemeId,
|
parsePathCustomThemeId,
|
||||||
|
PLAYING_THEME_ID_STORAGE_KEY,
|
||||||
wrapThemeDefaultSounds,
|
wrapThemeDefaultSounds,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { getDefaultTheme } from './themes/default';
|
import { getDefaultTheme } from './themes/default';
|
||||||
|
@ -33,6 +34,11 @@ const errorTip = (tip: string) => {
|
||||||
|
|
||||||
// 加载成功后数据转换(runtime)以及转场
|
// 加载成功后数据转换(runtime)以及转场
|
||||||
const successTrans = (theme: Theme<any>) => {
|
const successTrans = (theme: Theme<any>) => {
|
||||||
|
sessionStorage.setItem(
|
||||||
|
PLAYING_THEME_ID_STORAGE_KEY,
|
||||||
|
customThemeIdFromPath || theme.title
|
||||||
|
);
|
||||||
|
|
||||||
wrapThemeDefaultSounds(theme);
|
wrapThemeDefaultSounds(theme);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -72,7 +78,7 @@ const loadTheme = () => {
|
||||||
Bmob.Query('config')
|
Bmob.Query('config')
|
||||||
.get(customThemeIdFromPath)
|
.get(customThemeIdFromPath)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const { content } = res as any;
|
const { content, increment } = res as any;
|
||||||
localStorage.setItem(customThemeIdFromPath, content);
|
localStorage.setItem(customThemeIdFromPath, content);
|
||||||
try {
|
try {
|
||||||
const customTheme = JSON.parse(content);
|
const customTheme = JSON.parse(content);
|
||||||
|
@ -80,6 +86,10 @@ const loadTheme = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorTip('主题配置解析失败');
|
errorTip('主题配置解析失败');
|
||||||
}
|
}
|
||||||
|
// 统计访问次数
|
||||||
|
increment('visitNum');
|
||||||
|
// @ts-ignore
|
||||||
|
res.save();
|
||||||
})
|
})
|
||||||
.catch(({ error }) => {
|
.catch(({ error }) => {
|
||||||
errorTip(error);
|
errorTip(error);
|
||||||
|
|
|
@ -25,6 +25,7 @@ export const getDefaultTheme: () => Theme<DefaultSoundNames> = () => {
|
||||||
title: '有解的羊了个羊',
|
title: '有解的羊了个羊',
|
||||||
desc: '真的可以通关~',
|
desc: '真的可以通关~',
|
||||||
dark: true,
|
dark: true,
|
||||||
|
maxLevel: 5,
|
||||||
backgroundColor: '#8dac85',
|
backgroundColor: '#8dac85',
|
||||||
icons: icons.map((icon) => ({
|
icons: icons.map((icon) => ({
|
||||||
name: icon,
|
name: icon,
|
||||||
|
|
27
src/utils.ts
27
src/utils.ts
|
@ -1,6 +1,7 @@
|
||||||
import { Theme } from './themes/interface';
|
import { Theme } from './themes/interface';
|
||||||
import { getDefaultTheme } from './themes/default';
|
import { getDefaultTheme } from './themes/default';
|
||||||
|
|
||||||
|
// local
|
||||||
export const LAST_LEVEL_STORAGE_KEY = 'lastLevel';
|
export const LAST_LEVEL_STORAGE_KEY = 'lastLevel';
|
||||||
export const LAST_SCORE_STORAGE_KEY = 'lastScore';
|
export const LAST_SCORE_STORAGE_KEY = 'lastScore';
|
||||||
export const LAST_TIME_STORAGE_KEY = 'lastTime';
|
export const LAST_TIME_STORAGE_KEY = 'lastTime';
|
||||||
|
@ -11,6 +12,10 @@ export const CUSTOM_THEME_FILE_VALIDATE_STORAGE_KEY = 'customThemeFileValidate';
|
||||||
export const DEFAULT_BGM_STORAGE_KEY = 'defaultBgm';
|
export const DEFAULT_BGM_STORAGE_KEY = 'defaultBgm';
|
||||||
export const DEFAULT_TRIPLE_SOUND_STORAGE_KEY = 'defaultTripleSound';
|
export const DEFAULT_TRIPLE_SOUND_STORAGE_KEY = 'defaultTripleSound';
|
||||||
export const DEFAULT_CLICK_SOUND_STORAGE_KEY = 'defaultClickSound';
|
export const DEFAULT_CLICK_SOUND_STORAGE_KEY = 'defaultClickSound';
|
||||||
|
export const USER_NAME_STORAGE_KEY = 'username';
|
||||||
|
export const USER_ID_STORAGE_KEY = 'userId';
|
||||||
|
// session
|
||||||
|
export const PLAYING_THEME_ID_STORAGE_KEY = 'playingThemeId';
|
||||||
|
|
||||||
export const linkReg = /^(https|data):+/;
|
export const linkReg = /^(https|data):+/;
|
||||||
|
|
||||||
|
@ -142,3 +147,25 @@ export const getFileBase64String: (file: File) => Promise<string> = (
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const timestampToUsedTimeString: (time: number) => string = (time) => {
|
||||||
|
try {
|
||||||
|
const hours = Math.floor(time / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor(
|
||||||
|
(time - 1000 * 60 * 60 * hours) / (1000 * 60)
|
||||||
|
);
|
||||||
|
const seconds = (
|
||||||
|
(time - 1000 * 60 * 60 * hours - 1000 * 60 * minutes) /
|
||||||
|
1000
|
||||||
|
).toFixed(3);
|
||||||
|
if (hours) {
|
||||||
|
return `${hours}小时${minutes}分${seconds}秒`;
|
||||||
|
} else if (minutes) {
|
||||||
|
return `${minutes}分${seconds}秒`;
|
||||||
|
} else {
|
||||||
|
return `${seconds}秒`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return '时间转换出错';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user