Files
schoolNews/schoolNewsWeb/src/views/user/user-center/MyAchievementsView.vue
2025-11-25 17:55:23 +08:00

320 lines
10 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>
<div class="my-achievements">
<div class="level-achieve">
<!-- 用户当前的等级成就和下级成就进度 -->
<h1>我的等级</h1>
<div class="level">
<!-- 合并图标 -->
<div class="level-icons">
<!-- 星星图标 -->
<img class="level-star" :src="currentLevelStarIconUrl" alt="level-star" />
<!-- 等级图标 -->
<img class="level-badge" :src="currentLevelIconUrl" alt="level-icon" />
</div>
<!-- 等级文字图标 -->
<img class="level-word" :src="currentLevelWordIconUrl" alt="level-word" />
<!-- 距离下级时间 -->
<span class="next-tip">{{ nextDeltaText }}</span>
<!-- 进度条 -->
<el-progress :percentage="nextProgress" :color="progressColor" :stroke-width="12" />
<div class="level-range">
<!-- 起始等级 -->
<span class="start-level">Lv.{{ currentLevelDisplay }}</span>
<!-- 下级等级 -->
<span class="next-level">{{ nextLevelDisplay }}</span>
</div>
</div>
</div>
<div class="badge-achieve">
<!-- 用户已获得的勋章成就 -->
<h1>我的勋章</h1>
<!-- 勋章列表 -->
<div class="achievement-list">
<!-- 单个勋章 -->
<div
v-for="(a, idx) in filteredBadgeAchievements"
:key="a.achievementID || idx"
class="achievement-item"
:class="{ obtained: a.obtained }"
>
<div class="achv-name-wrap">
<!-- span在img里面渲染效果 -->
<img src="@/assets/imgs/achievement-name.svg"/>
<span class="achv-name">{{ a.name }}</span>
</div>
<!-- 成就图标 -->
<img class="achv-icon" :src="getIconUrl(a.icon)" alt="achievement-icon"/>
<!-- 成就条件 -->
<span class="achv-cond">{{ formatConditionValue(a.conditionType, a.conditionValue) }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { Trophy, Check, InfoFilled, Calendar, Star, Present } from '@element-plus/icons-vue';
import { achievementApi } from '@/apis/achievement';
import type { AchievementVO } from '@/types';
import { AchievementEnumHelper } from '@/types/enums/achievement-enums';
import { AchievementType } from '@/types/enums';
import { getAchievementIconUrl, getLevelIconUrl, getLevelWordIconUrl, getLevelStarIconUrl } from '@/utils/iconUtils';
import { UserCenterLayout } from '@/views/user/user-center';
// 响应式数据
const loading = ref(false);
const achievements = ref<AchievementVO[]>([]);
const selectedType = ref<number | undefined>(undefined);
const showOnlyEarned = ref(false);
// 枚举选项
const achievementTypeOptions = AchievementEnumHelper.getAllAchievementTypeOptions();
// 进度条颜色
const progressColor = [
{ color: '#f56c6c', percentage: 30 },
{ color: '#e6a23c', percentage: 60 },
{ color: '#5cb87a', percentage: 100 }
];
// 已获得数量
const earnedCount = computed(() => {
return achievements.value.filter(a => a.obtained).length;
});
// 总数量
const totalCount = computed(() => {
return achievements.value.length;
});
// 完成率
const completionRate = computed(() => {
if (totalCount.value === 0) return 0;
return Math.round((earnedCount.value / totalCount.value) * 100);
});
// 等级类成就
const levelAchievements = computed(() => {
return achievements.value.filter(a => a.type === AchievementType.LEVEL);
});
// 当前等级成就:优先已获得的最高等级,否则取最低等级
const currentLevelAchievement = computed<AchievementVO | undefined>(() => {
if (levelAchievements.value.length === 0) return undefined;
const obtainedLevels = levelAchievements.value
.filter(a => a.obtained)
.sort((a, b) => (b.level || 0) - (a.level || 0));
if (obtainedLevels.length > 0) {
return obtainedLevels[0];
}
// 没有已获得等级时,取等级最低的作为当前目标
return [...levelAchievements.value].sort((a, b) => (a.level || 0) - (b.level || 0))[0];
});
// 下一级等级成就:当前等级的下一个等级
const nextLevelAchievement = computed<AchievementVO | undefined>(() => {
if (!currentLevelAchievement.value) return undefined;
const currentLevel = currentLevelAchievement.value.level || 1;
const sortedLevels = [...levelAchievements.value].sort((a, b) => (a.level || 0) - (b.level || 0));
return sortedLevels.find(a => (a.level || 0) > currentLevel);
});
// 当前等级数字与图标
const currentLevel = computed(() => currentLevelAchievement.value?.level ?? 1);
const currentLevelIconUrl = computed(() => getLevelIconUrl(currentLevel.value));
const currentLevelWordIconUrl = computed(() => getLevelWordIconUrl(currentLevel.value));
const currentLevelStarIconUrl = computed(() => getLevelStarIconUrl(currentLevel.value));
// 展示用文案与数值
const currentLevelDisplay = computed(() => formatLevelNumber(currentLevelAchievement.value?.level ?? 1));
const nextLevelDisplay = computed(() => {
return nextLevelAchievement.value ? `Lv.${formatLevelNumber(nextLevelAchievement.value.level)}` : '满级';
});
const nextProgress = computed(() => {
if (!nextLevelAchievement.value) return 100;
const p = nextLevelAchievement.value.progressPercentage;
if (typeof p === 'number') return Math.max(0, Math.min(100, p));
const cur = nextLevelAchievement.value.currentValue || 0;
const tar = nextLevelAchievement.value.targetValue || 0;
if (tar <= 0) return 0;
return Math.max(0, Math.min(100, Math.round((cur / tar) * 100)));
});
const nextDeltaText = computed(() => {
if (!nextLevelAchievement.value) return '已达最高等级';
const cur = nextLevelAchievement.value.currentValue || 0;
const tar = nextLevelAchievement.value.targetValue || 0;
const need = Math.max(0, tar - cur);
if (need <= 0) return '即将升级';
return `距离下级:还差 ${formatConditionValue(nextLevelAchievement.value.conditionType, need)}`;
});
// 勋章类成就
const badgeAchievements = computed(() => {
return achievements.value.filter(a => a.type === AchievementType.BADGE);
});
// 右侧展示用的勋章成就(支持仅显示已获得)
const filteredBadgeAchievements = computed(() => {
let result = badgeAchievements.value;
if (showOnlyEarned.value) {
result = result.filter(a => a.obtained);
}
// 排序:已获得的在前,按等级/ID 排序
return result.sort((a, b) => {
if (a.obtained !== b.obtained) {
return a.obtained ? -1 : 1;
}
return (a.level || 0) - (b.level || 0);
});
});
// 获取成就类型标签
function getAchievementTypeLabel(type?: number): string {
if (type === undefined) return '未知';
return AchievementEnumHelper.getAchievementTypeDescription(type);
}
// 格式化条件值显示
function formatConditionValue(conditionType?: number, conditionValue?: number): string {
if (conditionType === undefined || conditionValue === undefined) return '';
return AchievementEnumHelper.formatConditionValue(conditionType, conditionValue);
}
// 格式化等级显示(例如 1.20
function formatLevelNumber(level?: number | null): string {
if (level === undefined || level === null) return '';
const num = Number(level);
if (Number.isNaN(num)) {
return String(level);
}
// 保留两位小数,方便展示类似 Lv.1.20 的效果
return num.toFixed(2);
}
// 格式化日期
function formatDate(dateStr?: string): string {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
// 获取图标完整路径
const getIconUrl = getAchievementIconUrl;
// 筛选成就
function filterAchievements() {
// 触发计算属性重新计算
}
// 加载成就数据
async function loadAchievements() {
try {
loading.value = true;
const result = await achievementApi.getMyAchievements();
achievements.value = result.dataList || [];
} catch (error) {
console.error('加载成就数据失败:', error);
ElMessage.error('加载成就数据失败');
} finally {
loading.value = false;
}
}
onMounted(() => {
loadAchievements();
});
</script>
<style lang="scss" scoped>
.my-achievements {
// padding: 20px 0;
height: 100%;
box-sizing: border-box;
display: flex;
gap: 24px;
.level-achieve{
display: flex;
flex-direction: column;
width: 380px;
.level{
background: linear-gradient(180deg, rgba(245, 250, 255, 1) 0%, rgba(255, 255, 255, 1) 100%);
border: 1px solid #e6effa;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
.level-icons{
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 120px;
.level-star{ width: 100%; height: auto; }
.level-badge{ position: absolute; width: 72px; height: 72px; }
}
.level-word{ height: 28px; }
.next-tip{ color: #64748b; font-size: 13px; }
.level-range{ width: 100%; display: flex; justify-content: space-between; font-size: 12px; color: #64748b; }
}
}
.badge-achieve{
display: flex;
flex-direction: column;
.achievement-list{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
.achievement-item{
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px;
border: 1px solid #e6effa;
border-radius: 12px;
background: linear-gradient(180deg, rgba(223, 239, 255, 0.4) 0%, rgba(223, 239, 255, 0) 84%);
&.obtained{ box-shadow: 0 0 0 2px #5cb87a33 inset; }
.achv-name-wrap{
position: relative;
width: 140px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
img{ width: 100%; height: 100%; }
.achv-name{ position: absolute; font-size: 12px; color: #1f2937; white-space: nowrap; max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
}
.achv-icon{ width: 72px; height: 72px; }
.achv-cond{ font-size: 12px; color: #64748b; }
}
.acievement-item{
display: flex;
flex-direction: column;
}
}
}
}
</style>