{{ t('user.hint.password_reset_to_view') }}
-- {{ t('cashback.use_default_rate', { rate: `${editPlayerForm.defaultCashbackRate.toFixed(2)}%` }) }} -
+ +{{ t('user.hint.password_reset_to_view') }}
+- {{ t('user.hint.password_reset_to_view') }} -
-+ {{ t('user.hint.password_reset_to_view') }} +
+{{ errorMsg }}
diff --git a/apps/player/src/composables/useAppLocale.ts b/apps/player/src/composables/useAppLocale.ts index edad58c..3ad703f 100644 --- a/apps/player/src/composables/useAppLocale.ts +++ b/apps/player/src/composables/useAppLocale.ts @@ -44,11 +44,25 @@ export function useAppLocale() { applyLocale(code); } + /** 将当前语言同步到后端,仅在有 token 时执行,不修改本地 locale */ + async function syncLocaleToBackend() { + if (!auth.token) return; + try { + await api.post('/player/language', { locale: locale.value }); + if (auth.user) { + auth.user = { ...auth.user, locale: locale.value }; + localStorage.setItem('user', JSON.stringify(auth.user)); + } + } catch { + /* ignore */ + } + } + function initFromUser(userLocale?: string | null) { if (userLocale && (SUPPORTED_LOCALES as readonly string[]).includes(userLocale)) { applyLocale(userLocale); } } - return { locales: APP_LOCALES, setLocale, applyLocale, initFromUser }; + return { locales: APP_LOCALES, setLocale, applyLocale, initFromUser, syncLocaleToBackend }; } diff --git a/apps/player/src/composables/usePullToRefresh.ts b/apps/player/src/composables/usePullToRefresh.ts index 9a8bb9f..54d5f91 100644 --- a/apps/player/src/composables/usePullToRefresh.ts +++ b/apps/player/src/composables/usePullToRefresh.ts @@ -16,8 +16,10 @@ export function usePullToRefresh(options: PullToRefreshOptions) { const progress = computed(() => Math.min(pullDistance.value / maxPull, 1)); let scrollEl: HTMLElement | null = null; + let startX = 0; let startY = 0; let pulling = false; + let locked = false; function findScrollEl(): HTMLElement | null { return document.querySelector('.layout > .main') as HTMLElement | null; @@ -41,19 +43,38 @@ export function usePullToRefresh(options: PullToRefreshOptions) { if (!scrollEl || refreshing.value) return; if (scrollEl.scrollTop > 4) return; if (isInsideScrollableChild(e.target)) return; + startX = e.touches[0].clientX; startY = e.touches[0].clientY; pulling = true; + locked = false; } function handleTouchMove(e: TouchEvent) { if (!pulling || refreshing.value) return; - const delta = e.touches[0].clientY - startY; - if (delta <= 0) { + const dx = e.touches[0].clientX - startX; + const dy = e.touches[0].clientY - startY; + + if (!locked) { + if (Math.abs(dx) > Math.abs(dy) * 1.5) { + pulling = false; + pullDistance.value = 0; + spinning.value = false; + return; + } + if (dy > 0) { + locked = true; + } + } + + if (!locked) return; + + if (dy <= 0) { pullDistance.value = 0; spinning.value = false; return; } - const damped = Math.min(delta * 0.7, maxPull); + e.preventDefault(); + const damped = Math.min(dy * 0.7, maxPull); pullDistance.value = damped; spinning.value = damped >= threshold * 0.5; } @@ -61,6 +82,7 @@ export function usePullToRefresh(options: PullToRefreshOptions) { function handleTouchEnd() { if (!pulling) return; pulling = false; + locked = false; if (pullDistance.value >= threshold && !refreshing.value) { refreshing.value = true; spinning.value = true; diff --git a/apps/player/src/layouts/MainLayout.vue b/apps/player/src/layouts/MainLayout.vue index 1bdfff6..1fe3773 100644 --- a/apps/player/src/layouts/MainLayout.vue +++ b/apps/player/src/layouts/MainLayout.vue @@ -69,10 +69,10 @@ watch( () => auth.token, (token) => { // 首页数据(公告、热门赛事)对所有人公开,始终加载 - void loadPlayerHome(); + void loadPlayerHome(true); // 个人资料仅登录用户需要 if (token) { - void loadProfile(); + void loadProfile(true); } }, { immediate: true }, diff --git a/apps/player/src/main.ts b/apps/player/src/main.ts index 98e3a6b..e103d77 100644 --- a/apps/player/src/main.ts +++ b/apps/player/src/main.ts @@ -17,6 +17,8 @@ const i18n = createI18n({ refreshing: '刷新中…', loading_more: '加载更多…', no_more: '没有更多了', + load_failed: '加载失败', + retry: '重试', }, nav: { home: '主页', bet: '投注', bet_history: '历史投注', wallet: '账单', profile: '我的' }, home: { @@ -76,9 +78,9 @@ const i18n = createI18n({ password: '密码', invite_code: '邀请码', optional: '选填', - captcha_placeholder: 'Captcha', + captcha_placeholder: '验证码', captcha_refresh: '点击换一张', - captcha_wrong: '请完成滑块验证', + captcha_wrong: '验证码错误', slide_to_verify: '向右滑动完成验证', click_to_verify: '点击验证', verified: '验证成功', @@ -110,7 +112,12 @@ const i18n = createI18n({ available: '可用', no_records: '暂无账单记录', tx_deposit: '充值', + tx_admin_deposit: '管理员上分', + tx_agent_deposit: '代理上分', + tx_player_deposit: '自助充值', tx_withdraw: '人工提款', + tx_admin_withdraw: '管理员下分', + tx_agent_withdraw: '代理下分', tx_adjust: '人工调整', tx_bet_freeze: '投注冻结', tx_bet_deduct: '投注扣款', @@ -121,6 +128,8 @@ const i18n = createI18n({ tx_bet_void: '投注撤销', tx_cashback: '返水入账', tx_resettle: '重新结算', + summary_bet: '注单 {betNo}', + summary_opening_bonus: '开户赠金', stats_income: '收入', stats_expense: '支出', stats_net: '净额', @@ -179,7 +188,7 @@ const i18n = createI18n({ submit_failed: '提交失败,请重试', file_must_be_image: '请上传图片文件', file_too_large: '文件不能超过 10MB', - status_pending: '审核中', + status_pending: '充值中', status_approved: '已通过', status_rejected: '已拒绝', no_orders: '暂无充值记录', @@ -391,6 +400,8 @@ const i18n = createI18n({ refreshing: 'Refreshing…', loading_more: 'Loading more…', no_more: 'No more', + load_failed: 'Failed to load', + retry: 'Retry', }, nav: { home: 'Home', bet: 'Bet', bet_history: 'History', wallet: 'Wallet', profile: 'Profile' }, home: { @@ -450,9 +461,9 @@ const i18n = createI18n({ password: 'Password', invite_code: 'Invitation Code', optional: 'Optional', - captcha_placeholder: 'Captcha', + captcha_placeholder: 'Code', captcha_refresh: 'Click to refresh', - captcha_wrong: 'Please complete the slider verification', + captcha_wrong: 'Incorrect captcha code', slide_to_verify: 'Slide to verify', click_to_verify: 'Click to verify', verified: 'Verified', @@ -484,7 +495,12 @@ const i18n = createI18n({ available: 'Available', no_records: 'No records', tx_deposit: 'Deposit', + tx_admin_deposit: 'Admin top-up', + tx_agent_deposit: 'Agent top-up', + tx_player_deposit: 'Self deposit', tx_withdraw: 'Withdrawal', + tx_admin_withdraw: 'Admin withdraw', + tx_agent_withdraw: 'Agent withdraw', tx_adjust: 'Manual Adjust', tx_bet_freeze: 'Bet Frozen', tx_bet_deduct: 'Bet Deducted', @@ -495,6 +511,8 @@ const i18n = createI18n({ tx_bet_void: 'Bet Voided', tx_cashback: 'Cashback credit', tx_resettle: 'Resettlement', + summary_bet: 'Bet {betNo}', + summary_opening_bonus: 'Opening bonus', stats_income: 'Income', stats_expense: 'Expense', stats_net: 'Net', @@ -553,7 +571,7 @@ const i18n = createI18n({ submit_failed: 'Submit failed, please retry', file_must_be_image: 'Please upload an image file', file_too_large: 'File exceeds 10MB', - status_pending: 'Pending', + status_pending: 'Processing', status_approved: 'Approved', status_rejected: 'Rejected', no_orders: 'No recharge records', @@ -765,6 +783,8 @@ const i18n = createI18n({ refreshing: 'Menyegarkan…', loading_more: 'Memuat lagi…', no_more: 'Tiada lagi', + load_failed: 'Gagal dimuat', + retry: 'Cuba lagi', }, nav: { home: 'Laman Utama', @@ -830,9 +850,9 @@ const i18n = createI18n({ password: 'Kata Laluan', invite_code: 'Kod Jemputan', optional: 'Pilihan', - captcha_placeholder: 'Captcha', + captcha_placeholder: 'Kod', captcha_refresh: 'Klik untuk muat semula', - captcha_wrong: 'Sila lengkapkan pengesahan gelongsor', + captcha_wrong: 'Kod captcha salah', slide_to_verify: 'Gelongsor untuk mengesahkan', click_to_verify: 'Klik untuk mengesahkan', verified: 'Disahkan', @@ -864,7 +884,12 @@ const i18n = createI18n({ available: 'Tersedia', no_records: 'Tiada rekod', tx_deposit: 'Deposit', + tx_admin_deposit: 'Tambah baki admin', + tx_agent_deposit: 'Tambah baki ejen', + tx_player_deposit: 'Deposit sendiri', tx_withdraw: 'Pengeluaran', + tx_admin_withdraw: 'Pengeluaran admin', + tx_agent_withdraw: 'Pengeluaran ejen', tx_adjust: 'Pelarasan Manual', tx_bet_freeze: 'Pertaruhan Ditahan', tx_bet_deduct: 'Pertaruhan Ditolak', @@ -875,6 +900,8 @@ const i18n = createI18n({ tx_bet_void: 'Pertaruhan Dibatalkan', tx_cashback: 'Kredit rebat', tx_resettle: 'Penyelesaian Semula', + summary_bet: 'Pertaruhan {betNo}', + summary_opening_bonus: 'Bonus pembukaan', stats_income: 'Pendapatan', stats_expense: 'Perbelanjaan', stats_net: 'Bersih', @@ -933,7 +960,7 @@ const i18n = createI18n({ submit_failed: 'Gagal, sila cuba lagi', file_must_be_image: 'Sila muat naik fail imej', file_too_large: 'Fail melebihi 10MB', - status_pending: 'Menunggu', + status_pending: 'Memproses', status_approved: 'Diluluskan', status_rejected: 'Ditolak', no_orders: 'Tiada rekod topup', diff --git a/apps/player/src/utils/walletTx.ts b/apps/player/src/utils/walletTx.ts index 61dbe90..62167fa 100644 --- a/apps/player/src/utils/walletTx.ts +++ b/apps/player/src/utils/walletTx.ts @@ -1,6 +1,11 @@ export const TX_KEY_MAP: Record{{ t('common.load_failed') }}
+ +