mirror of
https://ghproxy.com/https://github.com/StreakingMan/solvable-sheep-game
synced 2025-05-23 17:08:18 +08:00
feat: 排行榜
This commit is contained in:
parent
01d85610eb
commit
9682d31c49
|
@ -5,6 +5,7 @@ import {
|
|||
LAST_LEVEL_STORAGE_KEY,
|
||||
LAST_SCORE_STORAGE_KEY,
|
||||
LAST_TIME_STORAGE_KEY,
|
||||
PLAYING_THEME_ID_STORAGE_KEY,
|
||||
wrapThemeDefaultSounds,
|
||||
} from './utils';
|
||||
import { Theme } from './themes/interface';
|
||||
|
@ -30,6 +31,7 @@ const App: FC<{ theme: Theme<any> }> = ({ theme: initTheme }) => {
|
|||
const [diyDialogShow, setDiyDialogShow] = useState<boolean>(false);
|
||||
|
||||
const changeTheme = (theme: Theme<any>) => {
|
||||
sessionStorage.setItem(PLAYING_THEME_ID_STORAGE_KEY, theme.title);
|
||||
wrapThemeDefaultSounds(theme);
|
||||
domRelatedOptForTheme(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 {
|
||||
position: fixed;
|
||||
left: 8px;
|
||||
|
|
|
@ -4,6 +4,7 @@ import React, {
|
|||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
Suspense,
|
||||
} from 'react';
|
||||
import './Game.scss';
|
||||
import {
|
||||
|
@ -11,9 +12,11 @@ import {
|
|||
LAST_SCORE_STORAGE_KEY,
|
||||
LAST_TIME_STORAGE_KEY,
|
||||
randomString,
|
||||
timestampToUsedTimeString,
|
||||
waitTimeout,
|
||||
} from '../utils';
|
||||
import { Icon, Theme } from '../themes/interface';
|
||||
import Score from './Score';
|
||||
|
||||
interface MySymbol {
|
||||
id: string;
|
||||
|
@ -488,17 +491,20 @@ const Game: FC<{
|
|||
<br />
|
||||
得分{score}
|
||||
<br />
|
||||
用时{(usedTime / 1000).toFixed(3)}秒
|
||||
用时{timestampToUsedTimeString(usedTime)}
|
||||
</div>
|
||||
{/*提示弹窗*/}
|
||||
{finished && (
|
||||
<div className="modal">
|
||||
<h1>{tipText}</h1>
|
||||
<h1>得分{score}</h1>
|
||||
<h1>用时{(usedTime / 1000).toFixed(3)}秒</h1>
|
||||
<button onClick={restart}>再来一次</button>
|
||||
</div>
|
||||
)}
|
||||
{/*积分、排行榜*/}
|
||||
<Suspense fallback={<span>rank list</span>}>
|
||||
{finished && (
|
||||
<Score
|
||||
level={level}
|
||||
time={usedTime}
|
||||
score={score}
|
||||
success={level === maxLevel}
|
||||
restartMethod={restart}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
{/*bgm*/}
|
||||
{theme.bgm && (
|
||||
<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,
|
||||
domRelatedOptForTheme,
|
||||
parsePathCustomThemeId,
|
||||
PLAYING_THEME_ID_STORAGE_KEY,
|
||||
wrapThemeDefaultSounds,
|
||||
} from './utils';
|
||||
import { getDefaultTheme } from './themes/default';
|
||||
|
@ -33,6 +34,11 @@ const errorTip = (tip: string) => {
|
|||
|
||||
// 加载成功后数据转换(runtime)以及转场
|
||||
const successTrans = (theme: Theme<any>) => {
|
||||
sessionStorage.setItem(
|
||||
PLAYING_THEME_ID_STORAGE_KEY,
|
||||
customThemeIdFromPath || theme.title
|
||||
);
|
||||
|
||||
wrapThemeDefaultSounds(theme);
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -72,7 +78,7 @@ const loadTheme = () => {
|
|||
Bmob.Query('config')
|
||||
.get(customThemeIdFromPath)
|
||||
.then((res) => {
|
||||
const { content } = res as any;
|
||||
const { content, increment } = res as any;
|
||||
localStorage.setItem(customThemeIdFromPath, content);
|
||||
try {
|
||||
const customTheme = JSON.parse(content);
|
||||
|
@ -80,6 +86,10 @@ const loadTheme = () => {
|
|||
} catch (e) {
|
||||
errorTip('主题配置解析失败');
|
||||
}
|
||||
// 统计访问次数
|
||||
increment('visitNum');
|
||||
// @ts-ignore
|
||||
res.save();
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
errorTip(error);
|
||||
|
|
|
@ -25,6 +25,7 @@ export const getDefaultTheme: () => Theme<DefaultSoundNames> = () => {
|
|||
title: '有解的羊了个羊',
|
||||
desc: '真的可以通关~',
|
||||
dark: true,
|
||||
maxLevel: 5,
|
||||
backgroundColor: '#8dac85',
|
||||
icons: icons.map((icon) => ({
|
||||
name: icon,
|
||||
|
|
27
src/utils.ts
27
src/utils.ts
|
@ -1,6 +1,7 @@
|
|||
import { Theme } from './themes/interface';
|
||||
import { getDefaultTheme } from './themes/default';
|
||||
|
||||
// local
|
||||
export const LAST_LEVEL_STORAGE_KEY = 'lastLevel';
|
||||
export const LAST_SCORE_STORAGE_KEY = 'lastScore';
|
||||
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_TRIPLE_SOUND_STORAGE_KEY = 'defaultTripleSound';
|
||||
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):+/;
|
||||
|
||||
|
@ -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