huaanglimeng/pages/index/index.js

607 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// AI 配置 (建议后续迁移至后端以保证 API Key 安全)
const DEEPSEEK_API_KEY = 'sk-f659b4c3aa954baaa6cbdbd5cae0b7d1'; // 请在此处替换您的 API Key
const DEEPSEEK_BASE_URL = 'https://api.deepseek.com/chat/completions';
// --- 数据导入 ---
const { minorArcana } = require('../../data/tarot/index');
const { getQuestionType } = require('../../utils/questionType');
const { generateSpreadStory } = require('../../utils/spreadStory');
// --- Prompt 模板定义 ---
// 1. 基础版模板:平静、清晰、节制
const TAROT_PROMPT_BASE = `你是一位克制、温和、不制造依赖的塔罗解读者。
你的角色不是预测未来,而是陪伴当下的人理解此刻的状态。
【解读边界】
- 不给命令,不下结论,不使用“你应该”“结果是”“一定会”
- 不预测具体事件或时间,不制造焦虑或依赖
【语言风格】
- 平静、清晰、节制
- 使用“可能 / 正在 / 也许 / 可以感受到”
- 像是轻声说明,而不是说服或劝导,避免情绪渲染与神秘化表达
【解读结构】
1. 用 12 句话描绘这张牌当下呈现的状态氛围
2. 结合关键词与核心含义,解释它可能对应的内在状态
3. 用一句自然收口的话结束,不引导行动
【输出要求】
- 总字数 120180 字
- 只输出解读文本,不使用标题、编号、分段符号`;
// 2. 深夜版模板:更慢、更柔和、允许留白
const TAROT_PROMPT_NIGHT = `你是一位安静、克制、不制造依赖的塔罗解读者。
此刻的解读更像一次低声的陪伴,而不是解释或分析。
【解读边界】
- 不给建议,不下判断,不使用“你应该”“结果是”“接下来会”
- 不预测未来事件或变化,不放大情绪,不渲染痛苦
【语言风格】
- 更慢、更柔和
- 使用“也许 / 正在 / 像是 / 仿佛”
- 允许留白,不急着说清楚一切,文字像夜晚一样安静
【解读结构】
1. 先用一句贴近当下感受的描述开头
2. 围绕关键词展开,但重点放在“感受本身”
3. 用一句轻微、可停住的句子结束,不指向下一步
【输出要求】
- 只输出解读文本`;
// 3. 增强版模板:结构化深度解读
const TAROT_PROMPT_ENHANCED = `你是一位专业、温和且具有洞察力的塔罗解读者。
你的任务是根据选定的牌阵,为用户提供有深度、有层次且具有陪伴感的解读。
【解读边界】
- 不预测具体事件、不给绝对结论、不使用恐吓性语言
- 保持温和的引导口气,像是在进行一场心灵对话
【解读结构】
请严格按照以下 JSON 格式输出解读内容,不要包含任何其他说明性文字:
{
"theme": "用一句话概括本次解读的核心主题",
"status": "描述用户当下的情绪或处境状态",
"influence": "分析当前局面背后的潜在影响因素(内在或外在)",
"advice": "给出具体、温和且具有操作性的行动建议",
"positions": [
{
"posName": "位置名称",
"posMeaning": "由你结合牌面对该位置的详细解读(约 60-100 字)"
}
]
}`;
// --- 牌阵定义 ---
const SPREADS = [
{
"id": "one_card_guidance",
"name": "单张指引",
"cardCount": 1,
"positions": ["当下的指引"],
"description": "为你此刻的状态提供一个温和而清晰的方向提示。",
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "three_time_flow",
"name": "过去 · 现在 · 未来",
"cardCount": 3,
"positions": ["过去", "现在", "未来"],
"description": "帮助你理解事情的发展过程与可能走向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "three_problem_solution",
"name": "问题 · 阻碍 · 建议",
"cardCount": 3,
"positions": ["问题核心", "当前阻碍", "行动建议"],
"description": "聚焦关键问题,找出当下最可行的应对方式。",
"aiSchema": ["core_theme", "current_state", "action_advice"]
},
{
"id": "two_choice_decision",
"name": "二选一抉择",
"cardCount": 4,
"positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"],
"description": "对比两种选择的潜在走向,辅助理性决策。",
"aiSchema": ["core_theme", "potential_influence", "action_advice"]
},
{
"id": "five_situation_analysis",
"name": "现状分析",
"cardCount": 5,
"positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"],
"description": "从内外层面拆解局势,明确下一步行动。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "relationship_spread",
"name": "关系洞察",
"cardCount": 5,
"positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"],
"description": "理解一段关系中的互动模式与发展方向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "timeline_spread",
"name": "时间之流",
"cardCount": 5,
"positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"],
"description": "追溯事件的时间线,看清发展脉络。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "diamond_spread",
"name": "钻石牌阵",
"cardCount": 5,
"positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"],
"description": "多角度剖析问题,找到解决之道。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "spiritual_guidance",
"name": "灵性指引",
"cardCount": 7,
"positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"],
"description": "深入探索内在世界,获得灵性层面的启发。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "horseshoe_spread",
"name": "马蹄铁牌阵",
"cardCount": 7,
"positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"],
"description": "全面了解情况的来龙去脉与未来走向。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "celtic_cross",
"name": "凯尔特十字",
"cardCount": 10,
"positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"],
"description": "最经典的综合牌阵,深度解析生命议题。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
},
{
"id": "tree_of_life",
"name": "生命之树",
"cardCount": 10,
"positions": ["王冠", "智慧", "理解", "慈悲", "严厉", "美丽", "胜利", "荣耀", "基础", "王国"],
"description": "基于卡巴拉生命之树的深度灵性探索。",
"aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"]
}
];
Page({
data: {
todayDate: '', // 用于存储今天的日期
// 1. 状态管理: spread_select, idle, shuffling, drawing, flipping, revealed
state: 'spread_select', // 初始进入牌阵选择
selectedSpread: null,
question: '', // 用户输入的问题
questionType: '综合问题', // 问题类型
drawnCardIndices: [], // 用户选中的牌索引
drawnCards: [], // 实际抽到的牌对象
revealedCount: 0, // 已翻开的数量
cardBackImage: '/images/beimian.png',
spreads: SPREADS,
// 动画数据
deckAnimation: {},
cardAnimation: {},
infoAnimation: {},
guideAnimation: {},
// AI 相关
isAiLoading: false,
aiExplanation: '',
spreadStory: '', // 综合故事线
// 3. 前缀文案列表(随机抽取)
prefixList: [
"今天,这张牌提醒你:",
"此刻,宇宙传递的信息:",
"这张牌想告诉你:",
"请收下这份指引:"
],
// 2. 完整的 22 张大阿尔卡那牌组(包含逆位含义)
cardList: [
{
name: "愚者",
keyword: "新的开始",
meaning: "放下恐惧,勇敢迈出第一步,世界充满无限可能。",
reversedMeaning: "也许有些太冲动了?停下来检查一下背包,确保安全再出发。",
energy: "外放",
image: "/images/yuzhe.png"
},
{
name: "魔术师",
keyword: "创造力",
meaning: "你拥有实现目标所需的一切资源,现在是行动的时候。",
reversedMeaning: "你是否低估了自己的能力?或者在犹豫不决?相信自己,工具就在手边。",
energy: "外放",
image: "/images/moshushi.png"
},
{
name: "女祭司",
keyword: "直觉",
meaning: "倾听内心的声音,答案就在你潜意识的深处。",
reversedMeaning: "暂时听不见内心的声音了吗?别急着向外寻找,先让自己静下来。",
energy: "内敛",
image: "/images/nvjishi.png"
},
{
name: "皇后",
keyword: "丰饶",
meaning: "享受生活的美好,去创造、去关爱,收获也是一种能力。",
reversedMeaning: "是否忽略了对自己的关爱?或者有些过度保护?给彼此一点呼吸的空间。",
energy: "柔和",
image: "/images/nvhuang.png"
},
{
name: "皇帝",
keyword: "秩序",
meaning: "建立规则和结构,理性地掌控局面,承担起责任。",
reversedMeaning: "有时候太固执会让人疲惫,适度放权,弹性也是一种力量。",
energy: "坚实",
image: "/images/huangdi.png"
},
{
name: "教皇",
keyword: "指引",
meaning: "寻求传统智慧的帮助,或者成为他人的精神导师。",
reversedMeaning: "不必拘泥于陈规旧矩,试着打破常规,寻找适合你自己的那条路。",
energy: "庄重",
image: "/images/jiaohuang.png"
},
{
name: "恋人",
keyword: "选择",
meaning: "跟随你的心,在关系中寻找和谐,做出忠于自我的决定。",
reversedMeaning: "现在的关系是否有些失衡?先爱自己,才能更好地爱别人。",
energy: "柔和",
image: "/images/lianren.png"
},
{
name: "战车",
keyword: "意志",
meaning: "认准目标,克服相反的力量,胜利属于坚持到底的人。",
reversedMeaning: "感觉失去了方向控制?慢下来,重新调整导航,欲速则不达。",
energy: "激进",
image: "/images/zhanche.png"
},
{
name: "力量",
keyword: "勇气",
meaning: "真正的力量是温柔的坚持,用耐心驯服内心的野兽。",
reversedMeaning: "不要怀疑自己的韧性,面对内心的软弱并不是坏事,那是温柔的开始。",
energy: "柔和",
image: "/images/liliang.png"
},
{
name: "隐士",
keyword: "内省",
meaning: "暂时远离喧嚣,向内探索,寻找属于你自己的光。",
reversedMeaning: "`是不是一个人待太久了?试着走出洞穴,和外界建立一点连接吧。",
energy: "潜沉",
image: "/images/yinzhe.png"
},
{
name: "命运之轮",
keyword: "转折",
meaning: "改变是永恒的,顺势而为,抓住出现在你面前的机会。",
reversedMeaning: "运气暂时不在你这边,但没关系,低谷正是蓄力反弹的好时机。",
energy: "流转",
image: "/images/mingyunzhilun.png"
},
{
name: "正义",
keyword: "因果",
meaning: "诚实面对真相,所有的决定都会带来相应的结果。",
reversedMeaning: "即使结果不尽如人意,也要问心无愧。对自己诚实,比什么都重要。",
energy: "平衡",
image: "/images/zhengyi.png"
},
{
name: "倒吊人",
keyword: "换位",
meaning: "换一个角度看世界,有时候牺牲是为了更大的获得。",
reversedMeaning: "挣扎只会更累,不如彻底放松,换个角度看世界,也许心结就解开了。",
energy: "停滞",
image: "/images/daodiaoren.png"
},
{
name: "死神",
keyword: "新生",
meaning: "结束是为了新的开始,彻底告别过去,才能拥抱未来。",
reversedMeaning: "还在紧抓着过去不放吗?只有腾出双手,才能接住新的礼物。",
energy: "决绝",
image: "/images/sishen.png"
},
{
name: "节制",
keyword: "平衡",
meaning: "在极端之间寻找中庸之道,融合对立,通过耐心获得疗愈。",
reversedMeaning: "是不是有些失衡了?在这忙乱的世界里,找回你内心的节奏和中点。",
energy: "融合",
image: "/images/jiezhi.png"
},
{
name: "恶魔",
keyword: "束缚",
meaning: "觉察那些控制你的欲望或习惯,唯有觉知才能带来解脱。",
reversedMeaning: "别被眼前的诱惑蒙蔽,或者感觉被束缚。其实锁链是松的,你随时可以走。",
energy: "沉重",
image: "/images/emo.png"
},
{
name: "高塔",
keyword: "崩塌",
meaning: "不要害怕突如其来的剧变,它在摧毁虚假的根基,让你重建真实。",
reversedMeaning: "废墟中反而能看清地基。既然倒了,正好可以按照你真正想要的样子重建。",
energy: "剧烈",
image: "/images/ta.png"
},
{
name: "星星",
keyword: "希望",
meaning: "黑暗之后必有星光,保持信心,未来充满治愈与灵感。",
reversedMeaning: "暂时看不见星星也没关系,它们还在那里。给自己一点信心,黎明就在前方。",
energy: "明亮",
image: "/images/xingxing.png"
},
{
name: "月亮",
keyword: "幻象",
meaning: "直面内心的不安与迷茫,并非所有事情都如表象般真实。",
reversedMeaning: "恐惧只是长长的影子,别被它吓住。随着天亮,迷雾终会散去。",
energy: "幽静",
image: "/images/yueliang.png"
},
{
name: "太阳",
keyword: "喜悦",
meaning: "快乐、成功与活力,尽情地发光热,享受当下的幸福。",
reversedMeaning: "乌云暂时遮住了阳光,但这只是暂时的。保持乐观,你心里的光谁也偷不走。",
energy: "灼热",
image: "/images/taiyang.png"
},
{
name: "审判",
keyword: "觉醒",
meaning: "听到内心的召唤,回顾过往,做出决定,获得新生。",
reversedMeaning: "不要对他人的评价太敏感,也不要自我怀疑。如果你准备好了,就出发吧。",
energy: "回响",
image: "/images/shenpan.png"
},
{
name: "世界",
keyword: "圆满",
meaning: "一段旅程的完美终点,成就感与整合,准备开始新的循环。",
reversedMeaning: "机遇还没完全成熟,再耐心一点点。检查一下还有什么细节没完成?",
energy: "圆融",
image: "/images/shijie.png"
}
],
// 合并大牌和小牌
allCards: [],
aiResult: null
},
onLoad: function () {
// 获取当前日期
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const dateStr = `${year}-${month}-${day}`;
this.setData({
todayDate: dateStr,
allCards: [...this.data.cardList, ...minorArcana]
});
console.log(`塔罗牌已就位,共 ${this.data.allCards.length} 张。`)
},
// --- 1. 选择牌阵 ---
selectSpread: function (e) {
const id = e.currentTarget.dataset.id;
const spread = this.data.spreads.find(s => s.id === id);
this.setData({
selectedSpread: spread,
state: 'asking' // 先进入提问状态
});
},
// --- 1.5 确认问题并开始洗牌 ---
confirmQuestion: function () {
const question = this.data.question.trim();
const questionType = getQuestionType(question);
this.setData({
questionType: questionType,
state: 'shuffling'
});
// 模拟洗牌感应 2.5s
setTimeout(() => {
this.setData({ state: 'drawing' });
}, 2500);
},
// 输入问题
onQuestionInput: function (e) {
this.setData({
question: e.detail.value
});
},
// --- 2. 抽牌逻辑 ---
onCardTap: function (e) {
const index = e.currentTarget.dataset.index;
let { drawnCardIndices, selectedSpread } = this.data;
if (drawnCardIndices.includes(index)) {
drawnCardIndices = drawnCardIndices.filter(i => i !== index);
} else if (drawnCardIndices.length < selectedSpread.cardCount) {
drawnCardIndices.push(index);
}
this.setData({ drawnCardIndices });
},
// --- 3. 确认开启 ---
confirmDraw: function () {
const { drawnCardIndices, allCards } = this.data;
const drawnCards = drawnCardIndices.map(() => {
const randomIndex = Math.floor(Math.random() * allCards.length);
const card = Object.assign({}, allCards[randomIndex]);
card.isReversed = Math.random() >= 0.5;
// 如果这张牌没有图片(如小牌),使用背面图作为占位符,或保持原有逻辑
if (!card.image) {
card.image = this.data.cardBackImage;
}
return card;
});
this.setData({
drawnCards,
state: 'flipping',
revealedCount: 0
});
},
// --- 4. 逐一翻牌 ---
revealNext: function (e) {
const index = e.currentTarget.dataset.index;
if (index === this.data.revealedCount) {
const nextCount = index + 1;
this.setData({
revealedCount: nextCount
});
// 如果全部翻开,触发解读
if (nextCount === this.data.selectedSpread.cardCount) {
this.setData({ state: 'revealed' });
this.showInterpretation();
}
}
},
// --- 展示解读内容 ---
showInterpretation: function () {
const infoAnim = wx.createAnimation({
duration: 800,
timingFunction: 'ease'
});
infoAnim.opacity(1).translateY(0).step();
this.setData({ infoAnimation: infoAnim.export() });
// 异步获取 AI 解读
this.fetchAiInterpretation();
},
// --- 获取 AI 解读主逻辑 ---
fetchAiInterpretation: function () {
const { drawnCards, selectedSpread } = this.data;
if (!drawnCards.length) return;
this.setData({
isAiLoading: true,
aiResult: null
});
// 1. 构建卡牌信息
const cardDetails = drawnCards.map((card, index) => {
return `位置[${selectedSpread.positions[index]}]${card.name} (${card.isReversed ? '逆位' : '正位'}),关键词:${card.keyword || (card.keywords ? card.keywords.join(',') : '')}`;
}).join('\n');
const userPrompt = `牌阵:${selectedSpread.name}\n${cardDetails}`;
console.log("正在获取深度解读...");
wx.request({
url: DEEPSEEK_BASE_URL,
method: 'POST',
header: {
'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,
'Content-Type': 'application/json'
},
data: {
model: "deepseek-chat",
messages: [
{ role: "system", content: TAROT_PROMPT_ENHANCED },
{ role: "user", content: userPrompt }
],
stream: false,
response_format: { type: "json_object" }
},
timeout: 20000,
success: (res) => {
if (res.data && res.data.choices && res.data.choices[0]) {
try {
const aiResult = JSON.parse(res.data.choices[0].message.content);
const spreadStory = generateSpreadStory(drawnCards);
this.setData({
aiResult,
spreadStory: spreadStory,
isAiLoading: false
});
} catch (e) {
console.error("JSON 解析失败", e);
this.handleAiFallback();
}
} else {
this.handleAiFallback();
}
},
fail: (err) => {
console.error("请求失败", err);
this.handleAiFallback();
}
});
},
handleAiFallback: function () {
// 构建一个基础的回退对象
const fallback = {
theme: "能量感应受阻",
status: "当前思绪可能较为杂乱",
influence: "外界干扰或网络波动",
advice: "建议深呼吸,稍后再次尝试开启心灵对话",
positions: this.data.drawnCards.map((card, index) => ({
posName: this.data.selectedSpread.positions[index],
posMeaning: `${card.name}${card.isReversed ? '(逆位)' : '(正位)'}${card.isReversed ? (card.reversedMeaning || card.description) : (card.meaning || card.description)}`
}))
};
this.setData({
aiResult: fallback,
isAiLoading: false
});
},
resetSpread: function () {
this.setData({
state: 'spread_select',
selectedSpread: null,
drawnCardIndices: [],
drawnCards: [],
revealedCount: 0,
aiResult: null
});
},
// --- 导航优化:统一返回逻辑 ---
handleBack: function () {
wx.navigateBack({
delta: 1
});
}
})