PointsLog.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. <template>
  2. <div class="points-management">
  3. <el-card>
  4. <template #header>
  5. <div class="card-header">
  6. <span>积分明细管理</span>
  7. <div class="header-actions">
  8. <!-- <el-button type="primary" @click="showAdjustDialog">手动调整积分</el-button>
  9. <el-button @click="exportData">导出数据</el-button>-->
  10. </div>
  11. </div>
  12. </template>
  13. <!-- 搜索栏 -->
  14. <div class="search-bar">
  15. <el-form :model="searchForm" inline>
  16. <el-form-item label="用户">
  17. <el-input
  18. v-model="selectedUserDisplay"
  19. placeholder="点击选择用户"
  20. clearable
  21. style="width: 150px;"
  22. @click="showUserDialog"
  23. @clear="handleUserClear"
  24. >
  25. <!-- <template #suffix>
  26. <el-icon style="cursor: pointer;">
  27. <Search />
  28. </el-icon>
  29. </template>-->
  30. </el-input>
  31. </el-form-item>
  32. <el-form-item label="积分类型">
  33. <el-select
  34. v-model="searchForm.type"
  35. placeholder="请选择类型"
  36. clearable
  37. style="width: 150px;"
  38. >
  39. <el-option
  40. v-for="item in pointsTypes"
  41. :key="item.value"
  42. :label="item.label"
  43. :value="item.value"
  44. />
  45. </el-select>
  46. </el-form-item>
  47. <!-- <el-form-item label="业务类型">
  48. <el-select
  49. v-model="searchForm.biz_type"
  50. placeholder="请选择业务类型"
  51. clearable
  52. style="width: 150px;"
  53. >
  54. <el-option
  55. v-for="item in bizTypes"
  56. :key="item.value"
  57. :label="item.label"
  58. :value="item.value"
  59. />
  60. </el-select>
  61. </el-form-item>-->
  62. <el-form-item label="时间范围">
  63. <el-date-picker
  64. v-model="searchForm.dateRange"
  65. type="datetimerange"
  66. range-separator="至"
  67. start-placeholder="开始时间"
  68. end-placeholder="结束时间"
  69. format="YYYY-MM-DD HH:mm:ss"
  70. value-format="YYYY-MM-DD HH:mm:ss"
  71. style="width: 300px;"
  72. />
  73. </el-form-item>
  74. <el-form-item>
  75. <el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button>
  76. <el-button @click="handleReset">重置</el-button>
  77. </el-form-item>
  78. </el-form>
  79. </div>
  80. <!-- 统计卡片 -->
  81. <div class="stats-cards">
  82. <el-row :gutter="20" style="margin-bottom: 20px;">
  83. <el-col :span="6">
  84. <el-card class="stats-card">
  85. <div class="stats-item">
  86. <div class="stats-label">积分发放</div>
  87. <div class="stats-value increase">+{{ statsData.positive_total || 0 }}</div>
  88. </div>
  89. </el-card>
  90. </el-col>
  91. <el-col :span="6">
  92. <el-card class="stats-card">
  93. <div class="stats-item">
  94. <div class="stats-label">积分消耗</div>
  95. <div class="stats-value decrease">{{ statsData.negative_total || 0 }}</div>
  96. </div>
  97. </el-card>
  98. </el-col>
  99. </el-row>
  100. </div>
  101. <!-- 积分记录表格 -->
  102. <el-table
  103. :data="pointsList"
  104. border
  105. style="width: 100%"
  106. v-loading="loading"
  107. row-key="id"
  108. >
  109. <el-table-column prop="id" label="记录ID" width="80" />
  110. <el-table-column prop="user_id" label="用户ID" width="100" />
  111. <el-table-column prop="user.nickname" label="用户名" width="100" />
  112. <el-table-column label="积分变动" width="150">
  113. <template #default="{ row }">
  114. <span
  115. :class="{
  116. 'point-increase': row.point > 0,
  117. 'point-decrease': row.point < 0,
  118. 'point-freeze': row.freeze_point !== 0
  119. }"
  120. >
  121. {{ row.point > 0 ? '+' : '' }}{{ row.point }}
  122. <span v-if="row.freeze_point !== 0" class="freeze-point">
  123. (冻结: {{ row.freeze_point > 0 ? '+' : '' }}{{ row.freeze_point }})
  124. </span>
  125. </span>
  126. </template>
  127. </el-table-column>
  128. <el-table-column label="变动后余额" width="150">
  129. <template #default="{ row }">
  130. <div>
  131. <div>积分: {{ row.after_point }}</div>
  132. <div v-if="row.after_freeze_point !== 0" class="freeze-balance">
  133. 冻结: {{ row.after_freeze_point }}
  134. </div>
  135. </div>
  136. </template>
  137. </el-table-column>
  138. <el-table-column prop="type" label="变动类型" width="120">
  139. <template #default="{ row }">
  140. <el-tag
  141. :type="getTypeTagType(row.type)"
  142. size="small"
  143. >
  144. {{ getTypeName(row.type) }}
  145. </el-tag>
  146. </template>
  147. </el-table-column>
  148. <!-- <el-table-column prop="biz_type" label="业务类型" width="120">
  149. <template #default="{ row }">
  150. <el-tag
  151. type="info"
  152. size="small"
  153. >
  154. {{ getBizTypeName(row.biz_type) }}
  155. </el-tag>
  156. </template>
  157. </el-table-column>
  158. <el-table-column prop="biz_id" label="业务ID" width="100" />-->
  159. <el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
  160. <el-table-column prop="create_time" label="创建时间" width="180" />
  161. <!-- <el-table-column label="操作" width="120" fixed="right">
  162. <template #default="{ row }">
  163. <el-button
  164. type="text"
  165. size="small"
  166. @click="showDetailDialog(row)"
  167. >
  168. 查看详情
  169. </el-button>
  170. </template>
  171. </el-table-column>-->
  172. </el-table>
  173. <!-- 分页 -->
  174. <el-pagination
  175. :current-page="pagination.page"
  176. :page-size="pagination.limit"
  177. :total="pagination.total"
  178. :page-sizes="[10, 20, 50, 100]"
  179. layout="total, sizes, prev, pager, next, jumper"
  180. style="margin-top: 20px; text-align: right;"
  181. @size-change="handleSizeChange"
  182. @current-change="handleCurrentChange"
  183. />
  184. </el-card>
  185. <!-- 手动调整积分对话框 -->
  186. <el-dialog
  187. v-model="adjustDialogVisible"
  188. title="手动调整积分"
  189. width="500px"
  190. >
  191. <el-form
  192. ref="adjustFormRef"
  193. :model="adjustForm"
  194. :rules="adjustRules"
  195. label-width="100px"
  196. >
  197. <el-form-item label="用户ID" prop="user_id">
  198. <el-input
  199. v-model="adjustForm.user_id"
  200. placeholder="请输入用户ID"
  201. />
  202. </el-form-item>
  203. <el-form-item label="积分变动" prop="point">
  204. <el-input-number
  205. v-model="adjustForm.point"
  206. :min="-999999"
  207. :max="999999"
  208. placeholder="正数为增加,负数为减少"
  209. style="width: 100%;"
  210. />
  211. </el-form-item>
  212. <el-form-item label="冻结积分" prop="freeze_point">
  213. <el-input-number
  214. v-model="adjustForm.freeze_point"
  215. :min="-999999"
  216. :max="999999"
  217. placeholder="冻结积分变动量"
  218. style="width: 100%;"
  219. />
  220. </el-form-item>
  221. <el-form-item label="业务类型" prop="biz_type">
  222. <el-select
  223. v-model="adjustForm.biz_type"
  224. placeholder="请选择业务类型"
  225. style="width: 100%;"
  226. >
  227. <el-option
  228. v-for="item in bizTypes"
  229. :key="item.value"
  230. :label="item.label"
  231. :value="item.value"
  232. />
  233. </el-select>
  234. </el-form-item>
  235. <el-form-item label="业务ID" prop="biz_id">
  236. <el-input
  237. v-model="adjustForm.biz_id"
  238. placeholder="关联的业务ID(选填)"
  239. />
  240. </el-form-item>
  241. <el-form-item label="备注" prop="remark">
  242. <el-input
  243. v-model="adjustForm.remark"
  244. type="textarea"
  245. :rows="3"
  246. placeholder="请输入调整原因"
  247. />
  248. </el-form-item>
  249. </el-form>
  250. <template #footer>
  251. <span class="dialog-footer">
  252. <el-button @click="adjustDialogVisible = false">取消</el-button>
  253. <el-button
  254. type="primary"
  255. @click="handleAdjustPoints"
  256. :loading="adjustLoading"
  257. >
  258. 确定
  259. </el-button>
  260. </span>
  261. </template>
  262. </el-dialog>
  263. <!-- 详情对话框 -->
  264. <el-dialog
  265. v-model="detailDialogVisible"
  266. title="积分记录详情"
  267. width="600px"
  268. >
  269. <el-descriptions :column="2" border v-if="currentDetail">
  270. <el-descriptions-item label="记录ID">{{ currentDetail.id }}</el-descriptions-item>
  271. <el-descriptions-item label="用户ID">{{ currentDetail.user_id }}</el-descriptions-item>
  272. <el-descriptions-item label="积分变动">
  273. <span
  274. :class="{
  275. 'point-increase': currentDetail.point > 0,
  276. 'point-decrease': currentDetail.point < 0
  277. }"
  278. >
  279. {{ currentDetail.point > 0 ? '+' : '' }}{{ currentDetail.point }}
  280. </span>
  281. </el-descriptions-item>
  282. <el-descriptions-item label="冻结积分变动">
  283. <span v-if="currentDetail.freeze_point !== 0">
  284. {{ currentDetail.freeze_point > 0 ? '+' : '' }}{{ currentDetail.freeze_point }}
  285. </span>
  286. <span v-else>无</span>
  287. </el-descriptions-item>
  288. <el-descriptions-item label="变动后积分余额">{{ currentDetail.after_point }}</el-descriptions-item>
  289. <el-descriptions-item label="变动后冻结余额">{{ currentDetail.after_freeze_point }}</el-descriptions-item>
  290. <el-descriptions-item label="变动类型">
  291. <el-tag :type="getTypeTagType(currentDetail.type)" size="small">
  292. {{ getTypeName(currentDetail.type) }}
  293. </el-tag>
  294. </el-descriptions-item>
  295. <el-descriptions-item label="业务类型">
  296. <el-tag type="info" size="small">
  297. {{ getBizTypeName(currentDetail.biz_type) }}
  298. </el-tag>
  299. </el-descriptions-item>
  300. <el-descriptions-item label="业务ID">{{ currentDetail.biz_id || '无' }}</el-descriptions-item>
  301. <el-descriptions-item label="创建时间">{{ currentDetail.create_time }}</el-descriptions-item>
  302. <el-descriptions-item label="备注" :span="2">{{ currentDetail.remark || '无' }}</el-descriptions-item>
  303. </el-descriptions>
  304. </el-dialog>
  305. <!-- 用户选择对话框 -->
  306. <el-dialog
  307. v-model="userDialogVisible"
  308. title="选择用户"
  309. width="80%"
  310. >
  311. <div style="margin-bottom: 20px;">
  312. <el-input
  313. v-model="userSearchKeyword"
  314. placeholder="请输入用户ID、昵称或手机号进行搜索"
  315. clearable
  316. style="width: 300px;"
  317. @keyup.enter="handleUserSearch"
  318. >
  319. <template #append>
  320. <el-button @click="handleUserSearch">搜索</el-button>
  321. </template>
  322. </el-input>
  323. <el-button @click="resetUserSearch" style="margin-left: 10px;">重置</el-button>
  324. </div>
  325. <el-table :data="userList" @row-click="selectUser" v-loading="userLoading" max-height="400px">
  326. <el-table-column prop="id" label="ID" min-width="80"></el-table-column>
  327. <el-table-column label="头像" min-width="80">
  328. <template #default="{ row }">
  329. <el-avatar :size="40" :src="row.avatar" :alt="row.nickname">
  330. <template #default>
  331. <el-icon><User /></el-icon>
  332. </template>
  333. </el-avatar>
  334. </template>
  335. </el-table-column>
  336. <el-table-column prop="nickname" label="昵称" min-width="80"></el-table-column>
  337. <el-table-column prop="mobile" label="手机号" min-width="120">
  338. <template #default="{ row }">
  339. {{ row.mobile || '-' }}
  340. </template>
  341. </el-table-column>
  342. <el-table-column prop="point" label="当前积分" min-width="100">
  343. <template #default="{ row }">
  344. <span class="point-value">{{ row.point || 0 }}</span>
  345. </template>
  346. </el-table-column>
  347. <el-table-column prop="update_time" label="更新时间" min-width="200" />
  348. <el-table-column label="操作" width="80" fixed="right">
  349. <template #default="{ row }">
  350. <el-button type="primary" size="small" @click="selectUser(row)">选择</el-button>
  351. </template>
  352. </el-table-column>
  353. </el-table>
  354. <el-pagination
  355. :current-page="userPagination.page"
  356. :page-size="userPagination.limit"
  357. :total="userPagination.total"
  358. :page-sizes="[10, 20, 50]"
  359. layout="total, sizes, prev, pager, next"
  360. style="margin-top: 20px; text-align: right;"
  361. @size-change="handleUserSizeChange"
  362. @current-change="handleUserCurrentChange"
  363. />
  364. </el-dialog>
  365. </div>
  366. </template>
  367. <script>
  368. export default {
  369. name: 'PointsLog',
  370. props: {
  371. axiosInstance: {
  372. type: Object,
  373. default: null
  374. }
  375. },
  376. data() {
  377. return {
  378. // 搜索表单
  379. searchForm: {
  380. user_id: '',
  381. type: '',
  382. biz_type: '',
  383. dateRange: []
  384. },
  385. // 分页
  386. pagination: {
  387. page: 1,
  388. limit: 20,
  389. total: 0
  390. },
  391. // 数据
  392. pointsList: [],
  393. loading: false,
  394. statsData: {},
  395. // 枚举数据
  396. pointsTypes: [
  397. { value: 1, label: '新增' },
  398. { value: 2, label: '减少' },
  399. { value: 3, label: '对冲' }
  400. ],
  401. bizTypes: [
  402. { value: 1, label: '订单' },
  403. { value: 2, label: '签到' },
  404. { value: 3, label: '推荐奖励' },
  405. { value: 4, label: '手动调整' },
  406. { value: 5, label: '商城兑换' }
  407. ],
  408. // 手动调整对话框
  409. adjustDialogVisible: false,
  410. adjustLoading: false,
  411. adjustFormRef: null,
  412. adjustForm: {
  413. user_id: '',
  414. point: 0,
  415. freeze_point: 0,
  416. biz_type: 4,
  417. biz_id: '',
  418. remark: ''
  419. },
  420. adjustRules: {
  421. user_id: [
  422. { required: true, message: '请输入用户ID', trigger: 'blur' },
  423. { pattern: /^\d+$/, message: '用户ID必须是数字', trigger: 'blur' }
  424. ],
  425. point: [
  426. { required: true, message: '请输入积分变动量', trigger: 'blur' }
  427. ],
  428. biz_type: [
  429. { required: true, message: '请选择业务类型', trigger: 'change' }
  430. ],
  431. remark: [
  432. { required: true, message: '请输入调整原因', trigger: 'blur' }
  433. ]
  434. },
  435. // 详情对话框
  436. detailDialogVisible: false,
  437. currentDetail: null,
  438. // 用户选择对话框
  439. userDialogVisible: false,
  440. userSearchKeyword: '',
  441. userList: [],
  442. userLoading: false,
  443. userPagination: {
  444. page: 1,
  445. limit: 20,
  446. total: 0
  447. }
  448. }
  449. },
  450. computed: {
  451. // 添加计算属性用于显示用户昵称
  452. selectedUserDisplay: {
  453. get() {
  454. if (!this.searchForm.user_id) return ''
  455. const selectedUser = this.userList.find(user => user.id.toString() === this.searchForm.user_id)
  456. return selectedUser ? selectedUser.nickname : this.searchForm.user_id
  457. },
  458. set(value) {
  459. // 不需要设置逻辑,因为显示和实际值分离
  460. }
  461. }
  462. },
  463. mounted() {
  464. this.getUserPointLogs()
  465. },
  466. methods: {
  467. getTypeName(type) {
  468. const item = this.pointsTypes.find(item => item.value === type)
  469. return item ? item.label : '未知'
  470. },
  471. getTypeTagType(type) {
  472. const typeMap = {
  473. 1: 'success', // 新增
  474. 2: 'danger', // 减少
  475. 3: 'warning' // 对冲
  476. }
  477. return typeMap[type] || 'info'
  478. },
  479. getBizTypeName(bizType) {
  480. const item = this.bizTypes.find(item => item.value === bizType)
  481. return item ? item.label : '未知'
  482. },
  483. // 获取积分记录列表
  484. async getUserPointLogs() {
  485. try {
  486. this.loading = true
  487. const params = {
  488. page: this.pagination.page,
  489. limit: this.pagination.limit,
  490. ...this.searchForm
  491. }
  492. // 处理时间范围
  493. if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
  494. params.create_time = this.searchForm.dateRange
  495. }
  496. delete params.dateRange
  497. const response = await this.axiosInstance.get('/points/user_point_log', { params })
  498. this.pointsList = response.page.data || []
  499. this.pagination.total = response.page.total || 0
  500. this.statsData = response.data || {}
  501. } catch (error) {
  502. console.error('获取积分记录失败:', error)
  503. this.$message.error('获取积分记录失败')
  504. } finally {
  505. this.loading = false
  506. }
  507. },
  508. // 搜索
  509. handleSearch() {
  510. this.pagination.page = 1
  511. this.getUserPointLogs()
  512. },
  513. // 重置
  514. handleReset() {
  515. Object.assign(this.searchForm, {
  516. user_id: '',
  517. type: '',
  518. biz_type: '',
  519. dateRange: []
  520. })
  521. this.pagination.page = 1
  522. this.getUserPointLogs()
  523. },
  524. // 分页变化
  525. handleSizeChange(newSize) {
  526. this.pagination.limit = newSize
  527. this.pagination.page = 1
  528. this.getUserPointLogs()
  529. },
  530. handleCurrentChange(newPage) {
  531. this.pagination.page = newPage
  532. this.getUserPointLogs()
  533. },
  534. // 新增: 用户清除处理方法
  535. handleUserClear() {
  536. this.searchForm.user_id = ''
  537. },
  538. // 显示调整对话框
  539. showAdjustDialog() {
  540. Object.assign(this.adjustForm, {
  541. user_id: '',
  542. point: 0,
  543. freeze_point: 0,
  544. biz_type: 4,
  545. biz_id: '',
  546. remark: ''
  547. })
  548. this.adjustDialogVisible = true
  549. },
  550. // 手动调整积分
  551. async handleAdjustPoints() {
  552. try {
  553. // Note: In a real implementation, you'd need to properly validate the form
  554. // This is a simplified version
  555. this.adjustLoading = true
  556. const data = { ...this.adjustForm }
  557. if (!data.biz_id) {
  558. delete data.biz_id
  559. }
  560. await this.axiosInstance.post('/points/adjust_user_points', data)
  561. this.$message.success('积分调整成功')
  562. this.adjustDialogVisible = false
  563. this.getUserPointLogs()
  564. } catch (error) {
  565. console.error('积分调整失败:', error)
  566. if (error.message) {
  567. this.$message.error(error.message)
  568. }
  569. } finally {
  570. this.adjustLoading = false
  571. }
  572. },
  573. // 显示详情
  574. showDetailDialog(row) {
  575. this.currentDetail = row
  576. this.detailDialogVisible = true
  577. },
  578. // 显示用户选择对话框
  579. showUserDialog() {
  580. this.userDialogVisible = true
  581. this.getUserList()
  582. },
  583. // 获取用户列表
  584. async getUserList() {
  585. try {
  586. this.userLoading = true
  587. const params = {
  588. page: this.userPagination.page,
  589. limit: this.userPagination.limit,
  590. keyword: this.userSearchKeyword
  591. }
  592. const response = await this.axiosInstance.get('/points/user_point', { params })
  593. this.userList = response.page.data || []
  594. this.userPagination.total = response.page.total || 0
  595. } catch (error) {
  596. console.error('获取用户列表失败:', error)
  597. this.$message.error('获取用户列表失败')
  598. } finally {
  599. this.userLoading = false
  600. }
  601. },
  602. // 搜索用户
  603. handleUserSearch() {
  604. this.userPagination.page = 1
  605. this.getUserList()
  606. },
  607. // 重置用户搜索
  608. resetUserSearch() {
  609. this.userSearchKeyword = ''
  610. this.userPagination.page = 1
  611. this.getUserList()
  612. },
  613. // 选择用户
  614. selectUser(user) {
  615. this.searchForm.user_id = user.id.toString()
  616. this.userDialogVisible = false
  617. this.$message.success(`已选择用户: ${user.nickname} (ID: ${user.id})`)
  618. },
  619. // 用户列表分页处理
  620. handleUserSizeChange(newSize) {
  621. this.userPagination.limit = newSize
  622. this.userPagination.page = 1
  623. this.getUserList()
  624. },
  625. handleUserCurrentChange(newPage) {
  626. this.userPagination.page = newPage
  627. this.getUserList()
  628. },
  629. // 导出数据
  630. async exportData() {
  631. try {
  632. const params = { ...this.searchForm }
  633. // 处理时间范围
  634. if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
  635. params.start_time = this.searchForm.dateRange[0]
  636. params.end_time = this.searchForm.dateRange[1]
  637. }
  638. delete params.dateRange
  639. const response = await this.axiosInstance.get('/points/export_logs', { params })
  640. if (response.data.download_url) {
  641. // 创建下载链接
  642. const link = document.createElement('a')
  643. link.href = response.data.download_url
  644. link.download = response.data.filename || 'points_logs.xlsx'
  645. document.body.appendChild(link)
  646. link.click()
  647. document.body.removeChild(link)
  648. this.$message.success('导出成功')
  649. }
  650. } catch (error) {
  651. console.error('导出失败:', error)
  652. this.$message.error('导出失败')
  653. }
  654. }
  655. }
  656. }
  657. </script>
  658. <style scoped>
  659. .points-management {
  660. padding: 20px;
  661. }
  662. .card-header {
  663. display: flex;
  664. justify-content: space-between;
  665. align-items: center;
  666. font-weight: 600;
  667. font-size: 16px;
  668. }
  669. .header-actions {
  670. display: flex;
  671. gap: 10px;
  672. }
  673. .search-bar {
  674. background: #f5f7fa;
  675. padding: 15px;
  676. border-radius: 4px;
  677. margin-bottom: 20px;
  678. }
  679. .stats-cards .stats-card {
  680. text-align: center;
  681. }
  682. .stats-cards .stats-card .stats-item .stats-label {
  683. color: #909399;
  684. font-size: 14px;
  685. margin-bottom: 8px;
  686. }
  687. .stats-cards .stats-card .stats-item .stats-value {
  688. font-size: 24px;
  689. font-weight: bold;
  690. }
  691. .stats-cards .stats-card .stats-item .stats-value.increase {
  692. color: #67c23a;
  693. }
  694. .stats-cards .stats-card .stats-item .stats-value.decrease {
  695. color: #f56c6c;
  696. }
  697. .point-increase {
  698. color: #67c23a;
  699. font-weight: bold;
  700. }
  701. .point-decrease {
  702. color: #f56c6c;
  703. font-weight: bold;
  704. }
  705. .point-freeze {
  706. color: #e6a23c;
  707. }
  708. .freeze-point {
  709. color: #e6a23c;
  710. font-size: 12px;
  711. margin-left: 5px;
  712. }
  713. .freeze-balance {
  714. color: #e6a23c;
  715. font-size: 12px;
  716. }
  717. :deep(.el-form--inline .el-form-item) {
  718. margin-right: 15px;
  719. margin-bottom: 10px;
  720. }
  721. :deep(.el-table .el-table__cell) {
  722. padding: 8px 0;
  723. }
  724. :deep(.el-dialog .el-form-item) {
  725. margin-bottom: 20px;
  726. }
  727. :deep(.el-dialog .el-form-item__label) {
  728. font-weight: 500;
  729. }
  730. .dialog-footer {
  731. display: flex;
  732. justify-content: flex-end;
  733. gap: 10px;
  734. }
  735. .point-value {
  736. color: #67c23a;
  737. font-weight: 500;
  738. }
  739. :deep(.el-table .el-table__row) {
  740. cursor: pointer;
  741. }
  742. :deep(.el-table .el-table__row:hover) {
  743. background-color: #f5f7fa;
  744. }
  745. :deep(.el-input.is-readonly .el-input__inner) {
  746. cursor: pointer;
  747. }
  748. </style>