PointsLog.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <div class="points-log admin-page">
  3. <el-card>
  4. <div slot="header" class="admin-page-header">
  5. <span class="admin-page-title">积分记录</span>
  6. <div class="admin-page-actions">
  7. <el-select v-model="filter.type" placeholder="类型筛选" size="mini" clearable @change="loadData">
  8. <el-option label="全部" value="" />
  9. <el-option label="任务奖励" value="task" />
  10. <el-option label="系统调整" value="adjust" />
  11. <el-option label="兑换扣除" value="exchange" />
  12. <el-option label="退款返还" value="refund" />
  13. <el-option label="超时扣分" value="timeout" />
  14. </el-select>
  15. <el-select v-model="filter.category" placeholder="分类筛选" size="mini" clearable @change="loadData">
  16. <el-option label="全部" value="" />
  17. <el-option label="任务积分" value="task" />
  18. <el-option label="系统积分" value="system" />
  19. </el-select>
  20. <el-input v-model="filter.childKeyword" placeholder="搜索孩子 ID..." prefix-icon="el-icon-search" size="mini" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
  21. </div>
  22. </div>
  23. <div class="table-scroll-wrap-sm">
  24. <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
  25. <el-table-column prop="createdAt" label="时间" width="160">
  26. <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
  27. </el-table-column>
  28. <el-table-column prop="childId" label="孩子ID" width="80" />
  29. <el-table-column label="类型" width="100">
  30. <template slot-scope="{ row }">{{ typeLabel(row.type) }}</template>
  31. </el-table-column>
  32. <el-table-column label="分类" width="100">
  33. <template slot-scope="{ row }">
  34. <el-tag :type="row.category === 'task' ? 'success' : 'warning'" size="mini">
  35. {{ row.category === 'task' ? '任务积分' : '系统积分' }}
  36. </el-tag>
  37. </template>
  38. </el-table-column>
  39. <el-table-column label="变动" width="100">
  40. <template slot-scope="{ row }">
  41. <span :style="{ color: row.amount > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
  42. {{ row.amount > 0 ? '+' : '' }}{{ row.amount }}
  43. </span>
  44. </template>
  45. </el-table-column>
  46. <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
  47. <el-table-column prop="expireAt" label="过期时间" width="160">
  48. <template slot-scope="{ row }">
  49. <span v-if="row.expireAt" :style="{ color: row.expired ? '#999' : '#E6A23C' }">
  50. {{ formatTime(row.expireAt) }}
  51. </span>
  52. <span v-else style="color:#999">永久有效</span>
  53. </template>
  54. </el-table-column>
  55. <el-table-column label="操作" width="200" fixed="right">
  56. <template slot-scope="{ row }">
  57. <el-button size="mini" type="primary" @click="handleDetail(row)">详情</el-button>
  58. <el-dropdown trigger="hover" @command="(cmd) => handleMoreCmd(row, cmd)">
  59. <el-button size="mini">
  60. 更多<i class="el-icon-arrow-down el-icon--right"></i>
  61. </el-button>
  62. <el-dropdown-menu slot="dropdown">
  63. <el-dropdown-item command="adjust" icon="el-icon-edit">手动调整</el-dropdown-item>
  64. </el-dropdown-menu>
  65. </el-dropdown>
  66. </template>
  67. </el-table-column>
  68. </el-table>
  69. </div>
  70. <el-pagination
  71. v-if="total > 0"
  72. @size-change="handleSizeChange"
  73. @current-change="handlePageChange"
  74. :current-page="filter.page"
  75. :page-sizes="[10, 20, 50]"
  76. :page-size="filter.size"
  77. layout="total, sizes, prev, pager, next, jumper"
  78. :total="total"
  79. class="pagination-wrap"
  80. />
  81. </el-card>
  82. <!-- 详情对话框 -->
  83. <el-dialog title="积分记录详情" :visible.sync="detailVisible" width="500px">
  84. <div v-if="currentRow" class="detail-content">
  85. <el-descriptions :column="2" border>
  86. <el-descriptions-item label="记录ID">{{ currentRow.id }}</el-descriptions-item>
  87. <el-descriptions-item label="孩子ID">{{ currentRow.childId }}</el-descriptions-item>
  88. <el-descriptions-item label="类型">{{ typeLabel(currentRow.type) }}</el-descriptions-item>
  89. <el-descriptions-item label="分类">
  90. <el-tag :type="currentRow.category === 'task' ? 'success' : 'warning'" size="mini">
  91. {{ currentRow.category === 'task' ? '任务积分' : '系统积分' }}
  92. </el-tag>
  93. </el-descriptions-item>
  94. <el-descriptions-item label="变动">
  95. <span :style="{ color: currentRow.amount > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
  96. {{ currentRow.amount > 0 ? '+' : '' }}{{ currentRow.amount }}
  97. </span>
  98. </el-descriptions-item>
  99. <el-descriptions-item label="过期时间">
  100. {{ currentRow.expireAt ? formatTime(currentRow.expireAt) : '永久有效' }}
  101. </el-descriptions-item>
  102. <el-descriptions-item label="描述" :span="2">{{ currentRow.description || '—' }}</el-descriptions-item>
  103. <el-descriptions-item label="创建时间" :span="2">{{ formatTime(currentRow.createdAt) }}</el-descriptions-item>
  104. </el-descriptions>
  105. </div>
  106. </el-dialog>
  107. </div>
  108. </template>
  109. <script>
  110. import { getPointsLogs } from '@/api/admin'
  111. const TYPE_MAP = {
  112. task: { label: '任务奖励', type: 'success' },
  113. adjust: { label: '系统调整', type: 'warning' },
  114. exchange: { label: '兑换扣除', type: 'danger' },
  115. refund: { label: '退款返还', type: 'info' },
  116. timeout: { label: '超时扣分', type: 'danger' }
  117. }
  118. export default {
  119. name: 'PointsLog',
  120. computed: {
  121. tableHeight() {
  122. return window.innerHeight - 300
  123. }
  124. },
  125. data() {
  126. return {
  127. loading: false,
  128. list: [],
  129. total: 0,
  130. filter: {
  131. type: '',
  132. category: '',
  133. childKeyword: '',
  134. page: 1,
  135. size: 10
  136. },
  137. detailVisible: false,
  138. currentRow: null
  139. }
  140. },
  141. created() {
  142. this.loadData()
  143. },
  144. methods: {
  145. typeLabel(type) {
  146. return (TYPE_MAP[type] && TYPE_MAP[type].label) || type || '—'
  147. },
  148. formatTime(t) {
  149. if (!t) return '-'
  150. return t.replace ? t.replace('T', ' ').substring(0, 19) : t
  151. },
  152. async loadData() {
  153. this.loading = true
  154. try {
  155. const params = {
  156. page: this.filter.page,
  157. size: this.filter.size
  158. }
  159. if (this.filter.type) params.type = this.filter.type
  160. if (this.filter.category) params.category = this.filter.category
  161. if (this.filter.childKeyword) {
  162. const childId = parseInt(this.filter.childKeyword)
  163. if (!isNaN(childId)) params.memberId = childId
  164. }
  165. const res = await getPointsLogs(params)
  166. this.list = (res.data && res.data.records) || []
  167. this.total = (res.data && res.data.total) || 0
  168. } catch (e) {
  169. this.$message && this.$message.error && this.$message.error('加载积分记录失败')
  170. } finally {
  171. this.loading = false
  172. }
  173. },
  174. handleSizeChange(size) {
  175. this.filter.size = size
  176. this.filter.page = 1
  177. this.loadData()
  178. },
  179. handlePageChange(page) {
  180. this.filter.page = page
  181. this.loadData()
  182. },
  183. handleSearch() {
  184. this.filter.page = 1
  185. this.loadData()
  186. },
  187. handleDetail(row) {
  188. this.currentRow = row
  189. this.detailVisible = true
  190. },
  191. handleMoreCmd(row, cmd) {
  192. switch (cmd) {
  193. case 'adjust':
  194. this.$message.info('请在 "积分管理" 页面中对指定孩子进行积分调整')
  195. break
  196. }
  197. }
  198. }
  199. }
  200. </script>
  201. <style scoped>
  202. .points-log { padding: 20px; }
  203. .detail-content { padding: 10px 0; }
  204. </style>