Compare commits

..

3 Commits
v1.3 ... main

55 changed files with 6879 additions and 1177 deletions

54
app.js
View File

@ -1,5 +1,53 @@
App({
onLaunch() {
console.log('小程序启动啦!')
onShow: function (options) {
// 检查是否从分享卡片进入 (场景值 1007, 1008)
if (options && (options.scene === 1007 || options.scene === 1008)) {
console.log('[App] 用户通过分享卡片进入');
// 获取来源用户ID保留结构暂不使用
const fromUserId = options.query && options.query.fromUserId;
if (fromUserId) {
console.log('[App] 来源用户ID:', fromUserId);
}
})
// 只做欢迎提示,不发积分
wx.showToast({
title: '欢迎进入今日命运场 ✨',
icon: 'none',
duration: 2000
});
}
},
onLaunch: function () {
console.log('小程序启动啦!');
// 检查隐私协议同意状态
const agreed = wx.getStorageSync('privacyAgreed');
if (!agreed) {
wx.onAppRoute((res) => {
const isAgreed = wx.getStorageSync('privacyAgreed');
if (isAgreed) return;
if (res.path !== 'pages/privacy-agree/privacy-agree' &&
res.path !== 'pages/privacy/privacy') {
wx.reLaunch({
url: '/pages/privacy-agree/privacy-agree'
});
}
});
wx.reLaunch({
url: '/pages/privacy-agree/privacy-agree'
});
}
}
});

View File

@ -1,14 +1,22 @@
{
"pages": [
"pages/home/home",
"pages/zodiac/index",
"pages/index/index",
"pages/knowledge/index",
"pages/knowledge/list",
"pages/knowledge/article",
"pages/webview/index"
"pages/webview/index",
"pages/privacy/privacy",
"pages/privacy-agree/privacy-agree",
"pages/settings/settings",
"pages/user-agreement/user-agreement"
],
"window": {
"navigationBarTitleText": "我的第一个小程序"
"navigationBarTitleText": "塔罗星语",
"navigationBarBackgroundColor": "#1a1a2e",
"navigationBarTextStyle": "white",
"backgroundColor": "#1a1a2e"
},
"style": "v2",
"sitemapLocation": "sitemap.json"

View File

@ -0,0 +1,105 @@
Component({
properties: {
spread: { type: Object, value: null },
cards: { type: Array, value: [] },
revealedCount: { type: Number, value: 0 },
cardWidth: { type: Number, value: 160 } // 安全默认值 160
},
data: {
displayList: [] // 统一渲染列表 (merged with layout info)
},
lifetimes: {
attached() {
this.updateLayout();
}
},
observers: {
'spread, cards, cardWidth': function (spread, cards, cardWidth) {
this.updateLayout();
}
},
methods: {
updateLayout() {
const { spread, cards, cardWidth } = this.data;
if (!spread || !spread.layout || !cards || cards.length === 0) return;
const layoutType = spread.layout.type;
// 1. Grid 处理 (简单网格布局,不涉及复杂计算)
if (layoutType === 'grid') {
const list = cards.map((c, i) => ({
...c,
_posName: spread.positions[i],
_seq: i + 1,
_index: i,
_style: '' // 无内联样式,交给 grid CSS
}));
this.setData({ displayList: list });
return;
}
// 2. Celtic Cross (静止百分比布局 - 600x900rpx 容器适配)
if (layoutType === 'celtic') {
// 容器: 600rpx x 900rpx
// 卡牌: 140rpx x 210rpx
// 安全水平间距: 140/600 = 23.3% -> 中心间距需 > 23.3%
// 安全垂直间距: 210/900 = 23.3% -> 中心间距需 > 23.3%
// 中轴 X: 38% (228rpx) - 左移给右侧列留空间
// 左牌 X: 38% - 25% = 13% (78rpx) - 安全
// 右牌 X: 38% + 25% = 63% (378rpx) - 安全
// 右侧列 X: 85% (510rpx) - 离右牌 22% = 132rpx > 140rpx 略挤,调整到 87%
const positions = [
{ left: 38, top: 47 }, // 1 现状
{ left: 38, top: 47, rotate: 'rotate(90deg)' }, // 2 阻碍 (重叠)
{ left: 38, top: 73 }, // 3 根基 (47+26=73)
{ left: 13, top: 47 }, // 4 过去
{ left: 38, top: 21 }, // 5 可能性 (47-26=21)
{ left: 63, top: 47 }, // 6 近期未来
// 右侧列 X: 87%, 纵向间距 ~22% of 900 = 198rpx > 210rpx 略挤
// 调整为 4 张牌均匀分布在 10%-90% 之间
// 间距: (90-10)/3 = 26.7% = 240rpx > 210rpx ✓
{ left: 87, top: 15 }, // 7 你的态度
{ left: 87, top: 38 }, // 8 外部影响 (15+23=38)
{ left: 87, top: 61 }, // 9 希望与恐惧 (38+23=61)
{ left: 87, top: 84 }, // 10 最终结果 (61+23=84)
];
// Merge Layout + Card Data
const list = cards.map((c, i) => {
const pos = positions[i] || {};
// 直接生成内联样式,移除所有动态计算
const style = `left: ${pos.left}%; top: ${pos.top}%; transform: translate(-50%, -50%) ${pos.rotate || ''};`;
return {
...c,
_posName: spread.positions[i],
_seq: i + 1,
_index: i,
_style: style
};
});
this.setData({ displayList: list });
}
},
// 移除多余的 calcCelticLayout 方法,直接在 updateLayout 中处理
handleCardTap(e) {
const idx = e.currentTarget.dataset.index;
if (idx === undefined) return;
if (idx === this.data.revealedCount) {
this.triggerEvent('reveal', { index: idx });
} else if (idx < this.data.revealedCount) {
this.triggerEvent('viewDetail', { index: idx });
}
}
}
});

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,82 @@
<!-- components/spread-container/spread-container.wxml -->
<!-- 1. 卡牌单元模板 -->
<template name="card-item">
<view class="card-slot" style="{{extraStyle}}" bindtap="handleCardTap" data-index="{{card._index}}">
<!-- 位置信息 -->
<view class="pos-info">
<text class="pos-name">{{card._posName}}</text>
<!-- <text class="pos-seq">NO.{{card._seq}}</text> -->
</view>
<!-- 翻牌场景 -->
<view class="flip-scene">
<view class="flip-card {{card._index < revealedCount ? 'flipped' : ''}}">
<!-- 背面 -->
<view class="card-face face-back">
<image class="card-image" src="/images/beimian.png" mode="aspectFit" />
</view>
<!-- 正面 -->
<view class="card-face face-front">
<image
class="card-image {{card.isReversed ? 'reversed' : ''}}"
src="{{card.image}}"
mode="aspectFit"
/>
</view>
</view>
</view>
<!-- 牌名称 (翻开后显示) -->
<text class="card-name-sm" wx:if="{{card._index < revealedCount}}">{{card.name}}</text>
</view>
</template>
<view class="spread-wrapper {{spread.layout.type === 'celtic' ? 'celtic-mode' : ''}}">
<!-- =========================================
布局 1: Grid (网格)
直接遍历 displayList无特殊定位
========================================= -->
<view class="layout-grid" wx:if="{{spread.layout.type === 'grid'}}">
<block wx:for="{{displayList}}" wx:key="_index">
<template is="card-item" data="{{card: item, revealedCount: revealedCount, extraStyle: ''}}" />
</block>
</view>
<!-- =========================================
布局 2: Celtic Cross (舞台模式 - 600x900rpx)
使用 displayList 中的 _style 进行绝对定位
========================================= -->
<view class="spread-container" wx:if="{{spread.layout.type === 'celtic'}}">
<block wx:for="{{displayList}}" wx:key="_index">
<view class="stage-card" style="{{item._style}}" bindtap="handleCardTap" data-index="{{item._index}}">
<!-- 复用 card-item 内部结构,但不使用 template 以避免 style 冲突 -->
<view class="card-slot">
<view class="pos-info">
<text class="pos-name">{{item._posName}}</text>
</view>
<view class="flip-scene">
<view class="flip-card {{item._index < revealedCount ? 'flipped' : ''}}">
<view class="card-face face-back">
<image class="card-image" src="/images/beimian.png" mode="aspectFit" />
</view>
<view class="card-face face-front">
<image
class="card-image {{item.isReversed ? 'reversed' : ''}}"
src="{{item.image}}"
mode="aspectFit"
/>
</view>
</view>
</view>
<text class="card-name-sm" wx:if="{{item._index < revealedCount}}">{{item.name}}</text>
</view>
</view>
</block>
</view>
</view>

View File

@ -0,0 +1,165 @@
/* components/spread-container/spread-container.wxss */
/* 通用容器 */
.spread-wrapper {
width: 100%;
padding: 0 40rpx;
box-sizing: border-box;
}
/* =========================================
卡牌单元 (Card Item)
========================================= */
.card-slot {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
/* 默认尺寸grid 布局使用 */
width: 124rpx;
margin-bottom: 24rpx;
}
/* 翻牌场景 */
.flip-scene {
width: 100%;
/* 保持 160:260 比例 -> 1:1.625 */
/* 或 124:200 -> 1:1.61 */
aspect-ratio: 2/3;
perspective: 1000rpx;
position: relative;
}
.flip-card {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.6s;
}
.flip-card.flipped {
transform: rotateY(180deg);
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2);
}
.face-front {
transform: rotateY(180deg);
}
.card-image {
width: 100%;
height: 100%;
display: block;
}
.card-image.reversed {
transform: rotate(180deg);
}
/* 文字信息 */
.pos-info {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 8rpx;
}
.pos-name {
font-size: 22rpx;
color: #ddd;
line-height: 1.2;
text-align: center;
}
.pos-seq {
font-size: 18rpx;
color: #aaa;
}
.card-name-sm {
font-size: 20rpx;
color: #fff;
margin-top: 8rpx;
text-align: center;
max-width: 120%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* =========================================
布局 1: Grid (网格)
========================================= */
.layout-grid {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 30rpx;
padding: 40rpx 0;
}
/* =========================================
布局系统 2: Celtic Cross (从零重构 - 绝对锁定)
========================================= */
/* 1. 全屏垂直水平居中包装器 (仅 celtic 模式) */
.spread-wrapper.celtic-mode {
width: 100%;
/* 高度设为 100vh 可能会导致不可滚动,这里视情况调整 */
/* 用户虽然要求 100vh但在小程序组件中可能会溢出建议 min-height */
min-height: 80vh;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
box-sizing: border-box;
}
/* 2. 核心容器 (600x900rpx) */
.spread-container {
position: relative;
width: 600rpx;
height: 900rpx;
/* 调试辅助: border: 1px dashed rgba(255,255,255,0.2); */
}
/* 3. 卡牌通用样式 (绝对定位 + 居中) */
.stage-card {
position: absolute;
transform: translate(-50%, -50%); /* 核心锚点居中 */
z-index: 10;
width: 140rpx; /* 适配 600rpx 容器的推荐尺寸 */
display: flex;
flex-direction: column; /* 确保文字在下方 */
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
/* 复写卡牌槽样式以匹配新容器 */
.spread-container .card-slot {
width: 100%; /* 跟随 stage-card */
margin: 0;
}
.spread-container .flip-scene {
width: 100%;
height: 210rpx; /* 强制高度 (140 * 1.5) */
background: rgba(255, 255, 255, 0.1); /* 调试背景,确保占位可见 */
border-radius: 8rpx;
}
.spread-container .card-face {
background: #1a1a2e; /* 默认深色背景,防图挂 */
border: 1px solid rgba(255,255,255,0.2); /* 边框 */
}

View File

@ -1,13 +1,374 @@
const { getPoints, checkDailyReward, canWatchAd, getTodayAdCount, rewardFromAd, AD_REWARD_CONFIG } = require('../../utils/pointsManager');
const { getDailyAdvice } = require('../../utils/dailyAdvice');
const { getDailyArticle, getCategories } = require('../../utils/knowledgeData');
/**
* 🔧 广告功能开关 (上线前配置)
*
* 1. ENABLE_AD (总开关):
* - true: 开启广告功能
* - false: 关闭广告功能 (隐藏按钮, 点击提示"即将上线")
*
* 2. DEV_MODE (开发模式):
* - true: 使用模拟广告 (不拉起微信广告, 直接发奖励)
* - false: 使用真实广告 (拉起微信广告组件)
*
* 上线前:
* - 确保已开通流量主并填入真实 ID
* - DEV_MODE 改为 false
* - ENABLE_AD 改为 true
*/
const ENABLE_AD = false; // 👈 暂时关闭广告功能
const DEV_MODE = true; // 👈 开发测试模式
// 积分系统开关 (即便 ENABLE_AD 为 true, 如果此开关为 false, UI 也不显示积分)
const ENABLE_POINTS_UI = true;
Page({
data: {
currentPoints: 0,
dailyAdvice: '',
dailyArticle: {
id: '',
title: '',
summary: '',
category: '',
categoryName: '',
type: 'local'
},
showAdButton: ENABLE_AD, // 控制广告按钮显示
knowledgeVisible: false
},
onLoad: function () {
// 加载每日内容
this.loadDailyContent();
// 初始化激励视频广告
this.initRewardedAd();
},
// ... (keeping other methods)
// 跳转到星座模块
goToZodiac: function () {
wx.navigateTo({
url: '/pages/zodiac/index'
});
},
/**
* 初始化激励视频广告
* 需要在微信公众平台开通流量主并获取广告位ID
*/
initRewardedAd: function () {
// 1. 如果广告功能关闭,直接返回
if (!ENABLE_AD) {
console.log('[广告系统] 🔧 广告功能已关闭 (ENABLE_AD = false)');
return;
}
// 2. 如果是开发模式,使用模拟广告,不需要初始化真实组件
if (DEV_MODE) {
console.log('[广告系统] 🔧 开发模式:使用模拟广告 (DEV_MODE = true)');
return;
}
// 检查是否支持激励视频广告
if (!wx.createRewardedVideoAd) {
console.warn('[广告系统] 当前微信版本不支持激励视频广告');
return;
}
try {
// 创建激励视频广告实例
// ⚠️ TODO: 替换为你的真实广告位ID
// 只有在非开发模式且开启广告时才创建
this.rewardedAd = wx.createRewardedVideoAd({
adUnitId: 'adunit-xxxxxxxxxxxxxxxx' // 请替换为你的广告位ID
});
// 监听广告关闭事件
this.rewardedAd.onClose((res) => {
console.log('[广告系统] 广告关闭回调:', res);
// 用户看完广告isEnded = true 表示正常播放结束)
if (res && res.isEnded) {
console.log('[广告系统] 用户完整观看广告');
this.handleAdReward();
} else {
// 用户中途关闭广告
console.log('[广告系统] 用户未看完广告');
wx.showToast({
title: '未看完广告,无法获得积分',
icon: 'none',
duration: 2000
});
}
});
// 监听广告加载错误
this.rewardedAd.onError((err) => {
console.error('[广告系统] 广告加载失败:', err);
});
console.log('[广告系统] 激励视频广告初始化成功');
} catch (error) {
console.error('[广告系统] 初始化失败:', error);
}
},
/**
* 处理广告奖励
*/
handleAdReward: function () {
const result = rewardFromAd();
if (result.success) {
// 刷新积分显示
this.setData({
currentPoints: getPoints()
});
// 显示奖励提示
const message = `${result.message}!今日还可观看 ${result.remainingCount}`;
wx.showToast({
title: message,
icon: 'success',
duration: 2500
});
console.log('[广告系统] 奖励发放成功:', result);
} else {
// 显示失败提示(通常是次数用完)
wx.showToast({
title: result.message,
icon: 'none',
duration: 2000
});
}
},
onShow: function () {
if (ENABLE_POINTS_UI) {
// 检查每日登录奖励
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 () {
wx.navigateTo({
url: '/pages/index/index'
})
});
},
// 跳转到知识模块
goToKnowledge: function () {
wx.navigateTo({
url: '/pages/knowledge/index'
})
});
},
// 跳转到星座模块
goToZodiac: function () {
wx.navigateTo({
url: '/pages/zodiac/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);
},
// 跳转到设置页
goToSettings: function () {
wx.navigateTo({
url: '/pages/settings/settings'
});
},
// 显示积分说明 (切换为自定义浮层)
showPointsInfo: function () {
this.setData({
pointsVisible: true
});
},
// 关闭积分说明
hidePointsInfo: function () {
this.setData({
pointsVisible: false
});
},
// 观看广告获取积分(支持开发模式)
watchAdForPoints: function () {
// 1. 全局开关检查
if (!ENABLE_AD) {
wx.showToast({
title: '功能即将上线',
icon: 'none',
duration: 2000
});
return;
}
// 2. 检查是否还有观看次数
if (!canWatchAd()) {
wx.showToast({
title: '今日广告次数已用完',
icon: 'none',
duration: 2000
});
return;
}
// 🔧 开发模式:模拟广告播放
if (DEV_MODE) {
console.log('[广告系统] 🔧 开发模式:模拟广告播放中...');
wx.showLoading({
title: '广告播放中...',
mask: true
});
// 模拟广告播放时长1.5秒)
setTimeout(() => {
wx.hideLoading();
console.log('[广告系统] 🔧 开发模式:模拟广告播放完成');
this.handleAdReward();
}, 1500);
return;
}
// 生产模式:播放真实广告
// 检查广告实例是否存在
if (!this.rewardedAd) {
wx.showToast({
title: '广告功能暂不可用',
icon: 'none',
duration: 2000
});
console.warn('[广告系统] 广告实例不存在');
return;
}
// 显示广告
this.rewardedAd.show()
.catch((err) => {
console.log('[广告系统] 广告未加载,尝试重新加载:', err);
// 广告可能未加载,先加载再显示
this.rewardedAd.load()
.then(() => {
console.log('[广告系统] 广告加载成功,开始播放');
return this.rewardedAd.show();
})
.catch((loadErr) => {
console.error('[广告系统] 广告加载失败:', loadErr);
wx.showToast({
title: '广告加载失败,请稍后再试',
icon: 'none',
duration: 2000
});
});
});
}
});

View File

@ -1,29 +1,129 @@
<view class="container">
<!-- 顶部引导文案 -->
<view class="header-text">
<text class="line-one">今天,</text>
<text class="line-two">你想从哪里获得指引?</text>
<view class="tarot-table">
<!-- 顶部栏 (已移除) -->
<!-- 右上角积分徽章 -->
<view class="points-badge" bindtap="showPointsInfo" style="top: 100rpx;">
<text class="badge-icon">🎯</text>
<text class="badge-value">{{currentPoints}}</text>
</view>
<!-- 选择区域 -->
<view class="selection-area">
<!-- 塔罗入口 -->
<view class="direction-card" bindtap="goToTarot" hover-class="card-hover">
<view class="card-icon">🔮</view>
<view class="card-content">
<text class="card-title">塔罗指引</text>
<text class="card-subtitle">探索潜意识的答案</text>
<!-- 星座能量入口 (放在主牌堆上方) -->
<view class="zodiac-entry" bindtap="goToZodiac">
<view class="zodiac-content">
<text class="zodiac-title">今日星象能量</text>
<text class="zodiac-subtitle">看看今天宇宙给你的提示</text>
</view>
<view class="zodiac-icon">🌑</view>
</view>
<!-- 中央主牌堆 -->
<view class="main-deck" bindtap="goToTarot" hover-class="deck-hover">
<view class="deck-cards">
<view class="card-stack card-1">
<view class="card-bg"></view>
</view>
<view class="card-stack card-2">
<view class="card-bg"></view>
</view>
<view class="card-stack card-3">
<view class="card-bg"></view>
<view class="card-pattern">
<view class="card-symbol">✡</view>
</view>
</view>
</view>
<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 class="drawer-item" bindtap="goToSettings" hover-class="drawer-hover">
<text class="drawer-icon">⚙️</text>
<text class="drawer-text">更多设置</text>
</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 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="overlay {{pointsVisible ? 'show' : ''}}" bindtap="hidePointsInfo">
<view class="overlay-content points-content" catchtap="stopPropagation">
<view class="points-header">
<text class="points-big-icon">🎯</text>
<text class="points-big-value">{{currentPoints}}</text>
<text class="points-label">当前积分</text>
</view>
<view class="points-rules">
<view class="rule-item">
<text class="rule-icon">📅</text>
<view class="rule-text">
<text class="rule-title">每日登录</text>
<text class="rule-desc">自动获得 +3 积分</text>
</view>
</view>
<view class="rule-item">
<text class="rule-icon">🎁</text>
<view class="rule-text">
<text class="rule-title">分享给朋友</text>
<text class="rule-desc">获得 +10 积分 (每日限3次)</text>
</view>
</view>
<view class="rule-item">
<text class="rule-icon">🔮</text>
<view class="rule-text">
<text class="rule-title">消耗规则</text>
<text class="rule-desc">不同牌阵消耗不同积分</text>
</view>
</view>
</view>
<view class="overlay-close" bindtap="hidePointsInfo">知道了</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 File

@ -1,89 +1,564 @@
page {
background-color: #1a1a2e;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
/* ========== 主容器 ========== */
.tarot-table {
width: 100%;
padding: 40px 30px;
box-sizing: border-box;
align-items: flex-start; /* 左对齐更有叙述感 */
}
/* 顶部引导文案 */
.header-text {
margin-top: 40px;
margin-bottom: 60px;
height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding-top: 120rpx;
gap: 40px;
box-sizing: border-box;
}
.line-one {
color: #fff;
font-size: 28px;
font-weight: 300; /* 细一点显得高级 */
margin-bottom: 10px;
/* ========== 顶部栏 (已移除) ========== */
/* ========== 右上角积分徽章 ========== */
.points-badge {
position: absolute;
top: 30px; /* Restore original position */
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;
}
/* ========== 星座入口 ========== */
.zodiac-entry {
width: 80%;
max-width: 320px;
background: linear-gradient(90deg, #2a2a3e, #16213e);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
z-index: 10;
animation: float 6s ease-in-out infinite;
margin-top: 120rpx;
}
.entry-hover {
transform: scale(0.98);
opacity: 0.9;
}
.line-two {
color: #fff;
font-size: 22px;
font-weight: 300;
opacity: 0.7; /* 稍微淡一点 */
}
/* 选择区域 */
.selection-area {
width: 100%;
.zodiac-content {
display: flex;
flex-direction: column;
gap: 25px; /* 卡片之间的间距 */
gap: 4px;
}
/* 方向卡片 */
.direction-card {
background-color: rgba(255, 255, 255, 0.05); /* 极低透明度背景 */
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 25px;
.zodiac-title {
color: #fff;
font-size: 16px;
font-weight: bold;
letter-spacing: 1px;
}
.zodiac-subtitle {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}
.zodiac-icon {
font-size: 28px;
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.3));
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
.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 moved to .card-bg */
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
transition: all 0.3s ease;
/* overflow: hidden; Removed to allow glow to show outside */
}
.card-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #4a1942 0%, #8b3a62 50%, #e94560 100%);
border-radius: 12px;
z-index: 0;
}
/* 模糊光晕描边 */
.card-stack::after {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
z-index: -1;
background: linear-gradient(135deg, #7a5cff, #005bea);
border-radius: 14px;
filter: blur(2px); /* 微调模糊度 */
opacity: 1; /* 增强可见度 */
}
/* Card Pattern Overlay */
.card-stack::before {
content: "";
position: absolute;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border: 1px solid rgba(255, 215, 0, 0.2);
border-radius: 8px;
}
.card-pattern {
width: 100%;
height: 100%;
display: flex;
align-items: center;
transition: all 0.3s ease;
justify-content: center;
background-image: radial-gradient(circle at center, rgba(255,255,255,0.1) 0%, transparent 60%);
}
.card-symbol {
font-size: 200rpx; /* 最大化视局冲击 */
font-weight: bold;
background: linear-gradient(180deg, #FFD700 10%, #E0C3FC 50%, #8A2BE2 90%); /* 金色-淡紫-深紫渐变,增加神秘感 */
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 20rpx rgba(255, 215, 0, 0.5)) drop-shadow(0 0 50rpx rgba(138, 43, 226, 0.7)); /* 静态综合光晕 */
animation: pulseMystic 4s infinite ease-in-out;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
will-change: transform, opacity; /* 硬件加速优化 */
}
@keyframes pulseMystic {
0%, 100% { transform: scale(1); opacity: 0.85; }
50% { transform: scale(1.15); opacity: 1; }
}
.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); /* Semi-transparent white */
border: 1px solid rgba(255, 255, 255, 0.15); /* Light border */
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;
top: 50%;
transform: translateY(-50%);
}
/* 点击/按压时的效果 */
.card-hover {
background-color: rgba(255, 255, 255, 0.1);
transform: scale(0.98);
transform: translateY(-5px);
box-shadow: 0 12px 35px rgba(233, 69, 96, 0.3);
}
.energy-card {
left: 10px;
}
.knowledge-card {
right: 10px;
}
.card-icon {
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: 8px 10px 15px;
display: flex;
justify-content: space-around;
z-index: 20;
}
.drawer-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px 8px;
border-radius: 12px;
transition: all 0.3s ease;
}
.card-title {
color: #e94560;
.drawer-hover {
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-weight: bold;
letter-spacing: 1px;
margin-bottom: 20px;
display: block;
}
.panel-article-title {
color: #fff;
font-size: 20px;
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;
}
/* 积分浮层特有样式 */
.points-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 25px;
position: relative;
}
.points-big-icon {
font-size: 40px;
margin-bottom: 5px;
filter: drop-shadow(0 0 10px rgba(233, 69, 96, 0.4));
animation: float 4s ease-in-out infinite;
}
.points-big-value {
font-size: 48px;
font-weight: bold;
color: #fff;
text-shadow: 0 0 20px rgba(233, 69, 96, 0.6);
line-height: 1;
margin-bottom: 5px;
}
.card-subtitle {
color: #ccc;
font-size: 12px;
opacity: 0.6;
.points-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 2px;
}
.points-rules {
background: rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 15px;
width: 100%;
box-sizing: border-box;
margin-bottom: 25px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.rule-item {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.rule-item:last-child {
margin-bottom: 0;
}
.rule-icon {
font-size: 24px;
margin-right: 15px;
width: 30px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.rule-text {
display: flex;
flex-direction: column;
flex: 1;
}
.rule-title {
color: #ffffff !important;
font-size: 15px;
font-weight: bold;
margin-bottom: 4px;
text-align: left;
text-shadow: 0 1px 2px rgba(0,0,0,0.8);
}
.rule-desc {
color: #eeeeee !important;
font-size: 13px;
text-align: left;
opacity: 0.9;
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.points-content {
width: 70% !important;
max-width: 300px !important;
padding: 20px !important;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
{
"navigationBarTitleText": "塔罗指引",
"usingComponents": {}
"usingComponents": {
"spread-container": "/components/spread-container/spread-container"
}
}

View File

@ -4,27 +4,64 @@
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<!-- 1. 牌阵选择状态 -->
<!-- 1. 牌阵选择状态 - 卡片式V2 -->
<view class="spread-select-area" wx:if="{{state === 'spread_select'}}">
<view class="title-area">
<text class="app-title">选择你的牌阵</text>
<text class="instruction">请根据你的困惑选择一个适合的牌阵</text>
<!-- 顶部标题区域 -->
<view class="spread-header">
<text class="spread-title">选择适合你的牌阵</text>
<text class="spread-subtitle">不同的问题,需要不同的展开方式</text>
</view>
<!-- 标签筛选条 -->
<view class="tags-area">
<scroll-view class="tags-scroll" scroll-x="true" enable-flex="true">
<view class="tag-item {{activeTag === item ? 'active' : ''}}"
wx:for="{{spreadTags}}"
wx:key="*this"
bindtap="filterSpreads"
data-tag="{{item}}">
<text>{{item}}</text>
</view>
</scroll-view>
</view>
<!-- 两列卡片网格 -->
<view class="spread-grid">
<view class="spread-card"
wx:for="{{filteredSpreads}}"
wx:key="id"
bindtap="selectSpread"
data-id="{{item.id}}">
<!-- 右上角标签角标 -->
<view class="card-badge" wx:if="{{item.tags[0]}}">
<text>{{item.tags[0]}}</text>
</view>
<!-- 牌阵名称 -->
<text class="card-name">{{item.name}}</text>
<!-- 描述 -->
<text class="card-desc">{{item.description}}</text>
<!-- 底部信息 -->
<view class="card-footer">
<text class="card-count">{{item.cardCount}} 张牌</text>
<text class="card-cost">需 {{item.cost}} 积分</text>
</view>
<view class="spread-list">
<view class="spread-item" wx:for="{{spreads}}" wx:key="id" bindtap="selectSpread" data-id="{{item.id}}" hover-class="spread-hover">
<text class="spread-name">{{item.name}}</text>
<text class="spread-desc">{{item.description}}</text>
</view>
</view>
</view>
<!-- 1.5 提问状态 -->
<!-- 1.5 提问状态 - 沉浸式仪式感版本 -->
<view class="asking-area" wx:if="{{state === 'asking'}}">
<view class="title-area">
<text class="app-title">请输入你想询问的问题</text>
<text class="instruction">心中默念问题,塔罗牌将为你指引方向</text>
</view>
<view class="input-area">
<!-- 中央塔罗卡片式输入区 -->
<view class="input-card">
<textarea
class="question-input"
placeholder="例如:我们的关系会如何发展?我是否应该换工作?这个合作值得继续吗?"
@ -41,7 +78,26 @@
<text class="quality-text">提问质量:{{questionQuality.level}}</text>
</view>
</view>
<button class="start-btn" bindtap="confirmQuestion" hover-class="btn-hover">开始抽牌</button>
<!-- 温和分割线 -->
<view class="divider"></view>
<!-- 塔罗低语区域 -->
<view class="whispers-area">
<text class="whispers-title">塔罗为你低语…</text>
<scroll-view class="whispers-scroll" scroll-x="true" enable-flex="true">
<view class="whisper-card {{selectedWhisperIndex === index ? 'selected' : ''}}"
wx:for="{{tarotWhispers}}"
wx:key="text"
bindtap="selectWhisper"
data-index="{{index}}"
data-question="{{item.text}}">
<text class="whisper-text">{{item.text}}</text>
</view>
</scroll-view>
</view>
<button class="start-btn" bindtap="confirmQuestion">开启塔罗指引</button>
</view>
<!-- 2. 洗牌状态 (展示动画) -->
@ -90,33 +146,42 @@
module.exports.getTransform = function(indices, currentIdx, count) {
var pos = indices.indexOf(currentIdx);
if (pos !== -1) {
// 每排最多5张牌
var maxPerRow = 5;
var row = Math.floor(pos / maxPerRow); // 第几排0或1
var col = pos % maxPerRow; // 该排的第几个
var cardsInRow = (row === 0) ? Math.min(count, maxPerRow) : (count - maxPerRow);
var row = Math.floor(pos / maxPerRow);
var col = pos % maxPerRow;
// 槽位宽100rpx + gap 20rpx = 120rpx 间距
var slotWidth = 100;
var gap = 20;
var slotSpacing = slotWidth + gap;
var cardsRemaining = count - (row * maxPerRow);
var cardsInRow = (cardsRemaining > maxPerRow) ? maxPerRow : cardsRemaining;
// 计算该排槽位的水平居中偏移
// 从该排中间位置开始计算
var centerOffset = (col - (cardsInRow - 1) / 2) * slotSpacing;
// Container Calculation
var rowCount = Math.ceil(count / maxPerRow);
var contentHeight = (rowCount * 160) + ((rowCount - 1) * 20);
if (contentHeight < 240) { contentHeight = 240; } // Handle min-height: 240rpx
// 垂直偏移计算
// 第一排槽位:向上 -520rpx已验证接近正确
// 第二排:需要减少向上的距离
// 槽位高度160 + gap 20 = 180但实际可能需要微调
var firstRowY = -520;
var secondRowOffset = 200; // 增加偏移量让第二排更接近fan-deck
var yOffset = firstRowY + (row * secondRowOffset); // row=0: -520, row=1: -320
// Distance from Slot Container Top to Fan Deck Bottom
// FanHeight(380) + FanMarginTop(20) + ContainerMarginBottom(60) + ContainerHeight
var distToFanBottom = 380 + 20 + 60 + contentHeight;
// Determine Slot Center Y from Container Top
// Row 0 starts at 0. Center is 80.
// Row 1 starts at 180. Center is 260.
var slotCenterY = (row * 180) + 80;
// Target Y relative to Fan Bottom (0)
// SlotCenter is above FanBottom, so negative.
var targetY = -(distToFanBottom - slotCenterY);
// Adjust for Card Visual Center (Transform Origin: Bottom Center)
// Card Height 260 -> Scaled 0.65 -> 169
// Visual Center is 84.5 from bottom.
// We want visual center at targetY.
// Origin (Bottom) needs to be at targetY + 84.5
var yOffset = targetY + 84.5;
var centerOffset = (col - (cardsInRow - 1) / 2) * 120;
// 位移并缩小到0.65
return 'translate(' + centerOffset + 'rpx, ' + yOffset + 'rpx) scale(0.65) rotate(0deg)';
} else {
// 保持在牌堆中的放射状
return 'rotate(' + ((currentIdx - 10) * 8) + 'deg)';
}
};
@ -125,28 +190,29 @@
<button class="confirm-draw-btn" wx:if="{{drawnCardIndices.length === selectedSpread.cardCount}}" bindtap="confirmDraw">开启解读</button>
</view>
<!-- 4. 翻牌与结果展示状态 -->
<!-- 4. 翻牌与结果展示状态 -->
<view class="result-area" wx:if="{{state === 'flipping' || state === 'revealed'}}">
<!-- 卡牌阵列 -->
<view class="spread-display {{selectedSpread.id}}">
<view class="card-slot" wx:for="{{drawnCards}}" wx:key="index">
<text class="pos-name">{{selectedSpread.positions[index]}}</text>
<view class="flip-scene" bindtap="revealNext" data-index="{{index}}">
<view class="flip-card {{index < revealedCount ? 'flipped' : ''}}">
<view class="card-face face-back">
<image class="card-image" src="{{cardBackImage}}" mode="widthFix"></image>
</view>
<view class="card-face face-front">
<image class="card-image {{item.isReversed ? 'reversed' : ''}}" src="{{item.image}}" mode="widthFix"></image>
</view>
</view>
</view>
<text class="card-name-sm" wx:if="{{index < revealedCount}}">{{item.name}}{{item.isReversed ? '(逆)' : ''}}</text>
</view>
<!-- 卡牌阵列 (Unified Layout System) -->
<spread-container
spread="{{selectedSpread}}"
cards="{{drawnCards}}"
revealed-count="{{revealedCount}}"
bind:reveal="revealNext"
bind:viewDetail="viewCardDetail"
/>
<!-- 翻牌提示 -->
<view class="flip-hint" wx:if="{{state === 'flipping' && revealedCount < selectedSpread.cardCount}}">
<text class="flip-hint-icon">✦</text>
<text class="flip-hint-text">依次点击牌面,逐一揭示</text>
<text class="flip-hint-icon">✦</text>
</view>
<!-- 解读区域 -->
<view class="interpretation-area" wx:if="{{revealedCount === selectedSpread.cardCount}}" animation="{{infoAnimation}}">
<!-- 加载中 -->
<view class="loading-ai" wx:if="{{isAiLoading}}">
<text class="loading-dot">···</text>
<text>{{aiLoadingText}}</text>
@ -187,6 +253,20 @@
<text class="content">{{aiResult.advice}}</text>
</view>
<view class="ai-warning">
<text>⚠ 塔罗映照潜意识的微光,最终的选择始终在你手中。</text>
</view>
<!-- 🌟 新增:分享引导模块 -->
<view class="share-guidance">
<view class="guidance-text">
<text>“{{randomGuidanceText}}”</text>
</view>
<button class="share-btn" open-type="share">
<text>分享这次塔罗</text>
</button>
</view>
<button class="reset-btn" bindtap="resetSpread">重新开启</button>
</view>
</view>

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,10 @@ Page({
article: null
},
handleBack() {
wx.navigateBack({ delta: 1 });
},
onLoad(options) {
const id = options.id;
const article = getArticleById(id);

View File

@ -1,4 +1,8 @@
<view class="container" wx:if="{{article}}">
<view class="nav-back" bindtap="handleBack">
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<view class="article-header">
<text class="article-title">{{article.title}}</text>
<text class="article-summary">{{article.summary}}</text>

View File

@ -1,7 +1,32 @@
.container {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 40rpx 30rpx 120rpx;
padding: 140rpx 30rpx 120rpx;
}
/* 自定义返回按钮样式 */
.nav-back {
position: absolute;
top: 40rpx;
left: 30rpx;
display: flex;
align-items: center;
z-index: 100;
padding: 10rpx 20rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.05);
}
.back-icon {
font-size: 32rpx;
margin-right: 8rpx;
color: #c9a0dc;
}
.back-text {
font-size: 26rpx;
color: #c9a0dc;
letter-spacing: 2rpx;
}
.article-header {

View File

@ -11,6 +11,13 @@ Page({
});
},
// 返回上一页
handleBack() {
wx.navigateBack({
delta: 1
});
},
// 点击分类卡片
goToList(e) {
const category = e.currentTarget.dataset.category;

View File

@ -1,4 +1,8 @@
<view class="container">
<view class="nav-back" bindtap="handleBack">
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<view class="header">
<text class="page-title">塔罗知识</text>
</view>

View File

@ -1,7 +1,32 @@
.container {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 40rpx 30rpx;
padding: 140rpx 30rpx;
}
/* 自定义返回按钮样式 */
.nav-back {
position: absolute;
top: 40rpx;
left: 30rpx;
display: flex;
align-items: center;
z-index: 100;
padding: 10rpx 20rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.05);
}
.back-icon {
font-size: 32rpx;
margin-right: 8rpx;
color: #c9a0dc;
}
.back-text {
font-size: 26rpx;
color: #c9a0dc;
letter-spacing: 2rpx;
}
.header {

View File

@ -7,6 +7,10 @@ Page({
articles: []
},
handleBack() {
wx.navigateBack({ delta: 1 });
},
onLoad(options) {
const category = options.category || 'beginner';
const categories = getCategories();

View File

@ -1,4 +1,8 @@
<view class="container">
<view class="nav-back" bindtap="handleBack">
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<view class="header">
<text class="page-title">{{categoryName}}</text>
</view>

View File

@ -1,7 +1,32 @@
.container {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 40rpx 30rpx;
padding: 140rpx 30rpx;
}
/* 自定义返回按钮样式 */
.nav-back {
position: absolute;
top: 40rpx;
left: 30rpx;
display: flex;
align-items: center;
z-index: 100;
padding: 10rpx 20rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.05);
}
.back-icon {
font-size: 32rpx;
margin-right: 8rpx;
color: #c9a0dc;
}
.back-text {
font-size: 26rpx;
color: #c9a0dc;
letter-spacing: 2rpx;
}
.header {

6
pages/mine/mine.js Normal file
View File

@ -0,0 +1,6 @@
Page({
data: {
},
onLoad: function (options) {
}
})

3
pages/mine/mine.json Normal file
View File

@ -0,0 +1,3 @@
{
"navigationBarTitleText": "我的"
}

3
pages/mine/mine.wxml Normal file
View File

@ -0,0 +1,3 @@
<view class="container">
<text>Mine Page</text>
</view>

1
pages/mine/mine.wxss Normal file
View File

@ -0,0 +1 @@
/* pages/mine/mine.wxss */

View File

@ -0,0 +1,19 @@
Page({
data: {
show: false
},
onLoad() {
setTimeout(() => {
this.setData({ show: true })
}, 100)
},
handleAgree() {
wx.setStorageSync('privacyAgreed', true)
wx.reLaunch({
url: '/pages/home/home'
})
}
})

View File

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "隐私保护提示",
"navigationStyle": "default"
}

View File

@ -0,0 +1,25 @@
<view class="overlay">
<view class="modal {{show ? 'modal-show' : ''}}">
<view class="title">🔮 神秘仪式开启前</view>
<scroll-view scroll-y class="content">
<view class="text">
<view>在揭开塔罗的神秘面纱之前,</view>
<view>请您阅读并同意《隐私政策》。</view>
<view style="height: 20rpx;"></view>
<view>我们仅在必要范围内收集信息,</view>
<view>用于生成解读指引与积分功能。</view>
</view>
<navigator url="/pages/privacy/privacy" class="link">
查看完整《隐私政策》
</navigator>
</scroll-view>
<button class="agree-btn" bindtap="handleAgree">
✨ 同意并开启指引
</button>
</view>
</view>

View File

@ -0,0 +1,85 @@
/* 整体遮罩背景 */
page {
height: 100%;
overflow: hidden;
}
.overlay {
height: 100vh;
width: 100vw;
background: radial-gradient(circle at top, #1b1b3a, #0d0d1f);
display: flex;
justify-content: center;
align-items: center;
}
/* 弹窗卡片 */
.modal {
width: 75%;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20rpx);
border-radius: 30rpx;
padding: 50rpx 40rpx;
opacity: 0;
transform: translateY(40rpx);
transition: all 0.6s cubic-bezier(0.19, 1, 0.22, 1);
box-shadow: 0 0 40rpx rgba(122, 92, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* 动画显示 */
.modal-show {
opacity: 1;
transform: translateY(0);
}
/* 标题 */
.title {
font-size: 38rpx;
font-weight: bold;
color: #ffffff;
text-align: center;
margin-bottom: 40rpx;
text-shadow: 0 2rpx 10rpx rgba(122, 92, 255, 0.5);
}
/* 内容区域 */
.content {
height: 300rpx;
margin-bottom: 50rpx;
}
/* 文本 */
.text {
font-size: 28rpx;
color: #ddddff;
line-height: 1.8;
text-align: center;
}
/* 链接 */
.link {
color: #b499ff;
margin-top: 30rpx;
display: block;
text-align: center;
text-decoration: underline;
font-size: 26rpx;
}
/* 按钮 */
.agree-btn {
background: linear-gradient(135deg, #7a5cff, #b499ff);
color: #ffffff;
border-radius: 60rpx;
font-size: 32rpx;
padding: 10rpx 0;
box-shadow: 0 8rpx 30rpx rgba(122, 92, 255, 0.4);
font-weight: bold;
border: none;
}
.agree-btn:active {
transform: scale(0.96);
box-shadow: 0 4rpx 15rpx rgba(122, 92, 255, 0.4);
}

16
pages/privacy/privacy.js Normal file
View File

@ -0,0 +1,16 @@
Page({
data: {
},
onLoad: function (options) {
// 页面加载
},
onShareAppMessage: function () {
return {
title: '隐私政策 - 塔罗占卜小程序',
path: '/pages/privacy/privacy'
};
}
});

View File

@ -0,0 +1,5 @@
{
"navigationBarTitleText": "隐私政策",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
}

194
pages/privacy/privacy.wxml Normal file
View File

@ -0,0 +1,194 @@
<view class="privacy-container">
<view class="privacy-header">
<text class="privacy-title">隐私政策</text>
<text class="update-date">更新日期: 2026年2月14日</text>
</view>
<scroll-view class="privacy-content" scroll-y>
<view class="section">
<text class="section-title">引言</text>
<text class="section-text">
欢迎使用我们的塔罗占卜小程序。本小程序由【XXX】运营。
我们非常重视您的隐私保护和个人信息安全。
在您使用本小程序前,请您仔细阅读并充分理解本隐私政策。
当您点击“同意”并开始使用本小程序时,即表示您已阅读、
理解并同意本政策的全部内容。
</text>
</view>
<view class="section">
<text class="section-title">一、我们收集的信息</text>
<text class="section-text">
为向您提供塔罗解读服务,我们可能会收集以下信息:
</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">您输入的占卜问题内容</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">积分记录与使用情况</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">广告观看记录(仅用于积分奖励统计)</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">设备基础信息(用于保障运行及优化体验)</text>
</view>
<text class="section-note">
除使用第三方AI服务生成解读外
所有数据默认仅存储在您的设备本地,
我们不会主动上传至自有服务器。
</text>
</view>
<view class="section">
<text class="section-title">二、信息的使用目的</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">为您生成塔罗牌解读内容</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">管理积分系统与奖励机制</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">优化产品功能与用户体验</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">实现广告奖励功能</text>
</view>
</view>
<view class="section">
<text class="section-title">三、信息存储与删除</text>
<text class="section-text">
您的塔罗问题、积分记录等数据默认通过微信小程序
本地存储功能保存在您的设备中。
您可以通过删除小程序清除全部本地数据。
我们不会在服务器长期保存您的个人历史记录。
</text>
</view>
<view class="section">
<text class="section-title">四、第三方服务说明</text>
<text class="section-text">
为实现部分功能,我们可能会使用以下第三方服务:
</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">
DeepSeek AI 服务:用于生成塔罗解读内容。
您输入的占卜问题文本将发送至 DeepSeek API 接口进行处理,
仅用于生成解读结果,不会包含您的昵称、头像、
手机号等可识别身份信息。
</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">
微信激励视频广告:用于积分奖励功能。
广告服务由微信官方提供,数据处理遵循微信平台相关隐私政策。
</text>
</view>
</view>
<view class="section">
<text class="section-title">五、用户权利</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">查看和管理您的积分记录</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">选择是否观看广告获取奖励</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">删除小程序以清除本地数据</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">随时停止使用本服务</text>
</view>
</view>
<view class="section">
<text class="section-title">六、信息安全</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">与AI接口通信采用HTTPS加密传输</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">不收集敏感身份识别信息</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">遵循微信小程序平台安全规范</text>
</view>
</view>
<view class="section">
<text class="section-title">七、未成年人保护</text>
<text class="section-text">
如您为未成年人,请在监护人指导下阅读本政策并使用本服务。
</text>
</view>
<view class="section">
<text class="section-title">八、免责声明</text>
<text class="section-text">
本小程序提供的塔罗解读内容仅供娱乐和参考,
不构成任何现实决策建议或结果承诺。
</text>
</view>
<view class="section">
<text class="section-title">九、联系我们</text>
<text class="section-text">
本小程序运营者XXX
</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">联系邮箱xxx@xxx.com</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">反馈方式:小程序内反馈入口</text>
</view>
</view>
<view class="footer">
<text class="footer-text">
感谢您信任并使用我们的服务!
</text>
</view>
</scroll-view>
</view>

111
pages/privacy/privacy.wxss Normal file
View File

@ -0,0 +1,111 @@
/* 隐私政策页面样式 */
.privacy-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 0;
}
.privacy-header {
text-align: center;
padding: 40rpx 30rpx;
background: rgba(255, 255, 255, 0.95);
margin: 0 30rpx 30rpx;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
}
.privacy-title {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #2d3748;
margin-bottom: 16rpx;
}
.update-date {
display: block;
font-size: 24rpx;
color: #718096;
}
.privacy-content {
height: calc(100vh - 280rpx);
padding: 0 30rpx;
box-sizing: border-box;
width: 100%;
}
.section {
background: rgba(255, 255, 255, 0.95);
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
box-sizing: border-box;
width: 100%;
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: bold;
color: #2d3748;
margin-bottom: 20rpx;
padding-bottom: 12rpx;
border-bottom: 2rpx solid #e2e8f0;
}
.section-text {
display: block;
font-size: 28rpx;
color: #4a5568;
line-height: 1.8;
margin-bottom: 16rpx;
}
.list-item {
display: flex;
margin-bottom: 16rpx;
padding-left: 8rpx;
}
.bullet {
color: #667eea;
font-size: 28rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.list-text {
flex: 1;
font-size: 26rpx;
color: #4a5568;
line-height: 1.7;
}
.section-note {
display: block;
font-size: 24rpx;
color: #718096;
margin-top: 16rpx;
padding: 16rpx;
background: #f7fafc;
border-radius: 8rpx;
border-left: 4rpx solid #667eea;
}
.footer {
text-align: center;
padding: 40rpx 30rpx;
background: rgba(255, 255, 255, 0.95);
border-radius: 16rpx;
margin-bottom: 40rpx;
}
.footer-text {
display: block;
font-size: 28rpx;
color: #667eea;
font-weight: 500;
}

View File

@ -0,0 +1,55 @@
Page({
clearCache() {
wx.showModal({
title: '确认清除',
content: '将清除所有本地数据,包括积分记录,是否继续?',
success(res) {
if (res.confirm) {
try {
wx.clearStorageSync()
wx.showToast({
title: '已清除',
icon: 'none'
})
// Consider re-initializing necessary keys if needed, or just let app.js handle it on next launch
} catch (e) {
wx.showToast({
title: '清除失败',
icon: 'none'
})
}
}
}
})
},
revokePrivacy() {
wx.showModal({
title: '撤销隐私授权',
content: '撤销后将重置隐私协议状态,下次启动需重新同意,是否继续?',
success(res) {
if (res.confirm) {
try {
wx.removeStorageSync('privacyAgreed')
wx.showToast({
title: '已撤销',
icon: 'none'
})
// 延迟跳转,确保提示显示
setTimeout(() => {
wx.reLaunch({
url: '/pages/privacy-agree/privacy-agree'
})
}, 1500)
} catch (e) {
wx.showToast({
title: '操作失败',
icon: 'none'
})
}
}
}
})
}
})

View File

@ -0,0 +1,6 @@
{
"navigationBarTitleText": "设置",
"navigationStyle": "default",
"navigationBarBackgroundColor": "#0d0d1f",
"navigationBarTextStyle": "white"
}

View File

@ -0,0 +1,39 @@
<view class="settings-container">
<!-- 功能区 -->
<view class="group">
<view class="group-title">账户与数据</view>
<view class="item" bindtap="clearCache">
<text>清除本地数据</text>
<text>></text>
</view>
<view class="item" bindtap="revokePrivacy">
<text>撤销隐私授权</text>
<text>></text>
</view>
</view>
<!-- 协议区 -->
<view class="group">
<view class="group-title">协议与说明</view>
<navigator url="/pages/privacy/privacy" class="item">
<text>隐私政策</text>
<text>></text>
</navigator>
<navigator url="/pages/user-agreement/user-agreement" class="item">
<text>用户协议</text>
<text>></text>
</navigator>
<view class="item">
<text>当前版本</text>
<text>v1.0.0</text>
</view>
</view>
</view>

View File

@ -0,0 +1,35 @@
.settings-container {
padding: 30rpx;
background: #0d0d1f;
min-height: 100vh;
}
.group {
margin-bottom: 40rpx;
background: rgba(255,255,255,0.06);
border-radius: 20rpx;
padding: 0 20rpx;
}
.group-title {
font-size: 24rpx;
color: #b499ff;
margin-bottom: 10rpx;
padding-left: 10rpx;
padding-top: 20rpx;
}
.item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 10rpx;
border-bottom: 1rpx solid rgba(255,255,255,0.05);
color: #ffffff;
font-size: 28rpx;
}
.item:last-child {
border-bottom: none;
}

View File

@ -0,0 +1,8 @@
Page({
data: {
},
onLoad(options) {
}
})

View File

@ -0,0 +1,3 @@
{
"navigationBarTitleText": "用户协议"
}

View File

@ -0,0 +1,156 @@
<view class="agreement-container">
<view class="agreement-header">
<text class="agreement-title">用户服务协议</text>
<text class="update-date">更新日期2026年2月14日</text>
</view>
<scroll-view class="agreement-content" scroll-y>
<view class="section">
<text class="section-title">一、协议说明</text>
<text class="section-text">
欢迎使用本塔罗占卜小程序。本小程序由【XXX运营者名称】开发和运营。
在您使用本服务前,请您仔细阅读本协议全部内容。
当您点击“同意”或实际使用本服务,即表示您已阅读、
理解并同意受本协议约束。
</text>
</view>
<view class="section">
<text class="section-title">二、服务内容</text>
<text class="section-text">
本小程序为娱乐性质的塔罗解读服务,
通过人工智能技术生成塔罗牌解读内容。
所有解读内容仅供娱乐和参考,
不构成任何现实决策建议、投资建议、
医疗建议或法律建议。
</text>
</view>
<view class="section">
<text class="section-title">三、用户行为规范</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">不得利用本服务发布违法、违规内容</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">不得利用本服务进行欺诈或恶意行为</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">不得尝试破坏系统安全或恶意攻击接口</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">不得批量抓取或复制生成内容用于商业用途</text>
</view>
</view>
<view class="section">
<text class="section-title">四、AI内容免责声明重要提示</text>
<text class="section-text">
本小程序提供的塔罗解读内容由人工智能系统自动生成,
基于算法模型对塔罗象征意义进行语言表达,
不代表真实占卜师的专业判断。
</text>
<text class="section-text">
所有解读结果仅供娱乐和文化参考,
不构成任何形式的现实建议,
包括但不限于投资建议、医疗建议、
心理咨询建议、法律建议或情感决策建议。
</text>
<text class="section-text">
人工智能生成内容可能存在理解偏差、
信息不完整或表达不准确的情况,
我们不对解读结果的准确性、完整性、
时效性或适用性作任何保证。
</text>
<text class="section-text">
因依赖或使用解读内容所产生的任何直接或间接后果,
包括经济损失、决策失误或其他损害,
均由用户自行承担责任。
</text>
<text class="section-text">
如您对生成内容存在疑问,
请理性判断并结合现实情况作出决策。
</text>
<view class="section-note">
本服务不提供预测未来、保证结果或改变现实结果的能力。
塔罗解读仅作为象征性表达与自我思考工具。
</view>
</view>
<view class="section">
<text class="section-title">五、积分与广告机制</text>
<text class="section-text">
本小程序采用积分机制提供服务。
用户可通过观看微信官方激励视频广告获得积分奖励。
积分仅用于本小程序内使用,不可兑换现金或转让。
</text>
</view>
<view class="section">
<text class="section-title">六、知识产权</text>
<text class="section-text">
本小程序的界面设计、功能结构、代码、品牌标识等
均归【XXX运营者名称】所有。
未经许可,不得擅自复制、传播或用于商业用途。
</text>
</view>
<view class="section">
<text class="section-title">七、责任限制</text>
<text class="section-text">
因使用本服务所产生的任何直接或间接损失,
包括但不限于决策失误、投资损失等,
运营方不承担任何法律责任。
如因不可抗力或系统维护导致服务中断,
我们不承担由此产生的损失。
</text>
</view>
<view class="section">
<text class="section-title">八、协议修改</text>
<text class="section-text">
我们有权根据法律法规或运营需要对本协议进行修改。
修改后的协议将在小程序内公布。
您继续使用本服务即视为接受更新后的协议内容。
</text>
</view>
<view class="section">
<text class="section-title">九、联系我们</text>
<text class="section-text">
如您对本协议有任何疑问或建议,请联系:
</text>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">运营者名称【XXX运营者名称】</text>
</view>
<view class="list-item">
<text class="bullet">•</text>
<text class="list-text">联系邮箱【xxx@xxx.com】</text>
</view>
</view>
<view class="footer">
<text class="footer-text">
感谢您理性使用本服务,愿塔罗为您带来启发。
</text>
</view>
</scroll-view>
</view>

View File

@ -0,0 +1,112 @@
/* 协议页面样式 - 优化阅读体验 */
.agreement-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding-bottom: 40rpx;
}
.agreement-header {
text-align: center;
padding: 60rpx 40rpx;
background: rgba(255, 255, 255, 0.95);
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
margin: 40rpx 30rpx 30rpx;
border-radius: 20rpx;
}
.agreement-title {
display: block;
font-size: 40rpx;
font-weight: bold;
color: #2d3748;
margin-bottom: 16rpx;
letter-spacing: 2rpx;
}
.update-date {
display: block;
font-size: 24rpx;
color: #a0aec0;
}
.agreement-content {
height: calc(100vh - 240rpx);
padding: 0 30rpx;
box-sizing: border-box;
}
.section {
background: rgba(255, 255, 255, 0.95);
border-radius: 16rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #2d3748;
margin-bottom: 24rpx;
position: relative;
padding-left: 20rpx;
}
.section-title::before {
content: '';
position: absolute;
left: 0;
top: 8rpx;
bottom: 8rpx;
width: 8rpx;
background: #667eea;
border-radius: 4rpx;
}
.section-text {
display: block;
font-size: 28rpx;
color: #4a5568;
line-height: 1.8;
text-align: justify;
margin-bottom: 10rpx;
}
.list-item {
display: flex;
margin-bottom: 16rpx;
align-items: flex-start;
}
.bullet {
color: #667eea;
font-size: 24rpx; /* Smaller bullet for subtleness */
margin-right: 16rpx;
margin-top: 6rpx; /* Align with text */
flex-shrink: 0;
}
.list-text {
flex: 1;
font-size: 28rpx;
color: #4a5568;
line-height: 1.8;
text-align: justify;
}
.footer {
text-align: center;
padding: 60rpx 40rpx;
margin-top: 40rpx;
}
.footer-text {
display: block;
font-size: 26rpx;
color: #a0aec0;
letter-spacing: 1rpx;
}

View File

@ -1,326 +1,78 @@
const { getZodiacList } = require('../../utils/zodiacData');
const { fetchZodiacFortune, fetchDailyOverview } = require('../../utils/zodiacAI');
const { checkShareReward } = require('../../utils/pointsManager');
Page({
data: {
selectedZodiac: null,
currentFortune: null, // { keyword, today, lucky, notice, match, action, summary }
isMatchExpanded: false, // 控制关系提示的展开状态
currentFortune: null,
isLoading: false,
isMatchExpanded: false,
zodiacList: [],
dailyOverview: null // 新增:星象概览
},
// 12星座数据 - 增加 summary 字段
zodiacList: [
{
name: '白羊座',
symbol: '♈',
range: '3.21 4.19',
fortunes: [
{
keyword: '开启',
today: '感觉能量在一点点回升,像温柔的晨光,正如适合开启一个小计划。',
lucky: '试着做一件从未做过的小事,哪怕只是换条路漫步回家。',
notice: '稍微慢下来也没关系,深呼吸,给自己一点缓冲的时间。',
action: '也许可以尝试一项简单的运动,或者把那个想了很久的小决定做了。',
match: '狮子座',
summary: '整体氛围积极向上,适宜迈出新的一步。'
onLoad: async function () {
this.setData({
zodiacList: getZodiacList()
});
// 加载星象概览
const overview = await fetchDailyOverview();
this.setData({ dailyOverview: overview });
},
{
keyword: '直觉',
today: '直觉敏锐的一天,内心的声音会温柔地指引你去对的地方。',
lucky: '流一点汗,让身体的能量自由流动起来。',
notice: '倾听也是一种力量,试着先听听别人的想法。',
action: '相信你的第一反应,今天不需要过度分析,跟着感觉走就好。',
match: '射手座',
summary: '这是一个依靠感性而非逻辑的时刻,信任直觉。'
// 返回上一页或列表
goBack: function () {
console.log('[Zodiac] goBack triggered');
if (this.data.selectedZodiac) {
// 如果在详情页,返回列表
this.setData({
selectedZodiac: null
});
} else {
// 如果在列表页,尝试返回上一页
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack({
delta: 1
});
} else {
// 如果是直接打开(如分享进入),则返回首页
wx.reLaunch({
url: '/pages/home/home'
});
}
]
},
{
name: '金牛座',
symbol: '♉',
range: '4.20 5.20',
fortunes: [
{
keyword: '安稳',
today: '享受当下的安稳就好,物质的触感能带给你实实在在的安全感。',
lucky: '一杯温热的茶,或是一顿用心准备的晚餐,都是很好的滋养。',
notice: '柔软一点,你会发现世界比想象中更宽阔。',
action: '花点时间整理一下钱包或书桌,有序的环境会让你感到平静。',
match: '处女座',
summary: '平稳是今日的主基调,无需向外过多索求。'
},
{
keyword: '秩序',
today: '按部就班也很好,秩序感会让你感到踏实和放松。',
lucky: '在整理收纳中梳理思绪,享受物品归位的快乐。',
notice: '试着松开手,有时候放手反而能拥有更多。',
action: '去做一次舒缓的按摩,或者仅仅是静静地听一段白噪音。',
match: '摩羯座',
summary: '在规则与秩序中能找到内心的宁静。'
}
]
},
{
name: '双子座',
symbol: '♊',
range: '5.21 6.21',
fortunes: [
{
keyword: '灵感',
today: '思维很活跃,灵感像小火花一样在闪烁。',
lucky: '翻翻新书的目录,或和陌生人简单聊几句。',
notice: '试着一次只做一件事情,专注会让你更舒服。',
action: '随身带一个小本子,记录下今天那些有趣的小念头。',
match: '水瓶座',
summary: '思维跳跃的一天,由于灵感的迸发而显得生动。'
},
{
keyword: '表达',
today: '沟通的桥梁已经架好,适合真实而轻松地表达自己。',
lucky: '记录下脑海中一闪而过的念头,它们很珍贵。',
notice: '有时候稍微沉默一下,给自己留点空间。',
action: '给许久未见的老朋友发个信息,简单的问候就很好。',
match: '天秤座',
summary: '适宜交流与分享,信息的流动会带来新的机会。'
}
]
},
{
name: '巨蟹座',
symbol: '♋',
range: '6.22 7.22',
fortunes: [
{
keyword: '疗愈',
today: '情感细腻丰富,适合向内探索,疗愈旧日的伤口。',
lucky: '去水边走走,水的包容会让你感到平静。',
notice: '保护好自己的情绪边界,不用对所有人的话都太在意。',
action: '今晚给自己做一顿暖胃的食物,或者泡一个舒服的热水澡。',
match: '双鱼座',
summary: '向内关注自我,是一个适合自我修复的温和日子。'
},
{
keyword: '关怀',
today: '温柔是你的铠甲,你对他人的关怀也会回流到自己身上。',
lucky: '给家人打个电话,或者照顾一株植物。',
notice: '过去已去,当下才是最真实的,试着活在此时此刻。',
action: '整理一下旧照片,保留快乐的记忆就好。',
match: '天蝎座',
summary: '被温柔的情绪包围,付出与接受都自然发生。'
}
]
},
{
name: '狮子座',
symbol: '♌',
range: '7.23 8.22',
fortunes: [
{
keyword: '舞台',
today: '自信的光芒很美,你本身就是舞台的中心。',
lucky: '穿一件喜欢的亮色衣服,或者大方地接受别人的赞美。',
notice: '真正的王者懂得示弱的艺术,柔软也是一种力量。',
action: '对着镜子里的自己微笑,肯定自己的一个优点。',
match: '白羊座',
summary: '光芒外露,适宜展示自我,但也需张弛有度。'
},
{
keyword: '创造',
today: '创造力在涌动,与其等待机会,不如自己创造一个小舞台。',
lucky: '像孩子一样去玩耍,画画或者唱歌都很棒。',
notice: '保持谦逊,你会看到更多不一样的风景。',
action: '尝试一种新的娱乐方式,或者去一个从未去过的店。',
match: '射手座',
summary: '创造力充沛,是表达个性和释放天性的好时机。'
}
]
},
{
name: '处女座',
symbol: '♍',
range: '8.23 9.22',
fortunes: [
{
keyword: '清晰',
today: '逻辑很清晰,能看透事物的本质,混乱将离你而去。',
lucky: '列一张简单的清单,每划掉一项都是一种治愈。',
notice: '对自己宽容一点,完美是一个方向,而不是必须到达的终点。',
action: '清理手机里无用的截图和APP数字极简也能带来清爽。',
match: '金牛座',
summary: '条理清晰,事半功倍,适合处理复杂的细节事务。'
},
{
keyword: '服务',
today: '助人的愿望很美好,你的细心能帮到很多人。',
lucky: '做一次深度的清洁,无论是环境还是心灵。',
notice: '偶尔也要抬头看看森林,不要总是盯着细节看。',
action: '在这个周末,尝试“一次只做一件事”,体验专注的快乐。',
match: '摩羯座',
summary: '在付出中获得满足感,但也需注意微观与宏观的平衡。'
}
]
},
{
name: '天秤座',
symbol: '♎',
range: '9.23 10.23',
fortunes: [
{
keyword: '平衡',
today: '追求和谐与美,在平衡中找到内心的宁静。',
lucky: '去美术馆逛逛,或者欣赏一首优美的乐曲。',
notice: '不要为了取悦他人而委屈自己,温和地拒绝也是可以的。',
action: '更换一下手机或电脑的壁纸,换成一张让你觉得美的图片。',
match: '双子座',
summary: '适宜在审美与艺术中寻找平衡,享受片刻的优雅。'
},
{
keyword: '连接',
today: '人际关系很顺畅,适合化解误会,建立新的连接。',
lucky: '买一束花插在瓶中,为生活增添一点仪式感。',
notice: '犹豫不决时,抛硬币也是一种把命运交给天意的智慧。',
action: '主动赞美身边的一个人,真诚的赞美会让大家都很开心。',
match: '水瓶座',
summary: '良性的互动会带来好运,人际关系是今日的重点。'
}
]
},
{
name: '天蝎座',
symbol: '♏',
range: '10.24 11.22',
fortunes: [
{
keyword: '洞察',
today: '洞察力很强,能看穿表象,直达事物的核心。',
lucky: '在日记本里写下秘密,或者进行一次深刻的冥想。',
notice: '放下执念,学会像蛇蜕皮一样重生。',
action: '读一本悬疑小说或者看一部深度电影,满足你的探索欲。',
match: '巨蟹座',
summary: '直觉与洞察力并存,适合深入思考而非浅尝辄止。'
},
{
keyword: '坚定',
today: '意志力坚定,现在是攻克难题的好时机。',
lucky: '独处,享受一个人的高质量时光。',
notice: '试着交付一点信任,你会发现没有那么多需要防备的事。',
action: '尝试与一个信任的人进行一次深度谈话,不设防的那种。',
match: '双鱼座'
}
]
},
{
name: '射手座',
symbol: '♐',
range: '11.23 12.21',
fortunes: [
{
keyword: '自由',
today: '视野开阔,心向远方,自由的感觉真好。',
lucky: '规划一次未来的旅行,或者看一部异国电影。',
notice: '承诺之前先想一下,不要因为一时兴起而许下诺言。',
action: '去户外走走,甚至只是去楼下公园转一圈,看看天空。',
match: '白羊座',
summary: '心向自由,适合拓展视野,不宜被琐事困住眼界。'
},
{
keyword: '乐观',
today: '乐观的精神很有感染力,你是身边人的开心果。',
lucky: '去户外奔跑,去接触大自然和新鲜空气。',
notice: '检查一下随身物品,细心一点能避免小麻烦。',
action: '学习一个全新的冷知识,或者尝试一种异国料理。',
match: '狮子座',
summary: '积极乐观的一天,行动力是好运的来源。'
}
]
},
{
name: '摩羯座',
symbol: '♑',
range: '12.22 1.19',
fortunes: [
{
keyword: '踏实',
today: '脚踏实地,每一步都算数,成就感源于自律。',
lucky: '制定一个长期的目标,并迈出第一步。',
notice: '不要把自己逼得太紧,工作之余也要学会“虚度时光”。',
action: '复盘一下本周的计划,把完成的事项画上大大的勾。',
match: '金牛座',
summary: '耕耘即有收获,适合专注于当下脚踏实地的积累。'
},
{
keyword: '权威',
today: '权威感提升,你的专业度会得到大家的认可。',
lucky: '向一位长辈或导师请教,汲取传统的智慧。',
notice: '给生活加点糖,不用总是那么严肃。',
action: '给自己泡一杯好茶或好咖啡,在工作间隙享受片刻宁静。',
match: '处女座',
summary: '专业与稳重是今日的通行证,但也需适度放松。'
}
]
},
{
name: '水瓶座',
symbol: '♒',
range: '1.20 2.18',
fortunes: [
{
keyword: '创意',
today: '特立独行的一天,你的怪念头也许就是天才的创意。',
lucky: '尝试一种新的科技产品,或者研究一个小众领域。',
notice: '给身边人一点温度,理智之外也需要温情。',
action: '去浏览一些前沿科技或艺术资讯,给大脑“充充电”。',
match: '双子座',
summary: '思维活跃且独特,适合进行创造性的脑力活动。'
},
{
keyword: '理想',
today: '为了理想而战,你的视野超越了当下,看向未来。',
lucky: '参加一个社群活动,寻找志同道合的伙伴。',
notice: '保持开放的心态,固执己见和坚持真理只有一线之隔。',
action: '换一条平时不走的路去上班/回家,观察沿途的新风景。',
match: '天秤座',
summary: '目光长远,适合规划未来,不宜纠结于眼前的小节。'
}
]
},
{
name: '双鱼座',
symbol: '♓',
range: '2.19 3.20',
fortunes: [
{
keyword: '灵感',
today: '梦幻与现实的边界模糊,灵感如潮水般涌来。',
lucky: '听海浪的声音,或者在睡前读一首诗。',
notice: '勇敢面对现实问题,比躲进幻想更有效。',
action: '听一首纯音乐,闭上眼睛想象一个完美的场景。',
match: '巨蟹座',
summary: '感受力极强的一天,适合艺术创作与精神探索。'
},
{
keyword: '同理心',
today: '同理心爆棚,你能轻易感知他人的悲喜。',
lucky: '做一件善事,哪怕只是给流浪猫一点食物。',
notice: '学会自我净化,不要做情绪的海绵。',
action: '写下一件让你感到焦虑的事,然后把纸条撕碎扔掉。',
match: '天蝎座',
summary: '情感流动充沛,在关怀他人的同时也需照顾好自己。'
}
]
}
]
},
// 选择星座
selectZodiac: function (e) {
selectZodiac: async function (e) {
const index = e.currentTarget.dataset.index;
const zodiac = this.data.zodiacList[index];
const randomIdx = Math.floor(Math.random() * zodiac.fortunes.length);
const fortuneData = zodiac.fortunes[randomIdx];
// 1. 设置加载状态 & 选中星座
this.setData({
selectedZodiac: zodiac,
currentFortune: fortuneData,
isMatchExpanded: false // 重置展开状态
currentFortune: null, // 清空旧数据
isLoading: true, // 开启 Loading
isMatchExpanded: false
});
// 2. 获取运势 (AI 或 缓存)
try {
const fortuneData = await fetchZodiacFortune(zodiac.name);
// 3. 渲染数据
this.setData({
currentFortune: fortuneData,
isLoading: false
});
} catch (err) {
console.error('获取运势失败', err);
this.setData({ isLoading: false });
wx.showToast({ title: '网络小差,请重试', icon: 'none' });
}
},
// 切换关系提示展开状态
@ -335,7 +87,48 @@ Page({
this.setData({
selectedZodiac: null,
currentFortune: null,
isLoading: false,
isMatchExpanded: false // 重置展开状态
});
},
// 跳转回塔罗页面
goToTarot: function () {
wx.navigateTo({
url: '/pages/index/index'
});
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
const titles = [
"我的今日星象运势竟然是...",
"宇宙刚刚给我发了一条星语指引 ✨",
"准到离谱!快来看看你的今日星象",
"这是我此刻的星象能量状态 🌟",
"为你抽取了一份宇宙星象指引"
];
const randomTitle = titles[Math.floor(Math.random() * titles.length)];
// 尝试发放分享奖励
const rewardResult = checkShareReward();
if (rewardResult.success) {
wx.showToast({
title: rewardResult.message,
icon: 'none',
duration: 2000
});
}
// 模拟生成 fromUserId
const mockUserId = 'user_' + Math.random().toString(36).substr(2, 9);
return {
title: randomTitle,
path: `/pages/zodiac/index?fromUserId=${mockUserId}`,
imageUrl: '/images/share-cover.png' // 使用统一的分享图或后续专用图
};
}
})

View File

@ -1,7 +1,24 @@
<view class="container">
<!-- 自定义返回按钮 (仿塔罗页风格) -->
<view class="nav-back" bindtap="goBack">
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<!-- 状态1: 星座列表 -->
<view class="zodiac-grid" wx:if="{{!selectedZodiac}}">
<!-- 今日星象概览 (新增) -->
<view class="daily-overview" wx:if="{{dailyOverview}}">
<view class="overview-title">今日星象概览</view>
<view class="overview-content">{{dailyOverview.content}}</view>
<view class="overview-tags">
<block wx:for="{{dailyOverview.tags}}" wx:key="*this">
<text class="tag-item">{{item}}</text>
</block>
</view>
</view>
<view class="header-tip">选择你的星座</view>
<view class="grid-container">
<block wx:for="{{zodiacList}}" wx:key="name">
@ -14,64 +31,117 @@
</view>
</view>
<!-- 状态2: 运势展示 -->
<view class="result-container" wx:if="{{selectedZodiac}}">
<!-- 状态2: 加载中 -->
<view class="loading-container" wx:if="{{selectedZodiac && isLoading}}">
<view class="loading-symbol">{{selectedZodiac.symbol}}</view>
<view class="loading-text">正在接收宇宙讯息...</view>
</view>
<!-- 状态3: 运势展示 -->
<view class="result-container" wx:elif="{{selectedZodiac && !isLoading}}">
<view class="result-header">
<text class="result-symbol">{{selectedZodiac.symbol}}</text>
<text class="today-title">今日指引 · {{selectedZodiac.name}}</text>
</view>
<!-- 三段式卡片 -->
<!-- 滚动区域 (简单容器即可) -->
<view class="fortune-card">
<!-- 0. 今日关键词 (NEW) -->
<view class="keyword-card">
<!-- 1. 关键词与核心能量 -->
<view class="keyword-section">
<text class="keyword-label">✨ 今日关键词</text>
<text class="keyword-text">{{currentFortune.keyword}}</text>
<view class="core-energy">
<text class="energy-label">🌙 今日核心能量</text>
<text class="energy-text">{{currentFortune.core_energy}}</text>
</view>
</view>
<!-- 1. 今日运势 -->
<view class="section primary-section">
<text class="section-label">✨ 今日运势</text>
<text class="section-content">{{currentFortune.today}}</text>
<view class="divider"></view>
<!-- 2. 运势指数 -->
<view class="section-block">
<text class="section-title">📊 运势指数</text>
<view class="rating-grid">
<view class="rating-item">
<text class="rating-label">综合</text>
<view class="stars">
<block wx:for="{{[1,2,3,4,5]}}" wx:key="*this">
<text class="star">{{item <= currentFortune.ratings.overall ? '★' : '☆'}}</text>
</block>
</view>
</view>
<view class="rating-item">
<text class="rating-label">爱情</text>
<view class="stars">
<block wx:for="{{[1,2,3,4,5]}}" wx:key="*this">
<text class="star">{{item <= currentFortune.ratings.love ? '★' : '☆'}}</text>
</block>
</view>
</view>
<view class="rating-item">
<text class="rating-label">事业</text>
<view class="stars">
<block wx:for="{{[1,2,3,4,5]}}" wx:key="*this">
<text class="star">{{item <= currentFortune.ratings.career ? '★' : '☆'}}</text>
</block>
</view>
</view>
<view class="rating-item">
<text class="rating-label">财运</text>
<view class="stars">
<block wx:for="{{[1,2,3,4,5]}}" wx:key="*this">
<text class="star">{{item <= currentFortune.ratings.wealth ? '★' : '☆'}}</text>
</block>
</view>
</view>
</view>
</view>
<!-- 2. 幸运提示 -->
<view class="section">
<text class="section-label">🍀 幸运提示</text>
<text class="section-content">{{currentFortune.lucky}}</text>
<!-- 3. 详细运势 -->
<view class="section-block">
<text class="section-title">❤️ 感情运势</text>
<text class="section-content">{{currentFortune.fortune_text.love}}</text>
</view>
<!-- 3. 注意事项 -->
<view class="section">
<text class="section-label">💡 注意事项</text>
<text class="section-content">{{currentFortune.notice}}</text>
<view class="section-block">
<text class="section-title">💼 事业运势</text>
<text class="section-content">{{currentFortune.fortune_text.career}}</text>
</view>
<!-- 4. 今日行动建议 (NEW) -->
<view class="section last-section">
<text class="section-label">🌱 今日行动建议</text>
<view class="section-block">
<text class="section-title">💰 财运提醒</text>
<text class="section-content">{{currentFortune.fortune_text.wealth}}</text>
</view>
<view class="divider"></view>
<!-- 4. 幸运元素 -->
<view class="section-block">
<text class="section-title">🎨 幸运元素</text>
<view class="lucky-grid">
<view class="lucky-item">颜色:<text class="highlight">{{currentFortune.lucky_elements.color}}</text></view>
<view class="lucky-item">数字:<text class="highlight">{{currentFortune.lucky_elements.number}}</text></view>
<view class="lucky-item">时间:<text class="highlight">{{currentFortune.lucky_elements.time}}</text></view>
</view>
</view>
<!-- 5. 行动建议 -->
<view class="section-block last-section">
<text class="section-title">🌿 今日行动建议</text>
<text class="section-content">{{currentFortune.action}}</text>
</view>
<!-- 5. 关系提示 (可展开) -->
<view class="status-divider"></view> <!-- 分隔线 -->
<view class="match-module" bindtap="toggleMatch">
<text class="match-trigger">{{isMatchExpanded ? '收起关系提示' : '🌸 今日关系提示(点击查看)'}}</text>
</view>
<view class="match-content" wx:if="{{isMatchExpanded}}">
<text class="match-intro">今天,与你能量更顺的星座是:</text>
<text class="match-name">{{currentFortune.match}}</text>
</view>
</view>
</view>
<!-- 总体结论 (收束模块) -->
<view class="summary-text">{{currentFortune.summary}}</view>
<view class="action-area" bindtap="backToList">
<text class="back-btn">⬅ 选其他星座</text>
<view class="action-area">
<view class="back-btn" bindtap="backToList">⬅ 选其他星座</view>
<button class="share-btn" open-type="share">✨ 分享今日指引</button>
<view class="verify-btn" bindtap="goToTarot">前往塔罗验证能量</view>
</view>
<view class="disclaimer">
<text>内容仅供娱乐与参考,不构成现实决策依据</text>
</view>
</view>

View File

@ -2,24 +2,119 @@
page {
background-color: #1a1a2e;
height: 100%;
color: #fff;
box-sizing: border-box;
}
.container {
padding: 30px 20px;
padding: 100rpx 40rpx 60rpx; /* Increased top padding to avoid overlap */
box-sizing: border-box;
min-height: 100%;
display: flex;
flex-direction: column;
}
/* 自定义返回按钮样式 (同塔罗页) */
.nav-back {
position: absolute;
top: 30rpx;
left: 30rpx;
display: flex;
align-items: center;
z-index: 100;
padding: 10rpx 20rpx;
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.05);
}
.back-icon {
font-size: 32rpx;
margin-right: 8rpx;
color: #c9a0dc;
}
.back-text {
font-size: 26rpx;
color: #c9a0dc;
letter-spacing: 2rpx;
}
/* --- 加载状态样式 --- */
.loading-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 100rpx;
}
.loading-symbol {
font-size: 60px;
color: #e94560;
margin-bottom: 20px;
animation: breathe 2s infinite alternate;
}
.loading-text {
font-size: 16px;
color: rgba(255, 255, 255, 0.7);
letter-spacing: 2px;
}
@keyframes breathe {
0% { transform: scale(0.9); opacity: 0.7; text-shadow: 0 0 10px rgba(233, 69, 96, 0.3); }
100% { transform: scale(1.1); opacity: 1; text-shadow: 0 0 25px rgba(233, 69, 96, 0.8); }
}
/* --- 列表状态样式 --- */
.daily-overview {
background: rgba(255, 255, 255, 0.05); /* Unified dark theme background */
border-radius: 12px;
padding: 15px; /* Reduced padding */
margin-bottom: 20px; /* Reduced margin */
border: 1px solid rgba(255, 255, 255, 0.1);
animation: slideUp 0.6s ease;
}
.overview-title {
font-size: 15px; /* SLightly smaller */
font-weight: bold;
color: #fff; /* White text */
margin-bottom: 8px;
}
.overview-content {
font-size: 12px;
color: rgba(255, 255, 255, 0.8); /* Light grey text */
line-height: 1.5;
margin-bottom: 10px;
text-align: justify;
}
.overview-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tag-item {
font-size: 10px;
background-color: rgba(255, 255, 255, 0.1); /* Darker tag bg */
color: #a29bfe; /* Light purple tag text */
padding: 3px 8px;
border-radius: 10px;
font-weight: 500;
border: 1px solid rgba(162, 155, 254, 0.3);
}
.header-tip {
font-size: 20px;
margin-bottom: 30px;
font-size: 16px; /* Smaller header */
margin-bottom: 15px; /* Reduced margin */
text-align: center;
opacity: 0.9;
color: #fff;
opacity: 1;
letter-spacing: 1px;
font-weight: bold;
}
.grid-container {
@ -30,31 +125,35 @@ page {
.zodiac-item {
width: 30%; /* 三列布局 */
background-color: rgba(255, 255, 255, 0.05);
margin-bottom: 20px;
padding: 20px 0;
border-radius: 12px;
background-color: rgba(255, 255, 255, 0.08);
margin-bottom: 12px; /* Reduced margin */
padding: 12px 0; /* Reduced padding */
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
cursor: pointer;
}
.zodiac-symbol {
font-size: 28px;
margin-bottom: 5px;
font-size: 24px; /* Smaller symbol */
margin-bottom: 4px;
color: rgb(240, 240, 255);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.zodiac-name {
font-size: 14px;
opacity: 0.8;
font-size: 12px; /* Smaller name */
color: #fff;
opacity: 1;
font-weight: 500;
}
.zodiac-range {
font-size: 10px;
opacity: 0.5;
margin-top: 3px;
font-size: 9px; /* Smaller range */
color: rgba(255, 255, 255, 0.6);
margin-top: 2px;
}
/* --- 结果状态样式 --- */
@ -83,7 +182,11 @@ page {
.today-title {
font-size: 18px;
opacity: 0.8;
color: #fff;
opacity: 1;
font-weight: 500;
letter-spacing: 2px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.fortune-card {
@ -99,84 +202,194 @@ page {
flex-direction: column;
}
/* 0. 关键词卡片样式 */
.keyword-card {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 15px;
/* --- 新版结果展示样式 --- */
.keyword-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 25px;
border: 1px solid rgba(255, 255, 255, 0.05);
margin-bottom: 20px;
}
.keyword-label {
font-size: 12px;
color: #a29bfe;
margin-bottom: 5px;
opacity: 0.8;
margin-bottom: 5px;
}
.keyword-text {
font-size: 32px; /* 进一步加大关键词字号 */
font-size: 36px;
color: #fff;
font-weight: bold;
letter-spacing: 4px;
margin-top: 5px;
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
margin-bottom: 15px;
}
/* 三段式内容的样式 */
.section {
.core-energy {
background: rgba(255, 255, 255, 0.05);
padding: 10px 15px;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
}
.energy-label {
display: block;
font-size: 12px;
color: #fbaceb;
margin-bottom: 4px;
}
.energy-text {
font-size: 14px;
color: #fff;
line-height: 1.5;
}
.divider {
width: 100%;
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 20px 0;
}
.section-block {
margin-bottom: 25px;
width: 100%;
}
.section-title {
display: block;
font-size: 15px;
color: #e94560;
font-weight: bold;
margin-bottom: 10px;
}
.section-content {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.6;
text-align: left;
}
/* 评分网格 */
.rating-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.rating-item {
width: 48%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
background: rgba(0,0,0,0.2);
padding: 8px 10px;
border-radius: 6px;
box-sizing: border-box;
}
.rating-label {
font-size: 12px;
color: rgba(255,255,255,0.7);
}
.stars {
font-size: 12px;
color: #ffd700;
letter-spacing: 2px;
}
/* 幸运元素 */
.lucky-grid {
display: flex;
flex-direction: column;
margin-bottom: 25px;
border-bottom: 1px dashed rgba(255, 255, 255, 0.1); /* 分隔线 */
padding-bottom: 15px;
gap: 8px;
background: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 8px;
}
.lucky-item {
font-size: 14px;
color: rgba(255,255,255,0.8);
}
.lucky-item .highlight {
color: #fff;
font-weight: bold;
margin-left: 5px;
}
.last-section {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
}
.section-label {
font-size: 14px;
color: #a29bfe; /* 浅紫色标签 */
margin-bottom: 8px;
font-weight: bold;
/* 总体结论样式 */
.summary-text {
font-size: 13px;
color: rgba(255, 255, 255, 0.5); /* 中性灰 */
margin-top: -20px; /* 调整间距 */
margin-bottom: 30px;
text-align: center;
max-width: 80%;
line-height: 1.5;
}
.section-content {
font-size: 14px; /* 二级信息字号调小 */
line-height: 1.6;
.action-area {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
margin-bottom: 30px;
width: 100%;
}
.verify-btn {
background: linear-gradient(135deg, #e94560, #8b3a62);
color: #fff;
opacity: 0.75; /* 二级信息透明度降低 */
text-align: left;
font-size: 16px;
font-weight: bold;
padding: 12px 30px;
border-radius: 25px;
box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4);
letter-spacing: 1px;
animation: pulseBtn 2s infinite;
}
/* 一级信息强调样式 */
.primary-section .section-content {
font-size: 17px;
opacity: 1;
font-weight: 500;
line-height: 1.7;
letter-spacing: 0.5px;
.share-btn {
background: linear-gradient(135deg, #7a5cff, #005bea); /* 不同的蓝紫色渐变 */
color: #fff;
font-size: 16px;
font-weight: bold;
padding: 12px 30px;
border-radius: 25px;
box-shadow: 0 4px 15px rgba(122, 92, 255, 0.4);
letter-spacing: 1px;
width: auto !important;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1.2;
}
.primary-section .section-label {
font-size: 15px;
color: #e94560; /* 稍微强调标题颜色 */
opacity: 1;
@keyframes pulseBtn {
0% { transform: scale(1); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
50% { transform: scale(1.05); box-shadow: 0 6px 20px rgba(233, 69, 96, 0.6); }
100% { transform: scale(1); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
}
.back-btn {
font-size: 14px;
opacity: 0.6;
color: #c9a0dc; /* Light purple to match theme but be visible */
opacity: 0.9;
text-decoration: underline;
padding: 10px;
padding: 10px; /* Increased padding */
}
/* 简单的动效 */
@ -194,7 +407,7 @@ page {
.status-divider {
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.05);
background-color: rgba(255, 255, 255, 0.1);
margin: 15px 0;
}
@ -208,10 +421,11 @@ page {
.match-trigger {
font-size: 14px;
color: #fbaceb; /* 淡淡的粉色 */
opacity: 0.8;
color: #ffccff; /* Brighter pink/purple */
opacity: 1;
text-decoration: underline;
margin-bottom: 5px;
font-weight: 500;
}
.match-content {
@ -233,15 +447,22 @@ page {
font-size: 18px;
color: #fff;
font-weight: bold;
}
/* 总体结论样式 */
.summary-text {
font-size: 13px;
color: rgba(255, 255, 255, 0.5); /* 中性灰 */
margin-top: -20px; /* 调整间距 */
margin-bottom: 30px;
text-align: center;
max-width: 80%;
line-height: 1.5;
}
/* 免责声明 */
.disclaimer {
width: 100%;
text-align: center;
padding-bottom: 30px;
margin-top: 10px;
}
.disclaimer text {
font-size: 11px;
color: rgba(255, 255, 255, 0.3);
}

185
spreads_reference.js Normal file
View File

@ -0,0 +1,185 @@
// 牌阵配置 - 包含所有可用牌阵
const SPREADS = [
{
"id": "one_card_guidance",
"name": "单张指引",
"cardCount": 1,
"cost": 1,
"positions": ["当下的指引"],
"description": "为你此刻的状态提供一个温和而清晰的方向提示",
"tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "three_time_flow",
"name": "过去·现在·未来",
"cardCount": 3,
"cost": 3,
"positions": ["过去", "现在", "未来"],
"description": "帮助你理解事情的发展过程与可能走向",
"tags": ["综合", "决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "three_problem_solution",
"name": "问题·阻碍·建议",
"cardCount": 3,
"cost": 3,
"positions": ["问题核心", "当前阻碍", "行动建议"],
"description": "聚焦关键问题,找出当下最可行的应对方式",
"tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "love_spark",
"name": "爱情火花",
"cardCount": 3,
"cost": 3,
"positions": ["你的感受", "对方的感受", "关系潜力"],
"description": "探索彼此的真实感受与这段关系的可能性",
"tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "two_choice_decision",
"name": "二选一抉择",
"cardCount": 4,
"cost": 4,
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
"description": "对比两种选择的潜在走向,辅助理性决策",
"tags": ["决策"],
"aiSchema": ["core_theme", "potential_influence", "action_advice"]
},
{
"id": "relationship_healing",
"name": "关系修复",
"cardCount": 4,
"cost": 4,
"positions": ["问题根源", "你的责任", "对方的责任", "修复方向"],
"description": "深入理解关系裂痕,找到和解与修复的路径",
"tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "five_situation_analysis",
"name": "现状分析",
"cardCount": 5,
"cost": 5,
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
"description": "从内外层面拆解局势,明确下一步行动",
"tags": ["事业", "综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "relationship_spread",
"name": "关系洞察",
"cardCount": 5,
"cost": 5,
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
"description": "理解一段关系中的互动模式与发展方向",
"tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "destiny_connection",
"name": "缘分探索",
"cardCount": 5,
"cost": 5,
"positions": ["前世因缘", "今生相遇", "情感纽带", "考验挑战", "缘分走向"],
"description": "探索两人之间的灵性连接与命运安排",
"tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "career_breakthrough",
"name": "职场突破",
"cardCount": 5,
"cost": 5,
"positions": ["当前困境", "优势资源", "潜在机会", "需要克服", "突破方向"],
"description": "识别职场瓶颈,找到突破与晋升的关键",
"tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "timeline_spread",
"name": "时间之流",
"cardCount": 5,
"cost": 5,
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
"description": "追溯事件的时间线,看清发展脉络",
"tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "diamond_spread",
"name": "钻石牌阵",
"cardCount": 5,
"cost": 5,
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
"description": "多角度剖析问题,找到解决之道",
"tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "career_planning",
"name": "事业规划",
"cardCount": 6,
"cost": 6,
"positions": ["当前位置", "核心优势", "发展方向", "潜在障碍", "贵人助力", "长期目标"],
"description": "全面规划职业发展路径,明确长期目标",
"tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "wealth_analysis",
"name": "财运分析",
"cardCount": 6,
"cost": 6,
"positions": ["财务现状", "收入来源", "支出模式", "投资机会", "财务风险", "财富增长"],
"description": "深入分析财务状况,找到财富增长的路径",
"tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "spiritual_guidance",
"name": "灵性指引",
"cardCount": 7,
"cost": 7,
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
"description": "深入探索内在世界,获得灵性层面的启发",
"tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "horseshoe_spread",
"name": "马蹄铁牌阵",
"cardCount": 7,
"cost": 7,
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
"description": "全面了解情况的来龙去脉与未来走向",
"tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "celtic_cross",
"name": "凯尔特十字",
"cardCount": 10,
"cost": 10,
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
"description": "最经典的综合牌阵,深度解析生命议题",
"tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "tree_of_life",
"name": "生命之树",
"cardCount": 10,
"cost": 10,
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
"description": "基于卡巴拉生命之树的深度灵性探索",
"tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
}
];
module.exports = SPREADS;

14
styles/disclaimer.wxss Normal file
View File

@ -0,0 +1,14 @@
/* 免责声明样式 */
.disclaimer {
width: 100%;
text-align: center;
padding: 20rpx 0;
margin-top: 20rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.05);
}
.disclaimer text {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.3);
letter-spacing: 1rpx;
}

61
update_index_function.js Normal file
View File

@ -0,0 +1,61 @@
const fs = require('fs');
const path = 'e:\\I code\\pages\\index\\index.js';
try {
let content = fs.readFileSync(path, 'utf8');
// Define old and new function
// We use a simple string replace for the function head and body start
const oldFn = ` revealNext: function (e) {
const index = e.currentTarget.dataset.index;`;
const newFn = ` revealNext: function (e) {
const index = (e.detail && e.detail.index !== undefined) ? e.detail.index : e.currentTarget.dataset.index;`;
if (content.indexOf(oldFn) === -1) {
console.log('Could not find exact function match. Trying flexible match...');
// regex fallback
content = content.replace(
/revealNext:\s*function\s*\(e\)\s*\{\s*const\s*index\s*=\s*e\.currentTarget\.dataset\.index;/g,
`revealNext: function (e) {
const index = (e.detail && e.detail.index !== undefined) ? e.detail.index : e.currentTarget.dataset.index;`
);
} else {
content = content.replace(oldFn, newFn);
}
// Add viewCardDetail if missing
if (content.indexOf('viewCardDetail:') === -1) {
const insertPoint = ` }
},
// --- 展示解读内容 ---`;
// Find the end of revealNext and insert viewCardDetail
// revealNext ends, usually followed by showInterpretation
const showInterp = ` // --- 展示解读内容 ---`;
const newMethod = ` // 查看已翻开牌的详情
viewCardDetail: function(e) {
const index = (e.detail && e.detail.index !== undefined) ? e.detail.index : e.currentTarget.dataset.index;
const card = this.data.drawnCards[index];
if (card) {
wx.showToast({
title: card.name + (card.isReversed ? '(逆)' : ''),
icon: 'none',
duration: 1500
});
}
},
// --- 展示解读内容 ---`;
content = content.replace(showInterp, newMethod);
}
fs.writeFileSync(path, content, 'utf8');
console.log('Successfully updated index.js functions');
} catch (e) {
console.error(e);
}

55
update_spreads.js Normal file
View File

@ -0,0 +1,55 @@
const fs = require('fs');
const path = 'e:\\I code\\pages\\index\\index.js';
try {
let content = fs.readFileSync(path, 'utf8');
// Regex to match aiSchema: [...] and append layout
// We match "aiSchema": [ ... ] (multiline)
// And escape the replacement curlies
const regex = /("aiSchema":\s*\[[^\]]*\])/g;
let newContent = content.replace(regex, (match) => {
return match + ',\n layout: { type: \'grid\' }';
});
// Special handling for Celtic Cross
// Find the generated text for celtic_cross and replace grid with specific layout
// We need to look for "id": "celtic_cross" ... layout: { type: 'grid' }
const celticLayout = `layout: {
type: 'celtic',
areas: [
{ area: 'center', rotate: 0 },
{ area: 'center', rotate: 90 },
{ area: 'bottom' },
{ area: 'left' },
{ area: 'top' },
{ area: 'right' },
{ area: 'side' },
{ area: 'side' },
{ area: 'side' },
{ area: 'side' }
]
}`;
// Use a regex that finds the celtic_cross object and its layout
// Look for id: celtic_cross, then scan until layout: { type: 'grid' }
// This is tricky with regex.
// Let's just replacing the specific string unique to celtic cross context if possible.
// Easier: Split by "id": "celtic_cross"
const parts = newContent.split('"id": "celtic_cross"');
if (parts.length > 1) {
// part[1] starts after celtic_cross.
// Find the first layout: { type: 'grid' } in part[1] and replace it.
parts[1] = parts[1].replace("layout: { type: 'grid' }", celticLayout);
newContent = parts.join('"id": "celtic_cross"');
}
fs.writeFileSync(path, newContent, 'utf8');
console.log('Successfully updated index.js');
} catch (e) {
console.error('Error:', e);
}

View File

@ -8,16 +8,12 @@ let isRequesting = false;
/**
* 安全的 JSON 解析
* @param {string} text - 待解析的文本
* @returns {Object} 解析结果
*/
function safeJSONParse(text) {
try {
return JSON.parse(text);
} catch (e) {
console.log('AI_JSON_FALLBACK: 首次解析失败,尝试提取 JSON 代码块');
// 尝试提取 JSON 代码块(可能被包裹在 ```json ``` 中)
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/) || text.match(/```\s*([\s\S]*?)\s*```/);
if (jsonMatch && jsonMatch[1]) {
try {
@ -26,20 +22,12 @@ function safeJSONParse(text) {
console.error('AI_JSON_FALLBACK: JSON 代码块解析也失败');
}
}
// 完全失败,返回 fallback 状态
return {
status: 'fallback_text',
raw: text
};
return { status: 'fallback_text', raw: text };
}
}
/**
* 生成兜底解读内容
* @param {Array} drawnCards - 抽到的牌
* @param {Object} spread - 牌阵信息
* @returns {Object} 兜底解读
*/
function generateFallbackInterpretation(drawnCards, spread) {
const positions = drawnCards.map((card, index) => ({
@ -58,8 +46,6 @@ function generateFallbackInterpretation(drawnCards, spread) {
/**
* 执行单次 AI 请求
* @param {Object} config - 请求配置
* @returns {Promise} 请求结果
*/
function executeRequest(config) {
return new Promise((resolve, reject) => {
@ -87,9 +73,6 @@ function executeRequest(config) {
/**
* 带重试的请求执行
* @param {Object} config - 请求配置
* @param {number} retryCount - 当前重试次数
* @returns {Promise} 请求结果
*/
async function executeWithRetry(config, retryCount = 0) {
try {
@ -104,8 +87,6 @@ async function executeWithRetry(config, retryCount = 0) {
if (shouldRetry) {
console.log(`AI_REQUEST_RETRY: 第 ${retryCount + 1} 次重试`);
// 等待 1500ms 后重试
await new Promise(resolve => setTimeout(resolve, 1500));
return executeWithRetry(config, retryCount + 1);
}
@ -116,8 +97,6 @@ async function executeWithRetry(config, retryCount = 0) {
/**
* 安全的 AI 请求主函数
* @param {Object} payload - 请求参数
* @returns {Promise<Object>} 请求结果
*/
async function safeAIRequest(payload) {
const {
@ -165,20 +144,17 @@ async function safeAIRequest(payload) {
timeout: 90000
});
// 清除超时定时器
clearTimeout(timeoutTimer);
clearTimeout(progressTimer1);
clearTimeout(progressTimer2);
// 5. 检查手动超时
if (manualTimeout) {
throw new Error('Manual timeout');
}
// 6. 安全解析 JSON
// 5. 安全解析 JSON
const parsedResult = safeJSONParse(rawResponse);
// 7. 检查是否是 fallback 状态
if (parsedResult.status === 'fallback_text') {
console.log('AI_JSON_FALLBACK: 使用兜底解读');
isRequesting = false;
@ -199,14 +175,12 @@ async function safeAIRequest(payload) {
} catch (err) {
console.error('AI_REQUEST_FAILED:', err);
// 清除定时器
clearTimeout(timeoutTimer);
clearTimeout(progressTimer1);
clearTimeout(progressTimer2);
isRequesting = false;
// 返回兜底解读
return {
status: 'fallback',
data: generateFallbackInterpretation(drawnCards, spread),

87
utils/dailyAdvice.js Normal file
View File

@ -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
};

View File

@ -342,9 +342,52 @@ function getArticleById(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 = {
knowledgeData,
getCategories,
getArticlesByCategory,
getArticleById
getArticleById,
getRandomArticle,
getRandomLocalArticle,
getDailyArticle
};

360
utils/pointsManager.js Normal file
View File

@ -0,0 +1,360 @@
/**
* 积分管理模块
* 提供积分的初始化查询增减校验等功能
* 使用微信小程序本地存储实现
*/
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 STORAGE_KEY_SHARE_DATE = 'tarot_share_date'; // 分享奖励日期
const STORAGE_KEY_SHARE_COUNT = 'tarot_share_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 // 每日上限次数
};
// 分享奖励配置
const SHARE_REWARD_CONFIG = {
REWARD_POINTS: 10, // 分享奖励积分 (改为 10)
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
};
}
}
/**
* 检查并发放分享奖励
* @returns {object} { success: boolean, message: string, points: number }
*/
function checkShareReward() {
try {
const today = new Date().toDateString();
const lastShareDate = wx.getStorageSync(STORAGE_KEY_SHARE_DATE);
let shareCount = wx.getStorageSync(STORAGE_KEY_SHARE_COUNT) || 0;
// 如果日期变化,重置计数
if (lastShareDate !== today) {
shareCount = 0;
wx.setStorageSync(STORAGE_KEY_SHARE_DATE, today);
}
// 检查每日上限
if (shareCount >= SHARE_REWARD_CONFIG.DAILY_LIMIT) {
return {
success: false,
message: '今日分享奖励已达上限',
points: 0
};
}
// 发放奖励
const rewardPoints = SHARE_REWARD_CONFIG.REWARD_POINTS;
const newPoints = addPoints(rewardPoints, 'share_reward');
// 更新计数
shareCount++;
wx.setStorageSync(STORAGE_KEY_SHARE_COUNT, shareCount);
console.log(`[积分系统] 分享奖励 +${rewardPoints} 积分 (今日第 ${shareCount}/${SHARE_REWARD_CONFIG.DAILY_LIMIT} 次)`);
return {
success: true,
message: `分享奖励 +${rewardPoints} 积分`,
points: rewardPoints
};
} catch (error) {
console.error('[积分系统] 分享奖励检查失败:', error);
return {
success: false,
message: '奖励发放失败',
points: 0
};
}
}
module.exports = {
initPoints,
getPoints,
addPoints,
deductPoints,
hasEnough,
checkDailyReward,
// 广告奖励相关
getTodayAdCount,
canWatchAd,
getTodayAdCount,
canWatchAd,
rewardFromAd,
checkShareReward, // 导出新函数
// 常量导出
POINT_SOURCE,
AD_REWARD_CONFIG
};

244
utils/zodiacAI.js Normal file
View File

@ -0,0 +1,244 @@
const { getZodiacList } = require('./zodiacData');
// AI 配置
const API_KEY = 'sk-f659b4c3aa954baaa6cbdbd5cae0b7d1';
const BASE_URL = 'https://api.deepseek.com/chat/completions';
/**
* 获取今日日期字符串 (YYYY-MM-DD)
*/
function getTodayStr() {
const now = new Date();
return `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
}
/**
* 构建系统 Prompt
*/
function buildPrompt(zodiacName) {
return `你是一位温暖、疗愈且富有洞察力的星座运势师。
请根据今天的日期${zodiacName}生成一份专属运势
合规要求 - 极重要
1. 语言风格温柔诗意治愈仅供娱乐和心理参考
2. 禁止绝对化不要使用注定一定会必然等词汇使用可能倾向于建议等委婉表达
3. 禁止迷信承诺严禁承诺具体的发财升职脱单结果
4. 禁止现实决策严禁给出具体的医疗法律投资理财建议
5. 核心基调通过细微的生活洞察给人力量和抚慰侧重心理状态疏导
输出结构
请严格按照以下 JSON 格式输出不要包含 markdown 标记
{
"keyword": "今日关键词 (2个字)",
"core_energy": "核心能量 (15-20字一句话概括)",
"ratings": {
"overall": 4, // 1-5的整数
"love": 3,
"career": 5,
"wealth": 3
},
"fortune_text": {
"love": "感情运势 (30-50字)",
"career": "事业运势 (30-50字)",
"wealth": "财运提醒 (30-50字)"
},
"lucky_elements": {
"color": "颜色",
"number": "数字",
"time": "吉时 (如 15:00-17:00)"
},
"action": "行动建议 (20-30字)"
}`;
}
/**
* 安全解析 JSON
*/
function safeJSONParse(text) {
try {
const cleanText = text.replace(/```json/g, '').replace(/```/g, '').trim();
return JSON.parse(cleanText);
} catch (e) {
console.error('JSON解析失败:', e);
return null;
}
}
/**
* 获取本地兜底数据 (适配新格式)
*/
function getLocalFallback(zodiacName) {
const list = getZodiacList();
const target = list.find(z => z.name === zodiacName);
if (target && target.fortunes && target.fortunes.length > 0) {
const randomIdx = Math.floor(Math.random() * target.fortunes.length);
const oldData = target.fortunes[randomIdx];
// 适配旧数据到新格式
return {
keyword: oldData.keyword,
core_energy: oldData.today,
ratings: {
overall: 4,
love: 3,
career: 4,
wealth: 3
},
fortune_text: {
love: oldData.summary, // 暂用 summary 填充
career: oldData.today,
wealth: oldData.notice
},
lucky_elements: {
color: "今日专属色",
number: "7",
time: "09:00-11:00"
},
action: oldData.action
};
}
return null;
}
/**
* 获取星座运势 (优先缓存 -> AI -> 本地兜底)
* @param {string} zodiacName 星座名称
* @returns {Promise<object>} 运势数据对象
*/
async function fetchZodiacFortune(zodiacName) {
const today = getTodayStr();
const cacheKey = `zodiac_fortune_${zodiacName}_${today}`;
// 1. 检查缓存
try {
const cached = wx.getStorageSync(cacheKey);
if (cached) {
console.log(`[ZodiacAI] Hit cache for ${zodiacName}`);
return cached;
}
} catch (e) {
console.error('读取缓存失败:', e);
}
console.log(`[ZodiacAI] Fetching AI for ${zodiacName}...`);
// 2. 调用 AI 接口
try {
const res = await new Promise((resolve, reject) => {
wx.request({
url: BASE_URL,
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
data: {
model: "deepseek-chat",
messages: [
{ role: "system", content: buildPrompt(zodiacName) },
{ role: "user", content: `今天是 ${today},请解读。` }
],
temperature: 1.3 // 稍微提高创造性
},
timeout: 30000,
success: resolve,
fail: reject
});
});
if (res.statusCode === 200 && res.data.choices && res.data.choices[0]) {
const content = res.data.choices[0].message.content;
const parsedData = safeJSONParse(content);
if (parsedData) {
// 3. 写入缓存 (成功才缓存)
wx.setStorageSync(cacheKey, parsedData);
return parsedData;
}
}
console.warn('[ZodiacAI] AI response invalid, using fallback.');
} catch (err) {
console.error('[ZodiacAI] Request failed:', err);
}
// 4. 失败兜底 (返回本地数据)
return null;
}
/**
* 获取今日星象概览 (每日更新一次)
*/
async function fetchDailyOverview() {
const today = getTodayStr();
const cacheKey = `daily_overview_${today}`;
// 1. 检查缓存
try {
const cached = wx.getStorageSync(cacheKey);
if (cached) {
return cached;
}
} catch (e) {
console.error('读取缓存失败:', e);
}
const prompt = `你是一位专业占星师。请根据今天日期,生成一段通用的【今日星象概览】。
风格要求
- 客观简练有指导意义
- 描述今天的月亮位置主要行星相位如水星三合木星等及其对大众的影响
输出格式
严格 JSON 格式
{
"content": "星象描述 (40-60字)",
"tags": ["标签1", "标签2", "标签3"] // 3个短标签利于社交、适合学习、谨慎决策
}`;
// 2. 调用 AI
try {
const res = await new Promise((resolve, reject) => {
wx.request({
url: BASE_URL,
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
data: {
model: "deepseek-chat",
messages: [
{ role: "system", content: prompt },
{ role: "user", content: `今天是 ${today}` }
],
temperature: 1.0
},
timeout: 30000,
success: resolve,
fail: reject
});
});
if (res.statusCode === 200 && res.data.choices) {
const parsed = safeJSONParse(res.data.choices[0].message.content);
if (parsed) {
wx.setStorageSync(cacheKey, parsed);
return parsed;
}
}
} catch (err) {
console.error('星象概览获取失败:', err);
}
// 兜底数据
return {
content: "今日月亮能量温和,适合处理内心的情绪。水星状态良好,沟通效率较高。",
tags: ["平稳", "沟通顺畅", "宜内省"]
};
}
module.exports = {
fetchZodiacFortune,
fetchDailyOverview
};

322
utils/zodiacData.js Normal file
View File

@ -0,0 +1,322 @@
const zodiacList = [
{
name: '白羊座',
symbol: '♈',
range: '3.21 4.19',
fortunes: [
{
keyword: '开启',
today: '感觉能量在一点点回升,像温柔的晨光,正如适合开启一个小计划。',
lucky: '试着做一件从未做过的小事,哪怕只是换条路漫步回家。',
notice: '稍微慢下来也没关系,深呼吸,给自己一点缓冲的时间。',
action: '也许可以尝试一项简单的运动,或者把那个想了很久的小决定做了。',
match: '狮子座',
summary: '整体氛围积极向上,适宜迈出新的一步。'
},
{
keyword: '直觉',
today: '直觉敏锐的一天,内心的声音会温柔地指引你去对的地方。',
lucky: '流一点汗,让身体的能量自由流动起来。',
notice: '倾听也是一种力量,试着先听听别人的想法。',
action: '相信你的第一反应,今天不需要过度分析,跟着感觉走就好。',
match: '射手座',
summary: '这是一个依靠感性而非逻辑的时刻,信任直觉。'
}
]
},
{
name: '金牛座',
symbol: '♉',
range: '4.20 5.20',
fortunes: [
{
keyword: '安稳',
today: '享受当下的安稳就好,物质的触感能带给你实实在在的安全感。',
lucky: '一杯温热的茶,或是一顿用心准备的晚餐,都是很好的滋养。',
notice: '柔软一点,你会发现世界比想象中更宽阔。',
action: '花点时间整理一下钱包或书桌,有序的环境会让你感到平静。',
match: '处女座',
summary: '平稳是今日的主基调,无需向外过多索求。'
},
{
keyword: '秩序',
today: '按部就班也很好,秩序感会让你感到踏实和放松。',
lucky: '在整理收纳中梳理思绪,享受物品归位的快乐。',
notice: '试着松开手,有时候放手反而能拥有更多。',
action: '去做一次舒缓的按摩,或者仅仅是静静地听一段白噪音。',
match: '摩羯座',
summary: '在规则与秩序中能找到内心的宁静。'
}
]
},
{
name: '双子座',
symbol: '♊',
range: '5.21 6.21',
fortunes: [
{
keyword: '灵感',
today: '思维很活跃,灵感像小火花一样在闪烁。',
lucky: '翻翻新书的目录,或和陌生人简单聊几句。',
notice: '试着一次只做一件事情,专注会让你更舒服。',
action: '随身带一个小本子,记录下今天那些有趣的小念头。',
match: '水瓶座',
summary: '思维跳跃的一天,由于灵感的迸发而显得生动。'
},
{
keyword: '表达',
today: '沟通的桥梁已经架好,适合真实而轻松地表达自己。',
lucky: '记录下脑海中一闪而过的念头,它们很珍贵。',
notice: '有时候稍微沉默一下,给自己留点空间。',
action: '给许久未见的老朋友发个信息,简单的问候就很好。',
match: '天秤座',
summary: '适宜交流与分享,信息的流动会带来新的机会。'
}
]
},
{
name: '巨蟹座',
symbol: '♋',
range: '6.22 7.22',
fortunes: [
{
keyword: '疗愈',
today: '情感细腻丰富,适合向内探索,疗愈旧日的伤口。',
lucky: '去水边走走,水的包容会让你感到平静。',
notice: '保护好自己的情绪边界,不用对所有人的话都太在意。',
action: '今晚给自己做一顿暖胃的食物,或者泡一个舒服的热水澡。',
match: '双鱼座',
summary: '向内关注自我,是一个适合自我修复的温和日子。'
},
{
keyword: '关怀',
today: '温柔是你的铠甲,你对他人的关怀也会回流到自己身上。',
lucky: '给家人打个电话,或者照顾一株植物。',
notice: '过去已去,当下才是最真实的,试着活在此时此刻。',
action: '整理一下旧照片,保留快乐的记忆就好。',
match: '天蝎座',
summary: '被温柔的情绪包围,付出与接受都自然发生。'
}
]
},
{
name: '狮子座',
symbol: '♌',
range: '7.23 8.22',
fortunes: [
{
keyword: '舞台',
today: '自信的光芒很美,你本身就是舞台的中心。',
lucky: '穿一件喜欢的亮色衣服,或者大方地接受别人的赞美。',
notice: '真正的王者懂得示弱的艺术,柔软也是一种力量。',
action: '对着镜子里的自己微笑,肯定自己的一个优点。',
match: '白羊座',
summary: '光芒外露,适宜展示自我,但也需张弛有度。'
},
{
keyword: '创造',
today: '创造力在涌动,与其等待机会,不如自己创造一个小舞台。',
lucky: '像孩子一样去玩耍,画画或者唱歌都很棒。',
notice: '保持谦逊,你会看到更多不一样的风景。',
action: '尝试一种新的娱乐方式,或者去一个从未去过的店。',
match: '射手座',
summary: '创造力充沛,是表达个性和释放天性的好时机。'
}
]
},
{
name: '处女座',
symbol: '♍',
range: '8.23 9.22',
fortunes: [
{
keyword: '清晰',
today: '逻辑很清晰,能看透事物的本质,混乱将离你而去。',
lucky: '列一张简单的清单,每划掉一项都是一种治愈。',
notice: '对自己宽容一点,完美是一个方向,而不是必须到达的终点。',
action: '清理手机里无用的截图和APP数字极简也能带来清爽。',
match: '金牛座',
summary: '条理清晰,事半功倍,适合处理复杂的细节事务。'
},
{
keyword: '服务',
today: '助人的愿望很美好,你的细心能帮到很多人。',
lucky: '做一次深度的清洁,无论是环境还是心灵。',
notice: '偶尔也要抬头看看森林,不要总是盯着细节看。',
action: '在这个周末,尝试“一次只做一件事”,体验专注的快乐。',
match: '摩羯座',
summary: '在付出中获得满足感,但也需注意微观与宏观的平衡。'
}
]
},
{
name: '天秤座',
symbol: '♎',
range: '9.23 10.23',
fortunes: [
{
keyword: '平衡',
today: '追求和谐与美,在平衡中找到内心的宁静。',
lucky: '去美术馆逛逛,或者欣赏一首优美的乐曲。',
notice: '不要为了取悦他人而委屈自己,温和地拒绝也是可以的。',
action: '更换一下手机或电脑的壁纸,换成一张让你觉得美的图片。',
match: '双子座',
summary: '适宜在审美与艺术中寻找平衡,享受片刻的优雅。'
},
{
keyword: '连接',
today: '人际关系很顺畅,适合化解误会,建立新的连接。',
lucky: '买一束花插在瓶中,为生活增添一点仪式感。',
notice: '犹豫不决时,抛硬币也是一种把命运交给天意的智慧。',
action: '主动赞美身边的一个人,真诚的赞美会让大家都很开心。',
match: '水瓶座',
summary: '良性的互动会带来好运,人际关系是今日的重点。'
}
]
},
{
name: '天蝎座',
symbol: '♏',
range: '10.24 11.22',
fortunes: [
{
keyword: '洞察',
today: '洞察力很强,能看穿表象,直达事物的核心。',
lucky: '在日记本里写下秘密,或者进行一次深刻的冥想。',
notice: '放下执念,学会像蛇蜕皮一样重生。',
action: '读一本悬疑小说或者看一部深度电影,满足你的探索欲。',
match: '巨蟹座',
summary: '直觉与洞察力并存,适合深入思考而非浅尝辄止。'
},
{
keyword: '坚定',
today: '意志力坚定,现在是攻克难题的好时机。',
lucky: '独处,享受一个人的高质量时光。',
notice: '试着交付一点信任,你会发现没有那么多需要防备的事。',
action: '尝试与一个信任的人进行一次深度谈话,不设防的那种。',
match: '双鱼座'
}
]
},
{
name: '射手座',
symbol: '♐',
range: '11.23 12.21',
fortunes: [
{
keyword: '自由',
today: '视野开阔,心向远方,自由的感觉真好。',
lucky: '规划一次未来的旅行,或者看一部异国电影。',
notice: '承诺之前先想一下,不要因为一时兴起而许下诺言。',
action: '去户外走走,甚至只是去楼下公园转一圈,看看天空。',
match: '白羊座',
summary: '心向自由,适合拓展视野,不宜被琐事困住眼界。'
},
{
keyword: '乐观',
today: '乐观的精神很有感染力,你是身边人的开心果。',
lucky: '去户外奔跑,去接触大自然和新鲜空气。',
notice: '检查一下随身物品,细心一点能避免小麻烦。',
action: '学习一个全新的冷知识,或者尝试一种异国料理。',
match: '狮子座',
summary: '积极乐观的一天,行动力是好运的来源。'
}
]
},
{
name: '摩羯座',
symbol: '♑',
range: '12.22 1.19',
fortunes: [
{
keyword: '踏实',
today: '脚踏实地,每一步都算数,成就感源于自律。',
lucky: '制定一个长期的目标,并迈出第一步。',
notice: '不要把自己逼得太紧,工作之余也要学会“虚度时光”。',
action: '复盘一下本周的计划,把完成的事项画上大大的勾。',
match: '金牛座',
summary: '耕耘即有收获,适合专注于当下脚踏实地的积累。'
},
{
keyword: '权威',
today: '权威感提升,你的专业度会得到大家的认可。',
lucky: '向一位长辈或导师请教,汲取传统的智慧。',
notice: '给生活加点糖,不用总是那么严肃。',
action: '给自己泡一杯好茶或好咖啡,在工作间隙享受片刻宁静。',
match: '处女座',
summary: '专业与稳重是今日的通行证,但也需适度放松。'
}
]
},
{
name: '水瓶座',
symbol: '♒',
range: '1.20 2.18',
fortunes: [
{
keyword: '创意',
today: '特立独行的一天,你的怪念头也许就是天才的创意。',
lucky: '尝试一种新的科技产品,或者研究一个小众领域。',
notice: '给身边人一点温度,理智之外也需要温情。',
action: '去浏览一些前沿科技或艺术资讯,给大脑“充充电”。',
match: '双子座',
summary: '思维活跃且独特,适合进行创造性的脑力活动。'
},
{
keyword: '理想',
today: '为了理想而战,你的视野超越了当下,看向未来。',
lucky: '参加一个社群活动,寻找志同道合的伙伴。',
notice: '保持开放的心态,固执己见和坚持真理只有一线之隔。',
action: '换一条平时不走的路去上班/回家,观察沿途的新风景。',
match: '天秤座',
summary: '目光长远,适合规划未来,不宜纠结于眼前的小节。'
}
]
},
{
name: '双鱼座',
symbol: '♓',
range: '2.19 3.20',
fortunes: [
{
keyword: '灵感',
today: '梦幻与现实的边界模糊,灵感如潮水般涌来。',
lucky: '听海浪的声音,或者在睡前读一首诗。',
notice: '勇敢面对现实问题,比躲进幻想更有效。',
action: '听一首纯音乐,闭上眼睛想象一个完美的场景。',
match: '巨蟹座',
summary: '感受力极强的一天,适合艺术创作与精神探索。'
},
{
keyword: '同理心',
today: '同理心爆棚,你能轻易感知他人的悲喜。',
lucky: '做一件善事,哪怕只是给流浪猫一点食物。',
notice: '学会自我净化,不要做情绪的海绵。',
action: '写下一件让你感到焦虑的事,然后把纸条撕碎扔掉。',
match: '天蝎座',
summary: '情感流动充沛,在关怀他人的同时也需照顾好自己。'
}
]
}
];
// 获取所有星座列表
function getZodiacList() {
return zodiacList;
}
// 获取今日建议(随机)
function getDailySuggestion() {
// 简单实现:随机返回一个星座的一个运势
const zIdx = Math.floor(Math.random() * zodiacList.length);
const fIdx = Math.floor(Math.random() * zodiacList[zIdx].fortunes.length);
return {
zodiac: zodiacList[zIdx],
fortune: zodiacList[zIdx].fortunes[fIdx]
};
}
module.exports = {
getZodiacList,
getDailySuggestion
};