Files
gamegroup2/frontend/src/components/gameBlacklist/BlacklistGameCard.vue
T
congsh 60ad9a04cd feat: phase 4 - 积分竞猜和游戏黑名单 v0.3.0
竞猜功能:发起竞猜、下注、关闭、开奖、奖池分配
黑名单功能:标记游戏、按原因/严重程度筛选、详情展开
修复:双重结算、TOCTOU竞态、订阅泄漏、选项选择兼容性

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 00:21:43 +08:00

221 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
import { BlacklistReasonMap, BlacklistSeverityMap } from '@/types'
import type { BlacklistEntry, BlacklistSeverity } from '@/types'
const props = defineProps<{
gameName: string
entries: BlacklistEntry[]
}>()
const emit = defineEmits<{
click: []
}>()
// 被标记次数
const reportCount = computed(() => props.entries.length)
// 最新一条记录
const latestEntry = computed(() => props.entries[0])
// 最近标记原因
const latestReason = computed(() => {
const entry = latestEntry.value
if (!entry) return ''
return BlacklistReasonMap[entry.reason]
})
// 严重程度排序:severe > medium > mild
const severityOrder: Record<BlacklistSeverity, number> = {
mild: 1,
medium: 2,
severe: 3,
}
// 严重程度最高的一条
const maxSeverity = computed(() => {
let max: BlacklistSeverity = 'mild'
for (const entry of props.entries) {
if (severityOrder[entry.severity] > severityOrder[max]) {
max = entry.severity
}
}
return max
})
const maxSeverityLabel = computed(() => BlacklistSeverityMap[maxSeverity.value])
// 严重程度对应的 CSS class
const severityClass = computed(() => `game-card__severity--${maxSeverity.value}`)
// 第一条记录描述预览
const previewDesc = computed(() => {
const entry = latestEntry.value
if (!entry) return ''
return entry.description
})
</script>
<template>
<div class="game-card" @click="emit('click')">
<!-- 左侧游戏信息 -->
<div class="game-card__info">
<div class="game-card__name-row">
<span class="game-card__name">{{ gameName }}</span>
<span class="game-card__badge">{{ reportCount }} 次标记</span>
</div>
<!-- 标签行 -->
<div class="game-card__tags">
<span class="game-card__reason-tag">
{{ latestReason }}
</span>
<span class="game-card__severity" :class="severityClass">
{{ maxSeverityLabel }}
</span>
</div>
<!-- 描述预览 -->
<div v-if="previewDesc" class="game-card__desc">
{{ previewDesc }}
</div>
</div>
<!-- 右侧展开指示 -->
<div class="game-card__arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="game-card__arrow-icon">
<polyline points="6 9 12 15 18 9" />
</svg>
</div>
</div>
</template>
<style scoped>
.game-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
background: var(--gg-bg-card);
border: 1px solid var(--gg-border);
border-radius: var(--gg-radius-md);
padding: 16px 18px;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
}
.game-card:hover {
border-color: var(--gg-primary-light);
box-shadow: 0 0 20px rgba(5, 150, 105, 0.1);
transform: translateY(-1px);
}
/* 左侧信息 */
.game-card__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
/* 游戏名 + 次数 */
.game-card__name-row {
display: flex;
align-items: center;
gap: 10px;
}
.game-card__name {
font-size: 15px;
font-weight: 600;
color: var(--gg-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.game-card__badge {
flex-shrink: 0;
padding: 1px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
background: rgba(239, 68, 68, 0.1);
color: var(--gg-danger);
}
/* 标签行 */
.game-card__tags {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.game-card__reason-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
line-height: 18px;
background: rgba(245, 158, 11, 0.1);
color: var(--gg-warning);
}
.game-card__severity {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
line-height: 18px;
}
.game-card__severity--mild {
background: rgba(16, 185, 129, 0.1);
color: var(--gg-success);
}
.game-card__severity--medium {
background: rgba(245, 158, 11, 0.1);
color: var(--gg-warning);
}
.game-card__severity--severe {
background: rgba(239, 68, 68, 0.1);
color: var(--gg-danger);
}
/* 描述预览 */
.game-card__desc {
font-size: 13px;
color: var(--gg-text-secondary);
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
/* 展开箭头 */
.game-card__arrow {
flex-shrink: 0;
display: flex;
align-items: center;
}
.game-card__arrow-icon {
width: 18px;
height: 18px;
color: var(--gg-text-muted);
transition: transform 0.2s;
}
.game-card:hover .game-card__arrow-icon {
color: var(--gg-primary);
}
</style>