feat: phase 4 - 积分竞猜和游戏黑名单 v0.3.0

竞猜功能:发起竞猜、下注、关闭、开奖、奖池分配
黑名单功能:标记游戏、按原因/严重程度筛选、详情展开
修复:双重结算、TOCTOU竞态、订阅泄漏、选项选择兼容性

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-19 00:21:43 +08:00
parent 2d56df940d
commit 60ad9a04cd
24 changed files with 4047 additions and 14 deletions
@@ -0,0 +1,131 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { settleBet } from '@/api/bets'
import type { BetOption } from '@/types'
const visible = defineModel<boolean>({ default: false })
const props = defineProps<{
betId: string
options: BetOption[]
}>()
const emit = defineEmits<{
settled: []
}>()
const selectedOption = ref('')
const loading = ref(false)
function handleOpen() {
selectedOption.value = ''
}
async function handleSettle() {
if (!selectedOption.value) {
ElMessage.warning('请选择正确选项')
return
}
loading.value = true
try {
await settleBet(props.betId, selectedOption.value)
visible.value = false
ElMessage.success('开奖成功')
emit('settled')
} catch (error: any) {
ElMessage.error(error.message || '开奖失败')
} finally {
loading.value = false
}
}
</script>
<template>
<el-dialog
v-model="visible"
title="开奖"
width="420px"
@open="handleOpen"
>
<div class="settle-form">
<p class="settle-form__hint">请选择正确的选项来结算竞猜</p>
<el-radio-group v-model="selectedOption" class="settle-form__options">
<div
v-for="option in options"
:key="option.id"
class="settle-form__option"
>
<el-radio :value="option.id">
{{ option.content }}
</el-radio>
</div>
</el-radio-group>
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<button
class="settle-form__submit"
:disabled="loading || !selectedOption"
@click="handleSettle"
>
{{ loading ? '结算中...' : '确认开奖' }}
</button>
</template>
</el-dialog>
</template>
<style scoped>
.settle-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.settle-form__hint {
margin: 0;
font-size: 14px;
color: var(--gg-text-secondary);
}
.settle-form__options {
display: flex;
flex-direction: column;
gap: 10px;
}
.settle-form__option {
padding: 10px 14px;
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-sm);
transition: border-color 0.2s;
}
.settle-form__option:hover {
border-color: var(--gg-primary-light);
}
.settle-form__submit {
padding: 8px 20px;
border: none;
border-radius: var(--gg-radius-sm);
background: var(--gg-gradient);
color: #fff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 0.2s;
}
.settle-form__submit:hover {
opacity: 0.85;
}
.settle-form__submit:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>