V1.4: 首页沉浸式桌面 + 积分广告奖励系统
This commit is contained in:
parent
af8c17cf72
commit
c58f6faa3a
|
|
@ -1,13 +1,213 @@
|
||||||
|
const { getPoints, checkDailyReward, canWatchAd, getTodayAdCount, rewardFromAd, AD_REWARD_CONFIG } = require('../../utils/pointsManager');
|
||||||
|
const { getDailyAdvice } = require('../../utils/dailyAdvice');
|
||||||
|
const { getDailyArticle, getCategories } = require('../../utils/knowledgeData');
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
|
data: {
|
||||||
|
currentPoints: 0,
|
||||||
|
dailyAdvice: '',
|
||||||
|
dailyArticle: {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
summary: '',
|
||||||
|
category: '',
|
||||||
|
categoryName: '',
|
||||||
|
type: 'local'
|
||||||
|
},
|
||||||
|
energyVisible: false,
|
||||||
|
knowledgeVisible: false
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad: function () {
|
||||||
|
// 加载每日内容
|
||||||
|
this.loadDailyContent();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
// 检查每日登录奖励
|
||||||
|
const rewardResult = checkDailyReward();
|
||||||
|
|
||||||
|
// 如果获得了奖励,显示提示
|
||||||
|
if (rewardResult.rewarded) {
|
||||||
|
wx.showToast({
|
||||||
|
title: rewardResult.message,
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新积分显示
|
||||||
|
this.setData({
|
||||||
|
currentPoints: getPoints()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载每日内容
|
||||||
|
loadDailyContent: function () {
|
||||||
|
try {
|
||||||
|
// 获取今日建议
|
||||||
|
const advice = getDailyAdvice();
|
||||||
|
|
||||||
|
// 获取今日文章
|
||||||
|
const article = getDailyArticle();
|
||||||
|
|
||||||
|
// 获取分类名称
|
||||||
|
const categories = getCategories();
|
||||||
|
const category = categories.find(c => c.id === article.category);
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
dailyAdvice: advice,
|
||||||
|
dailyArticle: Object.assign({}, article, {
|
||||||
|
categoryName: category ? category.name : '塔罗知识'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[首页] 每日内容加载成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[首页] 每日内容加载失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示能量卡浮层
|
||||||
|
showEnergyOverlay: function () {
|
||||||
|
this.setData({
|
||||||
|
energyVisible: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 隐藏能量卡浮层
|
||||||
|
hideEnergyOverlay: function () {
|
||||||
|
this.setData({
|
||||||
|
energyVisible: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示知识卡半屏
|
||||||
|
showKnowledgePanel: function () {
|
||||||
|
this.setData({
|
||||||
|
knowledgeVisible: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 隐藏知识卡半屏
|
||||||
|
hideKnowledgePanel: function () {
|
||||||
|
this.setData({
|
||||||
|
knowledgeVisible: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 阻止事件冒泡
|
||||||
|
stopPropagation: function () {
|
||||||
|
// 空函数,用于阻止点击事件冒泡
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转到塔罗占卜
|
||||||
goToTarot: function () {
|
goToTarot: function () {
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: '/pages/index/index'
|
url: '/pages/index/index'
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 跳转到知识模块
|
||||||
goToKnowledge: function () {
|
goToKnowledge: function () {
|
||||||
wx.navigateTo({
|
wx.navigateTo({
|
||||||
url: '/pages/knowledge/index'
|
url: '/pages/knowledge/index'
|
||||||
})
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跳转到文章详情
|
||||||
|
goToArticle: function () {
|
||||||
|
const article = this.data.dailyArticle;
|
||||||
|
|
||||||
|
// 先关闭半屏
|
||||||
|
this.setData({
|
||||||
|
knowledgeVisible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 延迟跳转,等待动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
if (article.type === 'web') {
|
||||||
|
// 外链文章,跳转到 webview
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `/pages/webview/index?url=${encodeURIComponent(article.url)}&title=${encodeURIComponent(article.title)}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 本地文章,跳转到文章详情页
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `/pages/knowledge/article?id=${article.id}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示积分说明
|
||||||
|
showPointsInfo: function () {
|
||||||
|
try {
|
||||||
|
console.log('[首页] 点击积分徽章');
|
||||||
|
const remaining = AD_REWARD_CONFIG.DAILY_LIMIT - getTodayAdCount();
|
||||||
|
const canWatch = canWatchAd();
|
||||||
|
|
||||||
|
console.log('[首页] 剩余广告次数:', remaining);
|
||||||
|
console.log('[首页] 是否可以观看:', canWatch);
|
||||||
|
console.log('[首页] 当前积分:', this.data.currentPoints);
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
'当前积分:' + this.data.currentPoints,
|
||||||
|
'',
|
||||||
|
'每次占卜会消耗积分,不同牌阵消耗不同。',
|
||||||
|
'每日首次登录可获得 +3 积分奖励。',
|
||||||
|
'',
|
||||||
|
'看广告可获得积分(今日剩余 ' + remaining + ' 次)'
|
||||||
|
];
|
||||||
|
|
||||||
|
const content = lines.join('\n');
|
||||||
|
console.log('[首页] 弹窗内容:', content);
|
||||||
|
|
||||||
|
console.log('[首页] 准备调用 wx.showModal');
|
||||||
|
wx.showModal({
|
||||||
|
title: '积分说明',
|
||||||
|
content: content,
|
||||||
|
confirmText: canWatch ? '看广告' : '知道了',
|
||||||
|
cancelText: '关闭',
|
||||||
|
success: (res) => {
|
||||||
|
console.log('[首页] 弹窗回调:', res);
|
||||||
|
if (res.confirm && canWatch) {
|
||||||
|
this.watchAdForPoints();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[首页] 弹窗失败:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('[首页] wx.showModal 已调用');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[首页] showPointsInfo 错误:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 观看广告获取积分(模拟)
|
||||||
|
watchAdForPoints: function () {
|
||||||
|
const result = rewardFromAd();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 刷新积分显示
|
||||||
|
this.setData({
|
||||||
|
currentPoints: getPoints()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示奖励提示
|
||||||
|
wx.showToast({
|
||||||
|
title: result.message,
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 显示失败提示
|
||||||
|
wx.showToast({
|
||||||
|
title: result.message,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,67 @@
|
||||||
<view class="container">
|
<view class="tarot-table">
|
||||||
<!-- 顶部引导文案 -->
|
<!-- 右上角积分徽章 -->
|
||||||
<view class="header-text">
|
<view class="points-badge" bindtap="showPointsInfo">
|
||||||
<text class="line-one">今天,</text>
|
<text class="badge-icon">🎯</text>
|
||||||
<text class="line-two">你想从哪里获得指引?</text>
|
<text class="badge-value">{{currentPoints}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选择区域 -->
|
<!-- 中央主牌堆 -->
|
||||||
<view class="selection-area">
|
<view class="main-deck" bindtap="goToTarot" hover-class="deck-hover">
|
||||||
<!-- 塔罗入口 -->
|
<view class="deck-cards">
|
||||||
<view class="direction-card" bindtap="goToTarot" hover-class="card-hover">
|
<view class="card-stack card-1"></view>
|
||||||
<view class="card-icon">🔮</view>
|
<view class="card-stack card-2"></view>
|
||||||
<view class="card-content">
|
<view class="card-stack card-3"></view>
|
||||||
<text class="card-title">塔罗指引</text>
|
</view>
|
||||||
<text class="card-subtitle">探索潜意识的答案</text>
|
<text class="deck-title">开始抽牌</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 左侧今日能量卡 -->
|
||||||
|
<view class="energy-card side-card" bindtap="showEnergyOverlay" hover-class="card-hover">
|
||||||
|
<text class="card-icon">💫</text>
|
||||||
|
<text class="card-label">今日能量</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧今日知识卡 -->
|
||||||
|
<view class="knowledge-card side-card" bindtap="showKnowledgePanel" hover-class="card-hover">
|
||||||
|
<text class="card-icon">✨</text>
|
||||||
|
<text class="card-label">今日知识</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部功能抽屉 -->
|
||||||
|
<view class="function-drawer">
|
||||||
|
<view class="drawer-item" bindtap="goToTarot" hover-class="drawer-hover">
|
||||||
|
<text class="drawer-icon">🎴</text>
|
||||||
|
<text class="drawer-text">常用牌阵</text>
|
||||||
|
</view>
|
||||||
|
<view class="drawer-item" bindtap="goToKnowledge" hover-class="drawer-hover">
|
||||||
|
<text class="drawer-icon">📚</text>
|
||||||
|
<text class="drawer-text">学点塔罗</text>
|
||||||
|
</view>
|
||||||
|
<view class="drawer-item" bindtap="showPointsInfo" hover-class="drawer-hover">
|
||||||
|
<text class="drawer-icon">🎯</text>
|
||||||
|
<text class="drawer-text">积分说明</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 能量卡浮层 -->
|
||||||
|
<view class="overlay {{energyVisible ? 'show' : ''}}" bindtap="hideEnergyOverlay">
|
||||||
|
<view class="overlay-content energy-content" catchtap="stopPropagation">
|
||||||
|
<text class="overlay-title">💫 今日能量</text>
|
||||||
|
<text class="overlay-text">{{dailyAdvice}}</text>
|
||||||
|
<view class="overlay-close" bindtap="hideEnergyOverlay">关闭</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 知识卡半屏 -->
|
||||||
|
<view class="panel {{knowledgeVisible ? 'show' : ''}}" bindtap="hideKnowledgePanel">
|
||||||
|
<view class="panel-content" catchtap="stopPropagation">
|
||||||
|
<text class="panel-title">✨ 今日小知识</text>
|
||||||
|
<text class="panel-article-title">{{dailyArticle.title}}</text>
|
||||||
|
<text class="panel-summary">{{dailyArticle.summary}}</text>
|
||||||
|
<view class="panel-actions">
|
||||||
|
<view class="panel-btn" bindtap="goToArticle">查看详情</view>
|
||||||
|
<view class="panel-btn secondary" bindtap="hideKnowledgePanel">关闭</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 知识模块入口 -->
|
|
||||||
<view class="direction-card" bindtap="goToKnowledge" hover-class="card-hover">
|
|
||||||
<view class="card-icon">📚</view>
|
|
||||||
<view class="card-content">
|
|
||||||
<text class="card-title">学点塔罗</text>
|
|
||||||
<text class="card-subtitle">了解塔罗基础知识</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,349 @@
|
||||||
page {
|
page {
|
||||||
background-color: #1a1a2e;
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
overflow: hidden;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
/* ========== 主容器 ========== */
|
||||||
display: flex;
|
.tarot-table {
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px 30px;
|
height: 100vh;
|
||||||
box-sizing: border-box;
|
position: relative;
|
||||||
align-items: flex-start; /* 左对齐更有叙述感 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 顶部引导文案 */
|
|
||||||
.header-text {
|
|
||||||
margin-top: 40px;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-one {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 300; /* 细一点显得高级 */
|
|
||||||
margin-bottom: 10px;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-two {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 300;
|
|
||||||
opacity: 0.7; /* 稍微淡一点 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选择区域 */
|
|
||||||
.selection-area {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 25px; /* 卡片之间的间距 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 方向卡片 */
|
|
||||||
.direction-card {
|
|
||||||
background-color: rgba(255, 255, 255, 0.05); /* 极低透明度背景 */
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 25px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 右上角积分徽章 ========== */
|
||||||
|
.points-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
right: 20px;
|
||||||
|
background: linear-gradient(135deg, rgba(233, 69, 96, 0.25), rgba(233, 69, 96, 0.15));
|
||||||
|
border: 1px solid rgba(233, 69, 96, 0.5);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.2);
|
||||||
|
z-index: 2000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-value {
|
||||||
|
color: #e94560;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 中央主牌堆 ========== */
|
||||||
|
.main-deck {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 25px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-cards {
|
||||||
|
position: relative;
|
||||||
|
width: 180px;
|
||||||
|
height: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stack {
|
||||||
|
position: absolute;
|
||||||
|
width: 180px;
|
||||||
|
height: 260px;
|
||||||
|
background: linear-gradient(135deg, #e94560 0%, #8b3a62 50%, #4a1942 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 点击/按压时的效果 */
|
.card-1 {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: rotate(-3deg);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-2 {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-3 {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: rotate(3deg);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-hover .card-1 {
|
||||||
|
transform: rotate(-5deg) translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-hover .card-2 {
|
||||||
|
transform: rotate(0deg) translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-hover .card-3 {
|
||||||
|
transform: rotate(5deg) translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 侧边卡片 ========== */
|
||||||
|
.side-card {
|
||||||
|
position: absolute;
|
||||||
|
width: 90px;
|
||||||
|
height: 120px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
.card-hover {
|
.card-hover {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
transform: translateY(-5px);
|
||||||
transform: scale(0.98);
|
box-shadow: 0 12px 35px rgba(233, 69, 96, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-card {
|
||||||
|
left: 30px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.knowledge-card {
|
||||||
|
right: 30px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-icon {
|
.card-icon {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
margin-right: 20px;
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-content {
|
.card-label {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 底部功能抽屉 ========== */
|
||||||
|
.function-drawer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 15px 20px 25px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.drawer-hover {
|
||||||
color: #e94560;
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-text {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 能量卡浮层 ========== */
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.show {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-content {
|
||||||
|
background: linear-gradient(135deg, #2a2a3e 0%, #1a1a2e 100%);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 350px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.show .overlay-content {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-text {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-close {
|
||||||
|
background: linear-gradient(135deg, #e94560, #8b3a62);
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 25px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 知识卡半屏 ========== */
|
||||||
|
.panel {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 60vh;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel.show {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 55vh;
|
||||||
|
background: linear-gradient(180deg, #2a2a3e 0%, #1a1a2e 100%);
|
||||||
|
border-top-left-radius: 25px;
|
||||||
|
border-top-right-radius: 25px;
|
||||||
|
padding: 30px 25px;
|
||||||
|
box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel.show .panel-content {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
color: #fff;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
letter-spacing: 1px;
|
margin-bottom: 20px;
|
||||||
margin-bottom: 5px;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-subtitle {
|
.panel-article-title {
|
||||||
color: #ccc;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 20px;
|
||||||
opacity: 0.6;
|
font-weight: bold;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-summary {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: linear-gradient(135deg, #e94560, #8b3a62);
|
||||||
|
color: #fff;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-btn.secondary {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const { getQuestionType } = require('../../utils/questionType');
|
||||||
const { generateSpreadStory } = require('../../utils/spreadStory');
|
const { generateSpreadStory } = require('../../utils/spreadStory');
|
||||||
const { validateQuestion, scoreQuestion } = require('../../utils/questionFilter');
|
const { validateQuestion, scoreQuestion } = require('../../utils/questionFilter');
|
||||||
const { safeAIRequest } = require('../../utils/aiRequestManager');
|
const { safeAIRequest } = require('../../utils/aiRequestManager');
|
||||||
|
const { hasEnough, deductPoints } = require('../../utils/pointsManager');
|
||||||
|
|
||||||
// --- Prompt 模板定义 ---
|
// --- Prompt 模板定义 ---
|
||||||
|
|
||||||
|
|
@ -158,6 +159,7 @@ const SPREADS = [
|
||||||
"id": "one_card_guidance",
|
"id": "one_card_guidance",
|
||||||
"name": "单张指引",
|
"name": "单张指引",
|
||||||
"cardCount": 1,
|
"cardCount": 1,
|
||||||
|
"cost": 1,
|
||||||
"positions": ["当下的指引"],
|
"positions": ["当下的指引"],
|
||||||
"description": "为你此刻的状态提供一个温和而清晰的方向提示。",
|
"description": "为你此刻的状态提供一个温和而清晰的方向提示。",
|
||||||
"aiSchema": ["core_theme", "current_state", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "action_advice"]
|
||||||
|
|
@ -166,6 +168,7 @@ const SPREADS = [
|
||||||
"id": "three_time_flow",
|
"id": "three_time_flow",
|
||||||
"name": "过去 · 现在 · 未来",
|
"name": "过去 · 现在 · 未来",
|
||||||
"cardCount": 3,
|
"cardCount": 3,
|
||||||
|
"cost": 3,
|
||||||
"positions": ["过去", "现在", "未来"],
|
"positions": ["过去", "现在", "未来"],
|
||||||
"description": "帮助你理解事情的发展过程与可能走向。",
|
"description": "帮助你理解事情的发展过程与可能走向。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -174,6 +177,7 @@ const SPREADS = [
|
||||||
"id": "three_problem_solution",
|
"id": "three_problem_solution",
|
||||||
"name": "问题 · 阻碍 · 建议",
|
"name": "问题 · 阻碍 · 建议",
|
||||||
"cardCount": 3,
|
"cardCount": 3,
|
||||||
|
"cost": 3,
|
||||||
"positions": ["问题核心", "当前阻碍", "行动建议"],
|
"positions": ["问题核心", "当前阻碍", "行动建议"],
|
||||||
"description": "聚焦关键问题,找出当下最可行的应对方式。",
|
"description": "聚焦关键问题,找出当下最可行的应对方式。",
|
||||||
"aiSchema": ["core_theme", "current_state", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "action_advice"]
|
||||||
|
|
@ -182,6 +186,7 @@ const SPREADS = [
|
||||||
"id": "two_choice_decision",
|
"id": "two_choice_decision",
|
||||||
"name": "二选一抉择",
|
"name": "二选一抉择",
|
||||||
"cardCount": 4,
|
"cardCount": 4,
|
||||||
|
"cost": 4,
|
||||||
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
|
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
|
||||||
"description": "对比两种选择的潜在走向,辅助理性决策。",
|
"description": "对比两种选择的潜在走向,辅助理性决策。",
|
||||||
"aiSchema": ["core_theme", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "potential_influence", "action_advice"]
|
||||||
|
|
@ -190,6 +195,7 @@ const SPREADS = [
|
||||||
"id": "five_situation_analysis",
|
"id": "five_situation_analysis",
|
||||||
"name": "现状分析",
|
"name": "现状分析",
|
||||||
"cardCount": 5,
|
"cardCount": 5,
|
||||||
|
"cost": 5,
|
||||||
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
|
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
|
||||||
"description": "从内外层面拆解局势,明确下一步行动。",
|
"description": "从内外层面拆解局势,明确下一步行动。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -198,6 +204,7 @@ const SPREADS = [
|
||||||
"id": "relationship_spread",
|
"id": "relationship_spread",
|
||||||
"name": "关系洞察",
|
"name": "关系洞察",
|
||||||
"cardCount": 5,
|
"cardCount": 5,
|
||||||
|
"cost": 5,
|
||||||
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
|
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
|
||||||
"description": "理解一段关系中的互动模式与发展方向。",
|
"description": "理解一段关系中的互动模式与发展方向。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -206,6 +213,7 @@ const SPREADS = [
|
||||||
"id": "timeline_spread",
|
"id": "timeline_spread",
|
||||||
"name": "时间之流",
|
"name": "时间之流",
|
||||||
"cardCount": 5,
|
"cardCount": 5,
|
||||||
|
"cost": 5,
|
||||||
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
|
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
|
||||||
"description": "追溯事件的时间线,看清发展脉络。",
|
"description": "追溯事件的时间线,看清发展脉络。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -214,6 +222,7 @@ const SPREADS = [
|
||||||
"id": "diamond_spread",
|
"id": "diamond_spread",
|
||||||
"name": "钻石牌阵",
|
"name": "钻石牌阵",
|
||||||
"cardCount": 5,
|
"cardCount": 5,
|
||||||
|
"cost": 5,
|
||||||
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
|
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
|
||||||
"description": "多角度剖析问题,找到解决之道。",
|
"description": "多角度剖析问题,找到解决之道。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -222,6 +231,7 @@ const SPREADS = [
|
||||||
"id": "spiritual_guidance",
|
"id": "spiritual_guidance",
|
||||||
"name": "灵性指引",
|
"name": "灵性指引",
|
||||||
"cardCount": 7,
|
"cardCount": 7,
|
||||||
|
"cost": 7,
|
||||||
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
|
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
|
||||||
"description": "深入探索内在世界,获得灵性层面的启发。",
|
"description": "深入探索内在世界,获得灵性层面的启发。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -230,6 +240,7 @@ const SPREADS = [
|
||||||
"id": "horseshoe_spread",
|
"id": "horseshoe_spread",
|
||||||
"name": "马蹄铁牌阵",
|
"name": "马蹄铁牌阵",
|
||||||
"cardCount": 7,
|
"cardCount": 7,
|
||||||
|
"cost": 7,
|
||||||
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
|
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
|
||||||
"description": "全面了解情况的来龙去脉与未来走向。",
|
"description": "全面了解情况的来龙去脉与未来走向。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -238,6 +249,7 @@ const SPREADS = [
|
||||||
"id": "celtic_cross",
|
"id": "celtic_cross",
|
||||||
"name": "凯尔特十字",
|
"name": "凯尔特十字",
|
||||||
"cardCount": 10,
|
"cardCount": 10,
|
||||||
|
"cost": 10,
|
||||||
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
|
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
|
||||||
"description": "最经典的综合牌阵,深度解析生命议题。",
|
"description": "最经典的综合牌阵,深度解析生命议题。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -246,6 +258,7 @@ const SPREADS = [
|
||||||
"id": "tree_of_life",
|
"id": "tree_of_life",
|
||||||
"name": "生命之树",
|
"name": "生命之树",
|
||||||
"cardCount": 10,
|
"cardCount": 10,
|
||||||
|
"cost": 10,
|
||||||
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
|
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
|
||||||
"description": "基于卡巴拉生命之树的深度灵性探索。",
|
"description": "基于卡巴拉生命之树的深度灵性探索。",
|
||||||
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
|
||||||
|
|
@ -492,6 +505,18 @@ Page({
|
||||||
selectSpread: function (e) {
|
selectSpread: function (e) {
|
||||||
const id = e.currentTarget.dataset.id;
|
const id = e.currentTarget.dataset.id;
|
||||||
const spread = this.data.spreads.find(s => s.id === id);
|
const spread = this.data.spreads.find(s => s.id === id);
|
||||||
|
|
||||||
|
// 检查积分是否足够
|
||||||
|
if (!hasEnough(spread.cost)) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '积分不足',
|
||||||
|
content: `当前牌阵需要 ${spread.cost} 积分,您的积分不足,请稍后再来`,
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '知道了'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setData({
|
this.setData({
|
||||||
selectedSpread: spread,
|
selectedSpread: spread,
|
||||||
state: 'asking' // 先进入提问状态
|
state: 'asking' // 先进入提问状态
|
||||||
|
|
@ -597,7 +622,7 @@ Page({
|
||||||
|
|
||||||
// --- 3. 确认开启 ---
|
// --- 3. 确认开启 ---
|
||||||
confirmDraw: function () {
|
confirmDraw: function () {
|
||||||
const { drawnCardIndices, allCards } = this.data;
|
const { drawnCardIndices, allCards, selectedSpread } = this.data;
|
||||||
const drawnCards = drawnCardIndices.map(() => {
|
const drawnCards = drawnCardIndices.map(() => {
|
||||||
const randomIndex = Math.floor(Math.random() * allCards.length);
|
const randomIndex = Math.floor(Math.random() * allCards.length);
|
||||||
const card = Object.assign({}, allCards[randomIndex]);
|
const card = Object.assign({}, allCards[randomIndex]);
|
||||||
|
|
@ -609,6 +634,10 @@ Page({
|
||||||
return card;
|
return card;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 扣除积分(只扣一次)
|
||||||
|
deductPoints(selectedSpread.cost);
|
||||||
|
console.log(`[积分系统] 占卜消耗 ${selectedSpread.cost} 积分`);
|
||||||
|
|
||||||
this.setData({
|
this.setData({
|
||||||
drawnCards,
|
drawnCards,
|
||||||
state: 'flipping',
|
state: 'flipping',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* 每日建议生成器
|
||||||
|
* 基于日期生成随机建议,确保同一天返回相同内容
|
||||||
|
*/
|
||||||
|
|
||||||
|
const adviceList = [
|
||||||
|
// 情感类
|
||||||
|
"今天适合倾听内心的声音,答案就在你心中",
|
||||||
|
"保持开放的心态,接纳新的可能性",
|
||||||
|
"关注当下的感受,而不是未来的焦虑",
|
||||||
|
"真诚地面对自己,才能看清真相",
|
||||||
|
"给自己一些温柔,你已经做得很好了",
|
||||||
|
|
||||||
|
// 行动类
|
||||||
|
"今天是行动的好时机,迈出第一步",
|
||||||
|
"相信直觉,它会为你指引方向",
|
||||||
|
"放下犹豫,勇敢地做出选择",
|
||||||
|
"专注于你能控制的事情",
|
||||||
|
"小步前进,也是一种进步",
|
||||||
|
|
||||||
|
// 成长类
|
||||||
|
"每个挑战都是成长的机会",
|
||||||
|
"接纳不完美,这是成长的一部分",
|
||||||
|
"从过去的经验中学习,但不要被困住",
|
||||||
|
"保持好奇心,探索未知的领域",
|
||||||
|
"给自己时间,改变需要过程",
|
||||||
|
|
||||||
|
// 关系类
|
||||||
|
"真诚的沟通能化解许多误会",
|
||||||
|
"尊重他人的选择,也尊重自己的边界",
|
||||||
|
"倾听比说服更重要",
|
||||||
|
"关系需要双方的努力和理解",
|
||||||
|
"给彼此一些空间,距离产生美",
|
||||||
|
|
||||||
|
// 平静类
|
||||||
|
"深呼吸,让内心平静下来",
|
||||||
|
"放下执念,顺其自然",
|
||||||
|
"不必急于寻找答案,时机到了自然会明白",
|
||||||
|
"享受当下的宁静时刻",
|
||||||
|
"有些事情需要时间,耐心等待",
|
||||||
|
|
||||||
|
// 力量类
|
||||||
|
"你比自己想象的更强大",
|
||||||
|
"相信自己的判断力",
|
||||||
|
"困难只是暂时的,你能度过",
|
||||||
|
"你拥有改变现状的力量",
|
||||||
|
"勇敢地表达自己的需求"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取今日建议
|
||||||
|
* @returns {string} 今日建议文本
|
||||||
|
*/
|
||||||
|
function getDailyAdvice() {
|
||||||
|
try {
|
||||||
|
// 获取今天的日期字符串(格式:YYYY-MM-DD)
|
||||||
|
const today = new Date();
|
||||||
|
const dateStr = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// 使用日期字符串生成一个简单的哈希值作为随机种子
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < dateStr.length; i++) {
|
||||||
|
hash = ((hash << 5) - hash) + dateStr.charCodeAt(i);
|
||||||
|
hash = hash & hash; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用哈希值选择建议
|
||||||
|
const index = Math.abs(hash) % adviceList.length;
|
||||||
|
return adviceList[index];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[每日建议] 生成失败:', error);
|
||||||
|
return adviceList[0]; // 返回默认建议
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有建议列表(用于测试或展示)
|
||||||
|
* @returns {Array<string>} 建议列表
|
||||||
|
*/
|
||||||
|
function getAllAdvice() {
|
||||||
|
return adviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getDailyAdvice,
|
||||||
|
getAllAdvice
|
||||||
|
};
|
||||||
|
|
@ -342,9 +342,52 @@ function getArticleById(id) {
|
||||||
return knowledgeData.find(item => item.id === id);
|
return knowledgeData.find(item => item.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取随机文章
|
||||||
|
function getRandomArticle() {
|
||||||
|
const randomIndex = Math.floor(Math.random() * knowledgeData.length);
|
||||||
|
return knowledgeData[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取随机本地文章(优先本地内容)
|
||||||
|
function getRandomLocalArticle() {
|
||||||
|
const localArticles = knowledgeData.filter(item => item.type === 'local');
|
||||||
|
if (localArticles.length === 0) {
|
||||||
|
return getRandomArticle(); // 如果没有本地文章,返回任意文章
|
||||||
|
}
|
||||||
|
const randomIndex = Math.floor(Math.random() * localArticles.length);
|
||||||
|
return localArticles[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基于日期获取今日文章(确保同一天返回相同文章)
|
||||||
|
function getDailyArticle() {
|
||||||
|
try {
|
||||||
|
const today = new Date();
|
||||||
|
const dateStr = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// 使用日期字符串生成哈希值
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < dateStr.length; i++) {
|
||||||
|
hash = ((hash << 5) - hash) + dateStr.charCodeAt(i);
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先使用本地文章
|
||||||
|
const localArticles = knowledgeData.filter(item => item.type === 'local');
|
||||||
|
const articles = localArticles.length > 0 ? localArticles : knowledgeData;
|
||||||
|
const index = Math.abs(hash) % articles.length;
|
||||||
|
return articles[index];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[每日文章] 获取失败:', error);
|
||||||
|
return knowledgeData[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
knowledgeData,
|
knowledgeData,
|
||||||
getCategories,
|
getCategories,
|
||||||
getArticlesByCategory,
|
getArticlesByCategory,
|
||||||
getArticleById
|
getArticleById,
|
||||||
|
getRandomArticle,
|
||||||
|
getRandomLocalArticle,
|
||||||
|
getDailyArticle
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
/**
|
||||||
|
* 积分管理模块
|
||||||
|
* 提供积分的初始化、查询、增减、校验等功能
|
||||||
|
* 使用微信小程序本地存储实现
|
||||||
|
*/
|
||||||
|
|
||||||
|
const STORAGE_KEY_POINTS = 'tarot_points';
|
||||||
|
const STORAGE_KEY_LAST_LOGIN = 'tarot_last_login';
|
||||||
|
const STORAGE_KEY_AD_DATE = 'tarot_ad_date';
|
||||||
|
const STORAGE_KEY_AD_COUNT = 'tarot_ad_count';
|
||||||
|
const DEFAULT_POINTS = 20; // 新用户默认积分
|
||||||
|
const DAILY_REWARD = 3; // 每日登录奖励
|
||||||
|
|
||||||
|
// 积分来源常量
|
||||||
|
const POINT_SOURCE = {
|
||||||
|
DAILY_LOGIN: 'daily_login',
|
||||||
|
TAROT_USE: 'tarot_use',
|
||||||
|
KNOWLEDGE_READ: 'knowledge_read',
|
||||||
|
AD_REWARD: 'ad_reward'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 广告奖励配置
|
||||||
|
const AD_REWARD_CONFIG = {
|
||||||
|
REWARD_POINTS: 5, // 每次奖励积分
|
||||||
|
DAILY_LIMIT: 3 // 每日上限次数
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化积分系统
|
||||||
|
* 如果用户是首次使用,设置默认积分
|
||||||
|
*/
|
||||||
|
function initPoints() {
|
||||||
|
try {
|
||||||
|
const points = wx.getStorageSync(STORAGE_KEY_POINTS);
|
||||||
|
if (points === '' || points === null || points === undefined) {
|
||||||
|
wx.setStorageSync(STORAGE_KEY_POINTS, DEFAULT_POINTS);
|
||||||
|
console.log(`[积分系统] 新用户初始化,赋予 ${DEFAULT_POINTS} 积分`);
|
||||||
|
return DEFAULT_POINTS;
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 初始化失败:', error);
|
||||||
|
return DEFAULT_POINTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前积分
|
||||||
|
* @returns {number} 当前积分数
|
||||||
|
*/
|
||||||
|
function getPoints() {
|
||||||
|
try {
|
||||||
|
const points = wx.getStorageSync(STORAGE_KEY_POINTS);
|
||||||
|
if (points === '' || points === null || points === undefined) {
|
||||||
|
return initPoints();
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 获取积分失败:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加积分
|
||||||
|
* @param {number} num - 要增加的积分数
|
||||||
|
* @param {string} source - 积分来源(可选)
|
||||||
|
* @returns {number} 增加后的积分数
|
||||||
|
*/
|
||||||
|
function addPoints(num, source = '') {
|
||||||
|
try {
|
||||||
|
if (typeof num !== 'number' || num <= 0) {
|
||||||
|
console.warn('[积分系统] 无效的增加数值:', num);
|
||||||
|
return getPoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPoints = getPoints();
|
||||||
|
const newPoints = currentPoints + num;
|
||||||
|
wx.setStorageSync(STORAGE_KEY_POINTS, newPoints);
|
||||||
|
|
||||||
|
// 记录积分来源
|
||||||
|
if (source) {
|
||||||
|
console.log(`[积分系统] +${num} 积分,来源: ${source},当前: ${newPoints}`);
|
||||||
|
} else {
|
||||||
|
console.log(`[积分系统] 增加 ${num} 积分,当前: ${newPoints}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPoints;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 增加积分失败:', error);
|
||||||
|
return getPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣除积分
|
||||||
|
* @param {number} num - 要扣除的积分数
|
||||||
|
* @returns {number} 扣除后的积分数,如果积分不足则返回当前积分
|
||||||
|
*/
|
||||||
|
function deductPoints(num) {
|
||||||
|
try {
|
||||||
|
if (typeof num !== 'number' || num <= 0) {
|
||||||
|
console.warn('[积分系统] 无效的扣除数值:', num);
|
||||||
|
return getPoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPoints = getPoints();
|
||||||
|
if (currentPoints < num) {
|
||||||
|
console.warn(`[积分系统] 积分不足,当前: ${currentPoints},需要: ${num}`);
|
||||||
|
return currentPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPoints = currentPoints - num;
|
||||||
|
wx.setStorageSync(STORAGE_KEY_POINTS, newPoints);
|
||||||
|
console.log(`[积分系统] 扣除 ${num} 积分,剩余: ${newPoints}`);
|
||||||
|
return newPoints;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 扣除积分失败:', error);
|
||||||
|
return getPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查积分是否足够
|
||||||
|
* @param {number} num - 需要的积分数
|
||||||
|
* @returns {boolean} 是否足够
|
||||||
|
*/
|
||||||
|
function hasEnough(num) {
|
||||||
|
try {
|
||||||
|
if (typeof num !== 'number' || num < 0) {
|
||||||
|
console.warn('[积分系统] 无效的检查数值:', num);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPoints = getPoints();
|
||||||
|
return currentPoints >= num;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 检查积分失败:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并发放每日登录奖励
|
||||||
|
* @returns {object} { rewarded: boolean, points: number, message: string }
|
||||||
|
*/
|
||||||
|
function checkDailyReward() {
|
||||||
|
try {
|
||||||
|
const today = new Date().toDateString(); // 格式: "Sun Feb 09 2026"
|
||||||
|
const lastLogin = wx.getStorageSync(STORAGE_KEY_LAST_LOGIN);
|
||||||
|
|
||||||
|
// 如果是新的一天
|
||||||
|
if (lastLogin !== today) {
|
||||||
|
// 更新最后登录日期
|
||||||
|
wx.setStorageSync(STORAGE_KEY_LAST_LOGIN, today);
|
||||||
|
|
||||||
|
// 如果不是首次登录(首次登录已经有初始积分了)
|
||||||
|
if (lastLogin !== '' && lastLogin !== null && lastLogin !== undefined) {
|
||||||
|
const newPoints = addPoints(DAILY_REWARD, POINT_SOURCE.DAILY_LOGIN);
|
||||||
|
console.log(`[积分系统] 每日登录奖励 +${DAILY_REWARD} 积分`);
|
||||||
|
return {
|
||||||
|
rewarded: true,
|
||||||
|
points: newPoints,
|
||||||
|
message: `每日登录奖励 +${DAILY_REWARD} 积分`
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 首次登录,只记录日期,不发放奖励
|
||||||
|
console.log('[积分系统] 首次登录,已记录日期');
|
||||||
|
return {
|
||||||
|
rewarded: false,
|
||||||
|
points: getPoints(),
|
||||||
|
message: '欢迎使用塔罗占卜'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今天已经登录过了
|
||||||
|
return {
|
||||||
|
rewarded: false,
|
||||||
|
points: getPoints(),
|
||||||
|
message: '今日已签到'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[积分系统] 每日奖励检查失败:', error);
|
||||||
|
return {
|
||||||
|
rewarded: false,
|
||||||
|
points: getPoints(),
|
||||||
|
message: '签到检查失败'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取今日广告观看次数
|
||||||
|
* @returns {number} 今日已观看次数
|
||||||
|
*/
|
||||||
|
function getTodayAdCount() {
|
||||||
|
try {
|
||||||
|
const today = new Date().toDateString();
|
||||||
|
const lastDate = wx.getStorageSync(STORAGE_KEY_AD_DATE) || '';
|
||||||
|
|
||||||
|
// 如果不是今天,返回 0
|
||||||
|
if (lastDate !== today) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wx.getStorageSync(STORAGE_KEY_AD_COUNT) || 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[广告系统] 获取广告次数失败:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加广告观看次数
|
||||||
|
*/
|
||||||
|
function incrementAdCount() {
|
||||||
|
try {
|
||||||
|
const today = new Date().toDateString();
|
||||||
|
const count = getTodayAdCount();
|
||||||
|
|
||||||
|
wx.setStorageSync(STORAGE_KEY_AD_DATE, today);
|
||||||
|
wx.setStorageSync(STORAGE_KEY_AD_COUNT, count + 1);
|
||||||
|
|
||||||
|
console.log(`[广告系统] 今日广告次数: ${count + 1}/${AD_REWARD_CONFIG.DAILY_LIMIT}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[广告系统] 增加广告次数失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否可以观看广告
|
||||||
|
* @returns {boolean} 是否可以观看
|
||||||
|
*/
|
||||||
|
function canWatchAd() {
|
||||||
|
return getTodayAdCount() < AD_REWARD_CONFIG.DAILY_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广告奖励积分(核心方法)
|
||||||
|
* ⚠️ 未来只需修改此函数内部逻辑,UI 调用方式不变
|
||||||
|
* @returns {Object} { success, message, points, remainingCount }
|
||||||
|
*/
|
||||||
|
function rewardFromAd() {
|
||||||
|
try {
|
||||||
|
// 检查每日次数限制
|
||||||
|
if (!canWatchAd()) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '今日广告次数已用完',
|
||||||
|
points: 0,
|
||||||
|
remainingCount: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🎬 未来在此处接入真实广告 SDK
|
||||||
|
// 当前版本:直接模拟成功
|
||||||
|
|
||||||
|
// 增加积分
|
||||||
|
const rewardPoints = AD_REWARD_CONFIG.REWARD_POINTS;
|
||||||
|
addPoints(rewardPoints, POINT_SOURCE.AD_REWARD);
|
||||||
|
|
||||||
|
// 增加广告次数
|
||||||
|
incrementAdCount();
|
||||||
|
|
||||||
|
const remaining = AD_REWARD_CONFIG.DAILY_LIMIT - getTodayAdCount();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `获得 +${rewardPoints} 积分`,
|
||||||
|
points: rewardPoints,
|
||||||
|
remainingCount: remaining
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[广告奖励] 失败:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '广告奖励失败',
|
||||||
|
points: 0,
|
||||||
|
remainingCount: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initPoints,
|
||||||
|
getPoints,
|
||||||
|
addPoints,
|
||||||
|
deductPoints,
|
||||||
|
hasEnough,
|
||||||
|
checkDailyReward,
|
||||||
|
// 广告奖励相关
|
||||||
|
getTodayAdCount,
|
||||||
|
canWatchAd,
|
||||||
|
rewardFromAd,
|
||||||
|
// 常量导出
|
||||||
|
POINT_SOURCE,
|
||||||
|
AD_REWARD_CONFIG
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue