feat(player): 重构客服悬浮入口与设置抽屉,优化消息页交互

- 新增可拖拽悬浮客服按钮,支持贴边吸附与位置记忆
- 顶部栏改为设置抽屉入口,将语言/规则/登出等从个人页迁出
- 余额芯片集成用户头像,移除独立头像菜单
- 消息列表去掉冗余未读白点,统一消息页返回导航
- 补充 profile.settings 多语言文案

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-18 13:32:37 +08:00
parent fce2333322
commit 46155018ce
9 changed files with 885 additions and 181 deletions

View File

@@ -107,13 +107,12 @@ onActivated(() => {
<template>
<div class="inbox-hub" :class="{ 'inbox-hub--tabs': inboxEnabled }">
<header v-if="!inboxEnabled" class="page-header">
<header class="page-header">
<button type="button" class="back-btn" :aria-label="t('messages.back')" @click="goBack"></button>
<h1>{{ t(hubTitleKey) }}</h1>
</header>
<div v-if="inboxEnabled" class="hub-top-bar">
<button type="button" class="hub-back-btn" :aria-label="t('messages.back')" @click="goBack"></button>
<nav class="hub-tabs" role="tablist">
<button
type="button"
@@ -217,27 +216,10 @@ onActivated(() => {
}
.hub-top-bar {
display: flex;
align-items: stretch;
gap: 6px;
margin: 0;
border-bottom: 1px solid var(--border);
}
.hub-back-btn {
flex-shrink: 0;
align-self: center;
width: 32px;
height: 32px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-card);
color: var(--text);
font-size: 20px;
line-height: 1;
cursor: pointer;
}
.hub-tabs {
flex: 1;
display: flex;

View File

@@ -3,7 +3,6 @@ import { ref, onMounted, onActivated } from 'vue';
import { useRouter, RouterLink } from 'vue-router';
import { useI18n } from 'vue-i18n';
import api from '../api';
import LocaleFlag from '../components/LocaleFlag.vue';
import { useAuthStore } from '../stores/auth';
import { useAppLocale } from '../composables/useAppLocale';
import { usePlayerProfile } from '../composables/usePlayerProfile';
@@ -12,18 +11,15 @@ import { usePullToRefresh } from '../composables/usePullToRefresh';
import { parseCashbackApiData, sumCashbackAmount } from '../utils/cashback';
import { sumCashbackFromStats } from '../utils/walletStats';
import WalletBalanceCard from '../components/WalletBalanceCard.vue';
import { useInboxFeature } from '../composables/useInboxFeature';
const { t, locale } = useI18n();
const { t } = useI18n();
const router = useRouter();
const auth = useAuthStore();
const { locales, setLocale, initFromUser } = useAppLocale();
const { initFromUser } = useAppLocale();
const { profileRaw, refreshProfile } = usePlayerProfile();
const { hubRoute } = useInboxFeature();
const loading = ref(true);
const error = ref(false);
const rulesExpanded = ref(false);
const cashbackTotal = ref('0');
const displayAmount = ref(0);
@@ -74,15 +70,6 @@ const pullIndicatorStyle = () => ({
height: `${pullDistance.value}px`,
opacity: Math.min(pullDistance.value / 48, 1),
});
async function changeLocale(code: string) {
await setLocale(code);
}
function logout() {
auth.logout();
router.push('/');
}
</script>
<template>
@@ -144,82 +131,7 @@ function logout() {
<span class="cell-chevron" aria-hidden="true"></span>
</RouterLink>
<RouterLink :to="hubRoute" class="settings-cell settings-cell--accent-entry">
<span class="cell-main">
<svg class="cell-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v11A2.5 2.5 0 0 1 17.5 20H6.5A2.5 2.5 0 0 1 4 17.5v-11Z" />
<path d="m4 7 8 5.5L20 7" />
</svg>
<span class="cell-label">{{ t('messages.title') }}</span>
</span>
<span class="cell-chevron" aria-hidden="true"></span>
</RouterLink>
</section>
<section class="settings-group settings-group--profile">
<RouterLink to="/profile/edit" class="settings-cell">
<span class="cell-main">
<svg class="cell-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<circle cx="12" cy="8" r="3.5" />
<path d="M5 20c0-3.5 3.1-6 7-6s7 2.5 7 6" />
</svg>
<span class="cell-label">{{ t('profile.edit') }}</span>
</span>
<span class="cell-chevron" aria-hidden="true"></span>
</RouterLink>
<div class="rules-cell">
<button
type="button"
class="settings-cell rules-toggle"
:aria-expanded="rulesExpanded"
@click="rulesExpanded = !rulesExpanded"
>
<span class="cell-main">
<svg class="cell-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M7 4h10v16H7z" />
<path d="M10 8h6M10 12h6M10 16h4" />
</svg>
<span class="cell-label">{{ t('profile.rules_title') }}</span>
</span>
<span class="cell-chevron" :class="{ open: rulesExpanded }" aria-hidden="true"></span>
</button>
<div v-show="rulesExpanded" class="rules-body">
<p>{{ t('profile.rules_p1') }}</p>
<p>{{ t('profile.rules_p2') }}</p>
<p>{{ t('profile.rules_p3') }}</p>
<p>{{ t('profile.rules_p4') }}</p>
<p>{{ t('profile.rules_p5') }}</p>
</div>
</div>
<div class="settings-cell settings-cell--stack">
<div class="cell-head">
<svg class="cell-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18M12 3c2.5 2.8 4 6 4 9s-1.5 6.2-4 9M12 3c-2.5 2.8-4 6-4 9s1.5 6.2 4 9" />
</svg>
<span class="cell-label">{{ t('profile.language') }}</span>
</div>
<div class="lang-segment" role="group" :aria-label="t('profile.language')">
<button
v-for="item in locales"
:key="item.code"
type="button"
class="lang-opt"
:class="{ active: locale === item.code }"
@click="changeLocale(item.code)"
>
<LocaleFlag :locale="item.code" :size="14" />
<span>{{ item.label }}</span>
</button>
</div>
</div>
</section>
<button type="button" class="logout-btn" @click="logout">
{{ t('auth.logout') }}
</button>
</template>
</div>
</template>