|
@@ -0,0 +1,809 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="points-management">
|
|
|
|
|
+ <el-card>
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <span>积分明细管理</span>
|
|
|
|
|
+ <div class="header-actions">
|
|
|
|
|
+<!-- <el-button type="primary" @click="showAdjustDialog">手动调整积分</el-button>
|
|
|
|
|
+ <el-button @click="exportData">导出数据</el-button>-->
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索栏 -->
|
|
|
|
|
+ <div class="search-bar">
|
|
|
|
|
+ <el-form :model="searchForm" inline>
|
|
|
|
|
+ <el-form-item label="用户">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="selectedUserDisplay"
|
|
|
|
|
+ placeholder="点击选择用户"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 150px;"
|
|
|
|
|
+ @click="showUserDialog"
|
|
|
|
|
+ @clear="handleUserClear"
|
|
|
|
|
+ >
|
|
|
|
|
+<!-- <template #suffix>
|
|
|
|
|
+ <el-icon style="cursor: pointer;">
|
|
|
|
|
+ <Search />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ </template>-->
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="积分类型">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.type"
|
|
|
|
|
+ placeholder="请选择类型"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 150px;"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in pointsTypes"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+<!-- <el-form-item label="业务类型">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.biz_type"
|
|
|
|
|
+ placeholder="请选择业务类型"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 150px;"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in bizTypes"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>-->
|
|
|
|
|
+ <el-form-item label="时间范围">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="searchForm.dateRange"
|
|
|
|
|
+ type="datetimerange"
|
|
|
|
|
+ range-separator="至"
|
|
|
|
|
+ start-placeholder="开始时间"
|
|
|
|
|
+ end-placeholder="结束时间"
|
|
|
|
|
+ format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
+ style="width: 300px;"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button>
|
|
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 统计卡片 -->
|
|
|
|
|
+ <div class="stats-cards">
|
|
|
|
|
+ <el-row :gutter="20" style="margin-bottom: 20px;">
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card class="stats-card">
|
|
|
|
|
+ <div class="stats-item">
|
|
|
|
|
+ <div class="stats-label">积分发放</div>
|
|
|
|
|
+ <div class="stats-value increase">+{{ statsData.positive_total || 0 }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ <el-col :span="6">
|
|
|
|
|
+ <el-card class="stats-card">
|
|
|
|
|
+ <div class="stats-item">
|
|
|
|
|
+ <div class="stats-label">积分消耗</div>
|
|
|
|
|
+ <div class="stats-value decrease">{{ statsData.negative_total || 0 }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 积分记录表格 -->
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :data="pointsList"
|
|
|
|
|
+ border
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ v-loading="loading"
|
|
|
|
|
+ row-key="id"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column prop="id" label="记录ID" width="80" />
|
|
|
|
|
+ <el-table-column prop="user_id" label="用户ID" width="100" />
|
|
|
|
|
+ <el-table-column prop="user.nickname" label="用户名" width="100" />
|
|
|
|
|
+ <el-table-column label="积分变动" width="150">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ 'point-increase': row.point > 0,
|
|
|
|
|
+ 'point-decrease': row.point < 0,
|
|
|
|
|
+ 'point-freeze': row.freeze_point !== 0
|
|
|
|
|
+ }"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ row.point > 0 ? '+' : '' }}{{ row.point }}
|
|
|
|
|
+ <span v-if="row.freeze_point !== 0" class="freeze-point">
|
|
|
|
|
+ (冻结: {{ row.freeze_point > 0 ? '+' : '' }}{{ row.freeze_point }})
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="变动后余额" width="150">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div>积分: {{ row.after_point }}</div>
|
|
|
|
|
+ <div v-if="row.after_freeze_point !== 0" class="freeze-balance">
|
|
|
|
|
+ 冻结: {{ row.after_freeze_point }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="type" label="变动类型" width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag
|
|
|
|
|
+ :type="getTypeTagType(row.type)"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ getTypeName(row.type) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+<!-- <el-table-column prop="biz_type" label="业务类型" width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ getBizTypeName(row.biz_type) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="biz_id" label="业务ID" width="100" />-->
|
|
|
|
|
+ <el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="create_time" label="创建时间" width="180" />
|
|
|
|
|
+<!-- <el-table-column label="操作" width="120" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="showDetailDialog(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 查看详情
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>-->
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ :current-page="pagination.page"
|
|
|
|
|
+ :page-size="pagination.limit"
|
|
|
|
|
+ :total="pagination.total"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ style="margin-top: 20px; text-align: right;"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 手动调整积分对话框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="adjustDialogVisible"
|
|
|
|
|
+ title="手动调整积分"
|
|
|
|
|
+ width="500px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="adjustFormRef"
|
|
|
|
|
+ :model="adjustForm"
|
|
|
|
|
+ :rules="adjustRules"
|
|
|
|
|
+ label-width="100px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item label="用户ID" prop="user_id">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="adjustForm.user_id"
|
|
|
|
|
+ placeholder="请输入用户ID"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="积分变动" prop="point">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="adjustForm.point"
|
|
|
|
|
+ :min="-999999"
|
|
|
|
|
+ :max="999999"
|
|
|
|
|
+ placeholder="正数为增加,负数为减少"
|
|
|
|
|
+ style="width: 100%;"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="冻结积分" prop="freeze_point">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="adjustForm.freeze_point"
|
|
|
|
|
+ :min="-999999"
|
|
|
|
|
+ :max="999999"
|
|
|
|
|
+ placeholder="冻结积分变动量"
|
|
|
|
|
+ style="width: 100%;"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="业务类型" prop="biz_type">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="adjustForm.biz_type"
|
|
|
|
|
+ placeholder="请选择业务类型"
|
|
|
|
|
+ style="width: 100%;"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in bizTypes"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="业务ID" prop="biz_id">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="adjustForm.biz_id"
|
|
|
|
|
+ placeholder="关联的业务ID(选填)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="adjustForm.remark"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="3"
|
|
|
|
|
+ placeholder="请输入调整原因"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <span class="dialog-footer">
|
|
|
|
|
+ <el-button @click="adjustDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleAdjustPoints"
|
|
|
|
|
+ :loading="adjustLoading"
|
|
|
|
|
+ >
|
|
|
|
|
+ 确定
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 详情对话框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="detailDialogVisible"
|
|
|
|
|
+ title="积分记录详情"
|
|
|
|
|
+ width="600px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-descriptions :column="2" border v-if="currentDetail">
|
|
|
|
|
+ <el-descriptions-item label="记录ID">{{ currentDetail.id }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="用户ID">{{ currentDetail.user_id }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="积分变动">
|
|
|
|
|
+ <span
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ 'point-increase': currentDetail.point > 0,
|
|
|
|
|
+ 'point-decrease': currentDetail.point < 0
|
|
|
|
|
+ }"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ currentDetail.point > 0 ? '+' : '' }}{{ currentDetail.point }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="冻结积分变动">
|
|
|
|
|
+ <span v-if="currentDetail.freeze_point !== 0">
|
|
|
|
|
+ {{ currentDetail.freeze_point > 0 ? '+' : '' }}{{ currentDetail.freeze_point }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else>无</span>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="变动后积分余额">{{ currentDetail.after_point }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="变动后冻结余额">{{ currentDetail.after_freeze_point }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="变动类型">
|
|
|
|
|
+ <el-tag :type="getTypeTagType(currentDetail.type)" size="small">
|
|
|
|
|
+ {{ getTypeName(currentDetail.type) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="业务类型">
|
|
|
|
|
+ <el-tag type="info" size="small">
|
|
|
|
|
+ {{ getBizTypeName(currentDetail.biz_type) }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="业务ID">{{ currentDetail.biz_id || '无' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="创建时间">{{ currentDetail.create_time }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="备注" :span="2">{{ currentDetail.remark || '无' }}</el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 用户选择对话框 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="userDialogVisible"
|
|
|
|
|
+ title="选择用户"
|
|
|
|
|
+ width="80%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div style="margin-bottom: 20px;">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="userSearchKeyword"
|
|
|
|
|
+ placeholder="请输入用户ID、昵称或手机号进行搜索"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 300px;"
|
|
|
|
|
+ @keyup.enter="handleUserSearch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #append>
|
|
|
|
|
+ <el-button @click="handleUserSearch">搜索</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ <el-button @click="resetUserSearch" style="margin-left: 10px;">重置</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table :data="userList" @row-click="selectUser" v-loading="userLoading" max-height="400px">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
|
|
|
|
|
+ <el-table-column label="头像" min-width="80">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-avatar :size="40" :src="row.avatar" :alt="row.nickname">
|
|
|
|
|
+ <template #default>
|
|
|
|
|
+ <el-icon><User /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-avatar>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="nickname" label="昵称" min-width="80"></el-table-column>
|
|
|
|
|
+ <el-table-column prop="mobile" label="手机号" min-width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.mobile || '-' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="point" label="当前积分" min-width="100">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="point-value">{{ row.point || 0 }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="update_time" label="更新时间" min-width="200" />
|
|
|
|
|
+ <el-table-column label="操作" width="80" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" size="small" @click="selectUser(row)">选择</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ :current-page="userPagination.page"
|
|
|
|
|
+ :page-size="userPagination.limit"
|
|
|
|
|
+ :total="userPagination.total"
|
|
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next"
|
|
|
|
|
+ style="margin-top: 20px; text-align: right;"
|
|
|
|
|
+ @size-change="handleUserSizeChange"
|
|
|
|
|
+ @current-change="handleUserCurrentChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'PointsLog',
|
|
|
|
|
+ props: {
|
|
|
|
|
+ axiosInstance: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ // 搜索表单
|
|
|
|
|
+ searchForm: {
|
|
|
|
|
+ user_id: '',
|
|
|
|
|
+ type: '',
|
|
|
|
|
+ biz_type: '',
|
|
|
|
|
+ dateRange: []
|
|
|
|
|
+ },
|
|
|
|
|
+ // 分页
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ limit: 20,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+ },
|
|
|
|
|
+ // 数据
|
|
|
|
|
+ pointsList: [],
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ statsData: {},
|
|
|
|
|
+ // 枚举数据
|
|
|
|
|
+ pointsTypes: [
|
|
|
|
|
+ { value: 1, label: '新增' },
|
|
|
|
|
+ { value: 2, label: '减少' },
|
|
|
|
|
+ { value: 3, label: '对冲' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ bizTypes: [
|
|
|
|
|
+ { value: 1, label: '订单' },
|
|
|
|
|
+ { value: 2, label: '签到' },
|
|
|
|
|
+ { value: 3, label: '推荐奖励' },
|
|
|
|
|
+ { value: 4, label: '手动调整' },
|
|
|
|
|
+ { value: 5, label: '商城兑换' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ // 手动调整对话框
|
|
|
|
|
+ adjustDialogVisible: false,
|
|
|
|
|
+ adjustLoading: false,
|
|
|
|
|
+ adjustFormRef: null,
|
|
|
|
|
+ adjustForm: {
|
|
|
|
|
+ user_id: '',
|
|
|
|
|
+ point: 0,
|
|
|
|
|
+ freeze_point: 0,
|
|
|
|
|
+ biz_type: 4,
|
|
|
|
|
+ biz_id: '',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ adjustRules: {
|
|
|
|
|
+ user_id: [
|
|
|
|
|
+ { required: true, message: '请输入用户ID', trigger: 'blur' },
|
|
|
|
|
+ { pattern: /^\d+$/, message: '用户ID必须是数字', trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ point: [
|
|
|
|
|
+ { required: true, message: '请输入积分变动量', trigger: 'blur' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ biz_type: [
|
|
|
|
|
+ { required: true, message: '请选择业务类型', trigger: 'change' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ remark: [
|
|
|
|
|
+ { required: true, message: '请输入调整原因', trigger: 'blur' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ // 详情对话框
|
|
|
|
|
+ detailDialogVisible: false,
|
|
|
|
|
+ currentDetail: null,
|
|
|
|
|
+ // 用户选择对话框
|
|
|
|
|
+ userDialogVisible: false,
|
|
|
|
|
+ userSearchKeyword: '',
|
|
|
|
|
+ userList: [],
|
|
|
|
|
+ userLoading: false,
|
|
|
|
|
+ userPagination: {
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ limit: 20,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ // 添加计算属性用于显示用户昵称
|
|
|
|
|
+ selectedUserDisplay: {
|
|
|
|
|
+ get() {
|
|
|
|
|
+ if (!this.searchForm.user_id) return ''
|
|
|
|
|
+ const selectedUser = this.userList.find(user => user.id.toString() === this.searchForm.user_id)
|
|
|
|
|
+ return selectedUser ? selectedUser.nickname : this.searchForm.user_id
|
|
|
|
|
+ },
|
|
|
|
|
+ set(value) {
|
|
|
|
|
+ // 不需要设置逻辑,因为显示和实际值分离
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ getTypeName(type) {
|
|
|
|
|
+ const item = this.pointsTypes.find(item => item.value === type)
|
|
|
|
|
+ return item ? item.label : '未知'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getTypeTagType(type) {
|
|
|
|
|
+ const typeMap = {
|
|
|
|
|
+ 1: 'success', // 新增
|
|
|
|
|
+ 2: 'danger', // 减少
|
|
|
|
|
+ 3: 'warning' // 对冲
|
|
|
|
|
+ }
|
|
|
|
|
+ return typeMap[type] || 'info'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getBizTypeName(bizType) {
|
|
|
|
|
+ const item = this.bizTypes.find(item => item.value === bizType)
|
|
|
|
|
+ return item ? item.label : '未知'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 获取积分记录列表
|
|
|
|
|
+ async getUserPointLogs() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ page: this.pagination.page,
|
|
|
|
|
+ limit: this.pagination.limit,
|
|
|
|
|
+ ...this.searchForm
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理时间范围
|
|
|
|
|
+ if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
|
|
|
|
|
+ params.create_time = this.searchForm.dateRange
|
|
|
|
|
+ }
|
|
|
|
|
+ delete params.dateRange
|
|
|
|
|
+
|
|
|
|
|
+ const response = await this.axiosInstance.get('/points/user_point_log', { params })
|
|
|
|
|
+ this.pointsList = response.page.data || []
|
|
|
|
|
+ this.pagination.total = response.page.total || 0
|
|
|
|
|
+ this.statsData = response.data || {}
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取积分记录失败:', error)
|
|
|
|
|
+ this.$message.error('获取积分记录失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索
|
|
|
|
|
+ handleSearch() {
|
|
|
|
|
+ this.pagination.page = 1
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 重置
|
|
|
|
|
+ handleReset() {
|
|
|
|
|
+ Object.assign(this.searchForm, {
|
|
|
|
|
+ user_id: '',
|
|
|
|
|
+ type: '',
|
|
|
|
|
+ biz_type: '',
|
|
|
|
|
+ dateRange: []
|
|
|
|
|
+ })
|
|
|
|
|
+ this.pagination.page = 1
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 分页变化
|
|
|
|
|
+ handleSizeChange(newSize) {
|
|
|
|
|
+ this.pagination.limit = newSize
|
|
|
|
|
+ this.pagination.page = 1
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ handleCurrentChange(newPage) {
|
|
|
|
|
+ this.pagination.page = newPage
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 新增: 用户清除处理方法
|
|
|
|
|
+ handleUserClear() {
|
|
|
|
|
+ this.searchForm.user_id = ''
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 显示调整对话框
|
|
|
|
|
+ showAdjustDialog() {
|
|
|
|
|
+ Object.assign(this.adjustForm, {
|
|
|
|
|
+ user_id: '',
|
|
|
|
|
+ point: 0,
|
|
|
|
|
+ freeze_point: 0,
|
|
|
|
|
+ biz_type: 4,
|
|
|
|
|
+ biz_id: '',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+ })
|
|
|
|
|
+ this.adjustDialogVisible = true
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 手动调整积分
|
|
|
|
|
+ async handleAdjustPoints() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Note: In a real implementation, you'd need to properly validate the form
|
|
|
|
|
+ // This is a simplified version
|
|
|
|
|
+ this.adjustLoading = true
|
|
|
|
|
+
|
|
|
|
|
+ const data = { ...this.adjustForm }
|
|
|
|
|
+ if (!data.biz_id) {
|
|
|
|
|
+ delete data.biz_id
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await this.axiosInstance.post('/points/adjust_user_points', data)
|
|
|
|
|
+ this.$message.success('积分调整成功')
|
|
|
|
|
+ this.adjustDialogVisible = false
|
|
|
|
|
+ this.getUserPointLogs()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('积分调整失败:', error)
|
|
|
|
|
+ if (error.message) {
|
|
|
|
|
+ this.$message.error(error.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.adjustLoading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 显示详情
|
|
|
|
|
+ showDetailDialog(row) {
|
|
|
|
|
+ this.currentDetail = row
|
|
|
|
|
+ this.detailDialogVisible = true
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 显示用户选择对话框
|
|
|
|
|
+ showUserDialog() {
|
|
|
|
|
+ this.userDialogVisible = true
|
|
|
|
|
+ this.getUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 获取用户列表
|
|
|
|
|
+ async getUserList() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.userLoading = true
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ page: this.userPagination.page,
|
|
|
|
|
+ limit: this.userPagination.limit,
|
|
|
|
|
+ keyword: this.userSearchKeyword
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const response = await this.axiosInstance.get('/points/user_point', { params })
|
|
|
|
|
+ this.userList = response.page.data || []
|
|
|
|
|
+ this.userPagination.total = response.page.total || 0
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取用户列表失败:', error)
|
|
|
|
|
+ this.$message.error('获取用户列表失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.userLoading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索用户
|
|
|
|
|
+ handleUserSearch() {
|
|
|
|
|
+ this.userPagination.page = 1
|
|
|
|
|
+ this.getUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 重置用户搜索
|
|
|
|
|
+ resetUserSearch() {
|
|
|
|
|
+ this.userSearchKeyword = ''
|
|
|
|
|
+ this.userPagination.page = 1
|
|
|
|
|
+ this.getUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 选择用户
|
|
|
|
|
+ selectUser(user) {
|
|
|
|
|
+ this.searchForm.user_id = user.id.toString()
|
|
|
|
|
+ this.userDialogVisible = false
|
|
|
|
|
+ this.$message.success(`已选择用户: ${user.nickname} (ID: ${user.id})`)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 用户列表分页处理
|
|
|
|
|
+ handleUserSizeChange(newSize) {
|
|
|
|
|
+ this.userPagination.limit = newSize
|
|
|
|
|
+ this.userPagination.page = 1
|
|
|
|
|
+ this.getUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ handleUserCurrentChange(newPage) {
|
|
|
|
|
+ this.userPagination.page = newPage
|
|
|
|
|
+ this.getUserList()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 导出数据
|
|
|
|
|
+ async exportData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params = { ...this.searchForm }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理时间范围
|
|
|
|
|
+ if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
|
|
|
|
|
+ params.start_time = this.searchForm.dateRange[0]
|
|
|
|
|
+ params.end_time = this.searchForm.dateRange[1]
|
|
|
|
|
+ }
|
|
|
|
|
+ delete params.dateRange
|
|
|
|
|
+
|
|
|
|
|
+ const response = await this.axiosInstance.get('/points/export_logs', { params })
|
|
|
|
|
+
|
|
|
|
|
+ if (response.data.download_url) {
|
|
|
|
|
+ // 创建下载链接
|
|
|
|
|
+ const link = document.createElement('a')
|
|
|
|
|
+ link.href = response.data.download_url
|
|
|
|
|
+ link.download = response.data.filename || 'points_logs.xlsx'
|
|
|
|
|
+ document.body.appendChild(link)
|
|
|
|
|
+ link.click()
|
|
|
|
|
+ document.body.removeChild(link)
|
|
|
|
|
+ this.$message.success('导出成功')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('导出失败:', error)
|
|
|
|
|
+ this.$message.error('导出失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.points-management {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.header-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-bar {
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-cards .stats-card {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-cards .stats-card .stats-item .stats-label {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-cards .stats-card .stats-item .stats-value {
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-cards .stats-card .stats-item .stats-value.increase {
|
|
|
|
|
+ color: #67c23a;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-cards .stats-card .stats-item .stats-value.decrease {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.point-increase {
|
|
|
|
|
+ color: #67c23a;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.point-decrease {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.point-freeze {
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.freeze-point {
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ margin-left: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.freeze-balance {
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-form--inline .el-form-item) {
|
|
|
|
|
+ margin-right: 15px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table .el-table__cell) {
|
|
|
|
|
+ padding: 8px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-dialog .el-form-item) {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-dialog .el-form-item__label) {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.point-value {
|
|
|
|
|
+ color: #67c23a;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table .el-table__row) {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table .el-table__row:hover) {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-input.is-readonly .el-input__inner) {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|