qiangdanxitong/frontend/src/views/RewardRecords.vue
Your Name eeeba7b685 fix: 修复积分显示的浮点数精度问题
使用 Math.round(score * 100) / 100 避免 JavaScript 浮点数计算误差
例如 0.1 × 46 现在正确显示为 4.60 而不是 4.600000000000005
2026-01-15 15:47:59 +08:00

273 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<DefaultLayout>
<div class="reward-records-page">
<div class="container">
<!-- 页面标题 -->
<div class="page-header">
<a-button type="text" @click="router.back()" class="back-btn">
<Icon name="chevronLeft" size="md" color="#AFAFAF" />
</a-button>
<div class="header-info">
<h1 class="page-title">
<Icon name="clock" size="xl" color="#31f08b" style="margin-right: 12px;" />
奖励记录
</h1>
<p class="page-desc">查看你获得的所有奖励</p>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-value">{{ totalScore }}</div>
<div class="stat-label">累计获得积分</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ totalCount }}</div>
<div class="stat-label">获奖次数</div>
</div>
</div>
<!-- 记录列表 -->
<div class="records-section">
<a-table
:columns="columns"
:data-source="records"
:loading="loading"
:pagination="{
current: currentPage,
pageSize: pageSize,
total: total,
onChange: handlePageChange,
showSizeChanger: false
}"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'rewardType'">
<a-tag :color="getTypeColor(record.rewardType)">
{{ getTypeName(record.rewardType) }}
</a-tag>
</template>
<template v-if="column.key === 'rewardValue'">
<span class="reward-value">{{ parseRewardValue(record.rewardValue) }}</span>
</template>
<template v-if="column.key === 'createTime'">
{{ formatTime(record.createTime) }}
</template>
</template>
</a-table>
</div>
</div>
</div>
</DefaultLayout>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import Icon from '@/components/Icon.vue'
import dayjs from 'dayjs'
import {
getMyRewardRecords,
type RewardRecord
} from '@/api/rewardRule'
const router = useRouter()
const loading = ref(false)
const records = ref<RewardRecord[]>([])
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
const columns = [
{ title: '奖励规则', dataIndex: 'ruleName', key: 'ruleName' },
{ title: '奖励类型', dataIndex: 'rewardType', key: 'rewardType', width: 120 },
{ title: '奖励内容', dataIndex: 'rewardValue', key: 'rewardValue', width: 150 },
{ title: '获得时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
]
const totalScore = computed(() => {
const total = records.value.reduce((sum, r) => {
try {
const obj = JSON.parse(r.rewardValue)
return sum + (obj.score || 0)
} catch {
return sum
}
}, 0)
return formatScore(total)
})
// 格式化积分(保留两位小数,修复浮点数精度问题)
const formatScore = (score: number | null | undefined): string => {
if (score === null || score === undefined) return '0.00'
// 先乘以100,四舍五入取整,再除以100,避免浮点数精度问题
return (Math.round(Number(score) * 100) / 100).toFixed(2)
}
const totalCount = computed(() => total.value)
const getTypeColor = (type: string) => {
const colorMap: Record<string, string> = {
score: 'green',
badge: 'gold',
title: 'purple',
exp: 'blue'
}
return colorMap[type] || 'default'
}
const getTypeName = (type: string) => {
const nameMap: Record<string, string> = {
score: '积分',
badge: '徽章',
title: '称号',
exp: '经验值'
}
return nameMap[type] || type
}
const parseRewardValue = (value: string) => {
try {
const obj = JSON.parse(value)
if (obj.score) return `+${formatScore(obj.score)} 积分`
if (obj.badge) return `徽章: ${obj.badge}`
if (obj.title) return `称号: ${obj.title}`
return value
} catch {
return value
}
}
const formatTime = (time: string) => {
return dayjs(time).format('YYYY-MM-DD HH:mm')
}
const handlePageChange = (page: number) => {
currentPage.value = page
loadData()
}
const loadData = async () => {
loading.value = true
try {
const result = await getMyRewardRecords({
page: currentPage.value,
pageSize: pageSize.value
})
records.value = result.records || []
total.value = result.total || 0
} catch (error) {
console.error('加载奖励记录失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
@import "@/styles/variables.scss";
.reward-records-page {
min-height: 100vh;
padding: 24px;
background: $bg-primary;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.page-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 24px;
}
.back-btn {
margin-top: 4px;
}
.page-title {
font-size: 28px;
font-weight: 600;
color: $text-primary;
display: flex;
align-items: center;
margin-bottom: 8px;
}
.page-desc {
color: $text-secondary;
font-size: 14px;
}
.stats-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 32px;
}
.stat-card {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: 12px;
padding: 24px;
text-align: center;
.stat-value {
font-size: 36px;
font-weight: 700;
color: $brand-primary;
}
.stat-label {
font-size: 14px;
color: $text-secondary;
margin-top: 8px;
}
}
.records-section {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: 12px;
padding: 20px;
}
.reward-value {
color: $brand-primary;
font-weight: 600;
}
:deep(.ant-table) {
background: transparent;
.ant-table-thead > tr > th {
background: $bg-secondary;
border-bottom: 1px solid $border-primary;
color: $text-secondary;
}
.ant-table-tbody > tr > td {
border-bottom: 1px solid $border-primary;
color: $text-primary;
}
.ant-table-tbody > tr:hover > td {
background: $bg-secondary;
}
}
</style>