feat(theme-4): fix dark backgrounds, quick bet, inbox spacing, customer service theme
This commit is contained in:
327
apps/player/src/views/desktop/DesktopAnnouncementListView.vue
Normal file
327
apps/player/src/views/desktop/DesktopAnnouncementListView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user