可信源码风险提示:保存或下载前请阅读源码和权限声明。
const state = {
duration: 10,
count: 0,
running: false,
timer: null,
startTime: 0,
lastTapTime: 0,
taps: [],
finished: false
};
const view = ui.signal('menu');
function formatTime(ms) {
const totalSec = Math.floor(ms / 1000);
const remainMs = ms % 1000;
return totalSec + '.' + String(Math.floor(remainMs / 100)).padStart(1, '0') + 's';
}
function renderMenu() {
return [
ui.heading('手速测试', { lines: 1 }),
ui.text('选择测试时长', { lines: 1, size: 20, color: '#888888' }),
ui.buttonRow([
{ text: '10秒', onPress: () => startTest(10), id: 'btn10', tone: 'primary' },
{ text: '20秒', onPress: () => startTest(20), id: 'btn20', tone: 'primary' },
{ text: '30秒', onPress: () => startTest(30), id: 'btn30', tone: 'primary' },
{ text: '60秒', onPress: () => startTest(60), id: 'btn60', tone: 'primary' }
]),
ui.spacer(8),
ui.text('点击按钮开始测试', { lines: 1, size: 18, color: '#aaaaaa', align: 'center' })
];
}
function startTest(seconds) {
state.duration = seconds;
state.count = 0;
state.running = true;
state.finished = false;
state.taps = [];
state.startTime = Date.now();
state.lastTapTime = 0;
view.set('test');
ui.refresh();
state.timer = setInterval(() => {
const elapsed = Date.now() - state.startTime;
if (elapsed >= state.duration * 1000) {
finishTest();
} else {
ui.refresh();
}
}, 100);
}
function finishTest() {
if (!state.running) return;
state.running = false;
state.finished = true;
if (state.timer) {
clearInterval(state.timer);
state.timer = null;
}
view.set('result');
ui.refresh();
}
function handleTap() {
if (!state.running || state.finished) return;
const now = Date.now();
state.count++;
state.lastTapTime = now;
state.taps.push(now);
ui.refresh();
}
function renderTest() {
const elapsed = state.running ? Date.now() - state.startTime : state.duration * 1000;
const remain = Math.max(0, state.duration * 1000 - elapsed);
const remainSec = Math.ceil(remain / 1000);
const progress = Math.min(100, (elapsed / (state.duration * 1000)) * 100);
return [
ui.heading('手速测试', { lines: 1 }),
ui.text('剩余时间: ' + remainSec + '秒', { lines: 1, size: 22, align: 'center' }),
ui.progress('', progress, { accent: '#4CAF50', height: 12 }),
ui.spacer(12),
ui.text('点击次数: ' + state.count, { lines: 1, size: 28, align: 'center', bold: true }),
ui.spacer(20),
ui.button('点我!', handleTap, { id: 'tapBtn', tone: 'primary', height: 200 }),
ui.spacer(8),
ui.button('结束', () => finishTest(), { id: 'stopBtn', tone: 'neutral' })
];
}
function renderResult() {
const durationSec = state.duration;
const cps = state.count / durationSec;
const cpsStr = cps.toFixed(2);
let rating = '';
let ratingColor = '#888888';
if (cps >= 8) { rating = '神速!'; ratingColor = '#FF5722'; }
else if (cps >= 6) { rating = '超快!'; ratingColor = '#FF9800'; }
else if (cps >= 4) { rating = '很快!'; ratingColor = '#4CAF50'; }
else if (cps >= 2) { rating = '一般'; ratingColor = '#2196F3'; }
else { rating = '较慢'; ratingColor = '#9E9E9E'; }
return [
ui.heading('测试结果', { lines: 1 }),
ui.spacer(8),
ui.text('时长: ' + durationSec + '秒', { lines: 1, size: 20, align: 'center' }),
ui.text('总点击: ' + state.count + ' 次', { lines: 1, size: 20, align: 'center' }),
ui.spacer(12),
ui.text('手速: ' + cpsStr + ' 次/秒', { lines: 1, size: 32, align: 'center', bold: true }),
ui.text(rating, { lines: 1, size: 24, align: 'center', color: ratingColor, bold: true }),
ui.spacer(20),
ui.buttonRow([
{ text: '再测', onPress: () => { view.set('menu'); ui.refresh(); }, id: 'againBtn', tone: 'primary' },
{ text: '日志', onPress: () => ui.hide(), id: 'logBtn', tone: 'neutral' }
])
];
}
ui.render(() => {
const currentView = view.get();
if (currentView === 'menu') return renderMenu();
if (currentView === 'test') return renderTest();
if (currentView === 'result') return renderResult();
return renderMenu();
});
return main();