V1.6: Zodiac Share Logic, Back Button Fix, Points UI Polish

This commit is contained in:
huanglimeng 2026-02-20 15:10:23 +08:00
parent 9292e8dc0f
commit 2b532c0116
50 changed files with 4732 additions and 2180 deletions

54
app.js
View File

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

@ -2,15 +2,28 @@ const { getPoints, checkDailyReward, canWatchAd, getTodayAdCount, rewardFromAd,
const { getDailyAdvice } = require('../../utils/dailyAdvice'); const { getDailyAdvice } = require('../../utils/dailyAdvice');
const { getDailyArticle, getCategories } = require('../../utils/knowledgeData'); const { getDailyArticle, getCategories } = require('../../utils/knowledgeData');
/** /**
* 🔧 开发模式配置 * 🔧 广告功能开关 (上线前配置)
* *
* DEV_MODE = true: 使用模拟广告开发测试 * 1. ENABLE_AD (总开关):
* DEV_MODE = false: 使用真实广告正式上线 * - true: 开启广告功能
* - false: 关闭广告功能 (隐藏按钮, 点击提示"即将上线")
* *
* 上线前务必改为 false * 2. DEV_MODE (开发模式):
* - true: 使用模拟广告 (不拉起微信广告, 直接发奖励)
* - false: 使用真实广告 (拉起微信广告组件)
*
* 上线前:
* - 确保已开通流量主并填入真实 ID
* - DEV_MODE 改为 false
* - ENABLE_AD 改为 true
*/ */
const DEV_MODE = true; // 👈 开发时设为 true上线前改为 false const ENABLE_AD = false; // 👈 暂时关闭广告功能
const DEV_MODE = true; // 👈 开发测试模式
// 积分系统开关 (即便 ENABLE_AD 为 true, 如果此开关为 false, UI 也不显示积分)
const ENABLE_POINTS_UI = true;
Page({ Page({
data: { data: {
@ -24,7 +37,7 @@ Page({
categoryName: '', categoryName: '',
type: 'local' type: 'local'
}, },
energyVisible: false, showAdButton: ENABLE_AD, // 控制广告按钮显示
knowledgeVisible: false knowledgeVisible: false
}, },
@ -36,14 +49,29 @@ Page({
this.initRewardedAd(); this.initRewardedAd();
}, },
// ... (keeping other methods)
// 跳转到星座模块
goToZodiac: function () {
wx.navigateTo({
url: '/pages/zodiac/index'
});
},
/** /**
* 初始化激励视频广告 * 初始化激励视频广告
* 需要在微信公众平台开通流量主并获取广告位ID * 需要在微信公众平台开通流量主并获取广告位ID
*/ */
initRewardedAd: function () { initRewardedAd: function () {
// 🔧 开发模式:跳过真实广告初始化 // 1. 如果广告功能关闭,直接返回
if (!ENABLE_AD) {
console.log('[广告系统] 🔧 广告功能已关闭 (ENABLE_AD = false)');
return;
}
// 2. 如果是开发模式,使用模拟广告,不需要初始化真实组件
if (DEV_MODE) { if (DEV_MODE) {
console.log('[广告系统] 🔧 开发模式:使用模拟广告'); console.log('[广告系统] 🔧 开发模式:使用模拟广告 (DEV_MODE = true)');
return; return;
} }
@ -56,6 +84,7 @@ Page({
try { try {
// 创建激励视频广告实例 // 创建激励视频广告实例
// ⚠️ TODO: 替换为你的真实广告位ID // ⚠️ TODO: 替换为你的真实广告位ID
// 只有在非开发模式且开启广告时才创建
this.rewardedAd = wx.createRewardedVideoAd({ this.rewardedAd = wx.createRewardedVideoAd({
adUnitId: 'adunit-xxxxxxxxxxxxxxxx' // 请替换为你的广告位ID adUnitId: 'adunit-xxxxxxxxxxxxxxxx' // 请替换为你的广告位ID
}); });
@ -122,6 +151,7 @@ Page({
}, },
onShow: function () { onShow: function () {
if (ENABLE_POINTS_UI) {
// 检查每日登录奖励 // 检查每日登录奖励
const rewardResult = checkDailyReward(); const rewardResult = checkDailyReward();
@ -138,6 +168,7 @@ Page({
this.setData({ this.setData({
currentPoints: getPoints() currentPoints: getPoints()
}); });
}
}, },
// 加载每日内容 // 加载每日内容
@ -213,6 +244,13 @@ Page({
}); });
}, },
// 跳转到星座模块
goToZodiac: function () {
wx.navigateTo({
url: '/pages/zodiac/index'
});
},
// 跳转到文章详情 // 跳转到文章详情
goToArticle: function () { goToArticle: function () {
const article = this.data.dailyArticle; const article = this.data.dailyArticle;
@ -238,61 +276,40 @@ Page({
}, 300); }, 300);
}, },
// 跳转到隐私政策 // 跳转到设置页
goToPrivacy: function () { goToSettings: function () {
wx.navigateTo({ wx.navigateTo({
url: '/pages/privacy/privacy' url: '/pages/settings/settings'
}); });
}, },
// 显示积分说明 // 显示积分说明 (切换为自定义浮层)
showPointsInfo: function () { showPointsInfo: function () {
try { this.setData({
console.log('[首页] 点击积分徽章'); pointsVisible: true
const remaining = AD_REWARD_CONFIG.DAILY_LIMIT - getTodayAdCount(); });
const canWatch = canWatchAd(); },
console.log('[首页] 剩余广告次数:', remaining); // 关闭积分说明
console.log('[首页] 是否可以观看:', canWatch); hidePointsInfo: function () {
console.log('[首页] 当前积分:', this.data.currentPoints); this.setData({
pointsVisible: false
const lines = [
'当前积分:' + this.data.currentPoints,
'',
'每次占卜会消耗积分,不同牌阵消耗不同。',
'每日首次登录可获得 +3 积分奖励。',
'',
'看广告可获得积分(今日剩余 ' + remaining + ' 次)'
];
const content = lines.join('\n');
console.log('[首页] 弹窗内容:', content);
console.log('[首页] 准备调用 wx.showModal');
wx.showModal({
title: '积分说明',
content: content,
confirmText: canWatch ? '看广告' : '知道了',
cancelText: '关闭',
success: (res) => {
console.log('[首页] 弹窗回调:', res);
if (res.confirm && canWatch) {
this.watchAdForPoints();
}
},
fail: (err) => {
console.error('[首页] 弹窗失败:', err);
}
}); });
console.log('[首页] wx.showModal 已调用');
} catch (error) {
console.error('[首页] showPointsInfo 错误:', error);
}
}, },
// 观看广告获取积分(支持开发模式) // 观看广告获取积分(支持开发模式)
watchAdForPoints: function () { watchAdForPoints: function () {
// 检查是否还有观看次数 // 1. 全局开关检查
if (!ENABLE_AD) {
wx.showToast({
title: '功能即将上线',
icon: 'none',
duration: 2000
});
return;
}
// 2. 检查是否还有观看次数
if (!canWatchAd()) { if (!canWatchAd()) {
wx.showToast({ wx.showToast({
title: '今日广告次数已用完', title: '今日广告次数已用完',

View File

@ -1,16 +1,37 @@
<view class="tarot-table"> <view class="tarot-table">
<!-- 顶部栏 (已移除) -->
<!-- 右上角积分徽章 --> <!-- 右上角积分徽章 -->
<view class="points-badge" bindtap="showPointsInfo"> <view class="points-badge" bindtap="showPointsInfo" style="top: 100rpx;">
<text class="badge-icon">🎯</text> <text class="badge-icon">🎯</text>
<text class="badge-value">{{currentPoints}}</text> <text class="badge-value">{{currentPoints}}</text>
</view> </view>
<!-- 星座能量入口 (放在主牌堆上方) -->
<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="main-deck" bindtap="goToTarot" hover-class="deck-hover">
<view class="deck-cards"> <view class="deck-cards">
<view class="card-stack card-1"></view> <view class="card-stack card-1">
<view class="card-stack card-2"></view> <view class="card-bg"></view>
<view class="card-stack card-3"></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> </view>
<text class="deck-title">开始抽牌</text> <text class="deck-title">开始抽牌</text>
</view> </view>
@ -41,9 +62,9 @@
<text class="drawer-icon">🎯</text> <text class="drawer-icon">🎯</text>
<text class="drawer-text">积分说明</text> <text class="drawer-text">积分说明</text>
</view> </view>
<view class="drawer-item" bindtap="goToPrivacy" hover-class="drawer-hover"> <view class="drawer-item" bindtap="goToSettings" hover-class="drawer-hover">
<text class="drawer-icon">🔒</text> <text class="drawer-icon">⚙️</text>
<text class="drawer-text">隐私政策</text> <text class="drawer-text">更多设置</text>
</view> </view>
</view> </view>
@ -56,6 +77,43 @@
</view> </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 {{knowledgeVisible ? 'show' : ''}}" bindtap="hideKnowledgePanel">
<view class="panel-content" catchtap="stopPropagation"> <view class="panel-content" catchtap="stopPropagation">

View File

@ -10,14 +10,20 @@ page {
height: 100vh; height: 100vh;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding-top: 120rpx;
gap: 40px;
box-sizing: border-box;
} }
/* ========== 顶部栏 (已移除) ========== */
/* ========== 右上角积分徽章 ========== */ /* ========== 右上角积分徽章 ========== */
.points-badge { .points-badge {
position: absolute; position: absolute;
top: 30px; top: 30px; /* Restore original position */
right: 20px; right: 20px;
background: linear-gradient(135deg, rgba(233, 69, 96, 0.25), rgba(233, 69, 96, 0.15)); 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: 1px solid rgba(233, 69, 96, 0.5);
@ -31,6 +37,56 @@ page {
cursor: pointer; 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;
}
.zodiac-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.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 { .badge-icon {
font-size: 16px; font-size: 16px;
} }
@ -61,10 +117,79 @@ page {
position: absolute; position: absolute;
width: 180px; width: 180px;
height: 260px; height: 260px;
background: linear-gradient(135deg, #e94560 0%, #8b3a62 50%, #4a1942 100%); /* background moved to .card-bg */
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
transition: all 0.3s ease; 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;
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 { .card-1 {
@ -113,8 +238,8 @@ page {
position: absolute; position: absolute;
width: 90px; width: 90px;
height: 120px; height: 120px;
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.08); /* Semi-transparent white */
border: 1px solid rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.15); /* Light border */
border-radius: 12px; border-radius: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -124,6 +249,8 @@ page {
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease; transition: all 0.3s ease;
z-index: 5; z-index: 5;
top: 50%;
transform: translateY(-50%);
} }
.card-hover { .card-hover {
@ -132,15 +259,11 @@ page {
} }
.energy-card { .energy-card {
left: 30px; left: 10px;
top: 50%;
transform: translateY(-50%);
} }
.knowledge-card { .knowledge-card {
right: 30px; right: 10px;
top: 50%;
transform: translateY(-50%);
} }
.card-icon { .card-icon {
@ -163,7 +286,7 @@ page {
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.1); border-top: 1px solid rgba(255, 255, 255, 0.1);
padding: 15px 20px 25px; padding: 8px 10px 15px;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
z-index: 20; z-index: 20;
@ -173,8 +296,8 @@ page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 8px; gap: 2px;
padding: 10px 15px; padding: 4px 8px;
border-radius: 12px; border-radius: 12px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -347,3 +470,95 @@ page {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
box-shadow: none; 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;
}
.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;
}

View File

@ -64,7 +64,20 @@ const TAROT_PROMPT_NIGHT = `你是一位安静、克制、不制造依赖的塔
// 3. 增强版模板:解读稳定器 + 高关联性结构化解读 // 3. 增强版模板:解读稳定器 + 高关联性结构化解读
const TAROT_PROMPT_ENHANCED = `📏 解读稳定规则(高优先级执行) const TAROT_PROMPT_ENHANCED = `📏 解读稳定规则(高优先级执行)
你必须保持解读风格稳定具体且一致不允许出现质量波动 合规红线 - 必须遵守
1. **娱乐属性**明确告知用户内容仅供参考不构成现实决策依据
2. **严禁迷信**禁止使用注定必然灾祸鬼神等封建迷信词汇
3. **禁止决策建议**严禁提供任何医疗法律投资理财方面的具体建议
4. **正向引导**解读必须积极向上侧重心理疏导和个人成长避免制造焦虑
1. **结构化输出**必须严格遵循JSON格式包含 theme, status, influence, advice, positions (数组)
2. **字数控制**
- 核心主题15-20精炼一句话
- 单个位置解读60-100深度但不过长每张牌2句以内
- 当前状态23
- 发展趋势23
- 行动建议35条短句
禁止超长段落大段心理学科普冗长比喻
解读长度控制 解读长度控制
整体长度400700 不少于 300 不超过 900 整体长度400700 不少于 300 不超过 900
@ -168,7 +181,8 @@ const SPREADS = [
"positions": ["当下的指引"], "positions": ["当下的指引"],
"description": "为你此刻的状态提供一个温和而清晰的方向提示", "description": "为你此刻的状态提供一个温和而清晰的方向提示",
"tags": ["综合"], "tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "three_time_flow", "id": "three_time_flow",
@ -178,7 +192,8 @@ const SPREADS = [
"positions": ["过去", "现在", "未来"], "positions": ["过去", "现在", "未来"],
"description": "帮助你理解事情的发展过程与可能走向", "description": "帮助你理解事情的发展过程与可能走向",
"tags": ["综合", "决策"], "tags": ["综合", "决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "three_problem_solution", "id": "three_problem_solution",
@ -188,7 +203,8 @@ const SPREADS = [
"positions": ["问题核心", "当前阻碍", "行动建议"], "positions": ["问题核心", "当前阻碍", "行动建议"],
"description": "聚焦关键问题,找出当下最可行的应对方式", "description": "聚焦关键问题,找出当下最可行的应对方式",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "love_spark", "id": "love_spark",
@ -198,7 +214,8 @@ const SPREADS = [
"positions": ["你的感受", "对方的感受", "关系潜力"], "positions": ["你的感受", "对方的感受", "关系潜力"],
"description": "探索彼此的真实感受与这段关系的可能性", "description": "探索彼此的真实感受与这段关系的可能性",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "two_choice_decision", "id": "two_choice_decision",
@ -208,7 +225,8 @@ const SPREADS = [
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"], "positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
"description": "对比两种选择的潜在走向,辅助理性决策", "description": "对比两种选择的潜在走向,辅助理性决策",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "relationship_healing", "id": "relationship_healing",
@ -218,7 +236,8 @@ const SPREADS = [
"positions": ["问题根源", "你的责任", "对方的责任", "修复方向"], "positions": ["问题根源", "你的责任", "对方的责任", "修复方向"],
"description": "深入理解关系裂痕,找到和解与修复的路径", "description": "深入理解关系裂痕,找到和解与修复的路径",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "five_situation_analysis", "id": "five_situation_analysis",
@ -228,7 +247,8 @@ const SPREADS = [
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"], "positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
"description": "从内外层面拆解局势,明确下一步行动", "description": "从内外层面拆解局势,明确下一步行动",
"tags": ["事业", "综合"], "tags": ["事业", "综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "relationship_spread", "id": "relationship_spread",
@ -238,7 +258,8 @@ const SPREADS = [
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"], "positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
"description": "理解一段关系中的互动模式与发展方向", "description": "理解一段关系中的互动模式与发展方向",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "destiny_connection", "id": "destiny_connection",
@ -248,7 +269,8 @@ const SPREADS = [
"positions": ["前世因缘", "今生相遇", "情感纽带", "考验挑战", "缘分走向"], "positions": ["前世因缘", "今生相遇", "情感纽带", "考验挑战", "缘分走向"],
"description": "探索两人之间的灵性连接与命运安排", "description": "探索两人之间的灵性连接与命运安排",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "career_breakthrough", "id": "career_breakthrough",
@ -258,7 +280,8 @@ const SPREADS = [
"positions": ["当前困境", "优势资源", "潜在机会", "需要克服", "突破方向"], "positions": ["当前困境", "优势资源", "潜在机会", "需要克服", "突破方向"],
"description": "识别职场瓶颈,找到突破与晋升的关键", "description": "识别职场瓶颈,找到突破与晋升的关键",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "timeline_spread", "id": "timeline_spread",
@ -268,7 +291,8 @@ const SPREADS = [
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"], "positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
"description": "追溯事件的时间线,看清发展脉络", "description": "追溯事件的时间线,看清发展脉络",
"tags": ["综合"], "tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "diamond_spread", "id": "diamond_spread",
@ -278,7 +302,8 @@ const SPREADS = [
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"], "positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
"description": "多角度剖析问题,找到解决之道", "description": "多角度剖析问题,找到解决之道",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "career_planning", "id": "career_planning",
@ -288,7 +313,8 @@ const SPREADS = [
"positions": ["当前位置", "核心优势", "发展方向", "潜在障碍", "贵人助力", "长期目标"], "positions": ["当前位置", "核心优势", "发展方向", "潜在障碍", "贵人助力", "长期目标"],
"description": "全面规划职业发展路径,明确长期目标", "description": "全面规划职业发展路径,明确长期目标",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "wealth_analysis", "id": "wealth_analysis",
@ -298,7 +324,8 @@ const SPREADS = [
"positions": ["财务现状", "收入来源", "支出模式", "投资机会", "财务风险", "财富增长"], "positions": ["财务现状", "收入来源", "支出模式", "投资机会", "财务风险", "财富增长"],
"description": "深入分析财务状况,找到财富增长的路径", "description": "深入分析财务状况,找到财富增长的路径",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "spiritual_guidance", "id": "spiritual_guidance",
@ -308,7 +335,8 @@ const SPREADS = [
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"], "positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
"description": "深入探索内在世界,获得灵性层面的启发", "description": "深入探索内在世界,获得灵性层面的启发",
"tags": ["深度"], "tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "horseshoe_spread", "id": "horseshoe_spread",
@ -318,7 +346,8 @@ const SPREADS = [
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"], "positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
"description": "全面了解情况的来龙去脉与未来走向", "description": "全面了解情况的来龙去脉与未来走向",
"tags": ["综合"], "tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "celtic_cross", "id": "celtic_cross",
@ -328,7 +357,8 @@ const SPREADS = [
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"], "positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
"description": "最经典的综合牌阵,深度解析生命议题", "description": "最经典的综合牌阵,深度解析生命议题",
"tags": ["深度"], "tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "tree_of_life", "id": "tree_of_life",
@ -338,7 +368,8 @@ const SPREADS = [
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"], "positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
"description": "基于卡巴拉生命之树的深度灵性探索", "description": "基于卡巴拉生命之树的深度灵性探索",
"tags": ["深度"], "tags": ["深度"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
// 新增爱情牌阵 // 新增爱情牌阵
{ {
@ -349,7 +380,8 @@ const SPREADS = [
"positions": ["核心关系", "你的状态", "对方状态", "未来发展"], "positions": ["核心关系", "你的状态", "对方状态", "未来发展"],
"description": "适合恋人关系 & 互动解析", "description": "适合恋人关系 & 互动解析",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "new_love", "id": "new_love",
@ -359,7 +391,8 @@ const SPREADS = [
"positions": ["当前状态", "遇见契机", "发展方向"], "positions": ["当前状态", "遇见契机", "发展方向"],
"description": "适合新的感情 & 遇见新欢,为单身的你或刚分手的你带来新爱提示", "description": "适合新的感情 & 遇见新欢,为单身的你或刚分手的你带来新爱提示",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "relationship_basic", "id": "relationship_basic",
@ -369,7 +402,8 @@ const SPREADS = [
"positions": ["你的想法", "对方想法", "关系走向"], "positions": ["你的想法", "对方想法", "关系走向"],
"description": "适合梳理关系 & 拉近距离,回溯结识初衷,梳理人际关系,拉近彼此距离", "description": "适合梳理关系 & 拉近距离,回溯结识初衷,梳理人际关系,拉近彼此距离",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "love_tree_spread", "id": "love_tree_spread",
@ -379,7 +413,8 @@ const SPREADS = [
"positions": ["核心问题", "过去影响", "现在状况", "未来趋势", "最终结果"], "positions": ["核心问题", "过去影响", "现在状况", "未来趋势", "最终结果"],
"description": "适合深度求爱 & 寻找症结,解析前因后果,回溯爱情过往,探究本源核心", "description": "适合深度求爱 & 寻找症结,解析前因后果,回溯爱情过往,探究本源核心",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "love_cross_spread", "id": "love_cross_spread",
@ -389,7 +424,8 @@ const SPREADS = [
"positions": ["你的状态", "对方状态", "过去影响", "现在情况", "未来发展"], "positions": ["你的状态", "对方状态", "过去影响", "现在情况", "未来发展"],
"description": "适合解析两性关系 & 爱情状况,基于洞悉彼此关系中的情感状况并分析结果", "description": "适合解析两性关系 & 爱情状况,基于洞悉彼此关系中的情感状况并分析结果",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "mr_right", "id": "mr_right",
@ -399,7 +435,8 @@ const SPREADS = [
"positions": ["内心期待", "现实阻碍", "理想对象", "行动建议", "遇见时机"], "positions": ["内心期待", "现实阻碍", "理想对象", "行动建议", "遇见时机"],
"description": "单身探索有缘人 & 合适对象,探索内心的想法,改善外在的行为,遇到适合的人", "description": "单身探索有缘人 & 合适对象,探索内心的想法,改善外在的行为,遇到适合的人",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "search_lover", "id": "search_lover",
@ -409,7 +446,8 @@ const SPREADS = [
"positions": ["当前状态", "优势特质", "改进方向", "寻找方式", "目标确定"], "positions": ["当前状态", "优势特质", "改进方向", "寻找方式", "目标确定"],
"description": "适合寻找意中人 & 有缘人,睁全宫中人的愿景,帮助自己确定目标", "description": "适合寻找意中人 & 有缘人,睁全宫中人的愿景,帮助自己确定目标",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "inspiration_correspondence", "id": "inspiration_correspondence",
@ -419,7 +457,8 @@ const SPREADS = [
"positions": ["情感现状", "内心需求", "对方感受", "互动方式", "发展建议", "最终走向"], "positions": ["情感现状", "内心需求", "对方感受", "互动方式", "发展建议", "最终走向"],
"description": "适合情感困惑 & 人际关系,增长解析指缘联接或情感互动,帮您交融状态", "description": "适合情感困惑 & 人际关系,增长解析指缘联接或情感互动,帮您交融状态",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "gypsy_spread", "id": "gypsy_spread",
@ -429,7 +468,8 @@ const SPREADS = [
"positions": ["你的想法", "对方想法", "过去经历", "现在状况", "未来发展"], "positions": ["你的想法", "对方想法", "过去经历", "现在状况", "未来发展"],
"description": "适合情侣分析 & 关系延展,探索彼此内心想法,找到合适的相处方式", "description": "适合情侣分析 & 关系延展,探索彼此内心想法,找到合适的相处方式",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "venus_spread", "id": "venus_spread",
@ -439,7 +479,8 @@ const SPREADS = [
"positions": ["你的状态", "对方状态", "关系开始", "当前情况", "核心问题", "最终结果", "外部影响", "内在感受"], "positions": ["你的状态", "对方状态", "关系开始", "当前情况", "核心问题", "最终结果", "外部影响", "内在感受"],
"description": "适合爱情发展 & 判断走向,全面的爱情解析牌阵,深入分析两性关系", "description": "适合爱情发展 & 判断走向,全面的爱情解析牌阵,深入分析两性关系",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "lover_tree", "id": "lover_tree",
@ -449,7 +490,8 @@ const SPREADS = [
"positions": ["你的行为", "对方行为", "你的想法", "对方想法", "你的感受", "对方感受", "关系走向"], "positions": ["你的行为", "对方行为", "你的想法", "对方想法", "你的感受", "对方感受", "关系走向"],
"description": "适合探究对方心理 & 行为模式,对比情侣间的行为差异,进而根据事实做出判断", "description": "适合探究对方心理 & 行为模式,对比情侣间的行为差异,进而根据事实做出判断",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "marriage_spread", "id": "marriage_spread",
@ -459,7 +501,8 @@ const SPREADS = [
"positions": ["婚姻基础", "你的期望", "对方期望", "核心问题", "外部影响", "内在感受", "未来走向"], "positions": ["婚姻基础", "你的期望", "对方期望", "核心问题", "外部影响", "内在感受", "未来走向"],
"description": "适合婚姻状况 & 内在剖析,针对婚姻状况和期望进行解析,通过细致分析把握婚姻走向", "description": "适合婚姻状况 & 内在剖析,针对婚姻状况和期望进行解析,通过细致分析把握婚姻走向",
"tags": ["爱情"], "tags": ["爱情"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
// 新增事业牌阵 // 新增事业牌阵
{ {
@ -470,7 +513,8 @@ const SPREADS = [
"positions": ["问题核心", "左侧因素", "右侧因素"], "positions": ["问题核心", "左侧因素", "右侧因素"],
"description": "适合财富探索 & 问题解析,清楚审视整个问题的来龙去脉,进而做出正确决策", "description": "适合财富探索 & 问题解析,清楚审视整个问题的来龙去脉,进而做出正确决策",
"tags": ["事业", "财富"], "tags": ["事业", "财富"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "success_star", "id": "success_star",
@ -480,7 +524,8 @@ const SPREADS = [
"positions": ["当前位置", "行动方向", "成功目标"], "positions": ["当前位置", "行动方向", "成功目标"],
"description": "适合走位辨路 & 累积行动,锁定目标破除阻碍,告诉星光,心有明月再出发", "description": "适合走位辨路 & 累积行动,锁定目标破除阻碍,告诉星光,心有明月再出发",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "career_pyramid", "id": "career_pyramid",
@ -490,7 +535,8 @@ const SPREADS = [
"positions": ["核心优势", "需要改进", "外部机会", "未来发展"], "positions": ["核心优势", "需要改进", "外部机会", "未来发展"],
"description": "适合解析财富运势 & 提升财商,明晰自我的优缺点,帮助自己除弊趋利在事业中获得优势", "description": "适合解析财富运势 & 提升财商,明晰自我的优缺点,帮助自己除弊趋利在事业中获得优势",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "wealth_tree", "id": "wealth_tree",
@ -500,7 +546,8 @@ const SPREADS = [
"positions": ["财务基础", "收入来源", "支出状况", "当前状态", "未来趋势"], "positions": ["财务基础", "收入来源", "支出状况", "当前状态", "未来趋势"],
"description": "适合事业发展 & 状态评估,通过对财富的梳理,健全适合自己的财务模式", "description": "适合事业发展 & 状态评估,通过对财富的梳理,健全适合自己的财务模式",
"tags": ["事业", "财富"], "tags": ["事业", "财富"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "x_opportunity", "id": "x_opportunity",
@ -510,7 +557,8 @@ const SPREADS = [
"positions": ["当前时机", "有利因素", "不利因素", "行动建议", "结果预测"], "positions": ["当前时机", "有利因素", "不利因素", "行动建议", "结果预测"],
"description": "适合时机捕捉 & 临场决策,以事物处理时机为主线轴,牵挂问题解决的成功概率", "description": "适合时机捕捉 & 临场决策,以事物处理时机为主线轴,牵挂问题解决的成功概率",
"tags": ["事业", "决策"], "tags": ["事业", "决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "job_interview", "id": "job_interview",
@ -520,7 +568,8 @@ const SPREADS = [
"positions": ["自我状态", "优势展现", "需要注意", "面试官印象", "录用可能"], "positions": ["自我状态", "优势展现", "需要注意", "面试官印象", "录用可能"],
"description": "适合解析应聘面试 & 求职状况,洞察对方需求与自我留言,有效提高面试成功率", "description": "适合解析应聘面试 & 求职状况,洞察对方需求与自我留言,有效提高面试成功率",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "work_problems", "id": "work_problems",
@ -530,7 +579,8 @@ const SPREADS = [
"positions": ["问题核心", "上级态度", "同事关系", "个人能力", "外部环境", "解决方案"], "positions": ["问题核心", "上级态度", "同事关系", "个人能力", "外部环境", "解决方案"],
"description": "适合聚焦目标 & 优化路径,当你明确自己的目标,全世界都会为你让路", "description": "适合聚焦目标 & 优化路径,当你明确自己的目标,全世界都会为你让路",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "turbulent_finances", "id": "turbulent_finances",
@ -540,7 +590,8 @@ const SPREADS = [
"positions": ["财务起点", "第一波动", "稳定期", "第二波动", "调整期", "未来趋势"], "positions": ["财务起点", "第一波动", "稳定期", "第二波动", "调整期", "未来趋势"],
"description": "适合借力打力 & 多空对比,追问本质,结合正反两方面的分析,做出理智判断", "description": "适合借力打力 & 多空对比,追问本质,结合正反两方面的分析,做出理智判断",
"tags": ["事业", "财富"], "tags": ["事业", "财富"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "desired_job", "id": "desired_job",
@ -550,7 +601,8 @@ const SPREADS = [
"positions": ["理想工作", "个人优势", "需要提升", "寻找方向", "关键因素", "实现可能"], "positions": ["理想工作", "个人优势", "需要提升", "寻找方向", "关键因素", "实现可能"],
"description": "适合评估工作前景 & 关联因素,剖析工作前景,理清相关因素,给出明确的思路", "description": "适合评估工作前景 & 关联因素,剖析工作前景,理清相关因素,给出明确的思路",
"tags": ["事业"], "tags": ["事业"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
// 新增决策牌阵 // 新增决策牌阵
{ {
@ -561,7 +613,8 @@ const SPREADS = [
"positions": ["选项A", "选项B", "建议方向"], "positions": ["选项A", "选项B", "建议方向"],
"description": "适合评测选项 & 做出抉择,在两难的处境中给出建议,选出适合自己的抉择", "description": "适合评测选项 & 做出抉择,在两难的处境中给出建议,选出适合自己的抉择",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "three_options", "id": "three_options",
@ -571,7 +624,8 @@ const SPREADS = [
"positions": ["当前状况", "参考方向一", "参考方向二"], "positions": ["当前状况", "参考方向一", "参考方向二"],
"description": "提供参考方向 & 行动指向,遇到3个选择时,给你提供参考的方向", "description": "提供参考方向 & 行动指向,遇到3个选择时,给你提供参考的方向",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "daily_tree", "id": "daily_tree",
@ -581,7 +635,8 @@ const SPREADS = [
"positions": ["今日核心", "需要注意", "有利因素", "行动建议"], "positions": ["今日核心", "需要注意", "有利因素", "行动建议"],
"description": "适长当日难题 & 当日解决,天天没烦恼,解决每天遇到的问题", "description": "适长当日难题 & 当日解决,天天没烦恼,解决每天遇到的问题",
"tags": ["综合"], "tags": ["综合"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "weigh_two", "id": "weigh_two",
@ -591,7 +646,8 @@ const SPREADS = [
"positions": ["当前状况", "选项A优势", "选项B优势", "选项A劣势", "选项B劣势"], "positions": ["当前状况", "选项A优势", "选项B优势", "选项A劣势", "选项B劣势"],
"description": "主要用于抉择 & 判断,二选一,适用于二种情况选择其中一种", "description": "主要用于抉择 & 判断,二选一,适用于二种情况选择其中一种",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "comparing_choices", "id": "comparing_choices",
@ -601,7 +657,8 @@ const SPREADS = [
"positions": ["选项A现状", "选项A发展", "选项A结果", "选项B现状", "选项B发展", "选项B结果"], "positions": ["选项A现状", "选项A发展", "选项A结果", "选项B现状", "选项B发展", "选项B结果"],
"description": "适合判断情势 & 决定方向,多层面聚焦两个方向,更多的线索更多的细节", "description": "适合判断情势 & 决定方向,多层面聚焦两个方向,更多的线索更多的细节",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "weigh_three", "id": "weigh_three",
@ -611,7 +668,8 @@ const SPREADS = [
"positions": ["当前状况", "选项A优势", "选项B优势", "选项A劣势", "选项C优势", "选项C劣势", "选项B劣势"], "positions": ["当前状况", "选项A优势", "选项B优势", "选项A劣势", "选项C优势", "选项C劣势", "选项B劣势"],
"description": "适合事情抉择 & 选择评估,对利弊关系进行分析判断,权衡利弊,做出最终的选择", "description": "适合事情抉择 & 选择评估,对利弊关系进行分析判断,权衡利弊,做出最终的选择",
"tags": ["决策"], "tags": ["决策"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
// 新增深度探索牌阵 // 新增深度探索牌阵
{ {
@ -622,7 +680,8 @@ const SPREADS = [
"positions": ["身体", "心智", "灵性", "整体状态"], "positions": ["身体", "心智", "灵性", "整体状态"],
"description": "适合自我探索 & 了解自己,透彻审视自我,更直切明了自身成长路径", "description": "适合自我探索 & 了解自己,透彻审视自我,更直切明了自身成长路径",
"tags": ["成长", "综合"], "tags": ["成长", "综合"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "four_elements", "id": "four_elements",
@ -632,7 +691,8 @@ const SPREADS = [
"positions": ["感性", "理性", "物质", "行动"], "positions": ["感性", "理性", "物质", "行动"],
"description": "适合问题探索 & 多方解析,从感性、理性、物质、行动四方面分析,进而接近实质", "description": "适合问题探索 & 多方解析,从感性、理性、物质、行动四方面分析,进而接近实质",
"tags": ["探索", "综合"], "tags": ["探索", "综合"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "self_discovery", "id": "self_discovery",
@ -642,7 +702,8 @@ const SPREADS = [
"positions": ["核心自我", "潜能", "优势", "需要提升"], "positions": ["核心自我", "潜能", "优势", "需要提升"],
"description": "适合认识自我 & 提升潜能,开发自身潜力,提高对自己的认识", "description": "适合认识自我 & 提升潜能,开发自身潜力,提高对自己的认识",
"tags": ["探索", "成长"], "tags": ["探索", "成长"],
"aiSchema": ["core_theme", "current_state", "action_advice"] "aiSchema": ["core_theme", "current_state", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "know_yourself", "id": "know_yourself",
@ -652,7 +713,8 @@ const SPREADS = [
"positions": ["核心自我", "思想", "行为", "情感", "环境影响"], "positions": ["核心自我", "思想", "行为", "情感", "环境影响"],
"description": "适合自我剖析 & 境况认知,人无时无刻不受环境的影响,在各种关系的交织中成长", "description": "适合自我剖析 & 境况认知,人无时无刻不受环境的影响,在各种关系的交织中成长",
"tags": ["成长", "综合"], "tags": ["成长", "综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "your_breakthrough", "id": "your_breakthrough",
@ -662,7 +724,8 @@ const SPREADS = [
"positions": ["当前困境", "阻碍因素", "突破点", "行动方向", "预期结果"], "positions": ["当前困境", "阻碍因素", "突破点", "行动方向", "预期结果"],
"description": "适合打破僵环 & 突破自我,识别固定模式,突破往复循环", "description": "适合打破僵环 & 突破自我,识别固定模式,突破往复循环",
"tags": ["成长"], "tags": ["成长"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "elemental_cross", "id": "elemental_cross",
@ -672,7 +735,8 @@ const SPREADS = [
"positions": ["核心问题", "火元素", "水元素", "风元素", "土元素", "解决方案"], "positions": ["核心问题", "火元素", "水元素", "风元素", "土元素", "解决方案"],
"description": "主要用于带入问题 & 解决问题,根据现实情况全面运用四元素能提升自己", "description": "主要用于带入问题 & 解决问题,根据现实情况全面运用四元素能提升自己",
"tags": ["探索", "综合"], "tags": ["探索", "综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "wheel_spread", "id": "wheel_spread",
@ -682,7 +746,8 @@ const SPREADS = [
"positions": ["中心", "过去", "现在", "未来", "机会", "挑战", "结果"], "positions": ["中心", "过去", "现在", "未来", "机会", "挑战", "结果"],
"description": "适合专注自我 & 探寻改变,有时走出循环照顾的舒适圈,有所改变时,才能成长", "description": "适合专注自我 & 探寻改变,有时走出循环照顾的舒适圈,有所改变时,才能成长",
"tags": ["成长"], "tags": ["成长"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "seven_planets", "id": "seven_planets",
@ -692,7 +757,8 @@ const SPREADS = [
"positions": ["核心", "月亮", "水星", "金星", "太阳", "火星", "木星"], "positions": ["核心", "月亮", "水星", "金星", "太阳", "火星", "木星"],
"description": "适合审视目前状态 & 了解自己,综合分析当下状况,全面的了解自己", "description": "适合审视目前状态 & 了解自己,综合分析当下状况,全面的了解自己",
"tags": ["成长", "综合"], "tags": ["成长", "综合"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
}, },
{ {
"id": "dream_mirror", "id": "dream_mirror",
@ -702,7 +768,8 @@ const SPREADS = [
"positions": ["梦境表象", "潜意识", "情感", "符号", "启示", "行动", "整合"], "positions": ["梦境表象", "潜意识", "情感", "符号", "启示", "行动", "整合"],
"description": "适合分析梦境 & 获得启迪,梦像一面镜子,解读梦境,是理解自己的渠道", "description": "适合分析梦境 & 获得启迪,梦像一面镜子,解读梦境,是理解自己的渠道",
"tags": ["探索"], "tags": ["探索"],
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"],
layout: { type: 'grid' }
} }
]; ];
@ -739,17 +806,29 @@ Page({
// AI 解读相关 // AI 解读相关
aiResult: null, aiResult: null,
isAiLoading: false, isAiLoading: false,
aiLoadingText: '正在抽取牌面…', // 动态加载提示 aiLoadingText: '正在深度解读...', // 动态加载提示
spreadStory: '', spreadStory: '',
// 3. 前缀文案列表(随机抽取) // 3. 前缀文案列表(随机抽取)
prefixList: [ prefixList: [
"今天,这张牌提醒你:", "透过层层迷雾,牌面显示...",
"此刻,宇宙传递的信息:", "连接此刻的能量,这张牌意味着...",
"这张牌想告诉你:", "这是一个特别的信号...",
"请收下这份指引:" "如同命运的低语...",
"在潜意识的深处..."
], ],
// 4. 分享引导文案
randomGuidanceText: "",
guidanceList: [
"这组牌很少见",
"你的能量状态正在转折",
"这个答案值得记录",
"一次直抵内心的对话",
"命运在这一刻给予了回应"
],
// 2. 完整的 22 张大阿尔卡那牌组(包含逆位含义) // 2. 完整的 22 张大阿尔卡那牌组(包含逆位含义)
cardList: [ cardList: [
{ {
@ -1206,7 +1285,7 @@ Page({
// --- 4. 逐一翻牌 --- // --- 4. 逐一翻牌 ---
revealNext: function (e) { revealNext: function (e) {
const index = e.currentTarget.dataset.index; const index = (e.detail && e.detail.index !== undefined) ? e.detail.index : e.currentTarget.dataset.index;
if (index === this.data.revealedCount) { if (index === this.data.revealedCount) {
const nextCount = index + 1; const nextCount = index + 1;
this.setData({ this.setData({
@ -1215,12 +1294,34 @@ Page({
// 如果全部翻开,触发解读 // 如果全部翻开,触发解读
if (nextCount === this.data.selectedSpread.cardCount) { if (nextCount === this.data.selectedSpread.cardCount) {
this.setData({ state: 'revealed' }); // 随机生成分享引导文案
// Note: guidanceList needs to be defined in data or elsewhere for this to work.
// For now, assuming it exists or will be added.
const guidanceIdx = Math.floor(Math.random() * (this.data.guidanceList ? this.data.guidanceList.length : 1));
const guidanceText = this.data.guidanceList ? this.data.guidanceList[guidanceIdx] : '分享你的塔罗解读,获得更多指引!';
this.setData({
state: 'revealed',
randomGuidanceText: guidanceText // 设置引导文案
});
this.showInterpretation(); this.showInterpretation();
} }
} }
}, },
// 查看已翻开牌的详情
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
});
}
},
// --- 展示解读内容 --- // --- 展示解读内容 ---
showInterpretation: function () { showInterpretation: function () {
const infoAnim = wx.createAnimation({ const infoAnim = wx.createAnimation({
@ -1242,6 +1343,7 @@ Page({
this.setData({ this.setData({
isAiLoading: true, isAiLoading: true,
aiResult: null, aiResult: null,
aiStreamText: '',
aiLoadingText: '正在抽取牌面…' aiLoadingText: '正在抽取牌面…'
}); });
@ -1305,11 +1407,7 @@ ${cardMeanings}
// 5. 处理结果 // 5. 处理结果
if (result.status === 'blocked') { if (result.status === 'blocked') {
wx.showToast({ wx.showToast({ title: result.message, icon: 'none', duration: 2000 });
title: result.message,
icon: 'none',
duration: 2000
});
this.setData({ isAiLoading: false }); this.setData({ isAiLoading: false });
return; return;
} }
@ -1326,14 +1424,69 @@ ${cardMeanings}
// 8. 如果是 fallback显示提示 // 8. 如果是 fallback显示提示
if (result.status === 'fallback' && result.error) { if (result.status === 'fallback' && result.error) {
wx.showToast({ wx.showToast({ title: 'AI 解读失败,已使用备用解读', icon: 'none', duration: 3000 });
title: 'AI 解读失败,已使用备用解读',
icon: 'none',
duration: 3000
});
} }
}, },
// --- 打字机效果:逐字显示 AI 解读 ---
typewriterReveal: function (data) {
// 初始化空的 aiResult 结构,先占位
const emptyResult = {
theme: '',
status: '',
influence: '',
advice: '',
positions: (data.positions || []).map(p => ({
posName: p.posName,
posMeaning: ''
}))
};
this.setData({ aiResult: emptyResult });
// 把所有需要打字机显示的字段排成队列
const fields = [
{ key: 'aiResult.theme', text: data.theme || '' },
{ key: 'aiResult.status', text: data.status || '' },
{ key: 'aiResult.influence', text: data.influence || '' },
{ key: 'aiResult.advice', text: data.advice || '' },
];
// 加入每个位置的解读
(data.positions || []).forEach((pos, i) => {
fields.push({
key: `aiResult.positions[${i}].posMeaning`,
text: pos.posMeaning || ''
});
});
// 逐字段、逐字符地显示
let fieldIndex = 0;
const revealNextField = () => {
if (fieldIndex >= fields.length) return; // 全部完成
const field = fields[fieldIndex];
const fullText = field.text;
let charIndex = 0;
// 每个字符间隔 25ms约 40字/秒)
const timer = setInterval(() => {
charIndex++;
const partial = fullText.slice(0, charIndex);
this.setData({ [field.key]: partial });
if (charIndex >= fullText.length) {
clearInterval(timer);
fieldIndex++;
// 字段间停顿 150ms 再开始下一个字段
setTimeout(revealNextField, 150);
}
}, 25);
};
revealNextField();
},
handleAiFallback: function () { handleAiFallback: function () {
// 构建一个基础的回退对象 // 构建一个基础的回退对象
const fallback = { const fallback = {
@ -1401,11 +1554,30 @@ ${cardMeanings}
wx.navigateBack({ wx.navigateBack({
delta: 1 delta: 1
}); });
} else {
// 其他状态直接返回主页
wx.navigateBack({
delta: 1
});
} }
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
const titles = [
"我刚抽到一组很准的塔罗…",
"这次塔罗结果让我有点意外",
"今天的塔罗指引比我想象深",
"这是我此刻的能量状态✨",
"这里有一个答案,也许你也需要"
];
const randomTitle = titles[Math.floor(Math.random() * titles.length)];
// 模拟生成 fromUserId (实际应从用户信息获取)
// 这里简单使用一个随机字符串代替,或者保留为空等待后续接入用户系统
const mockUserId = 'user_' + Math.random().toString(36).substr(2, 9);
return {
title: randomTitle,
path: `/pages/home/home?fromUserId=${mockUserId}`,
imageUrl: '/images/share-cover.png' // 建议后续添加一张通用的分享图
};
} }
}) })

View File

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

View File

@ -4,6 +4,7 @@
<text class="back-icon">⇠</text> <text class="back-icon">⇠</text>
<text class="back-text">返回</text> <text class="back-text">返回</text>
</view> </view>
<!-- 1. 牌阵选择状态 - 卡片式V2 --> <!-- 1. 牌阵选择状态 - 卡片式V2 -->
<view class="spread-select-area" wx:if="{{state === 'spread_select'}}"> <view class="spread-select-area" wx:if="{{state === 'spread_select'}}">
<!-- 顶部标题区域 --> <!-- 顶部标题区域 -->
@ -47,7 +48,7 @@
<!-- 底部信息 --> <!-- 底部信息 -->
<view class="card-footer"> <view class="card-footer">
<text class="card-count">{{item.cardCount}} 张牌</text> <text class="card-count">{{item.cardCount}} 张牌</text>
<text class="card-cost">消耗 {{item.cost}} 积分</text> <text class="card-cost"> {{item.cost}} 积分</text>
</view> </view>
</view> </view>
</view> </view>
@ -145,33 +146,42 @@
module.exports.getTransform = function(indices, currentIdx, count) { module.exports.getTransform = function(indices, currentIdx, count) {
var pos = indices.indexOf(currentIdx); var pos = indices.indexOf(currentIdx);
if (pos !== -1) { if (pos !== -1) {
// 每排最多5张牌
var maxPerRow = 5; var maxPerRow = 5;
var row = Math.floor(pos / maxPerRow); // 第几排0或1 var row = Math.floor(pos / maxPerRow);
var col = pos % maxPerRow; // 该排的第几个 var col = pos % maxPerRow;
var cardsInRow = (row === 0) ? Math.min(count, maxPerRow) : (count - maxPerRow);
// 槽位宽100rpx + gap 20rpx = 120rpx 间距 var cardsRemaining = count - (row * maxPerRow);
var slotWidth = 100; var cardsInRow = (cardsRemaining > maxPerRow) ? maxPerRow : cardsRemaining;
var gap = 20;
var slotSpacing = slotWidth + gap;
// 计算该排槽位的水平居中偏移 // Container Calculation
// 从该排中间位置开始计算 var rowCount = Math.ceil(count / maxPerRow);
var centerOffset = (col - (cardsInRow - 1) / 2) * slotSpacing; var contentHeight = (rowCount * 160) + ((rowCount - 1) * 20);
if (contentHeight < 240) { contentHeight = 240; } // Handle min-height: 240rpx
// 垂直偏移计算 // Distance from Slot Container Top to Fan Deck Bottom
// 第一排槽位:向上 -520rpx已验证接近正确 // FanHeight(380) + FanMarginTop(20) + ContainerMarginBottom(60) + ContainerHeight
// 第二排:需要减少向上的距离 var distToFanBottom = 380 + 20 + 60 + contentHeight;
// 槽位高度160 + gap 20 = 180但实际可能需要微调
var firstRowY = -520; // Determine Slot Center Y from Container Top
var secondRowOffset = 200; // 增加偏移量让第二排更接近fan-deck // Row 0 starts at 0. Center is 80.
var yOffset = firstRowY + (row * secondRowOffset); // row=0: -520, row=1: -320 // 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)'; return 'translate(' + centerOffset + 'rpx, ' + yOffset + 'rpx) scale(0.65) rotate(0deg)';
} else { } else {
// 保持在牌堆中的放射状
return 'rotate(' + ((currentIdx - 10) * 8) + 'deg)'; return 'rotate(' + ((currentIdx - 10) * 8) + 'deg)';
} }
}; };
@ -184,25 +194,25 @@
<!-- 4. 翻牌与结果展示状态 --> <!-- 4. 翻牌与结果展示状态 -->
<view class="result-area" wx:if="{{state === 'flipping' || state === 'revealed'}}"> <view class="result-area" wx:if="{{state === 'flipping' || state === 'revealed'}}">
<!-- 卡牌阵列 --> <!-- 卡牌阵列 -->
<view class="spread-display {{selectedSpread.id}}"> <!-- 卡牌阵列 (Unified Layout System) -->
<view class="card-slot" wx:for="{{drawnCards}}" wx:key="index"> <spread-container
<text class="pos-name">{{selectedSpread.positions[index]}}(第{{index + 1}}张)</text> spread="{{selectedSpread}}"
<view class="flip-scene" bindtap="revealNext" data-index="{{index}}"> cards="{{drawnCards}}"
<view class="flip-card {{index < revealedCount ? 'flipped' : ''}}"> revealed-count="{{revealedCount}}"
<view class="card-face face-back"> bind:reveal="revealNext"
<image class="card-image" src="{{cardBackImage}}" mode="widthFix"></image> bind:viewDetail="viewCardDetail"
</view> />
<view class="card-face face-front">
<image class="card-image {{item.isReversed ? 'reversed' : ''}}" src="{{item.image}}" mode="widthFix"></image> <!-- 翻牌提示 -->
</view> <view class="flip-hint" wx:if="{{state === 'flipping' && revealedCount < selectedSpread.cardCount}}">
</view> <text class="flip-hint-icon">✦</text>
</view> <text class="flip-hint-text">依次点击牌面,逐一揭示</text>
<text class="card-name-sm" wx:if="{{index < revealedCount}}">{{item.name}}{{item.isReversed ? '(逆)' : ''}}</text> <text class="flip-hint-icon">✦</text>
</view>
</view> </view>
<!-- 解读区域 --> <!-- 解读区域 -->
<view class="interpretation-area" wx:if="{{revealedCount === selectedSpread.cardCount}}" animation="{{infoAnimation}}"> <view class="interpretation-area" wx:if="{{revealedCount === selectedSpread.cardCount}}" animation="{{infoAnimation}}">
<!-- 加载中 -->
<view class="loading-ai" wx:if="{{isAiLoading}}"> <view class="loading-ai" wx:if="{{isAiLoading}}">
<text class="loading-dot">···</text> <text class="loading-dot">···</text>
<text>{{aiLoadingText}}</text> <text>{{aiLoadingText}}</text>
@ -243,6 +253,20 @@
<text class="content">{{aiResult.advice}}</text> <text class="content">{{aiResult.advice}}</text>
</view> </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> <button class="reset-btn" bindtap="resetSpread">重新开启</button>
</view> </view>
</view> </view>

View File

@ -505,14 +505,14 @@ page {
/* 10张牌 - 凯尔特十字 */ /* 10张牌 - 凯尔特十字 */
.spread-display.celtic_cross { .spread-display.celtic_cross {
display: grid; display: grid;
grid-template-columns: repeat(6, 90rpx); grid-template-columns: repeat(6, 104rpx);
grid-template-rows: repeat(4, auto); grid-template-rows: repeat(4, auto);
gap: 8rpx; gap: 8rpx;
max-width: 580rpx; max-width: 100%;
} }
.spread-display.celtic_cross .card-slot { width: 90rpx; } .spread-display.celtic_cross .card-slot { width: 104rpx; }
.spread-display.celtic_cross .flip-scene { width: 90rpx; height: 150rpx; } .spread-display.celtic_cross .flip-scene { width: 104rpx; height: 174rpx; }
.spread-display.celtic_cross .card-slot:nth-child(1) { grid-area: 2 / 3; } .spread-display.celtic_cross .card-slot:nth-child(1) { grid-area: 2 / 3; }
.spread-display.celtic_cross .card-slot:nth-child(2) { grid-area: 2 / 3; transform: rotate(90deg); z-index: 1; } .spread-display.celtic_cross .card-slot:nth-child(2) { grid-area: 2 / 3; transform: rotate(90deg); z-index: 1; }
@ -528,14 +528,14 @@ page {
/* 10张牌 - 生命之树 */ /* 10张牌 - 生命之树 */
.spread-display.tree_of_life { .spread-display.tree_of_life {
display: grid; display: grid;
grid-template-columns: repeat(3, 90rpx); grid-template-columns: repeat(3, 104rpx);
grid-template-rows: repeat(5, auto); grid-template-rows: repeat(5, auto);
gap: 10rpx; gap: 10rpx;
max-width: 310rpx; max-width: 100%;
} }
.spread-display.tree_of_life .card-slot { width: 90rpx; } .spread-display.tree_of_life .card-slot { width: 104rpx; }
.spread-display.tree_of_life .flip-scene { width: 90rpx; height: 150rpx; } .spread-display.tree_of_life .flip-scene { width: 104rpx; height: 174rpx; }
.spread-display.tree_of_life .card-slot:nth-child(1) { grid-area: 1 / 2; } .spread-display.tree_of_life .card-slot:nth-child(1) { grid-area: 1 / 2; }
.spread-display.tree_of_life .card-slot:nth-child(2) { grid-area: 2 / 1; } .spread-display.tree_of_life .card-slot:nth-child(2) { grid-area: 2 / 1; }
@ -1091,24 +1091,38 @@ page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 110rpx; width: 124rpx;
margin-bottom: 8rpx; margin-bottom: 8rpx;
} }
.pos-name { .pos-info {
font-size: 20rpx;
color: #8a8a9d;
margin-bottom: 8rpx;
text-align: center;
height: 40rpx;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: flex-end;
min-height: 60rpx;
margin-bottom: 8rpx;
width: 140%; /* Allow spilling over slightly */
margin-left: -20%; /* Center the spill */
}
.pos-text-main {
font-size: 24rpx;
color: #8a8a9d;
text-align: center;
line-height: 1.2; line-height: 1.2;
white-space: nowrap; /* Force single line if possible, or allow wrap controlling height */
}
.pos-seq {
font-size: 20rpx;
color: rgba(138, 138, 157, 0.6);
margin-top: 4rpx;
} }
.flip-scene { .flip-scene {
width: 110rpx; width: 124rpx;
height: 185rpx; height: 208rpx;
perspective: 1000rpx; perspective: 1000rpx;
} }
@ -1141,17 +1155,17 @@ page {
.card-name-sm { .card-name-sm {
margin-top: 12rpx; margin-top: 12rpx;
font-size: 18rpx; font-size: 22rpx;
color: #c9a0dc; color: #c9a0dc;
text-align: center; text-align: center;
} }
/* 5. 深度解读区域 */ /* 5. 深度解读区域 */
.interpretation-area { .interpretation-area {
margin-top: 60rpx; margin-top: 20rpx;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
border-radius: 24rpx; border-radius: 24rpx;
padding: 40rpx; padding: 20rpx;
} }
.theme-box { .theme-box {
@ -1245,7 +1259,7 @@ page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 80rpx 0; padding: 20rpx 0;
color: #c9a0dc; color: #c9a0dc;
font-size: 26rpx; font-size: 26rpx;
} }
@ -1484,3 +1498,106 @@ page {
box-shadow: 0 4rpx 20rpx rgba(201, 160, 220, 0.3); box-shadow: 0 4rpx 20rpx rgba(201, 160, 220, 0.3);
} }
/* =========================================
流式 AI 解读输出样式
========================================= */
.stream-result {
padding: 30rpx 20rpx;
animation: fadeIn 0.3s ease;
}
.stream-text {
display: block;
font-size: 28rpx;
color: #e8e0f0;
line-height: 1.8;
white-space: pre-wrap; /* 保留换行 */
word-break: break-all;
letter-spacing: 0.5rpx;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10rpx); }
to { opacity: 1; transform: translateY(0); }
}
/* 翻牌提示 */
.flip-hint {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 16rpx;
margin-top: 32rpx;
animation: hintPulse 2s ease-in-out infinite;
}
.flip-hint-text {
font-size: 26rpx;
color: #c9a0dc;
letter-spacing: 2rpx;
}
.flip-hint-icon {
font-size: 20rpx;
color: #c9a0dc;
}
@keyframes hintPulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
/* AI 警示语精简版 */
.ai-warning {
margin-top: 40rpx;
text-align: center;
font-size: 22rpx;
color: rgba(201, 160, 220, 0.4);
letter-spacing: 1rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 40rpx;
}
/* 5. 分享引导模块 */
.share-guidance {
width: 100%;
max-width: 600rpx;
background: linear-gradient(135deg, rgba(201, 160, 220, 0.1) 0%, rgba(201, 160, 220, 0.05) 100%);
border: 1px solid rgba(201, 160, 220, 0.3);
border-radius: 20rpx;
padding: 30rpx;
margin: 40rpx 0;
text-align: center;
box-sizing: border-box;
}
.guidance-text {
font-size: 28rpx;
color: #c9a0dc;
margin-bottom: 24rpx;
font-style: italic;
letter-spacing: 2rpx;
text-shadow: 0 0 10rpx rgba(201, 160, 220, 0.3);
}
.share-btn {
background: linear-gradient(90deg, #ffd700, #daa520);
color: #1a1a2e;
font-size: 30rpx;
font-weight: 600;
border-radius: 40rpx;
padding: 0 40rpx;
line-height: 80rpx;
border: none;
box-shadow: 0 4rpx 16rpx rgba(218, 165, 32, 0.3);
transition: all 0.3s ease;
}
.share-btn:active {
transform: scale(0.96);
box-shadow: 0 2rpx 8rpx rgba(218, 165, 32, 0.3);
}
/* @import '../../styles/disclaimer.wxss'; */

View File

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

View File

@ -1,4 +1,8 @@
<view class="container" wx:if="{{article}}"> <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"> <view class="article-header">
<text class="article-title">{{article.title}}</text> <text class="article-title">{{article.title}}</text>
<text class="article-summary">{{article.summary}}</text> <text class="article-summary">{{article.summary}}</text>

View File

@ -1,7 +1,32 @@
.container { .container {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 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 { .article-header {

View File

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

View File

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

View File

@ -1,7 +1,32 @@
.container { .container {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 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 { .header {

View File

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

View File

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

View File

@ -1,7 +1,32 @@
.container { .container {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); 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 { .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);
}

View File

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

View File

@ -32,6 +32,8 @@
.privacy-content { .privacy-content {
height: calc(100vh - 280rpx); height: calc(100vh - 280rpx);
padding: 0 30rpx; padding: 0 30rpx;
box-sizing: border-box;
width: 100%;
} }
.section { .section {
@ -40,6 +42,8 @@
padding: 32rpx; padding: 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
box-sizing: border-box;
width: 100%;
} }
.section-title { .section-title {

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({ Page({
data: { data: {
selectedZodiac: null, selectedZodiac: null,
currentFortune: null, // { keyword, today, lucky, notice, match, action, summary } currentFortune: null,
isMatchExpanded: false, // 控制关系提示的展开状态 isLoading: false,
isMatchExpanded: false,
zodiacList: [],
dailyOverview: null // 新增:星象概览
},
// 12星座数据 - 增加 summary 字段 onLoad: async function () {
zodiacList: [ this.setData({
{ zodiacList: getZodiacList()
name: '白羊座', });
symbol: '♈',
range: '3.21 4.19', // 加载星象概览
fortunes: [ const overview = await fetchDailyOverview();
{ this.setData({ dailyOverview: overview });
keyword: '开启',
today: '感觉能量在一点点回升,像温柔的晨光,正如适合开启一个小计划。',
lucky: '试着做一件从未做过的小事,哪怕只是换条路漫步回家。',
notice: '稍微慢下来也没关系,深呼吸,给自己一点缓冲的时间。',
action: '也许可以尝试一项简单的运动,或者把那个想了很久的小决定做了。',
match: '狮子座',
summary: '整体氛围积极向上,适宜迈出新的一步。'
}, },
{
keyword: '直觉', // 返回上一页或列表
today: '直觉敏锐的一天,内心的声音会温柔地指引你去对的地方。', goBack: function () {
lucky: '流一点汗,让身体的能量自由流动起来。', console.log('[Zodiac] goBack triggered');
notice: '倾听也是一种力量,试着先听听别人的想法。', if (this.data.selectedZodiac) {
action: '相信你的第一反应,今天不需要过度分析,跟着感觉走就好。', // 如果在详情页,返回列表
match: '射手座', this.setData({
summary: '这是一个依靠感性而非逻辑的时刻,信任直觉。' 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 index = e.currentTarget.dataset.index;
const zodiac = this.data.zodiacList[index]; const zodiac = this.data.zodiacList[index];
const randomIdx = Math.floor(Math.random() * zodiac.fortunes.length); // 1. 设置加载状态 & 选中星座
const fortuneData = zodiac.fortunes[randomIdx];
this.setData({ this.setData({
selectedZodiac: zodiac, selectedZodiac: zodiac,
currentFortune: fortuneData, currentFortune: null, // 清空旧数据
isMatchExpanded: false // 重置展开状态 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({ this.setData({
selectedZodiac: null, selectedZodiac: null,
currentFortune: null, currentFortune: null,
isLoading: false,
isMatchExpanded: 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="container">
<!-- 自定义返回按钮 (仿塔罗页风格) -->
<view class="nav-back" bindtap="goBack">
<text class="back-icon">⇠</text>
<text class="back-text">返回</text>
</view>
<!-- 状态1: 星座列表 --> <!-- 状态1: 星座列表 -->
<view class="zodiac-grid" wx:if="{{!selectedZodiac}}"> <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="header-tip">选择你的星座</view>
<view class="grid-container"> <view class="grid-container">
<block wx:for="{{zodiacList}}" wx:key="name"> <block wx:for="{{zodiacList}}" wx:key="name">
@ -14,64 +31,117 @@
</view> </view>
</view> </view>
<!-- 状态2: 运势展示 --> <!-- 状态2: 加载中 -->
<view class="result-container" wx:if="{{selectedZodiac}}"> <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"> <view class="result-header">
<text class="result-symbol">{{selectedZodiac.symbol}}</text> <text class="result-symbol">{{selectedZodiac.symbol}}</text>
<text class="today-title">今日指引 · {{selectedZodiac.name}}</text> <text class="today-title">今日指引 · {{selectedZodiac.name}}</text>
</view> </view>
<!-- 三段式卡片 --> <!-- 滚动区域 (简单容器即可) -->
<view class="fortune-card"> <view class="fortune-card">
<!-- 0. 今日关键词 (NEW) --> <!-- 1. 关键词与核心能量 -->
<view class="keyword-card"> <view class="keyword-section">
<text class="keyword-label">✨ 今日关键词</text> <text class="keyword-label">✨ 今日关键词</text>
<text class="keyword-text">{{currentFortune.keyword}}</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> </view>
<!-- 1. 今日运势 --> <view class="divider"></view>
<view class="section primary-section">
<text class="section-label">✨ 今日运势</text> <!-- 2. 运势指数 -->
<text class="section-content">{{currentFortune.today}}</text> <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> </view>
<!-- 2. 幸运提示 --> <!-- 3. 详细运势 -->
<view class="section"> <view class="section-block">
<text class="section-label">🍀 幸运提示</text> <text class="section-title">❤️ 感情运势</text>
<text class="section-content">{{currentFortune.lucky}}</text> <text class="section-content">{{currentFortune.fortune_text.love}}</text>
</view> </view>
<!-- 3. 注意事项 --> <view class="section-block">
<view class="section"> <text class="section-title">💼 事业运势</text>
<text class="section-label">💡 注意事项</text> <text class="section-content">{{currentFortune.fortune_text.career}}</text>
<text class="section-content">{{currentFortune.notice}}</text>
</view> </view>
<!-- 4. 今日行动建议 (NEW) --> <view class="section-block">
<view class="section last-section"> <text class="section-title">💰 财运提醒</text>
<text class="section-label">🌱 今日行动建议</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> <text class="section-content">{{currentFortune.action}}</text>
</view> </view>
<!-- 5. 关系提示 (可展开) --> </view>
<view class="status-divider"></view> <!-- 分隔线 -->
<view class="match-module" bindtap="toggleMatch">
<text class="match-trigger">{{isMatchExpanded ? '收起关系提示' : '🌸 今日关系提示(点击查看)'}}</text>
<view class="match-content" wx:if="{{isMatchExpanded}}"> <view class="action-area">
<text class="match-intro">今天,与你能量更顺的星座是:</text> <view class="back-btn" bindtap="backToList">⬅ 选其他星座</view>
<text class="match-name">{{currentFortune.match}}</text> <button class="share-btn" open-type="share">✨ 分享今日指引</button>
</view> <view class="verify-btn" bindtap="goToTarot">前往塔罗验证能量</view>
</view> </view>
</view> <view class="disclaimer">
<text>内容仅供娱乐与参考,不构成现实决策依据</text>
<!-- 总体结论 (收束模块) -->
<view class="summary-text">{{currentFortune.summary}}</view>
<view class="action-area" bindtap="backToList">
<text class="back-btn">⬅ 选其他星座</text>
</view> </view>
</view> </view>

View File

@ -2,24 +2,119 @@
page { page {
background-color: #1a1a2e; background-color: #1a1a2e;
height: 100%; height: 100%;
color: #fff; box-sizing: border-box;
} }
.container { .container {
padding: 30px 20px; padding: 100rpx 40rpx 60rpx; /* Increased top padding to avoid overlap */
box-sizing: border-box; box-sizing: border-box;
min-height: 100%; min-height: 100%;
display: flex; display: flex;
flex-direction: column; 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 { .header-tip {
font-size: 20px; font-size: 16px; /* Smaller header */
margin-bottom: 30px; margin-bottom: 15px; /* Reduced margin */
text-align: center; text-align: center;
opacity: 0.9; color: #fff;
opacity: 1;
letter-spacing: 1px; letter-spacing: 1px;
font-weight: bold;
} }
.grid-container { .grid-container {
@ -30,31 +125,35 @@ page {
.zodiac-item { .zodiac-item {
width: 30%; /* 三列布局 */ width: 30%; /* 三列布局 */
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 255, 255, 0.08);
margin-bottom: 20px; margin-bottom: 12px; /* Reduced margin */
padding: 20px 0; padding: 12px 0; /* Reduced padding */
border-radius: 12px; border-radius: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
border: 1px solid rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1);
cursor: pointer; cursor: pointer;
} }
.zodiac-symbol { .zodiac-symbol {
font-size: 28px; font-size: 24px; /* Smaller symbol */
margin-bottom: 5px; margin-bottom: 4px;
color: rgb(240, 240, 255);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
} }
.zodiac-name { .zodiac-name {
font-size: 14px; font-size: 12px; /* Smaller name */
opacity: 0.8; color: #fff;
opacity: 1;
font-weight: 500;
} }
.zodiac-range { .zodiac-range {
font-size: 10px; font-size: 9px; /* Smaller range */
opacity: 0.5; color: rgba(255, 255, 255, 0.6);
margin-top: 3px; margin-top: 2px;
} }
/* --- 结果状态样式 --- */ /* --- 结果状态样式 --- */
@ -83,7 +182,11 @@ page {
.today-title { .today-title {
font-size: 18px; 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 { .fortune-card {
@ -99,84 +202,194 @@ page {
flex-direction: column; flex-direction: column;
} }
/* 0. 关键词卡片样式 */ /* --- 新版结果展示样式 --- */
.keyword-card { .keyword-section {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-bottom: 25px; margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.05);
} }
.keyword-label { .keyword-label {
font-size: 12px; font-size: 12px;
color: #a29bfe; color: #a29bfe;
margin-bottom: 5px;
opacity: 0.8; opacity: 0.8;
margin-bottom: 5px;
} }
.keyword-text { .keyword-text {
font-size: 32px; /* 进一步加大关键词字号 */ font-size: 36px;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
letter-spacing: 4px; letter-spacing: 4px;
margin-top: 5px;
text-shadow: 0 2px 10px rgba(0,0,0,0.3); text-shadow: 0 2px 10px rgba(0,0,0,0.3);
margin-bottom: 15px;
} }
/* 三段式内容的样式 */ .core-energy {
.section { 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; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 25px; gap: 8px;
border-bottom: 1px dashed rgba(255, 255, 255, 0.1); /* 分隔线 */ background: rgba(0,0,0,0.2);
padding-bottom: 15px; 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 { .last-section {
margin-bottom: 0; margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
} }
.section-label { /* 总体结论样式 */
font-size: 14px; .summary-text {
color: #a29bfe; /* 浅紫色标签 */ font-size: 13px;
margin-bottom: 8px; color: rgba(255, 255, 255, 0.5); /* 中性灰 */
font-weight: bold; margin-top: -20px; /* 调整间距 */
margin-bottom: 30px;
text-align: center;
max-width: 80%;
line-height: 1.5;
} }
.section-content { .action-area {
font-size: 14px; /* 二级信息字号调小 */ display: flex;
line-height: 1.6; flex-direction: column;
align-items: center;
gap: 15px;
margin-bottom: 30px;
width: 100%;
}
.verify-btn {
background: linear-gradient(135deg, #e94560, #8b3a62);
color: #fff; color: #fff;
opacity: 0.75; /* 二级信息透明度降低 */ font-size: 16px;
text-align: left; 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;
} }
/* 一级信息强调样式 */ .share-btn {
.primary-section .section-content { background: linear-gradient(135deg, #7a5cff, #005bea); /* 不同的蓝紫色渐变 */
font-size: 17px; color: #fff;
opacity: 1; font-size: 16px;
font-weight: 500; font-weight: bold;
line-height: 1.7; padding: 12px 30px;
letter-spacing: 0.5px; 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 { @keyframes pulseBtn {
font-size: 15px; 0% { transform: scale(1); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
color: #e94560; /* 稍微强调标题颜色 */ 50% { transform: scale(1.05); box-shadow: 0 6px 20px rgba(233, 69, 96, 0.6); }
opacity: 1; 100% { transform: scale(1); box-shadow: 0 4px 15px rgba(233, 69, 96, 0.4); }
} }
.back-btn { .back-btn {
font-size: 14px; font-size: 14px;
opacity: 0.6; color: #c9a0dc; /* Light purple to match theme but be visible */
opacity: 0.9;
text-decoration: underline; text-decoration: underline;
padding: 10px; padding: 10px; /* Increased padding */
} }
/* 简单的动效 */ /* 简单的动效 */
@ -194,7 +407,7 @@ page {
.status-divider { .status-divider {
width: 100%; width: 100%;
height: 1px; height: 1px;
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 255, 255, 0.1);
margin: 15px 0; margin: 15px 0;
} }
@ -208,10 +421,11 @@ page {
.match-trigger { .match-trigger {
font-size: 14px; font-size: 14px;
color: #fbaceb; /* 淡淡的粉色 */ color: #ffccff; /* Brighter pink/purple */
opacity: 0.8; opacity: 1;
text-decoration: underline; text-decoration: underline;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: 500;
} }
.match-content { .match-content {
@ -233,15 +447,22 @@ page {
font-size: 18px; font-size: 18px;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
}
/* 总体结论样式 */
.summary-text {
font-size: 13px;
color: rgba(255, 255, 255, 0.5); /* 中性灰 */
margin-top: -20px; /* 调整间距 */ margin-top: -20px; /* 调整间距 */
margin-bottom: 30px; margin-bottom: 30px;
text-align: center; text-align: center;
max-width: 80%; max-width: 80%;
line-height: 1.5; 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);
}

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

View File

@ -8,6 +8,8 @@ const STORAGE_KEY_POINTS = 'tarot_points';
const STORAGE_KEY_LAST_LOGIN = 'tarot_last_login'; const STORAGE_KEY_LAST_LOGIN = 'tarot_last_login';
const STORAGE_KEY_AD_DATE = 'tarot_ad_date'; const STORAGE_KEY_AD_DATE = 'tarot_ad_date';
const STORAGE_KEY_AD_COUNT = 'tarot_ad_count'; 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 DEFAULT_POINTS = 20; // 新用户默认积分
const DAILY_REWARD = 3; // 每日登录奖励 const DAILY_REWARD = 3; // 每日登录奖励
@ -25,6 +27,12 @@ const AD_REWARD_CONFIG = {
DAILY_LIMIT: 3 // 每日上限次数 DAILY_LIMIT: 3 // 每日上限次数
}; };
// 分享奖励配置
const SHARE_REWARD_CONFIG = {
REWARD_POINTS: 10, // 分享奖励积分 (改为 10)
DAILY_LIMIT: 3 // 每日上限次数 (新增)
};
/** /**
* 初始化积分系统 * 初始化积分系统
* 如果用户是首次使用设置默认积分 * 如果用户是首次使用设置默认积分
@ -282,6 +290,56 @@ function rewardFromAd() {
} }
} }
/**
* 检查并发放分享奖励
* @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 = { module.exports = {
initPoints, initPoints,
getPoints, getPoints,
@ -292,7 +350,10 @@ module.exports = {
// 广告奖励相关 // 广告奖励相关
getTodayAdCount, getTodayAdCount,
canWatchAd, canWatchAd,
getTodayAdCount,
canWatchAd,
rewardFromAd, rewardFromAd,
checkShareReward, // 导出新函数
// 常量导出 // 常量导出
POINT_SOURCE, POINT_SOURCE,
AD_REWARD_CONFIG 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
};