diff --git a/pages/my/index.vue b/pages/my/index.vue
index 0863d22..ba5071a 100644
--- a/pages/my/index.vue
+++ b/pages/my/index.vue
@@ -33,6 +33,48 @@
+
+
+
+
+
+ {{ label }}
+
+
+
+ {{ day.label }}
+
+
+
+
+
+
@@ -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; */
diff --git a/pages/subscribe/index.vue b/pages/subscribe/index.vue
index ee0653e..a98cd60 100644
--- a/pages/subscribe/index.vue
+++ b/pages/subscribe/index.vue
@@ -2,13 +2,16 @@
-
-
-
-
- 盲盒订阅
- 每月惊喜,专属香氛体验
+
+
+
+
+
+ 盲盒订阅
+ 每月惊喜,专属香氛体验
+
+
当前等级
@@ -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 {