fix: rating
This commit is contained in:
183
components/SuccessPopup/SuccessPopup.vue
Normal file
183
components/SuccessPopup/SuccessPopup.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<view class="success-popup" v-if="visible" @touchmove.stop.prevent>
|
||||||
|
<view class="mask" :class="{ 'mask-show': showAnim }" @tap="close"></view>
|
||||||
|
<view class="popup-content" :class="{ 'content-show': showAnim }">
|
||||||
|
<view class="icon-wrap">
|
||||||
|
<text class="emoji">{{ actionData?.emoji || '✨' }}</text>
|
||||||
|
<!-- 环形扩散特效 -->
|
||||||
|
<view class="ring ring-1"></view>
|
||||||
|
<view class="ring ring-2"></view>
|
||||||
|
<!-- 星星点缀 -->
|
||||||
|
<view class="sparkle s-1">✨</view>
|
||||||
|
<view class="sparkle s-2">✨</view>
|
||||||
|
<view class="sparkle s-3">✨</view>
|
||||||
|
</view>
|
||||||
|
<text class="title">评分成功</text>
|
||||||
|
<text class="subtitle">你已将TA评价为「{{ actionData?.label || '...' }}」</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const showAnim = ref(false);
|
||||||
|
const actionData = ref(null);
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
const show = (action) => {
|
||||||
|
actionData.value = action;
|
||||||
|
visible.value = true;
|
||||||
|
|
||||||
|
// 留一点时间让DOM渲染,然后添加动画类
|
||||||
|
setTimeout(() => {
|
||||||
|
showAnim.value = true;
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
// 2秒后自动关闭
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
close();
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
showAnim.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false;
|
||||||
|
}, 300); // 等待淡出动画结束
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
close
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.success-popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask.mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content {
|
||||||
|
position: relative;
|
||||||
|
width: 460rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 64rpx 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(20rpx);
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-content.content-show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
font-size: 100rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
animation: emojiBounce 1.2s infinite alternate ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% { transform: translateY(0) scale(1); }
|
||||||
|
100% { transform: translateY(-12rpx) scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #4d44f1;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-1 {
|
||||||
|
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-2 {
|
||||||
|
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite 0.8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0% { width: 100rpx; height: 100rpx; opacity: 0.8; }
|
||||||
|
100% { width: 260rpx; height: 260rpx; opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkle {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 32rpx;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-1 { top: -10rpx; right: -20rpx; animation: popStar 1.4s ease-out infinite 0.1s; }
|
||||||
|
.s-2 { bottom: 10rpx; left: -30rpx; animation: popStar 1.4s ease-out infinite 0.6s; font-size: 24rpx; }
|
||||||
|
.s-3 { top: 60rpx; left: -40rpx; animation: popStar 1.4s ease-out infinite 1.1s; font-size: 28rpx; }
|
||||||
|
|
||||||
|
@keyframes popStar {
|
||||||
|
0% { transform: scale(0) rotate(0deg); opacity: 0; }
|
||||||
|
30% { transform: scale(1.2) rotate(45deg); opacity: 1; }
|
||||||
|
80% { transform: scale(0) rotate(90deg); opacity: 0; }
|
||||||
|
100% { transform: scale(0) rotate(90deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #111;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -115,6 +115,9 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 评分成功特效弹窗 -->
|
||||||
|
<SuccessPopup ref="successPopupRef" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -123,6 +126,7 @@ import { ref } from 'vue';
|
|||||||
import { getStatusBarHeight } from "@/utils/system";
|
import { getStatusBarHeight } from "@/utils/system";
|
||||||
import { onLoad, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
|
import { onLoad, onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
|
||||||
import AttitudePanel from "@/components/AttitudePanel/AttitudePanel.vue";
|
import AttitudePanel from "@/components/AttitudePanel/AttitudePanel.vue";
|
||||||
|
import SuccessPopup from "@/components/SuccessPopup/SuccessPopup.vue";
|
||||||
import { fetchTopicRatingItems } from "@/api/topicItem";
|
import { fetchTopicRatingItems } from "@/api/topicItem";
|
||||||
import { FILE_BASE_URL } from "@/utils/constants";
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
import { topicItemScore } from "@/api/topicItem";
|
import { topicItemScore } from "@/api/topicItem";
|
||||||
@@ -131,6 +135,7 @@ const statusBarHeight = ref(getStatusBarHeight() || 44);
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const hasMore = ref(true);
|
const hasMore = ref(true);
|
||||||
const currentAction = ref('');
|
const currentAction = ref('');
|
||||||
|
const successPopupRef = ref(null);
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
@@ -152,9 +157,11 @@ const itemId = ref('');
|
|||||||
const handleActionChange = async (action) => {
|
const handleActionChange = async (action) => {
|
||||||
const originAction = detailData.value.userAction;
|
const originAction = detailData.value.userAction;
|
||||||
const originScore = detailData.value.score;
|
const originScore = detailData.value.score;
|
||||||
|
const isCancel = detailData.value.userAction === action.label;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
detailData.value.userAction = detailData.value.userAction === action.label ? '' : action.label;
|
detailData.value.userAction = isCancel ? '' : action.label;
|
||||||
currentAction.value = action.label;
|
currentAction.value = detailData.value.userAction;
|
||||||
|
|
||||||
const res = await topicItemScore({
|
const res = await topicItemScore({
|
||||||
topicId: detailData.value.topicId,
|
topicId: detailData.value.topicId,
|
||||||
@@ -162,11 +169,12 @@ const handleActionChange = async (action) => {
|
|||||||
scoreType: action.scoreType
|
scoreType: action.scoreType
|
||||||
});
|
});
|
||||||
detailData.value.score = res?.scoreAvg || originScore;
|
detailData.value.score = res?.scoreAvg || originScore;
|
||||||
|
|
||||||
|
// 如果是成功评分(非取消),展示特效弹窗
|
||||||
|
if (!isCancel && successPopupRef.value) {
|
||||||
|
successPopupRef.value.show(action);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.showToast({
|
|
||||||
title: '评分失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
detailData.value.userAction = originAction;
|
detailData.value.userAction = originAction;
|
||||||
detailData.value.score = originScore;
|
detailData.value.score = originScore;
|
||||||
currentAction.value = originAction;
|
currentAction.value = originAction;
|
||||||
|
|||||||
@@ -148,6 +148,8 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 评分成功特效弹窗 -->
|
||||||
|
<SuccessPopup ref="successPopupRef" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -158,6 +160,7 @@ import { onPullDownRefresh, onReachBottom, onLoad } from "@dcloudio/uni-app";
|
|||||||
import { fetchTopicDetail, fetchTopicRatingItems } from "@/api/topic";
|
import { fetchTopicDetail, fetchTopicRatingItems } from "@/api/topic";
|
||||||
import { FILE_BASE_URL } from "@/utils/constants";
|
import { FILE_BASE_URL } from "@/utils/constants";
|
||||||
import RatingCard from "@/components/RatingCard/RatingCard.vue";
|
import RatingCard from "@/components/RatingCard/RatingCard.vue";
|
||||||
|
import SuccessPopup from "@/components/SuccessPopup/SuccessPopup.vue";
|
||||||
import { topicItemScore } from "@/api/topicItem";
|
import { topicItemScore } from "@/api/topicItem";
|
||||||
// 状态栏高度处理
|
// 状态栏高度处理
|
||||||
const statusBarHeight = ref(getStatusBarHeight() || 44);
|
const statusBarHeight = ref(getStatusBarHeight() || 44);
|
||||||
@@ -166,6 +169,7 @@ const loading = ref(false);
|
|||||||
const hasMore = ref(true);
|
const hasMore = ref(true);
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const currentTopicId = ref(null);
|
const currentTopicId = ref(null);
|
||||||
|
const successPopupRef = ref(null);
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
uni.navigateBack({
|
uni.navigateBack({
|
||||||
@@ -287,19 +291,22 @@ const comments = ref([...mockComments]);
|
|||||||
const handleAction = async (item, action) => {
|
const handleAction = async (item, action) => {
|
||||||
const originAction = item.userAction;
|
const originAction = item.userAction;
|
||||||
const originScore = item.scoreAvg;
|
const originScore = item.scoreAvg;
|
||||||
|
const isCancel = item.userAction === action.label;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
item.userAction = item.userAction === action.label ? '' : action.label;
|
item.userAction = isCancel ? '' : action.label;
|
||||||
const res = await topicItemScore({
|
const res = await topicItemScore({
|
||||||
topicId: currentTopicId.value,
|
topicId: currentTopicId.value,
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
scoreType: action.scoreType
|
scoreType: action.scoreType
|
||||||
});
|
});
|
||||||
item.scoreAvg = res?.scoreAvg || originScore;
|
item.scoreAvg = res?.scoreAvg || originScore;
|
||||||
|
|
||||||
|
// 如果是成功评分(非取消),展示特效弹窗
|
||||||
|
if (!isCancel && successPopupRef.value) {
|
||||||
|
successPopupRef.value.show(action);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.showToast({
|
|
||||||
title: '评分失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
item.userAction = originAction;
|
item.userAction = originAction;
|
||||||
item.scoreAvg = originScore;
|
item.scoreAvg = originScore;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user