feat(theme-4): fix dark backgrounds, quick bet, inbox spacing, customer service theme

This commit is contained in:
mars
2026-07-09 18:03:57 +08:00
parent dcf9afacd6
commit ecfea51545
124 changed files with 26710 additions and 8578 deletions

View File

@@ -0,0 +1,327 @@
<script setup lang="ts">
import { computed, onActivated } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { formatLocalMatchDateTime } from '@thebet365/shared';
import GoldSpinner from '../../components/GoldSpinner.vue';
import defaultBannerImg from '../../assets/images/banner.webp';
import { usePlayerHome, type PlayerContentItem } from '../../composables/usePlayerHome';
import { stripHtml } from '../../utils/html';
const router = useRouter();
const { t, locale } = useI18n();
const { hubContentItems, loading, load } = usePlayerHome();
const items = computed(() => hubContentItems.value);
function goBack() {
if (window.history.length > 1) {
router.back();
return;
}
router.push('/');
}
function openDetail(id: string) {
router.push(`/announcements/${id}`);
}
function formatDate(createdAt?: string) {
if (!createdAt) return '';
return formatLocalMatchDateTime(createdAt, locale.value, { variant: 'full' });
}
function itemTitle(item: PlayerContentItem) {
const title = item.translation?.title?.trim();
if (title) return title;
return stripHtml(item.translation?.body ?? '') || t('home.announcement_badge');
}
function itemPreview(item: PlayerContentItem) {
const title = item.translation?.title?.trim();
const bodyText = stripHtml(item.translation?.body ?? '');
if (bodyText && bodyText !== title) return bodyText;
return '';
}
function typeLabel(item: PlayerContentItem) {
switch (item.contentType) {
case 'BANNER':
return t('announcements.type_banner');
case 'TICKER':
return t('announcements.type_ticker');
default:
return t('announcements.type_notice');
}
}
function coverUrl(item: PlayerContentItem) {
const url = item.translation?.imageUrl?.trim();
if (url) return url;
if (item.contentType === 'BANNER') return defaultBannerImg;
return '';
}
onActivated(() => {
void load(true);
});
</script>
<template>
<div class="desktop-announce-center">
<header class="center-hero">
<button type="button" class="back-btn" @click="goBack">
<span class="back-icon" aria-hidden="true"></span>
{{ t('announcements.back') }}
</button>
<div class="hero-main">
<div class="hero-title-row">
<svg class="hero-icon" viewBox="0 0 24 24" aria-hidden="true">
<path fill="currentColor" d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/>
</svg>
<h1>{{ t('announcements.title') }}</h1>
</div>
<p class="hero-sub">{{ t('announcements.center_desc') }}</p>
</div>
<div v-if="items.length" class="hero-count">{{ t('announcements.total_count', { count: items.length }) }}</div>
</header>
<div v-if="loading && !items.length" class="state-wrap">
<GoldSpinner :size="40" />
</div>
<div v-else-if="!items.length" class="empty-wrap">
<p>{{ t('announcements.empty') }}</p>
</div>
<div v-else class="announce-grid">
<button
v-for="item in items"
:key="item.id"
type="button"
class="announce-card"
@click="openDetail(item.id)"
>
<div v-if="coverUrl(item)" class="card-cover">
<img :src="coverUrl(item)" :alt="itemTitle(item)" loading="lazy" />
</div>
<div class="card-body">
<div class="card-top">
<span class="type-badge">{{ typeLabel(item) }}</span>
<span v-if="item.createdAt" class="card-date">{{ formatDate(item.createdAt) }}</span>
</div>
<h2 class="card-title">{{ itemTitle(item) }}</h2>
<p v-if="itemPreview(item)" class="card-preview">{{ itemPreview(item) }}</p>
<span class="card-more">{{ t('announcements.read_more') }} </span>
</div>
</button>
</div>
</div>
</template>
<style scoped>
.desktop-announce-center {
width: 100%;
max-width: 1180px;
margin: 0 auto;
padding: 8px 0 40px;
}
.center-hero {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 24px;
padding: 18px 20px;
border: 1px solid var(--border);
border-radius: 10px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(10, 42, 68, 0.95) 42%);
}
.back-btn {
flex-shrink: 0;
margin-top: 2px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 10px;
background: #0A2540;
color: var(--primary-light);
font-size: 13px;
font-weight: 700;
line-height: 1;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
.back-btn:hover {
border-color: var(--border-gold-soft);
background: rgba(255, 255, 255, 0.08);
}
.back-icon {
font-size: 18px;
line-height: 1;
}
.hero-main {
flex: 1;
min-width: 0;
}
.hero-title-row {
display: flex;
align-items: center;
gap: 10px;
}
.hero-icon {
width: 22px;
height: 22px;
color: var(--primary-light);
}
.center-hero h1 {
margin: 0;
font-size: 22px;
font-weight: 800;
color: var(--primary-light);
}
.hero-sub {
margin: 8px 0 0;
font-size: 13px;
line-height: 1.5;
color: var(--text-muted);
}
.hero-count {
flex-shrink: 0;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid var(--border-gold-soft);
background: rgba(255, 255, 255, 0.08);
font-size: 12px;
font-weight: 700;
color: var(--primary-light);
white-space: nowrap;
}
.state-wrap,
.empty-wrap {
display: flex;
justify-content: center;
align-items: center;
min-height: 240px;
color: var(--text-muted);
font-size: 14px;
}
.announce-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
@media (max-width: 1100px) {
.announce-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.announce-card {
display: flex;
flex-direction: column;
min-height: 100%;
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
background: rgba(10, 42, 68, 0.96);
text-align: left;
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
}
.announce-card:hover {
border-color: var(--border-gold-soft);
transform: translateY(-2px);
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
}
.card-cover {
aspect-ratio: 16 / 7;
background: #001A33;
overflow: hidden;
}
.card-cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.card-body {
display: flex;
flex-direction: column;
gap: 8px;
padding: 14px 14px 16px;
flex: 1;
}
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.type-badge {
flex-shrink: 0;
padding: 2px 7px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.22);
font-size: 10px;
font-weight: 700;
color: var(--primary-light);
}
.card-date {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
}
.card-title {
margin: 0;
font-size: 15px;
font-weight: 800;
line-height: 1.45;
color: #fff;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-preview {
margin: 0;
font-size: 12px;
line-height: 1.55;
color: var(--text-muted);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-more {
margin-top: auto;
padding-top: 4px;
font-size: 12px;
font-weight: 700;
color: var(--primary-light);
}
</style>