feat: 新增翻牌幸运值悬浮球及相关逻辑
- .claude/settings.local.json:新增 Read 权限路径(Read(//e/code/github/Yi/Yi.Ai.Vue3/**)) - Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue: - 新增 luckyValue 响应式状态与 updateLuckyValue() 方法,并在获取任务状态后更新幸运值 - 新增悬浮球 UI(SVG 进度环、图标、百分比文本)及样式和动画 - 调整了 v-loading 为 false,并注释了部分错误提示(可能为调试遗留) - 说明:样式使用嵌套写法(scss/sass 风格),请确认构建流程支持;建议确认 v-loading 与错误提示变更是否为预期并视情况修正。
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(dotnet build \"E:\\code\\github\\Yi\\Yi.Abp.Net8\\module\\ai-hub\\Yi.Framework.AiHub.Application\\Yi.Framework.AiHub.Application.csproj\" --no-restore)"
|
"Bash(dotnet build \"E:\\code\\github\\Yi\\Yi.Abp.Net8\\module\\ai-hub\\Yi.Framework.AiHub.Application\\Yi.Framework.AiHub.Application.csproj\" --no-restore)",
|
||||||
|
"Read(//e/code/github/Yi/Yi.Ai.Vue3/**)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ const showInviteSection = ref(false);
|
|||||||
// 翻牌动画状态
|
// 翻牌动画状态
|
||||||
const flippingCards = ref<Set<number>>(new Set());
|
const flippingCards = ref<Set<number>>(new Set());
|
||||||
|
|
||||||
|
// 幸运值状态
|
||||||
|
const luckyValue = ref(0); // 当前幸运值 0-100
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchTaskStatus();
|
fetchTaskStatus();
|
||||||
});
|
});
|
||||||
@@ -35,6 +38,9 @@ async function fetchTaskStatus() {
|
|||||||
try {
|
try {
|
||||||
const res = await getWeeklyTaskStatus();
|
const res = await getWeeklyTaskStatus();
|
||||||
taskData.value = res.data;
|
taskData.value = res.data;
|
||||||
|
|
||||||
|
// 根据已翻牌次数更新幸运值
|
||||||
|
updateLuckyValue();
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
ElMessage.error(error?.message || '获取任务状态失败');
|
ElMessage.error(error?.message || '获取任务状态失败');
|
||||||
@@ -49,6 +55,15 @@ async function fetchTaskStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新幸运值
|
||||||
|
function updateLuckyValue() {
|
||||||
|
if (taskData.value) {
|
||||||
|
const flips = taskData.value.totalFlips;
|
||||||
|
// 每次翻牌增加10%幸运值
|
||||||
|
luckyValue.value = Math.min(flips * 12, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 翻牌
|
// 翻牌
|
||||||
async function handleFlipCard(record: CardFlipRecord) {
|
async function handleFlipCard(record: CardFlipRecord) {
|
||||||
// 检查是否可以翻牌(任何未翻过的牌都可以)
|
// 检查是否可以翻牌(任何未翻过的牌都可以)
|
||||||
@@ -183,7 +198,7 @@ async function handleUseInviteCode() {
|
|||||||
await fetchTaskStatus();
|
await fetchTaskStatus();
|
||||||
}
|
}
|
||||||
catch (error: any) {
|
catch (error: any) {
|
||||||
ElMessage.error(error?.message || '使用邀请码失败');
|
// ElMessage.error(error?.message || '使用邀请码失败');
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -261,7 +276,39 @@ function toggleInviteSection() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-loading="loading" class="card-flip-container">
|
<div v-loading="false" class="card-flip-container">
|
||||||
|
<!-- 幸运值悬浮球 -->
|
||||||
|
<div class="lucky-float-ball" :class="{ 'lucky-full': luckyValue >= 100 }">
|
||||||
|
<div class="lucky-circle">
|
||||||
|
<svg class="lucky-progress" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="luckyGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle
|
||||||
|
class="lucky-bg"
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="40"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
class="lucky-bar"
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="40"
|
||||||
|
:stroke-dasharray="`${luckyValue * 2.51} 251`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="lucky-content">
|
||||||
|
<div class="lucky-icon">{{ luckyValue >= 100 ? '🍀' : '✨' }}</div>
|
||||||
|
<div class="lucky-text">{{ luckyValue }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lucky-label">翻牌幸运值</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 初始加载骨架屏 -->
|
<!-- 初始加载骨架屏 -->
|
||||||
<div v-if="initialLoading" class="loading-skeleton">
|
<div v-if="initialLoading" class="loading-skeleton">
|
||||||
<div class="skeleton-stats">
|
<div class="skeleton-stats">
|
||||||
@@ -545,6 +592,128 @@ function toggleInviteSection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 幸运值悬浮球 */
|
||||||
|
.lucky-float-ball {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
/* left: 20px; */
|
||||||
|
/* top: 20px; */
|
||||||
|
z-index: 999;
|
||||||
|
bottom: 20px;
|
||||||
|
transition: all 0.3s
|
||||||
|
cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lucky-full {
|
||||||
|
animation: lucky-celebration 2s ease-in-out infinite;
|
||||||
|
|
||||||
|
.lucky-circle {
|
||||||
|
box-shadow: 0 0 20px rgba(255, 215, 0, 0.8), 0 0 40px rgba(255, 215, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-content {
|
||||||
|
animation: lucky-pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-circle {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2), 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-progress {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
|
||||||
|
.lucky-bg {
|
||||||
|
fill: none;
|
||||||
|
stroke: rgba(102, 126, 234, 0.1);
|
||||||
|
stroke-width: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-bar {
|
||||||
|
fill: none;
|
||||||
|
stroke: url(#luckyGradient);
|
||||||
|
stroke-width: 6;
|
||||||
|
stroke-linecap: round;
|
||||||
|
transition: stroke-dasharray 0.5s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
.lucky-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lucky-label {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(255, 255, 255, 0.95);
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lucky-celebration {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: scale(1.1) rotate(-5deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.15) rotate(0deg);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: scale(1.1) rotate(5deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lucky-pulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 顶部统计栏 */
|
/* 顶部统计栏 */
|
||||||
.top-stats {
|
.top-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user