383 lines
10 KiB
Vue
383 lines
10 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue'
|
||
import { useProducts } from '@/composables/useProducts'
|
||
import { useAccounts } from '@/composables/useAccounts'
|
||
import { useActivity } from '@/composables/useActivity'
|
||
import { useToast } from '@/composables/useToast'
|
||
import { QueryXiaodianExchangeRecords } from '@wails/go/app/App'
|
||
import AccountTable from '@/components/account/AccountTable.vue'
|
||
|
||
const { selectedProductId, selectedProductName } = useProducts()
|
||
const { accounts } = useAccounts()
|
||
const { currentActivity } = useActivity()
|
||
const { showToast } = useToast()
|
||
|
||
const loading = ref(false)
|
||
const queryResult = ref<any>(null)
|
||
|
||
const isXiaodian = computed(() => currentActivity.value?.id === 'xiaodian')
|
||
const rows = computed(() => {
|
||
const results = queryResult.value?.results || {}
|
||
return accounts.value.map(account => ({
|
||
name: account.name,
|
||
...(results[account.name] || { success: false, error: '尚未查询' })
|
||
}))
|
||
})
|
||
const matchedCount = computed(() => rows.value.filter(row => row.success && row.matched).length)
|
||
const noMatchCount = computed(() => rows.value.filter(row => row.success && !row.matched).length)
|
||
const failedCount = computed(() => rows.value.filter(row => !row.success).length)
|
||
|
||
const queryRecords = async () => {
|
||
if (!isXiaodian.value) {
|
||
showToast('暂不支持', '兑换记录查询目前仅支持和平小店', 'warn')
|
||
return
|
||
}
|
||
if (!selectedProductId.value) {
|
||
showToast('提示', '请先在左侧商品列表选择商品', 'warn')
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
try {
|
||
const result = await QueryXiaodianExchangeRecords(
|
||
selectedProductId.value,
|
||
selectedProductName.value || ''
|
||
)
|
||
queryResult.value = result
|
||
if (!result.success) {
|
||
showToast('查询失败', result.error || '兑换记录接口未返回成功', 'error')
|
||
return
|
||
}
|
||
const total = Object.keys(result.results || {}).length
|
||
showToast('查询完成', `已查询 ${total} 个账号,当前商品命中 ${matchedCount.value} 个`, 'success')
|
||
} catch (e: any) {
|
||
showToast('查询失败', e?.message || '', 'error')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const formatTime = (value: string) => {
|
||
if (!value) return '-'
|
||
const numeric = Number(value)
|
||
if (Number.isFinite(numeric) && numeric > 0) {
|
||
return new Date(numeric < 10_000_000_000 ? numeric * 1000 : numeric).toLocaleString('zh-CN')
|
||
}
|
||
return value.replace('T', ' ')
|
||
}
|
||
|
||
watch(selectedProductId, () => {
|
||
queryResult.value = null
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<section class="exchange-page">
|
||
<div class="exchange-head">
|
||
<div>
|
||
<h2>兑换记录</h2>
|
||
<p>查询每个账号的和平小店订单,并核对当前商品</p>
|
||
</div>
|
||
<button class="btn btn-primary" :disabled="loading || !selectedProductId || !isXiaodian" @click="queryRecords">
|
||
{{ loading ? '查询中...' : '查询兑换记录' }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="selected-product-bar">
|
||
<span class="bar-label">当前商品</span>
|
||
<span v-if="selectedProductId" class="bar-product">{{ selectedProductName || selectedProductId }}</span>
|
||
<span v-else class="bar-empty">请先在左侧商品列表选择商品</span>
|
||
<span v-if="selectedProductId" class="bar-id">ID {{ selectedProductId }}</span>
|
||
</div>
|
||
|
||
<div v-if="queryResult?.error" class="exchange-error">{{ queryResult.error }}</div>
|
||
|
||
<template v-if="queryResult?.success">
|
||
<div class="exchange-stats">
|
||
<div class="exchange-stat matched">
|
||
<span>已兑换当前商品</span><strong>{{ matchedCount }}</strong>
|
||
</div>
|
||
<div class="exchange-stat no-match">
|
||
<span>未兑换当前商品</span><strong>{{ noMatchCount }}</strong>
|
||
</div>
|
||
<div class="exchange-stat failed">
|
||
<span>查询失败</span><strong>{{ failedCount }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="record-table-wrap">
|
||
<table class="record-table">
|
||
<thead>
|
||
<tr>
|
||
<th>账号</th>
|
||
<th class="status-column">当前商品</th>
|
||
<th class="count-column">记录</th>
|
||
<th>兑换明细</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="rows.length === 0">
|
||
<td colspan="4" class="record-empty">暂无账号</td>
|
||
</tr>
|
||
<tr v-for="row in rows" :key="row.name">
|
||
<td class="record-account">{{ row.name }}</td>
|
||
<td class="status-column">
|
||
<span v-if="!row.success" class="record-status error">查询失败</span>
|
||
<span v-else-if="row.matched" class="record-status matched">已兑换</span>
|
||
<span v-else class="record-status no-match">未兑换</span>
|
||
</td>
|
||
<td class="count-column">{{ row.success ? row.records?.length || 0 : '-' }}</td>
|
||
<td>
|
||
<div v-if="!row.success" class="record-error">{{ row.error }}</div>
|
||
<div v-else-if="!row.records?.length" class="record-muted">暂无记录</div>
|
||
<div v-else class="record-details">
|
||
<div v-for="(record, index) in row.records" :key="`${row.name}-${index}`" class="record-detail">
|
||
<span class="detail-name">{{ record.commodityName || record.commodityId }}</span>
|
||
<span class="detail-quantity">×{{ record.quantity || 1 }}</span>
|
||
<span class="detail-time">{{ formatTime(record.buyTime) }}</span>
|
||
<span class="detail-status">状态 {{ record.status || '-' }}</span>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
<div v-else-if="!queryResult" class="record-placeholder">
|
||
选择商品后点击查询,结果会显示所有账号的订单记录。
|
||
</div>
|
||
|
||
<section class="exchange-account-section">
|
||
<div class="account-section-head">
|
||
<span>账号表格</span>
|
||
<span class="account-section-meta">可查看账号选择、凭据和资产状态</span>
|
||
</div>
|
||
<AccountTable />
|
||
</section>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.exchange-page {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
padding: 22px 24px 28px;
|
||
}
|
||
|
||
.exchange-head,
|
||
.selected-product-bar,
|
||
.exchange-stats {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.exchange-head {
|
||
justify-content: space-between;
|
||
gap: 18px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.exchange-head h2 {
|
||
color: var(--text-strong);
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.exchange-head p {
|
||
margin-top: 4px;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.selected-product-bar {
|
||
min-height: 42px;
|
||
gap: 10px;
|
||
padding: 8px 12px;
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--surface-2);
|
||
}
|
||
|
||
.bar-label {
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.bar-product {
|
||
overflow: hidden;
|
||
color: var(--text-strong);
|
||
font-weight: 600;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.bar-id,
|
||
.bar-empty {
|
||
color: var(--muted-2);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.bar-id {
|
||
margin-left: auto;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
}
|
||
|
||
.exchange-error {
|
||
margin-top: 14px;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--danger);
|
||
border-radius: var(--radius-sm);
|
||
color: var(--danger);
|
||
background: var(--danger-soft);
|
||
}
|
||
|
||
.exchange-stats {
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin: 16px 0;
|
||
}
|
||
|
||
.exchange-stat {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 18px;
|
||
min-width: 160px;
|
||
padding: 9px 12px;
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--radius-sm);
|
||
background: var(--surface-2);
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.exchange-stat strong {
|
||
font-size: 18px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.exchange-stat.matched strong { color: var(--success); }
|
||
.exchange-stat.no-match strong { color: var(--warning); }
|
||
.exchange-stat.failed strong { color: var(--danger); }
|
||
|
||
.record-table-wrap {
|
||
overflow: auto;
|
||
border: 1px solid var(--line);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
.record-table {
|
||
width: 100%;
|
||
min-width: 700px;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
}
|
||
|
||
.record-table th,
|
||
.record-table td {
|
||
padding: 10px 12px;
|
||
border-bottom: 1px solid var(--line);
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.record-table th {
|
||
background: var(--surface-2);
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.record-table tbody tr:last-child td { border-bottom: 0; }
|
||
.record-table tbody tr:hover { background: var(--surface-2); }
|
||
|
||
.status-column { width: 116px; }
|
||
.count-column { width: 64px; text-align: center !important; }
|
||
.record-account { color: var(--text-strong); font-weight: 500; }
|
||
|
||
.record-status {
|
||
display: inline-flex;
|
||
padding: 3px 8px;
|
||
border: 1px solid currentColor;
|
||
border-radius: 999px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.record-status.matched { color: var(--success); background: var(--success-soft); }
|
||
.record-status.no-match { color: var(--warning); background: var(--warning-soft); }
|
||
.record-status.error { color: var(--danger); background: var(--danger-soft); }
|
||
|
||
.record-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
max-height: 180px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.record-detail {
|
||
display: grid;
|
||
grid-template-columns: minmax(130px, 1fr) 34px 145px 58px;
|
||
gap: 8px;
|
||
align-items: baseline;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.detail-name {
|
||
overflow: hidden;
|
||
color: var(--text);
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.detail-quantity,
|
||
.detail-status { color: var(--muted-2); }
|
||
.detail-time { font-variant-numeric: tabular-nums; }
|
||
.record-muted,
|
||
.record-error,
|
||
.record-placeholder,
|
||
.record-empty { color: var(--muted); font-size: 12px; }
|
||
.record-error { color: var(--danger); }
|
||
.record-empty { height: 100px; text-align: center !important; vertical-align: middle !important; }
|
||
|
||
.record-placeholder {
|
||
padding: 54px 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.exchange-account-section {
|
||
margin-top: 22px;
|
||
}
|
||
|
||
.account-section-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
color: var(--text-strong);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.account-section-meta {
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.exchange-account-section :deep(.panel) {
|
||
min-height: 260px;
|
||
}
|
||
|
||
@media (max-width: 760px) {
|
||
.exchange-page { padding: 16px; }
|
||
.exchange-head { align-items: flex-start; flex-direction: column; }
|
||
.exchange-head .btn { width: 100%; }
|
||
.record-detail { grid-template-columns: minmax(120px, 1fr) 34px; }
|
||
.detail-time, .detail-status { grid-column: span 2; }
|
||
}
|
||
</style>
|