feat(theme-4): PC 列表详情弹窗、实心面板与收件箱样式修复
投注/资金/充值/返水列表点击改为海军蓝弹窗,深链 query 自动打开;修复冻结字段 i18n 与悬浮客服按钮过白问题。
This commit is contained in:
@@ -1,9 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useViewport } from '../composables/useViewport';
|
||||
import MobileBetDetailView from './MobileBetDetailView.vue';
|
||||
import DesktopBetDetailView from './desktop/DesktopBetDetailView.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isDesktop } = useViewport();
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) return;
|
||||
const betNo = route.params.betNo;
|
||||
if (betNo) {
|
||||
void router.replace({ path: '/bets', query: { betNo: String(betNo) } });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DesktopBetDetailView v-if="isDesktop" />
|
||||
<MobileBetDetailView v-else />
|
||||
|
||||
@@ -159,7 +159,11 @@ onActivated(() => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<MessageListPanel v-if="inboxEnabled" v-show="activeTab === 'messages'" />
|
||||
<MessageListPanel
|
||||
v-if="inboxEnabled"
|
||||
v-show="activeTab === 'messages'"
|
||||
@click-message="(id) => router.push(`/messages/${id}`)"
|
||||
/>
|
||||
<CustomerServicePanel v-if="activeTab === 'support'" />
|
||||
|
||||
<ConfirmDialog
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useViewport } from '../composables/useViewport';
|
||||
import MobileRechargeDetailView from './MobileRechargeDetailView.vue';
|
||||
import DesktopRechargeDetailView from './desktop/DesktopRechargeDetailView.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isDesktop } = useViewport();
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) return;
|
||||
const id = route.params.id;
|
||||
if (id) {
|
||||
void router.replace({
|
||||
path: '/wallet/recharge/history',
|
||||
query: { orderId: String(id) },
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useViewport } from '../composables/useViewport';
|
||||
import MobileWalletTransactionDetailView from './MobileWalletTransactionDetailView.vue';
|
||||
import DesktopWalletTransactionDetailView from './desktop/DesktopWalletTransactionDetailView.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { isDesktop } = useViewport();
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) return;
|
||||
const transactionId = route.params.transactionId;
|
||||
if (transactionId) {
|
||||
void router.replace({
|
||||
path: '/wallet/detail',
|
||||
query: { transactionId: String(transactionId) },
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DesktopWalletTransactionDetailView v-if="isDesktop" />
|
||||
<MobileWalletTransactionDetailView v-else />
|
||||
|
||||
@@ -1,22 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onActivated, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import api from '../../api';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import Pagination from '../../components/desktop/Pagination.vue';
|
||||
import DesktopWalletSubNav from '../../components/desktop/DesktopWalletSubNav.vue';
|
||||
import DesktopCashbackDetailModal from '../../components/desktop/DesktopCashbackDetailModal.vue';
|
||||
import { formatMoney, formatMoneyCompact } from '../../utils/localeDisplay';
|
||||
import { parseCashbackApiData, type CashbackRecord } from '../../utils/cashback';
|
||||
|
||||
const COL_COUNT = 6;
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const detailModalVisible = ref(false);
|
||||
const detailRecord = ref<CashbackRecord | null>(null);
|
||||
const allItems = ref<CashbackRecord[]>([]);
|
||||
const loading = ref(true);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(20);
|
||||
|
||||
function openDetail(record: CashbackRecord) {
|
||||
detailRecord.value = record;
|
||||
detailModalVisible.value = true;
|
||||
}
|
||||
|
||||
function openDetailFromQuery() {
|
||||
const batchNo = route.query.batchNo;
|
||||
if (typeof batchNo !== 'string' || !batchNo) return;
|
||||
const match = allItems.value.find((row) => row.batchNo === batchNo);
|
||||
if (match) {
|
||||
openDetail(match);
|
||||
const { batchNo: _removed, ...rest } = route.query;
|
||||
void router.replace({ path: route.path, query: rest });
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => route.query.batchNo, openDetailFromQuery);
|
||||
watch(allItems, openDetailFromQuery);
|
||||
watch(detailModalVisible, (visible) => {
|
||||
if (!visible) detailRecord.value = null;
|
||||
});
|
||||
|
||||
const total = computed(() => allItems.value.length);
|
||||
|
||||
const items = computed(() => {
|
||||
@@ -75,8 +103,14 @@ watch(pageSize, () => {
|
||||
page.value = 1;
|
||||
});
|
||||
|
||||
onMounted(loadData);
|
||||
onActivated(loadData);
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
openDetailFromQuery();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await loadData();
|
||||
openDetailFromQuery();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -113,7 +147,12 @@ onActivated(loadData);
|
||||
</td>
|
||||
</tr>
|
||||
<template v-else-if="items.length">
|
||||
<tr v-for="row in items" :key="row.id" class="hover-row">
|
||||
<tr
|
||||
v-for="row in items"
|
||||
:key="row.id"
|
||||
class="hover-row clickable-row"
|
||||
@click="openDetail(row)"
|
||||
>
|
||||
<td>{{ formatPeriod(row.periodStart, row.periodEnd) }}</td>
|
||||
<td>{{ formatMoneyCompact(row.effectiveStake, locale) }}</td>
|
||||
<td>{{ formatRate(row.rate) }}</td>
|
||||
@@ -140,6 +179,8 @@ onActivated(loadData);
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DesktopCashbackDetailModal v-model:visible="detailModalVisible" :record="detailRecord" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import api from '../../api';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import Pagination from '../../components/desktop/Pagination.vue';
|
||||
import DesktopBetDetailModal from '../../components/desktop/DesktopBetDetailModal.vue';
|
||||
import { type BetHistoryItem } from '../../components/BetHistoryCard.vue';
|
||||
import { useOnLocaleChange } from '../../composables/useOnLocaleChange';
|
||||
|
||||
const COL_COUNT = 8;
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const detailModalVisible = ref(false);
|
||||
const detailBetNo = ref<string | null>(null);
|
||||
|
||||
function openDetail(betNo: string) {
|
||||
detailBetNo.value = betNo;
|
||||
detailModalVisible.value = true;
|
||||
}
|
||||
|
||||
function openDetailFromQuery() {
|
||||
const betNo = route.query.betNo;
|
||||
if (typeof betNo === 'string' && betNo) {
|
||||
openDetail(betNo);
|
||||
const { betNo: _removed, ...rest } = route.query;
|
||||
void router.replace({ path: route.path, query: rest });
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => route.query.betNo, openDetailFromQuery);
|
||||
watch(detailModalVisible, (visible) => {
|
||||
if (!visible) detailBetNo.value = null;
|
||||
});
|
||||
|
||||
const items = ref<BetHistoryItem[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
@@ -62,7 +86,10 @@ useOnLocaleChange(() => {
|
||||
});
|
||||
|
||||
onActivated(() => { void loadPage(page.value); });
|
||||
onMounted(() => { void loadPage(1); });
|
||||
onMounted(() => {
|
||||
void loadPage(1);
|
||||
openDetailFromQuery();
|
||||
});
|
||||
|
||||
function statusClass(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
@@ -154,7 +181,7 @@ function statusLabel(status: string) {
|
||||
v-for="bet in items"
|
||||
:key="bet.betNo"
|
||||
class="hover-row clickable-row"
|
||||
@click="router.push(`/bets/${bet.betNo}`)"
|
||||
@click="openDetail(bet.betNo)"
|
||||
>
|
||||
<td class="id-cell">{{ bet.betNo }}</td>
|
||||
<td>{{ bet.matchTitle || '-' }}</td>
|
||||
@@ -187,6 +214,8 @@ function statusLabel(status: string) {
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DesktopBetDetailModal v-model:visible="detailModalVisible" :bet-no="detailBetNo" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,28 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { RouterLink, useRouter } from 'vue-router';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import api from '../../api';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import Pagination from '../../components/desktop/Pagination.vue';
|
||||
import DesktopWalletSubNav from '../../components/desktop/DesktopWalletSubNav.vue';
|
||||
import DesktopRechargeDetailModal from '../../components/desktop/DesktopRechargeDetailModal.vue';
|
||||
import { formatMoney } from '../../utils/localeDisplay';
|
||||
import { enrichDepositOrder, findDepositOrderById, type PlayerDepositOrder } from '../../utils/depositOrderLookup';
|
||||
|
||||
const COL_COUNT = 5;
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
type RechargeOrder = {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
amount: string;
|
||||
status: string;
|
||||
methodType?: string;
|
||||
bankName?: string | null;
|
||||
paymentMethodName?: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
const detailModalVisible = ref(false);
|
||||
const detailOrder = ref<PlayerDepositOrder | null>(null);
|
||||
|
||||
async function openDetail(order: PlayerDepositOrder) {
|
||||
detailOrder.value = await enrichDepositOrder(order);
|
||||
detailModalVisible.value = true;
|
||||
}
|
||||
|
||||
async function openDetailFromQuery() {
|
||||
const orderId = route.query.orderId;
|
||||
if (typeof orderId !== 'string' || !orderId) return;
|
||||
|
||||
const existing = items.value.find((order) => order.id === orderId);
|
||||
const order = existing ?? await findDepositOrderById(orderId);
|
||||
if (order) await openDetail(order);
|
||||
|
||||
const { orderId: _removed, ...rest } = route.query;
|
||||
void router.replace({ path: route.path, query: rest });
|
||||
}
|
||||
|
||||
watch(() => route.query.orderId, () => {
|
||||
void openDetailFromQuery();
|
||||
});
|
||||
watch(detailModalVisible, (visible) => {
|
||||
if (!visible) detailOrder.value = null;
|
||||
});
|
||||
|
||||
type RechargeOrder = PlayerDepositOrder;
|
||||
|
||||
const items = ref<RechargeOrder[]>([]);
|
||||
const loading = ref(true);
|
||||
@@ -46,8 +67,14 @@ async function loadPage(p: number) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadPage(1));
|
||||
onActivated(() => loadPage(page.value));
|
||||
onMounted(async () => {
|
||||
await loadPage(1);
|
||||
await openDetailFromQuery();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await loadPage(page.value);
|
||||
await openDetailFromQuery();
|
||||
});
|
||||
|
||||
function methodLabel(order: RechargeOrder) {
|
||||
if (order.methodType === 'USDT') return 'USDT';
|
||||
@@ -109,7 +136,7 @@ function statusLabel(status: string) {
|
||||
v-for="order in items"
|
||||
:key="order.id"
|
||||
class="hover-row clickable-row"
|
||||
@click="router.push(`/wallet/recharge/history/${order.id}`)"
|
||||
@click="openDetail(order)"
|
||||
>
|
||||
<td class="id-cell">{{ order.orderNo || order.id }}</td>
|
||||
<td>{{ methodLabel(order) }}</td>
|
||||
@@ -139,5 +166,7 @@ function statusLabel(status: string) {
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DesktopRechargeDetailModal v-model:visible="detailModalVisible" :order="detailOrder" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import api from '../../api';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import DesktopTxDetailModal from '../../components/desktop/DesktopTxDetailModal.vue';
|
||||
import { formatMoney } from '../../utils/localeDisplay';
|
||||
import { txTypeKey, txDisplayType, txAmountClass, txSummaryLabel } from '../../utils/walletTx';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
function goBack() {
|
||||
if (window.history.state && window.history.state.back) {
|
||||
router.back();
|
||||
} else {
|
||||
router.push('/wallet');
|
||||
const detailModalVisible = ref(false);
|
||||
const detailTxId = ref<string | null>(null);
|
||||
|
||||
function openDetail(txId: string) {
|
||||
detailTxId.value = txId;
|
||||
detailModalVisible.value = true;
|
||||
}
|
||||
|
||||
function openDetailFromQuery() {
|
||||
const transactionId = route.query.transactionId;
|
||||
if (typeof transactionId === 'string' && transactionId) {
|
||||
openDetail(transactionId);
|
||||
const { transactionId: _removed, ...rest } = route.query;
|
||||
void router.replace({ path: route.path, query: rest });
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => route.query.transactionId, openDetailFromQuery);
|
||||
watch(detailModalVisible, (visible) => {
|
||||
if (!visible) detailTxId.value = null;
|
||||
});
|
||||
|
||||
type Transaction = {
|
||||
transactionType: string;
|
||||
displayType?: string;
|
||||
@@ -59,8 +75,14 @@ async function loadPage(p: number) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadPage(1));
|
||||
onActivated(() => loadPage(1));
|
||||
onMounted(() => {
|
||||
void loadPage(1);
|
||||
openDetailFromQuery();
|
||||
});
|
||||
onActivated(() => {
|
||||
void loadPage(1);
|
||||
openDetailFromQuery();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -94,7 +116,7 @@ onActivated(() => loadPage(1));
|
||||
v-for="tx in items"
|
||||
:key="tx.transactionId"
|
||||
class="hover-row clickable-row"
|
||||
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
||||
@click="openDetail(tx.transactionId)"
|
||||
>
|
||||
<td class="type-cell">{{ txLabel(tx) }}</td>
|
||||
<td class="note-cell">{{ txSummaryLabel(tx, t) || '-' }}</td>
|
||||
@@ -118,6 +140,11 @@ onActivated(() => loadPage(1));
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<DesktopTxDetailModal
|
||||
v-model:visible="detailModalVisible"
|
||||
:transaction-id="detailTxId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -146,10 +173,10 @@ onActivated(() => loadPage(1));
|
||||
flex-direction: column;
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--desktop-surface-solid);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-wrap { flex: 1; overflow-x: auto; overflow-y: auto; }
|
||||
@@ -163,7 +190,7 @@ onActivated(() => loadPage(1));
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
background: #0A2540;
|
||||
background: var(--desktop-surface-header);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
border-bottom: 1px solid var(--desktop-border);
|
||||
|
||||
@@ -109,11 +109,11 @@ function txLabel(tx: TxDetail): string {
|
||||
<span class="row-val mono">{{ formatMoney(detail.balanceAfter, locale) }}</span>
|
||||
</div>
|
||||
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="detail-row">
|
||||
<span class="row-label">{{ t('wallet.frozen_before') || '冻结前' }}</span>
|
||||
<span class="row-label">{{ t('wallet.detail_frozen_before') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.frozenBefore, locale) }}</span>
|
||||
</div>
|
||||
<div v-if="parseFloat(detail.frozenBefore || '0') > 0 || parseFloat(detail.frozenAfter || '0') > 0" class="detail-row">
|
||||
<span class="row-label">{{ t('wallet.frozen_after') || '冻结后' }}</span>
|
||||
<span class="row-label">{{ t('wallet.detail_frozen_after') }}</span>
|
||||
<span class="row-val mono">{{ formatMoney(detail.frozenAfter, locale) }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter, RouterLink } from 'vue-router';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import api from '../../api';
|
||||
import GoldSpinner from '../../components/GoldSpinner.vue';
|
||||
import Pagination from '../../components/desktop/Pagination.vue';
|
||||
import DesktopWalletSubNav from '../../components/desktop/DesktopWalletSubNav.vue';
|
||||
import DesktopTxDetailModal from '../../components/desktop/DesktopTxDetailModal.vue';
|
||||
import { formatMoney } from '../../utils/localeDisplay';
|
||||
import { txTypeKey, txDisplayType, txAmountClass, txSummaryLabel } from '../../utils/walletTx';
|
||||
import { parseCashbackApiData, sumCashbackAmount } from '../../utils/cashback';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
const detailModalVisible = ref(false);
|
||||
const detailTxId = ref<string | null>(null);
|
||||
|
||||
function openDetail(txId: string) {
|
||||
detailTxId.value = txId;
|
||||
detailModalVisible.value = true;
|
||||
}
|
||||
|
||||
type Transaction = {
|
||||
transactionType: string;
|
||||
@@ -113,7 +121,7 @@ onActivated(() => fetchData(1));
|
||||
v-for="tx in items"
|
||||
:key="tx.transactionId"
|
||||
class="hover-row clickable-row"
|
||||
@click="router.push(`/wallet/transactions/${tx.transactionId}`)"
|
||||
@click="openDetail(tx.transactionId)"
|
||||
>
|
||||
<td class="type-cell">{{ txLabel(tx) }}</td>
|
||||
<td class="muted-cell">{{ txSubtitle(tx) || '-' }}</td>
|
||||
@@ -139,6 +147,8 @@ onActivated(() => fetchData(1));
|
||||
<span>{{ t('wallet.no_records') }}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DesktopTxDetailModal v-model:visible="detailModalVisible" :transaction-id="detailTxId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user