|
|
|
|
@@ -0,0 +1,353 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="beans-container">
|
|
|
|
|
<div class="header">
|
|
|
|
|
<h2>融豆管理</h2>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 融豆统计卡片 -->
|
|
|
|
|
<div class="stats-cards">
|
|
|
|
|
<el-card class="stat-card">
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats.totalBeans || 0 }}</div>
|
|
|
|
|
<div class="stat-label">总融豆</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-icon class="stat-icon" color="#409eff"><Coin /></el-icon>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-card class="stat-card">
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats.totalEarned || 0 }}</div>
|
|
|
|
|
<div class="stat-label">总获得融豆</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-icon class="stat-icon" color="#67c23a"><TrendCharts /></el-icon>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-card class="stat-card">
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats.totalSpent || 0 }}</div>
|
|
|
|
|
<div class="stat-label">总消费融豆</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-icon class="stat-icon" color="#e6a23c"><ShoppingCart /></el-icon>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-card class="stat-card">
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<div class="stat-value">{{ stats.activeUsers || 0 }}</div>
|
|
|
|
|
<div class="stat-label">活跃用户</div>
|
|
|
|
|
</div>
|
|
|
|
|
<el-icon class="stat-icon" color="#f56c6c"><User /></el-icon>
|
|
|
|
|
</el-card>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="filters">
|
|
|
|
|
<el-form :inline="true" :model="filters" class="filter-form">
|
|
|
|
|
<el-form-item label="用户名">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="filters.username"
|
|
|
|
|
placeholder="请输入用户名"
|
|
|
|
|
clearable
|
|
|
|
|
@keyup.enter="loadBeansHistory"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="融豆类型">
|
|
|
|
|
<el-select v-model="filters.type" placeholder="全部类型" clearable style="display: inline-block; width: 150px;">
|
|
|
|
|
<el-option label="全部类型" value="" selected />
|
|
|
|
|
<el-option label="注册奖励" value="register" />
|
|
|
|
|
<el-option label="商品兑换" value="purchase" />
|
|
|
|
|
<el-option label="订单退款" value="refund" />
|
|
|
|
|
<el-option label="转账确认收款奖励融豆" value="transfer_received" />
|
|
|
|
|
<el-option label="订单完成奖励" value="order_completed" />
|
|
|
|
|
<el-option label="融豆获得" value="earn" />
|
|
|
|
|
<el-option label="融豆消费" value="spend" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="融豆变化">
|
|
|
|
|
<el-select v-model="filters.change" placeholder="全部变化" clearable style="display: inline-block; width: 150px;">
|
|
|
|
|
<el-option label="全部变化" value="" selected />
|
|
|
|
|
<el-option label="增加" value="positive" />
|
|
|
|
|
<el-option label="减少" value="negative" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="时间范围">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="filters.dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
format="YYYY-MM-DD"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" @click="loadBeansHistory">搜索</el-button>
|
|
|
|
|
<el-button @click="resetFilters">重置</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-table :data="beansHistory" v-loading="loading" stripe>
|
|
|
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
|
|
|
<el-table-column prop="username" label="用户" width="120" />
|
|
|
|
|
<el-table-column prop="type" label="类型" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getTypeColor(row.type)">{{ getTypeText(row.type) }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="beans" label="融豆变化" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span :class="row.beans > 0 ? 'beans-positive' : 'beans-negative'">
|
|
|
|
|
{{ row.beans > 0 ? '+' : '' }}{{ row.beans }}
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="balance_after" label="变化后余额" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="beans-text">{{ row.balance_after }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="description" label="描述" min-width="200" />
|
|
|
|
|
<el-table-column prop="created_at" label="时间" width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatDate(row.created_at) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<div class="pagination">
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="pagination.page"
|
|
|
|
|
v-model:page-size="pagination.limit"
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
:total="pagination.total"
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
@size-change="loadBeansHistory"
|
|
|
|
|
@current-change="loadBeansHistory"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { Coin, TrendCharts, ShoppingCart, User } from '@element-plus/icons-vue'
|
|
|
|
|
import api from '@/utils/api'
|
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const beansHistory = ref([])
|
|
|
|
|
const stats = ref({})
|
|
|
|
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
username: '',
|
|
|
|
|
type: '', // 默认显示全部类型
|
|
|
|
|
change: '', // 默认显示全部变化
|
|
|
|
|
dateRange: null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
page: 1,
|
|
|
|
|
limit: 20,
|
|
|
|
|
total: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 加载融豆统计
|
|
|
|
|
const loadStats = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const {data} = await api.beans.getStats()
|
|
|
|
|
stats.value = data.data.stats
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载融豆统计失败:', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载融豆历史
|
|
|
|
|
const loadBeansHistory = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
page: pagination.page,
|
|
|
|
|
limit: pagination.limit,
|
|
|
|
|
username: filters.username,
|
|
|
|
|
type: filters.type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.change) {
|
|
|
|
|
params.change = filters.change
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.dateRange && filters.dateRange.length === 2) {
|
|
|
|
|
params.startDate = filters.dateRange[0]
|
|
|
|
|
params.endDate = filters.dateRange[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {data} = await api.beans.getHistory(params)
|
|
|
|
|
beansHistory.value = data.data.history
|
|
|
|
|
pagination.total = data.data.total
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error('加载融豆历史失败')
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 重置筛选条件
|
|
|
|
|
const resetFilters = () => {
|
|
|
|
|
Object.assign(filters, {
|
|
|
|
|
username: '',
|
|
|
|
|
type: '',
|
|
|
|
|
change: '',
|
|
|
|
|
dateRange: null
|
|
|
|
|
})
|
|
|
|
|
pagination.page = 1
|
|
|
|
|
loadBeansHistory()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取类型颜色
|
|
|
|
|
const getTypeColor = (type) => {
|
|
|
|
|
const colors = {
|
|
|
|
|
register: 'success',
|
|
|
|
|
purchase: 'danger',
|
|
|
|
|
refund: 'info',
|
|
|
|
|
transfer_received: 'success',
|
|
|
|
|
order_completed: 'success',
|
|
|
|
|
earn: 'success',
|
|
|
|
|
spend: 'danger'
|
|
|
|
|
}
|
|
|
|
|
return colors[type] || 'info'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取类型文本
|
|
|
|
|
const getTypeText = (type) => {
|
|
|
|
|
const texts = {
|
|
|
|
|
register: '注册奖励',
|
|
|
|
|
purchase: '商品兑换',
|
|
|
|
|
refund: '订单退款',
|
|
|
|
|
transfer_received: '转账确认收款奖励融豆',
|
|
|
|
|
order_completed: '订单完成奖励',
|
|
|
|
|
earn: '融豆获得',
|
|
|
|
|
spend: '融豆消费'
|
|
|
|
|
}
|
|
|
|
|
return texts[type] || type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化日期
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
|
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadBeansHistory()
|
|
|
|
|
loadStats()
|
|
|
|
|
// 确保筛选器默认值正确显示
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
// 强制更新筛选器显示
|
|
|
|
|
if (filters.type === '') {
|
|
|
|
|
filters.type = ''
|
|
|
|
|
}
|
|
|
|
|
if (filters.change === '') {
|
|
|
|
|
filters.change = ''
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.beans-container {
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header h2 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stats-cards {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
|
|
gap: 20px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: transform 0.2s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card:hover {
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card :deep(.el-card__body) {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon {
|
|
|
|
|
font-size: 40px;
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filters {
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filter-form {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.beans-text {
|
|
|
|
|
color: #e6a23c;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.beans-positive {
|
|
|
|
|
color: #67c23a;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.beans-negative {
|
|
|
|
|
color: #f56c6c;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
</style>
|