feat: 完善见素起名小程序功能

- 添加收藏锦囊功能,支持查看和删除收藏
- 实现积分系统,每日赠送5次灵感次数
- 添加静心阅读功能,阅读15秒可获得额外次数
- 实现灵感广场,展示用户分享的名字
- 添加字源溯源组件,长按汉字查看详情
- 优化空状态和结语卡片样式统一
- 添加音频控制(静音/风铃/雨落/古琴/白噪音/森林/溪流)
- 优化名字生成逻辑,确保每次返回5个不重复名字
- 修复卡片翻转样式问题
- 移除首页动态提醒气泡
This commit is contained in:
王鹏
2026-04-18 16:56:31 +08:00
parent be1f5722ab
commit 2c47fb8f65
59 changed files with 4643 additions and 145 deletions

View File

@@ -4,20 +4,47 @@ Page({
showDetail: false,
selectedItem: {},
selectedIndex: -1,
openid: 'test_openid' // 实际应从登录获取
openid: null,
creditsInfo: {
dailyCredits: 5,
totalCredits: 0,
watchedAdCount: 0
},
// 静心阅读
showMeditation: false,
meditationPoem: null,
meditationProgress: 0
},
onLoad() {
this.loadFavorites();
this.setOpenid();
},
onShow() {
// 每次显示页面时刷新列表
this.loadFavorites();
// 加载积分信息
this.loadCreditsInfo();
},
// 设置 openid
setOpenid() {
const openid = getApp().getOpenid();
if (openid) {
this.setData({ openid });
this.loadFavorites();
} else {
// openid 还未获取到,延迟重试
setTimeout(() => this.setOpenid(), 500);
}
},
// 加载收藏列表
loadFavorites() {
if (!this.data.openid) {
console.log('openid 未获取到,跳过加载收藏');
return;
}
const apiBaseUrl = getApp().globalData.apiBaseUrl;
wx.request({
url: `${apiBaseUrl}/api/favorites/list`,
@@ -38,6 +65,140 @@ Page({
wx.navigateBack();
},
// 加载积分信息
loadCreditsInfo() {
const openid = getApp().getOpenid();
if (!openid) {
console.log('openid 未获取到,跳过加载积分');
return;
}
const apiBaseUrl = getApp().globalData.apiBaseUrl;
wx.request({
url: `${apiBaseUrl}/api/credits/info`,
data: { openid },
success: (res) => {
if (res.data && res.data.success) {
this.setData({ creditsInfo: res.data.data });
}
}
});
},
// 点击积分区域
onCreditsTap() {
const { creditsInfo } = this.data;
// 如果还有剩余次数,显示提示
if (creditsInfo.dailyCredits > 0) {
wx.showToast({
title: `今日还有 ${creditsInfo.dailyCredits} 次灵感`,
icon: 'none'
});
return;
}
// 如果次数用完且还可以看广告
if (creditsInfo.watchedAdCount < 5) {
wx.showModal({
title: '静心阅读',
content: '观看 15 秒静心画报,即可获得 3 次灵感',
confirmText: '开始阅读',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.watchAd();
}
}
});
} else {
wx.showToast({
title: '今日次数已达上限,明日再来',
icon: 'none'
});
}
},
// 静心阅读 - 模拟 15 秒阅读体验
watchAd() {
const openid = getApp().getOpenid();
if (!openid) {
wx.showToast({ title: '请先登录', icon: 'none' });
return;
}
// 随机选择一首诗词
const poems = [
{ text: '「采菊东篱下,悠然见南山」', author: '陶渊明《饮酒》' },
{ text: '「行到水穷处,坐看云起时」', author: '王维《终南别业》' },
{ text: '「人闲桂花落,夜静春山空」', author: '王维《鸟鸣涧》' },
{ text: '「松风吹解带,山月照弹琴」', author: '王维《酬张少府》' },
{ text: '「曲径通幽处,禅房花木深」', author: '常建《题破山寺后禅院》' },
{ text: '「明月松间照,清泉石上流」', author: '王维《山居秋暝》' }
];
const poem = poems[Math.floor(Math.random() * poems.length)];
// 显示静心阅读页面
this.setData({
showMeditation: true,
meditationPoem: poem,
meditationProgress: 0
});
// 15 秒倒计时 - 每 150ms 增加 1%,总共 150 * 100 = 15000ms = 15秒
let progress = 0;
const timer = setInterval(() => {
progress += 1;
this.setData({ meditationProgress: progress });
if (progress >= 100) {
clearInterval(timer);
this.meditationTimer = null;
// 延迟一下再关闭,让用户看到完成状态
setTimeout(() => {
this.setData({ showMeditation: false });
// 领取奖励
this.rewardAd(openid);
}, 500);
}
}, 150);
// 保存 timer 以便可以提前关闭
this.meditationTimer = timer;
},
// 提前关闭静心阅读
closeMeditation() {
if (this.meditationTimer) {
clearInterval(this.meditationTimer);
this.meditationTimer = null;
}
this.setData({ showMeditation: false });
},
// 领取广告奖励
rewardAd(openid) {
const apiBaseUrl = getApp().globalData.apiBaseUrl;
wx.request({
url: `${apiBaseUrl}/api/credits/reward`,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: `openid=${encodeURIComponent(openid)}`,
success: (res) => {
if (res.data && res.data.success) {
wx.showToast({ title: '获得 3 次灵感', icon: 'success' });
this.loadCreditsInfo();
} else {
wx.showToast({ title: res.data.message || '领取失败', icon: 'none' });
}
},
fail: () => {
wx.showToast({ title: '领取失败', icon: 'none' });
}
});
},
// 点击收藏项
onItemTap(e) {
const index = e.currentTarget.dataset.index;
@@ -79,12 +240,8 @@ Page({
if (res.confirm) {
const apiBaseUrl = getApp().globalData.apiBaseUrl;
wx.request({
url: `${apiBaseUrl}/api/favorites/remove`,
url: `${apiBaseUrl}/api/favorites/remove?openid=${this.data.openid}&name=${encodeURIComponent(item.name)}`,
method: 'POST',
data: {
openid: this.data.openid,
name: item.name
},
success: (res) => {
if (res.data && res.data.success) {
wx.showToast({ title: '已移除', icon: 'success' });

View File

@@ -6,6 +6,18 @@
<view class="header-spacer"></view>
</view>
<!-- 积分信息 - 克制地放在角落 -->
<view class="credits-bar" bindtap="onCreditsTap">
<view class="credits-item">
<text class="credits-icon">✦</text>
<text class="credits-text">今日灵感 {{creditsInfo.dailyCredits || 0}}/5</text>
</view>
<view class="credits-item watch-ad" wx:if="{{(creditsInfo.dailyCredits || 0) === 0 && (creditsInfo.watchedAdCount || 0) < 5}}">
<text class="credits-icon">📖</text>
<text class="credits-text">静心阅读 +3</text>
</view>
</view>
<!-- 收藏列表 -->
<scroll-view scroll-y class="favorites-scroll" wx:if="{{favorites.length > 0}}">
<view class="favorites-grid">
@@ -46,4 +58,23 @@
</view>
</view>
</view>
<!-- 静心阅读全屏页 -->
<view class="meditation-page {{showMeditation ? 'visible' : ''}}">
<view class="meditation-close" bindtap="closeMeditation">×</view>
<view class="meditation-content">
<view class="meditation-title">静心阅读</view>
<view class="meditation-poem" wx:if="{{meditationPoem}}">
<text class="poem-text">{{meditationPoem.text}}</text>
<text class="poem-author">——{{meditationPoem.author}}</text>
</view>
<view class="meditation-hint">静观 15 秒,心随诗远</view>
</view>
<view class="meditation-progress">
<view class="progress-bar">
<view class="progress-fill" style="width: {{meditationProgress}}%"></view>
</view>
<text class="progress-text">{{meditationProgress}}%</text>
</view>
</view>
</view>

View File

@@ -36,6 +36,47 @@ page {
width: 76rpx;
}
/* 积分信息 - 克制地放在角落 */
.credits-bar {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 20rpx;
margin-bottom: 30rpx;
padding: 0 10rpx;
}
.credits-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 20rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 30rpx;
opacity: 0.7;
transition: all 0.3s;
}
.credits-item:active {
opacity: 1;
transform: scale(0.98);
}
.credits-item.watch-ad {
background: #F5F5F0;
opacity: 1;
}
.credits-icon {
font-size: 24rpx;
}
.credits-text {
font-size: 22rpx;
color: #666;
letter-spacing: 2rpx;
}
/* 收藏列表 */
.favorites-scroll {
height: calc(100vh - 200rpx);
@@ -215,3 +256,113 @@ page {
.remove-btn:active {
background: #E8E8E8;
}
/* 静心阅读全屏页 */
.meditation-page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: all 0.5s ease;
}
.meditation-page.visible {
opacity: 1;
visibility: visible;
}
.meditation-close {
position: absolute;
top: 60rpx;
right: 40rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
color: rgba(255, 255, 255, 0.6);
z-index: 10;
}
.meditation-content {
text-align: center;
padding: 60rpx;
}
.meditation-title {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 8rpx;
margin-bottom: 80rpx;
}
.meditation-poem {
margin-bottom: 60rpx;
}
.poem-text {
display: block;
font-family: "KaiTi", "STKaiti", serif;
font-size: 56rpx;
color: #ffffff;
line-height: 1.8;
letter-spacing: 8rpx;
margin-bottom: 40rpx;
text-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
}
.poem-author {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 4rpx;
}
.meditation-hint {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.4);
letter-spacing: 4rpx;
margin-top: 60rpx;
}
.meditation-progress {
position: absolute;
bottom: 100rpx;
left: 80rpx;
right: 80rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.progress-bar {
flex: 1;
height: 4rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 2rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 2rpx;
transition: width 0.15s linear;
}
.progress-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
min-width: 60rpx;
text-align: right;
}