fix: check-in

This commit is contained in:
zzc
2026-02-25 15:20:30 +08:00
parent 8d47d6d494
commit 8dfd7612b1
2 changed files with 214 additions and 30 deletions

View File

@@ -1,8 +1,15 @@
import { request } from "@/utils/request.js"; import { request } from "@/utils/request.js";
export const getUserSignInfo = async (data) => { export const getUserSignInfo = async () => {
return request({ return request({
url: "/api/sign/info", url: "/api/sign/info",
method: "GET", method: "GET",
}); });
}; };
export const userSignIn = async () => {
return request({
url: "/api/sign/in",
method: "POST",
});
};

View File

@@ -67,18 +67,35 @@
signInfo.isSignedToday ? "今日已开启" : "开启今日好运" signInfo.isSignedToday ? "今日已开启" : "开启今日好运"
}}</text> }}</text>
</button> </button>
<view class="progress-section"> <view class="week-sign-section">
<view class="progress-info"> <view class="sign-header">
<text>已连续领好运 {{ continuousDays }} </text> <text class="sign-title">已连续签到 {{ continuousDays }} </text>
<text>{{ currentMonthDays }} / {{ totalMonthDays }}</text> <text class="sign-tip">连续7天得大奖</text>
</view> </view>
<view class="progress-bar-bg"> <view class="week-days">
<view <view
class="progress-bar-fill" class="day-item"
:style="{ v-for="(day, index) in weekDays"
width: (currentMonthDays / totalMonthDays) * 100 + '%', :key="index"
}" :class="{ 'is-today': day.isToday, 'is-signed': day.isSigned }"
></view> >
<text class="day-label">{{ day.label }}</text>
<view class="status-icon">
<uni-icons
v-if="day.isSigned"
type="checkmarkempty"
size="14"
color="#fff"
/>
<uni-icons
v-else-if="day.isToday && !day.isSigned"
type="plus"
size="14"
color="#d81e06"
/>
<text v-else class="dot"></text>
</view>
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -215,7 +232,7 @@ import { getStatusBarHeight } from "@/utils/system";
import { onShareAppMessage, onShareTimeline, onShow } from "@dcloudio/uni-app"; import { onShareAppMessage, onShareTimeline, onShow } from "@dcloudio/uni-app";
import { useUserStore } from "@/stores/user"; import { useUserStore } from "@/stores/user";
import { getRecommendList } from "@/api/system"; import { getRecommendList } from "@/api/system";
import { getUserSignInfo } from "@/api/user"; import { getUserSignInfo, userSignIn } from "@/api/user";
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue"; import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
import LuckyPopup from "@/components/LuckyPopup/LuckyPopup.vue"; import LuckyPopup from "@/components/LuckyPopup/LuckyPopup.vue";
@@ -228,6 +245,79 @@ const userInfo = computed(() => userStore?.userInfo || {});
const isLoggedIn = computed(() => !!userStore.userInfo.nickName); const isLoggedIn = computed(() => !!userStore.userInfo.nickName);
const signInfo = ref({}); // 用户签到信息 const signInfo = ref({}); // 用户签到信息
const weekDays = computed(() => {
const now = new Date();
const todayStr =
signInfo.value.today ||
`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const current = new Date(todayStr);
const day = current.getDay() || 7; // 1 (Mon) - 7 (Sun)
const monday = new Date(current);
monday.setDate(current.getDate() - day + 1);
const days = [];
const labels = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
for (let i = 0; i < 7; i++) {
const d = new Date(monday);
d.setDate(monday.getDate() + i);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const da = String(d.getDate()).padStart(2, "0");
const dateStr = `${y}-${m}-${da}`;
let isSigned = false;
const isToday = dateStr === todayStr;
const isFuture = dateStr > todayStr;
if (signInfo.value.continuousDays > 0) {
const continuous = signInfo.value.continuousDays;
const signedToday = signInfo.value.isSignedToday;
const streakEndDate = new Date(todayStr);
if (!signedToday) {
streakEndDate.setDate(streakEndDate.getDate() - 1);
}
const streakStartDate = new Date(streakEndDate);
streakStartDate.setDate(streakEndDate.getDate() - continuous + 1);
const checkDate = new Date(dateStr);
const checkTime = checkDate.getTime();
// Reset time components to avoid issues
const sTime = new Date(
streakStartDate.getFullYear(),
streakStartDate.getMonth(),
streakStartDate.getDate(),
).getTime();
const eTime = new Date(
streakEndDate.getFullYear(),
streakEndDate.getMonth(),
streakEndDate.getDate(),
).getTime();
const cTime = new Date(
checkDate.getFullYear(),
checkDate.getMonth(),
checkDate.getDate(),
).getTime();
if (cTime >= sTime && cTime <= eTime) {
isSigned = true;
}
}
days.push({
label: labels[i],
date: dateStr,
isToday,
isSigned,
isFuture,
});
}
return days;
});
const greetingText = computed(() => { const greetingText = computed(() => {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour < 6) return "凌晨好"; if (hour < 6) return "凌晨好";
@@ -331,12 +421,42 @@ const onWalletTap = () => {
uni.navigateTo({ url: "/pages/mine/vip" }); uni.navigateTo({ url: "/pages/mine/vip" });
}; };
const onOpenLucky = () => { const onOpenLucky = async () => {
if (!isLoggedIn.value) { if (!isLoggedIn.value) {
uni.$emit("show-login-popup"); uni.$emit("show-login-popup");
return; return;
} }
if (signInfo.value.isSignedToday) {
luckyPopupRef.value?.open(); luckyPopupRef.value?.open();
return;
}
uni.showLoading({ title: "开启好运...", mask: true });
try {
const res = await userSignIn();
if (res && res.success) {
signInfo.value.continuousDays = res.continuousDays;
signInfo.value.isSignedToday = true;
if (typeof res.totalDays === "number") {
signInfo.value.totalDays = res.totalDays;
} else {
signInfo.value.totalDays = (signInfo.value.totalDays || 0) + 1;
}
// Update user assets (points)
userStore.fetchUserAssets();
luckyPopupRef.value?.open();
} else {
uni.showToast({ title: "签到失败", icon: "none" });
}
} catch (e) {
console.error(e);
uni.showToast({ title: "网络错误,请稍后重试", icon: "none" });
} finally {
uni.hideLoading();
}
}; };
const navTo = (url) => { const navTo = (url) => {
@@ -554,27 +674,84 @@ onShareTimeline(() => {
} }
} }
.progress-section { .week-sign-section {
.progress-info { width: 100%;
.sign-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.sign-title {
font-size: 24rpx;
color: #a85a5a;
font-weight: bold;
}
.sign-tip {
font-size: 20rpx;
color: #a85a5a;
opacity: 0.7;
}
}
.week-days {
display: flex;
justify-content: space-between;
.day-item {
display: flex;
flex-direction: column;
align-items: center;
.day-label {
font-size: 20rpx; font-size: 20rpx;
color: #a85a5a; color: #a85a5a;
margin-bottom: 12rpx; margin-bottom: 12rpx;
opacity: 0.8; opacity: 0.8;
} }
.progress-bar-bg { .status-icon {
width: 100%; width: 44rpx;
height: 8rpx; height: 44rpx;
background: rgba(255, 255, 255, 0.3); border-radius: 50%;
border-radius: 4rpx; background: rgba(255, 255, 255, 0.4);
overflow: hidden; display: flex;
align-items: center;
justify-content: center;
.progress-bar-fill { .dot {
height: 100%; width: 8rpx;
background: #d81e06; // 或渐变色 height: 8rpx;
border-radius: 4rpx; border-radius: 50%;
background: #a85a5a;
opacity: 0.3;
}
}
&.is-today {
.day-label {
font-weight: bold;
opacity: 1;
}
.status-icon {
background: #fff;
box-shadow: 0 4rpx 12rpx rgba(216, 30, 6, 0.1);
}
}
&.is-signed {
.status-icon {
background: #d81e06;
box-shadow: 0 4rpx 12rpx rgba(216, 30, 6, 0.2);
}
.day-label {
color: #d81e06;
}
}
} }
} }
} }