// 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'); const { validateQuestion, scoreQuestion } = require('../../utils/questionFilter'); const { safeAIRequest } = require('../../utils/aiRequestManager'); const { hasEnough, deductPoints } = require('../../utils/pointsManager'); // --- Prompt 模板定义 --- // 1. 基础版模板:平静、清晰、节制 const TAROT_PROMPT_BASE = `你是一位克制、温和、不制造依赖的塔罗解读者。 你的角色不是预测未来,而是陪伴当下的人理解此刻的状态。 【解读边界】 - 不给命令,不下结论,不使用“你应该”“结果是”“一定会” - 不预测具体事件或时间,不制造焦虑或依赖 【语言风格】 - 平静、清晰、节制 - 使用“可能 / 正在 / 也许 / 可以感受到” - 像是轻声说明,而不是说服或劝导,避免情绪渲染与神秘化表达 【解读结构】 1. 用 1–2 句话描绘这张牌当下呈现的状态氛围 2. 结合关键词与核心含义,解释它可能对应的内在状态 3. 用一句自然收口的话结束,不引导行动 【输出要求】 - 总字数 120–180 字 - 只输出解读文本,不使用标题、编号、分段符号`; // 2. 深夜版模板:更慢、更柔和、允许留白 const TAROT_PROMPT_NIGHT = `你是一位安静、克制、不制造依赖的塔罗解读者。 此刻的解读更像一次低声的陪伴,而不是解释或分析。 【解读边界】 - 不给建议,不下判断,不使用“你应该”“结果是”“接下来会” - 不预测未来事件或变化,不放大情绪,不渲染痛苦 【语言风格】 - 更慢、更柔和 - 使用“也许 / 正在 / 像是 / 仿佛” - 允许留白,不急着说清楚一切,文字像夜晚一样安静 【解读结构】 1. 先用一句贴近当下感受的描述开头 2. 围绕关键词展开,但重点放在“感受本身” 3. 用一句轻微、可停住的句子结束,不指向下一步 【输出要求】 - 只输出解读文本`; // 3. 增强版模板:解读稳定器 + 高关联性结构化解读 const TAROT_PROMPT_ENHANCED = `📏 解读稳定规则(高优先级执行) 你必须保持解读风格稳定、具体且一致,不允许出现质量波动。 【一、解读长度控制】 • 整体长度:400~700 字(不少于 300 字,不超过 900 字) • 各模块建议: - 问题回应:2~3句 - 牌面分析:每张牌2句以内 - 当前状态:2~3句 - 发展趋势:2~3句 - 行动建议:3~5条短句 • 禁止:超长段落、大段心理学科普、冗长比喻 【二、结构稳定规则(必须严格执行)】 输出必须包含且仅包含以下5个部分,不允许新增其他模块: 1. 问题回应 2. 牌面分析 3. 当前状态 4. 发展趋势 5. 行动建议 【三、解读深度控制】 每段必须包含至少一个:情境描述、心理状态、行为倾向、关系互动 禁止:单纯解释牌意、抽象空话、能量类泛词堆叠 【四、情绪语气稳定器】 • 必须:温和、理解用户、提供支持 • 避免:过度肯定(一定会)、绝对否定(绝不会)、命令式建议、恐吓式预测 • 推荐句式:"你当前可能正在经历…" "这可能让你感到…" "可以尝试…" 【五、百科化防护规则(非常重要)】 禁止出现:"通常这张牌代表…" "根据牌义…" "在传统塔罗中…" 如果出现类似内容,必须立即转化为与用户问题相关的情境说明。 【六、参考牌义权重限制】 信息优先级:用户问题 > 提问领域 > 抽到的牌 > 参考牌义 参考牌义只能用于理解,不允许复述或改写复述。 【七、质量自检(生成前内部执行)】 在输出前检查: ✓ 开头是否回应问题 ✓ 是否逐张牌关联问题 ✓ 是否包含可执行建议 ✓ 是否出现百科语句 ✓ 是否结构完整 --- 🎯 角色设定 你是一位专业、温和、具有洞察力的塔罗解读师。 你的解读必须围绕用户提出的问题展开,而不是输出通用牌义。 ⚠️ 本次解读优先级: 用户问题 > 提问领域 > 抽到的牌 > 参考牌义 🧭 解读核心要求(非常重要) 1️⃣ 解读必须紧密围绕「用户问题」展开 2️⃣ 开头第一句话必须直接回应用户的问题情境 3️⃣ 每一张牌都要说明它与该问题的具体关系 4️⃣ 禁止输出通用百科式牌义解释 5️⃣ 必须结合提问领域进行情境化分析 6️⃣ 解读重点放在状态、趋势与建议,而非绝对预测 🎨 语言风格要求 • 温和、具体、有针对性 • 使用"你当前的情况可能是…" • 避免绝对判断和命令式语气 • 不要出现:"根据牌义来说…" "通常这张牌代表…" 🚫 禁止行为 • 不要复述参考牌义原文 • 不要脱离用户问题进行泛泛而谈 • 不要写百科解释 • 不要生成医疗、法律、金融建议 📤 输出格式 请严格按照以下 JSON 格式输出,不要包含任何其他说明性文字: { "theme": "直接回应用户问题的核心主题(必须提及问题关键词)", "status": "用户在这个问题上的当前状态", "influence": "影响这个问题的潜在因素", "advice": "温和、可执行的个人成长建议", "positions": [ { "posName": "位置名称", "posMeaning": "解释这张牌如何影响该问题情境(60-100字,必须联系问题)" } ] }`; // --- 牌阵定义 --- const SPREADS = [ { "id": "one_card_guidance", "name": "单张指引", "cardCount": 1, "cost": 1, "positions": ["当下的指引"], "description": "为你此刻的状态提供一个温和而清晰的方向提示。", "aiSchema": ["core_theme", "current_state", "action_advice"] }, { "id": "three_time_flow", "name": "过去 · 现在 · 未来", "cardCount": 3, "cost": 3, "positions": ["过去", "现在", "未来"], "description": "帮助你理解事情的发展过程与可能走向。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "three_problem_solution", "name": "问题 · 阻碍 · 建议", "cardCount": 3, "cost": 3, "positions": ["问题核心", "当前阻碍", "行动建议"], "description": "聚焦关键问题,找出当下最可行的应对方式。", "aiSchema": ["core_theme", "current_state", "action_advice"] }, { "id": "two_choice_decision", "name": "二选一抉择", "cardCount": 4, "cost": 4, "positions": ["选择A的发展", "选择A的结果", "选择B的发展", "选择B的结果"], "description": "对比两种选择的潜在走向,辅助理性决策。", "aiSchema": ["core_theme", "potential_influence", "action_advice"] }, { "id": "five_situation_analysis", "name": "现状分析", "cardCount": 5, "cost": 5, "positions": ["现状", "内在因素", "外在影响", "行动方向", "可能结果"], "description": "从内外层面拆解局势,明确下一步行动。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "relationship_spread", "name": "关系洞察", "cardCount": 5, "cost": 5, "positions": ["你的位置", "对方的位置", "关系现状", "隐藏影响", "未来趋势"], "description": "理解一段关系中的互动模式与发展方向。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "timeline_spread", "name": "时间之流", "cardCount": 5, "cost": 5, "positions": ["远古根源", "过去影响", "当下状态", "近期发展", "未来趋势"], "description": "追溯事件的时间线,看清发展脉络。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "diamond_spread", "name": "钻石牌阵", "cardCount": 5, "cost": 5, "positions": ["问题本质", "过去原因", "未来发展", "外部资源", "最佳行动"], "description": "多角度剖析问题,找到解决之道。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "spiritual_guidance", "name": "灵性指引", "cardCount": 7, "cost": 7, "positions": ["当前能量", "内在阻碍", "潜在天赋", "灵性课题", "指导建议", "未来机遇", "最高指引"], "description": "深入探索内在世界,获得灵性层面的启发。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "horseshoe_spread", "name": "马蹄铁牌阵", "cardCount": 7, "cost": 7, "positions": ["过去", "现在", "未来", "你的态度", "他人影响", "障碍", "最终结果"], "description": "全面了解情况的来龙去脉与未来走向。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "celtic_cross", "name": "凯尔特十字", "cardCount": 10, "cost": 10, "positions": ["现状", "挑战", "根基", "过去", "可能性", "近期未来", "你的态度", "外部影响", "希望与恐惧", "最终结果"], "description": "最经典的综合牌阵,深度解析生命议题。", "aiSchema": ["core_theme", "current_state", "potential_influence", "action_advice"] }, { "id": "tree_of_life", "name": "生命之树", "cardCount": 10, "cost": 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: '综合问题', // 问题类型 questionQuality: null, // 问题质量评分 drawnCardIndices: [], // 用户选中的牌索引 drawnCards: [], // 实际抽到的牌对象 revealedCount: 0, // 已翻开的数量 cardBackImage: '/images/beimian.png', spreads: SPREADS, // 动画数据 deckAnimation: {}, cardAnimation: {}, infoAnimation: {}, guideAnimation: {}, // AI 解读相关 aiResult: null, isAiLoading: false, aiLoadingText: '正在抽取牌面…', // 动态加载提示 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); // 检查积分是否足够 if (!hasEnough(spread.cost)) { wx.showModal({ title: '积分不足', content: `当前牌阵需要 ${spread.cost} 积分,您的积分不足,请稍后再来`, showCancel: false, confirmText: '知道了' }); return; } this.setData({ selectedSpread: spread, state: 'asking' // 先进入提问状态 }); }, // --- 1.5 确认问题并开始洗牌 --- confirmQuestion: function () { const question = this.data.question.trim(); console.log('confirmQuestion 被调用,问题:', question); // 新增:过滤验证 const filterResult = validateQuestion(question); console.log('过滤结果:', filterResult); if (filterResult.status === 'reject') { console.log('触发拒绝弹窗'); // 显示拒绝弹窗 wx.showModal({ title: '温馨提示', content: filterResult.reason, showCancel: false, confirmText: '我知道了' }); return; } if (filterResult.status === 'rewrite') { console.log('触发改写建议弹窗'); // 显示改写建议弹窗 - 简化版本 const firstSuggestion = filterResult.suggestions[0]; const modalContent = `${filterResult.reason}\n\n${firstSuggestion}`; console.log('弹窗内容:', modalContent); wx.showModal({ title: '提问建议', content: modalContent, confirmText: '使用建议', cancelText: '继续提问', success: (res) => { console.log('弹窗回调:', res); if (res.confirm) { // 使用建议问题 this.setData({ question: firstSuggestion, questionQuality: scoreQuestion(firstSuggestion) }); } // 无论选择哪个,都继续流程 this.proceedToShuffle(); }, fail: (err) => { console.error('弹窗显示失败:', err); // 即使弹窗失败,也继续流程 this.proceedToShuffle(); } }); return; } console.log('通过验证,继续流程'); // pass 状态:直接继续 this.proceedToShuffle(); }, // 抽离洗牌逻辑 proceedToShuffle: function () { const questionType = getQuestionType(this.data.question); this.setData({ questionType: questionType, state: 'shuffling' }); // 模拟洗牌感应 2.5s setTimeout(() => { this.setData({ state: 'drawing' }); }, 2500); }, // 输入问题 onQuestionInput: function (e) { const question = e.detail.value; const quality = scoreQuestion(question); this.setData({ question: question, questionQuality: quality }); }, // --- 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, selectedSpread } = 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; }); // 扣除积分(只扣一次) deductPoints(selectedSpread.cost); console.log(`[积分系统] 占卜消耗 ${selectedSpread.cost} 积分`); 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: async function () { const { drawnCards, selectedSpread, question, questionType } = this.data; if (!drawnCards.length) return; this.setData({ isAiLoading: true, aiResult: null, aiLoadingText: '正在抽取牌面…' }); // 1. 构建卡牌信息 const cardDetails = drawnCards.map((card, index) => { return `位置[${selectedSpread.positions[index]}]:${card.name} (${card.isReversed ? '逆位' : '正位'}),关键词:${card.keyword || (card.keywords ? card.keywords.join(',') : '')}`; }).join('\n'); // 2. 构建参考牌义(供 AI 理解用) const cardMeanings = drawnCards.map((card, index) => { const meaning = card.isReversed ? (card.reversedMeaning || card.description) : (card.meaning || card.description); return `${card.name} (${card.isReversed ? '逆位' : '正位'}):${meaning}`; }).join('\n'); // 3. 构建完整的用户提示 const userPrompt = `📥 输入信息 用户问题: ${question || '未指定具体问题'} 提问领域: ${questionType} 牌阵: ${selectedSpread.name} 抽到的牌: ${cardDetails} 参考牌义(内部理解用): ${cardMeanings} ⚠️ 参考牌义仅用于理解,不允许逐字复述。请严格围绕用户的问题进行解读。`; console.log("正在获取深度解读..."); console.log("用户问题:", question); console.log("提问领域:", questionType); // 4. 使用稳态请求管理器 const result = await safeAIRequest({ url: DEEPSEEK_BASE_URL, 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" } }, drawnCards, spread: selectedSpread, onProgress: (text) => { this.setData({ aiLoadingText: text }); } }); // 5. 处理结果 if (result.status === 'blocked') { wx.showToast({ title: result.message, icon: 'none', duration: 2000 }); this.setData({ isAiLoading: false }); return; } // 6. 生成整体趋势 const spreadStory = generateSpreadStory(drawnCards); // 7. 更新数据 this.setData({ aiResult: result.data, spreadStory: spreadStory, isAiLoading: false }); // 8. 如果是 fallback,显示提示 if (result.status === 'fallback' && result.error) { wx.showToast({ title: 'AI 解读失败,已使用备用解读', icon: 'none', duration: 3000 }); } }, 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 }); } })