|
- <template>
- <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
- <h1 class="text-2xl sm:text-3xl font-bold text-slate-900 mb-2">账单明细</h1>
- <p class="text-sm text-slate-500 mb-8">查看你的点数变动记录</p>
-
- <!-- 账单统计 -->
- <div class="grid grid-cols-3 gap-4 mb-8">
- <div class="bg-white rounded-xl p-4 shadow-sm border border-slate-100 text-center">
- <p class="text-xs text-slate-400">累计充值</p>
- <p class="mt-1 text-xl font-bold text-green-600">{{ summary.totalRecharge }}</p>
- </div>
- <div class="bg-white rounded-xl p-4 shadow-sm border border-slate-100 text-center">
- <p class="text-xs text-slate-400">累计消费</p>
- <p class="mt-1 text-xl font-bold text-slate-900">{{ summary.totalConsume }}</p>
- </div>
- <div class="bg-white rounded-xl p-4 shadow-sm border border-slate-100 text-center">
- <p class="text-xs text-slate-400">累计奖励</p>
- <p class="mt-1 text-xl font-bold text-indigo-600">{{ summary.totalReward }}</p>
- </div>
- </div>
-
- <!-- Tab 筛选 -->
- <div class="flex gap-2 overflow-x-auto pb-2 mb-4">
- <button
- v-for="tab in billTabs"
- :key="tab.key"
- :class="[
- 'shrink-0 px-4 py-2 text-sm rounded-lg transition-colors cursor-pointer',
- activeType === tab.key
- ? 'bg-indigo-500 text-white font-medium'
- : 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50',
- ]"
- @click="activeType = tab.key"
- >
- {{ tab.label }}
- </button>
- </div>
-
- <!-- 加载中 -->
- <div v-if="loading" class="flex justify-center py-20">
- <svg class="w-8 h-8 text-indigo-500 animate-spin" fill="none" viewBox="0 0 24 24">
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
- </svg>
- </div>
-
- <!-- 账单列表 -->
- <div v-else-if="bills.length > 0" class="bg-white rounded-xl shadow-sm border border-slate-100 divide-y divide-slate-100">
- <div
- v-for="bill in bills"
- :key="bill.id"
- class="flex items-center justify-between px-5 py-4 hover:bg-slate-50/50 transition-colors"
- >
- <div class="min-w-0 flex-1">
- <p class="text-sm font-medium text-slate-800 truncate">{{ bill.remark || bill.type_name || '账单记录' }}</p>
- <p class="mt-0.5 text-xs text-slate-400">{{ formatDate(bill.created_at) }}</p>
- </div>
- <div class="shrink-0 text-right ml-4">
- <p :class="[
- 'text-sm font-semibold',
- bill.points > 0 ? 'text-green-600' : bill.points < 0 ? 'text-slate-700' : 'text-slate-400',
- ]">
- {{ bill.points > 0 ? '+' : '' }}{{ bill.points }}
- </p>
- <p class="text-xs text-slate-400">余额 {{ bill.balance }}</p>
- </div>
- </div>
- </div>
-
- <!-- 空状态 -->
- <div v-else class="text-center py-20">
- <svg class="w-16 h-16 mx-auto text-slate-200 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
- </svg>
- <p class="text-sm text-slate-500">暂无账单记录</p>
- </div>
-
- <!-- 加载更多 -->
- <div v-if="hasMore" class="flex justify-center mt-6">
- <button
- class="px-6 py-2.5 text-sm text-indigo-600 bg-indigo-50 hover:bg-indigo-100 rounded-lg transition-colors cursor-pointer"
- @click="loadMore"
- >
- 加载更多
- </button>
- </div>
- </div>
- </template>
-
- <script setup>
- useSeoMeta({
- title: '账单明细 - 奇想宇宙',
- description: '查看账户点数变动明细',
- })
-
- const { isLogin } = useUser()
- const { post } = useApi()
-
- const billTabs = [
- { label: '全部', key: 'all' },
- { label: '充值', key: 'recharge' },
- { label: '消费', key: 'consume' },
- { label: '奖励', key: 'reward' },
- ]
-
- const activeType = ref('all')
- const loading = ref(true)
- const bills = ref([])
- const page = ref(1)
- const hasMore = ref(false)
-
- const summary = reactive({
- totalRecharge: 0,
- totalConsume: 0,
- totalReward: 0,
- })
-
- function formatDate(dateStr) {
- if (!dateStr) return ''
- return String(dateStr).substring(0, 10)
- }
-
- async function loadBills(reset = false) {
- if (reset) {
- page.value = 1
- bills.value = []
- }
-
- loading.value = true
- try {
- const res = await post('/user/getbills', {
- type: activeType.value === 'all' ? '' : activeType.value,
- page: page.value,
- page_size: 20,
- client: 1,
- })
- if (res.code === 0) {
- const list = res.list || res.data || []
- if (reset) {
- bills.value = list
- } else {
- bills.value.push(...list)
- }
- hasMore.value = list.length >= 20
-
- // 更新统计
- if (res.data?.summary) {
- summary.totalRecharge = res.data.summary.recharge || 0
- summary.totalConsume = Math.abs(res.data.summary.consume || 0)
- summary.totalReward = res.data.summary.reward || 0
- }
- }
- } catch {
- // 忽略
- } finally {
- loading.value = false
- }
- }
-
- function loadMore() {
- page.value++
- loadBills(false)
- }
-
- watch(activeType, () => {
- loadBills(true)
- })
-
- onMounted(() => {
- if (isLogin.value) {
- loadBills(true)
- }
- })
- </script>
|