feat(uniapp_v2): gate sign-in on 签到广告 article view
Before triggering setSignIntegral, fetch the random required article; when one is returned, show a fullscreen overlay with title, image, and rich-text content (loaded via getArticleDetails) plus a 10s countdown gating the confirm button. Falls through to direct sign-in when the endpoint returns null or fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,14 @@ export function getSignCalendar(data) {
|
|||||||
return request.get('sign/calendar', data)
|
return request.get('sign/calendar', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签到前置阅读:随机返回"签到广告"分类下一条文章
|
||||||
|
* 后端无该分类或分类下没有可用文章时 data 为 null,前端可跳过门槛直接签到
|
||||||
|
*/
|
||||||
|
export function getSignRequiredArticle() {
|
||||||
|
return request.get('sign/required_article')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 活动状态
|
* 活动状态
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -100,6 +100,29 @@
|
|||||||
<base-calendar v-if="calendarVisible" :yearMonth="targetDate" :dataSource="signData" @dateChange="getSignCalendar" @clickChange="clickSign"></base-calendar>
|
<base-calendar v-if="calendarVisible" :yearMonth="targetDate" :dataSource="signData" @dateChange="getSignCalendar" @clickChange="clickSign"></base-calendar>
|
||||||
</view>
|
</view>
|
||||||
</uni-popup>
|
</uni-popup>
|
||||||
|
<view v-if="signAdVisible" class="sign-ad-overlay" :catchtouchmove="true">
|
||||||
|
<view class="sign-ad-header">
|
||||||
|
<view class="sign-ad-close" @click="closeSignAd">
|
||||||
|
<text class="iconfont icon-ic_Xempty"></text>
|
||||||
|
</view>
|
||||||
|
<view class="sign-ad-header-title">签到广告</view>
|
||||||
|
<view class="sign-ad-header-spacer"></view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="sign-ad-scroll">
|
||||||
|
<view class="sign-ad-body">
|
||||||
|
<view class="sign-ad-article-title">{{ signAdArticle.title }}</view>
|
||||||
|
<image v-if="signAdArticle.image" :src="signAdArticle.image" mode="widthFix" class="sign-ad-image"></image>
|
||||||
|
<rich-text v-if="signAdArticle.content" :nodes="signAdArticle.content" class="sign-ad-content"></rich-text>
|
||||||
|
<view v-else class="sign-ad-loading">文章加载中…</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="sign-ad-footer">
|
||||||
|
<view v-if="signAdCountdown > 0" class="sign-ad-tip">浏览满 {{ signAdCountdown }} 秒后可签到</view>
|
||||||
|
<view class="sign-ad-btn confirm" :class="{ disabled: signAdCountdown > 0 }" @click="confirmSignAfterAd">
|
||||||
|
{{ signAdCountdown > 0 ? `阅读中… ${signAdCountdown}s` : '我已阅读,立即签到' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<home></home>
|
<home></home>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -114,8 +137,10 @@
|
|||||||
setSignIntegral,
|
setSignIntegral,
|
||||||
signRemind,
|
signRemind,
|
||||||
getSignList,
|
getSignList,
|
||||||
getSignCalendar
|
getSignCalendar,
|
||||||
|
getSignRequiredArticle
|
||||||
} from '@/api/user.js';
|
} from '@/api/user.js';
|
||||||
|
import { getArticleDetails } from '@/api/api.js';
|
||||||
import BaseCalendar from '@/components/BaseCalendar.vue';
|
import BaseCalendar from '@/components/BaseCalendar.vue';
|
||||||
import emptyPage from '@/components/emptyPage.vue';
|
import emptyPage from '@/components/emptyPage.vue';
|
||||||
import {
|
import {
|
||||||
@@ -150,6 +175,11 @@
|
|||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
calendarVisible: false,
|
calendarVisible: false,
|
||||||
pageScrollStatus:false,
|
pageScrollStatus:false,
|
||||||
|
// 签到前置阅读门槛
|
||||||
|
signAdVisible: false,
|
||||||
|
signAdArticle: { id: 0, title: '', image: '', content: '' },
|
||||||
|
signAdCountdown: 0,
|
||||||
|
signAdTimer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapGetters(['isLogin']),
|
computed: mapGetters(['isLogin']),
|
||||||
@@ -283,12 +313,71 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
goSign: function(e) {
|
goSign: function(e) {
|
||||||
let that = this,
|
if (this.userInfo.is_day_sgin)
|
||||||
sum_sgin_day = that.userInfo.sum_sgin_day;
|
|
||||||
if (that.userInfo.is_day_sgin)
|
|
||||||
return this.$util.Tips({
|
return this.$util.Tips({
|
||||||
title: '您今日已签到!'
|
title: '您今日已签到!'
|
||||||
});
|
});
|
||||||
|
// 先取签到前置阅读文章;后端无文章/分类则跳过门槛
|
||||||
|
getSignRequiredArticle().then(res => {
|
||||||
|
const article = res && res.data ? res.data : null;
|
||||||
|
if (article && article.id) {
|
||||||
|
this.signAdArticle = {
|
||||||
|
id: article.id,
|
||||||
|
title: article.title || '',
|
||||||
|
image: article.image || '',
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
this.signAdVisible = true;
|
||||||
|
this.startSignAdCountdown(Number(article.view_required_seconds) || 10);
|
||||||
|
this.fetchSignAdContent(article.id);
|
||||||
|
} else {
|
||||||
|
this.doSignIntegral();
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// 拉文章失败兜底直接签到
|
||||||
|
this.doSignIntegral();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchSignAdContent(id) {
|
||||||
|
getArticleDetails(id).then(res => {
|
||||||
|
const data = res && res.data ? res.data : {};
|
||||||
|
this.signAdArticle = {
|
||||||
|
...this.signAdArticle,
|
||||||
|
title: data.title || this.signAdArticle.title,
|
||||||
|
content: data.content || ''
|
||||||
|
};
|
||||||
|
}).catch(() => {
|
||||||
|
// 仅富文本拉取失败,模态保持显示,content 留空
|
||||||
|
});
|
||||||
|
},
|
||||||
|
startSignAdCountdown(seconds) {
|
||||||
|
this.clearSignAdTimer();
|
||||||
|
this.signAdCountdown = seconds;
|
||||||
|
this.signAdTimer = setInterval(() => {
|
||||||
|
this.signAdCountdown -= 1;
|
||||||
|
if (this.signAdCountdown <= 0) {
|
||||||
|
this.clearSignAdTimer();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
clearSignAdTimer() {
|
||||||
|
if (this.signAdTimer) {
|
||||||
|
clearInterval(this.signAdTimer);
|
||||||
|
this.signAdTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeSignAd() {
|
||||||
|
this.clearSignAdTimer();
|
||||||
|
this.signAdCountdown = 0;
|
||||||
|
this.signAdVisible = false;
|
||||||
|
},
|
||||||
|
confirmSignAfterAd() {
|
||||||
|
if (this.signAdCountdown > 0) return;
|
||||||
|
this.closeSignAd();
|
||||||
|
this.doSignIntegral();
|
||||||
|
},
|
||||||
|
doSignIntegral() {
|
||||||
|
const that = this;
|
||||||
setSignIntegral()
|
setSignIntegral()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.$util.Tips({
|
this.$util.Tips({
|
||||||
@@ -724,4 +813,119 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sign-ad-overlay {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 88rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-close {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-header-title {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-header-spacer {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-scroll {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-body {
|
||||||
|
padding: 32rpx 32rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-article-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-image {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 600rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-content {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-loading {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-footer {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 16rpx 32rpx 32rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1rpx solid #eee;
|
||||||
|
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-tip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #FAAD14;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-btn {
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-btn.confirm {
|
||||||
|
background: linear-gradient(90deg, #FF7E30, #FAAD14);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-ad-btn.confirm.disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user