feat(player): 完善 H5 投注端与 API 演示数据

- 球赛/串关/优胜冠军、赛事详情、历史投注与个人资料编辑
- 固定顶栏、公告与底栏,仅内容区滚动
- 底部导航与站点 favicon 使用 logo,登录页精简
- API 种子、冠军盘与历史注单增强

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 17:18:11 +08:00
parent 7af2e418c3
commit b5dca1bfb1
75 changed files with 7077 additions and 384 deletions

View File

@@ -0,0 +1,150 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const input = ref('');
const code = ref('');
const canvasRef = ref<HTMLCanvasElement | null>(null);
const honeypot = ref('');
function generateCode() {
code.value = String(Math.floor(1000 + Math.random() * 9000));
}
function drawCaptcha() {
const canvas = canvasRef.value;
if (!canvas) return;
const w = 108;
const h = 44;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.fillStyle = '#7c3aed';
ctx.fillRect(0, 0, w, h);
for (let i = 0; i < 28; i++) {
ctx.fillStyle = `rgba(255,255,255,${0.15 + Math.random() * 0.35})`;
ctx.beginPath();
ctx.arc(Math.random() * w, Math.random() * h, Math.random() * 2.2, 0, Math.PI * 2);
ctx.fill();
}
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = `rgba(255,255,255,${0.2 + Math.random() * 0.3})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(Math.random() * w, Math.random() * h);
ctx.lineTo(Math.random() * w, Math.random() * h);
ctx.stroke();
}
ctx.save();
ctx.translate(w / 2, h / 2);
ctx.rotate((Math.random() - 0.5) * 0.12);
ctx.font = 'italic bold 26px Arial, sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(code.value, 0, 1);
ctx.restore();
}
function refresh() {
generateCode();
input.value = '';
drawCaptcha();
}
function validate(): boolean {
if (honeypot.value) {
refresh();
return false;
}
return input.value.trim() === code.value;
}
onMounted(refresh);
defineExpose({ validate, refresh });
</script>
<template>
<div class="captcha-row">
<input
v-model="honeypot"
type="text"
name="website"
tabindex="-1"
autocomplete="off"
class="hp-field"
aria-hidden="true"
/>
<input
v-model="input"
type="text"
inputmode="numeric"
maxlength="4"
class="captcha-input"
:placeholder="t('auth.captcha_placeholder')"
autocomplete="off"
/>
<canvas
ref="canvasRef"
class="captcha-canvas"
:title="t('auth.captcha_refresh')"
role="button"
tabindex="0"
@click="refresh"
@keydown.enter="refresh"
/>
</div>
</template>
<style scoped>
.captcha-row {
display: flex;
gap: 0;
align-items: stretch;
height: 44px;
}
.hp-field {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.captcha-input {
flex: 1;
min-width: 0;
padding: 0 14px;
border: none;
border-radius: 8px 0 0 8px;
background: #ffffff;
color: #111;
font-size: 15px;
font-weight: 500;
outline: none;
}
.captcha-input::placeholder {
color: #9ca3af;
}
.captcha-canvas {
flex-shrink: 0;
width: 108px;
height: 44px;
cursor: pointer;
display: block;
border-radius: 0 8px 8px 0;
}
</style>