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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到前置阅读:随机返回"签到广告"分类下一条文章
|
||||
* 后端无该分类或分类下没有可用文章时 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>
|
||||
</view>
|
||||
</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>
|
||||
</view>
|
||||
</template>
|
||||
@@ -114,8 +137,10 @@
|
||||
setSignIntegral,
|
||||
signRemind,
|
||||
getSignList,
|
||||
getSignCalendar
|
||||
getSignCalendar,
|
||||
getSignRequiredArticle
|
||||
} from '@/api/user.js';
|
||||
import { getArticleDetails } from '@/api/api.js';
|
||||
import BaseCalendar from '@/components/BaseCalendar.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import {
|
||||
@@ -150,6 +175,11 @@
|
||||
isScrolling: false,
|
||||
calendarVisible: false,
|
||||
pageScrollStatus:false,
|
||||
// 签到前置阅读门槛
|
||||
signAdVisible: false,
|
||||
signAdArticle: { id: 0, title: '', image: '', content: '' },
|
||||
signAdCountdown: 0,
|
||||
signAdTimer: null,
|
||||
};
|
||||
},
|
||||
computed: mapGetters(['isLogin']),
|
||||
@@ -283,12 +313,71 @@
|
||||
});
|
||||
},
|
||||
goSign: function(e) {
|
||||
let that = this,
|
||||
sum_sgin_day = that.userInfo.sum_sgin_day;
|
||||
if (that.userInfo.is_day_sgin)
|
||||
if (this.userInfo.is_day_sgin)
|
||||
return this.$util.Tips({
|
||||
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()
|
||||
.then((res) => {
|
||||
this.$util.Tips({
|
||||
@@ -724,4 +813,119 @@
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user