This commit is contained in:
左哥 2025-11-18 22:24:23 +08:00
parent 90dafc305d
commit c03d3149d2
2 changed files with 386 additions and 8 deletions

View File

@ -33,6 +33,48 @@
</view>
</view>
<view class="signin-card">
<view class="signin-header">
<view class="signin-headline">
<text class="signin-title">每日签到</text>
<text class="signin-desc">已连续签到 {{ signInfo.continuousDays }} </text>
</view>
<view class="signin-badge">+{{ signInfo.todayReward || 0 }} 积分</view>
</view>
<view class="signin-calendar">
<view class="signin-calendar-header">
<text class="signin-month">{{ signInfo.monthLabel }}</text>
<text class="signin-month-summary">本月已签 {{ signInfo.monthSignedDays || 0 }} </text>
</view>
<view class="signin-weekdays">
<text class="signin-weekday" v-for="label in weekdayLabels" :key="label">{{ label }}</text>
</view>
<view class="signin-calendar-grid">
<view class="signin-date" v-for="(day, index) in signCalendar" :key="index"
:class="[{ placeholder: day.isPlaceholder, signed: day.signed, today: day.isToday, missed: day.missed }]">
<text class="signin-date-number">{{ day.label }}</text>
</view>
</view>
</view>
<view class="signin-footer">
<view class="signin-stats">
<view class="signin-stat">
<text class="signin-stat-value">{{ signInfo.totalPoints }}</text>
<text class="signin-stat-label">累计积分</text>
</view>
<view class="signin-stat">
<text class="signin-stat-value">{{ signInfo.continuousDays }}</text>
<text class="signin-stat-label">连续天数</text>
</view>
</view>
<button class="signin-btn" :class="{ disabled: signInfo.signedToday || signLoading }"
@click="handleSignIn" :disabled="signInfo.signedToday || signLoading">
{{ signInfo.signedToday ? '今日已签到' : (signLoading ? '签到中...' : '立即签到') }}
</button>
<text class="signin-tip">{{ signInfo.nextRewardText || '坚持签到,更多奖励等你拿' }}</text>
</view>
</view>
<view class="tabs">
<view class="tab-item" v-for="(tab, index) in tabs" :key="tab" :class="{ active: index === currentTab }"
@click="handleTabClick(index)">
@ -186,6 +228,18 @@ export default {
return {
userInfo: {},
currentTab: 0,
signInfo: {
signedToday: false,
continuousDays: 0,
totalPoints: 0,
todayReward: 0,
nextRewardText: '',
monthLabel: '',
monthSignedDays: 0
},
signCalendar: [],
signLoading: false,
weekdayLabels: ['日', '一', '二', '三', '四', '五', '六'],
stats: [
{ icon: '🎁', value: '1280', label: '当前积分', color: '#7B43FF' },
{ icon: '🛍️', value: '23', label: '总订单数', color: '#2877FF' },
@ -315,6 +369,7 @@ export default {
return;
} else {
this.loadProfile();
this.loadSignInfo();
}
},
methods: {
@ -331,6 +386,140 @@ export default {
console.warn('加载个人信息失败', error);
}
},
loadSignInfo() {
try {
return request('xcx/sign_in', 'GET').then(res => {
const info = res || {};
const monthLabel = info.current_month || this.getMonthLabel();
const calendar = this.buildMonthlyCalendar(info.month_records || info.recent_records, monthLabel);
this.signCalendar = calendar;
const updatedSignInfo = {
...this.signInfo,
signedToday: !!info.signed_today,
continuousDays: info.continuous_days || 0,
totalPoints: info.total_points || 0,
todayReward: info.today_reward || 0,
nextRewardText: info.next_reward_text || '',
monthLabel,
monthSignedDays: calendar.filter(day => day.signed && !day.isPlaceholder).length
};
this.signInfo = updatedSignInfo;
if (this.stats && this.stats.length) {
const newPoints = updatedSignInfo.totalPoints;
this.$set(this.stats, 0, {
...this.stats[0],
value: newPoints ? String(newPoints) : this.stats[0].value
});
}
}).catch(error => {
console.warn('加载签到信息失败', error);
this.signCalendar = this.buildMonthlyCalendar();
this.signInfo = {
...this.signInfo,
monthLabel: this.getMonthLabel(),
monthSignedDays: 0
};
});
} catch (error) {
console.warn('获取签到状态异常', error);
this.signCalendar = this.buildMonthlyCalendar();
this.signInfo = {
...this.signInfo,
monthLabel: this.getMonthLabel(),
monthSignedDays: 0
};
}
},
buildMonthlyCalendar(list = [], referenceMonth = '') {
const today = new Date();
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
let year = today.getFullYear();
let month = today.getMonth() + 1;
if (referenceMonth && /^\d{4}-\d{2}$/.test(referenceMonth)) {
const parts = referenceMonth.split('-');
year = parseInt(parts[0], 10) || year;
month = parseInt(parts[1], 10) || month;
}
const firstDay = new Date(year, month - 1, 1);
const totalDays = new Date(year, month, 0).getDate();
const prefixDays = firstDay.getDay();
const recordMap = {};
if (Array.isArray(list)) {
list.forEach(item => {
const dateStr = item?.date || item?.day || item?.label;
if (dateStr) {
const status =
item?.checked ??
item?.signed ??
(typeof item?.status !== 'undefined' ? item.status === 1 : undefined) ??
item?.is_signed ??
false;
recordMap[dateStr] = !!status;
}
});
}
const calendar = [];
for (let i = 0; i < prefixDays; i++) {
calendar.push({ label: '', isPlaceholder: true });
}
for (let day = 1; day <= totalDays; day++) {
const dateStr = `${year}-${this.padZero(month)}-${this.padZero(day)}`;
const targetDate = new Date(year, month - 1, day);
const isToday = this.isSameDate(targetDate, todayStart);
const isFuture = targetDate.getTime() > todayStart.getTime();
const signed = !!recordMap[dateStr];
calendar.push({
label: day,
date: dateStr,
signed,
isToday,
isFuture,
isPlaceholder: false,
missed: !signed && !isFuture && !isToday
});
}
while (calendar.length % 7 !== 0) {
calendar.push({ label: '', isPlaceholder: true });
}
return calendar;
},
getMonthLabel() {
const today = new Date();
return `${today.getFullYear()}-${this.padZero(today.getMonth() + 1)}`;
},
padZero(num) {
return num < 10 ? `0${num}` : `${num}`;
},
isSameDate(dateA, dateB) {
return (
dateA.getFullYear() === dateB.getFullYear() &&
dateA.getMonth() === dateB.getMonth() &&
dateA.getDate() === dateB.getDate()
);
},
async handleSignIn() {
if (this.signInfo.signedToday || this.signLoading) {
return;
}
this.signLoading = true;
try {
const res = await request('xcx/sign_in', 'POST');
const rewardText = res?.message || '签到成功';
uni.showToast({
title: rewardText,
icon: 'success'
});
await this.loadSignInfo();
} catch (error) {
console.error('签到失败', error);
uni.showToast({
title: '签到失败,请稍后再试',
icon: 'none'
});
} finally {
this.signLoading = false;
}
},
uploadAvatar() {
console.log('uploadAvatar 被点击');
uni.chooseImage({
@ -418,6 +607,181 @@ export default {
padding-bottom: 24rpx;
}
.signin-card {
margin: 0 24rpx;
padding: 32rpx;
background: linear-gradient(120deg, #f6f1ff 0%, #ffeef5 100%);
border-radius: 28rpx;
box-shadow: 0 16rpx 32rpx rgba(139, 64, 255, 0.12);
display: flex;
flex-direction: column;
gap: 24rpx;
}
.signin-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.signin-headline {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.signin-title {
font-size: 36rpx;
font-weight: 700;
color: #33195d;
}
.signin-desc {
font-size: 26rpx;
color: #7b43ff;
}
.signin-badge {
padding: 12rpx 28rpx;
border-radius: 999rpx;
background: rgba(255, 60, 141, 0.12);
color: #ff3c8d;
font-size: 26rpx;
font-weight: 600;
}
.signin-calendar {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.signin-calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.signin-month {
font-size: 32rpx;
font-weight: 600;
color: #33195d;
}
.signin-month-summary {
font-size: 24rpx;
color: #7b43ff;
}
.signin-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
font-size: 24rpx;
color: #7b43ff;
text-align: center;
}
.signin-weekday {
opacity: 0.9;
}
.signin-calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 16rpx 12rpx;
}
.signin-date {
height: 80rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.7);
border: 2rpx dashed rgba(139, 64, 255, 0.16);
color: #4a2d71;
font-size: 28rpx;
}
.signin-date.placeholder {
background: transparent;
border-color: transparent;
}
.signin-date.signed {
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
color: #fff;
border-style: solid;
}
.signin-date.today {
border-color: #ff3c8d;
box-shadow: 0 0 0 3rpx rgba(255, 60, 141, 0.3);
}
.signin-date.missed {
border-style: solid;
border-color: rgba(255, 60, 141, 0.3);
color: rgba(51, 25, 93, 0.4);
}
.signin-date-number {
font-weight: 600;
}
.signin-footer {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.signin-stats {
display: flex;
gap: 32rpx;
}
.signin-stat {
flex: 1;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 24rpx;
padding: 16rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.signin-stat-value {
font-size: 36rpx;
font-weight: 700;
color: #33195d;
}
.signin-stat-label {
font-size: 24rpx;
color: #7b43ff;
}
.signin-btn {
width: 100%;
padding: 24rpx 0;
border-radius: 999rpx;
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
text-align: center;
}
.signin-btn.disabled {
opacity: 0.6;
}
.signin-tip {
font-size: 24rpx;
color: #7b43ff;
text-align: center;
}
.profile-header {
background: linear-gradient(135deg, #8b40ff 0%, #ff3c8d 100%);
/* border-radius: 24rpx 24rpx 0 0; */

View File

@ -2,13 +2,16 @@
<view class="subscribe-page">
<view class="banner">
<view class="banner-head">
<view class="banner-icon">
<text class="iconfont icon-lihe"></text>
</view>
<view class="banner-info">
<view class="banner-title">盲盒订阅</view>
<view class="banner-desc">每月惊喜专属香氛体验</view>
<view class="banner-title-row">
<view class="banner-icon">
<text class="iconfont icon-lihe"></text>
</view>
<view class="banner-info">
<view class="banner-title">盲盒订阅</view>
<view class="banner-desc">每月惊喜专属香氛体验</view>
</view>
</view>
<view class="banner-meta">
<view class="meta-item">
<view class="meta-label">当前等级</view>
@ -162,11 +165,15 @@ export default {
}
.banner-head {
/* display: flex;
align-items: center;
gap: 32rpx; */
}
.banner-title-row {
display: flex;
align-items: center;
gap: 32rpx;
gap: 24rpx;
}
.banner-icon {
width: 120rpx;
height: 120rpx;
@ -177,6 +184,11 @@ export default {
background: rgba(255, 255, 255, 0.16);
font-size: 60rpx;
box-shadow: inset 0 10rpx 20rpx rgba(255, 255, 255, 0.2);
text-align: center;
}
.banner-icon text{
font-size: 60rpx;
margin-left: 50%;
}
.banner-info {
@ -197,8 +209,10 @@ export default {
}
.banner-meta {
margin-top: 24rpx;
display: flex;
gap: 32rpx;
justify-content: space-between;
}
.meta-item {