家族树
🏛️ 家族
👤 我的
📦 材料
🔔 消息
📢
暂无动态,快来浇水祭拜吧~
⚡️快捷入口
💧浇水
🌱施肥
🙏祭拜
💎功德
👑 —
加载中...
5,234 / 20,000进化进度
👥128,456
🔥今日 45,231
🏛️族池 0

🏮 宗族链 · ClanChain

传承宗族文化,凝聚族人力量
每个姓氏一个家族,加入你的家族宗祠

🌳 家族宗祠系统
- 一个姓氏一个家族,先到先得
- 族长管理,长老议事,族人共建
- 祭祖敬宗,传承文化
📜 徽章收藏系统
- 9种珍贵徽章等你收集
- 功德积累,徽章掉落
- 材料合成,创造传奇
💎 功德系统
- 浇水、祭拜获取功德
- 每日上限 200 点
- 功德可以兑换材料、徽章
📷

用户名

UID: -

💎 个人功德
0
今日 0/180
🏛️ 家族功德池
0
家族公共资源
Pi 余额 0.00 π
所在家族未加入
我的角色-
注册时间-
最后活跃刚刚
📦 材料仓库
🍃
木叶
0
🕯️
香火
0
📜
文墨
0
❤️
连心
🔮
玄珠
0
天裂片
0
💎
天工石
0
🏅 我的徽章

李氏宗祠

陇西堂 · 全球李氏宗亲联合

李大明
⚙️ 管理
0 / 200
🌱 萌芽期 · 第1阶段
⚙️ 族长管理
📢 家族公告
🔗 邀请族人
👑 任命长老
🎖️ 颁发徽章
📜 发起提案
🧧 红包

🏆 功德榜

全部 >
// 后端 API 地址(本地测试用 localhost:3000,部署时改回 pijp.space) const API_BASE = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ? 'http://localhost:3000' : 'https://www.pijp.space'; // 挂载到 window 供其他脚本使用 window.API_BASE = API_BASE; // ===== 本地开发模式:自动绕过登录(仅 localhost) ===== const _isLocalDev = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); if (_isLocalDev && !localStorage.getItem('clanchain_piuser')) { // 注入测试用 Pi 用户(uid 固定,便于本地数据持久化) const _devPiUser = { uid: 'dev_local_user_001', username: 'LocalDevUser', accessToken: 'dev_token' }; localStorage.setItem('clanchain_piuser', JSON.stringify(_devPiUser)); window.currentPiUser = _devPiUser; console.log('[本地开发] 已自动注入测试 Pi 用户:', _devPiUser.username); } // 恢复 Pi 用户信息(从 localStorage) const savedPiUser = localStorage.getItem('clanchain_piuser'); if (savedPiUser) { try { window.currentPiUser = JSON.parse(savedPiUser); console.log('[启动] 已恢复 Pi 用户信息:', window.currentPiUser.username); // 自动隐藏登录覆盖层(DOM 可能还没 ready,用 DOMContentLoaded 保底) function _hideLoginOverlay() { const loginOverlay = document.getElementById('loginOverlay'); if (loginOverlay) loginOverlay.style.display = 'none'; } if (document.getElementById('loginOverlay')) { _hideLoginOverlay(); } else { document.addEventListener('DOMContentLoaded', _hideLoginOverlay); } } catch (e) { console.warn('[启动] 恢复 Pi 用户信息失败:', e); localStorage.removeItem('clanchain_piuser'); } } // ===== Pi Browser 环境:自动登录(如果 localStorage 没有 Pi 用户) ===== // 注意:Pi SDK 已在 window.onload 中初始化,此处直接使用 document.addEventListener('DOMContentLoaded', async () => { // 等待一小段时间,确保 game.js 已初始化 await new Promise(resolve => setTimeout(resolve, 200)); console.log('[自动登录检查] typeof Pi:', typeof Pi); console.log('[自动登录检查] currentPiUser:', window.currentPiUser); console.log('[自动登录检查] localStorage clanchain_piuser:', localStorage.getItem('clanchain_piuser')); console.log('[自动登录检查] syncPlayerAndFamilyFromBackend exists:', typeof window.syncPlayerAndFamilyFromBackend === 'function'); // 延迟一下,确保 window.onload 已完成(Pi SDK 已初始化) await new Promise(resolve => setTimeout(resolve, 300)); if (typeof Pi !== 'undefined' && !window.currentPiUser) { console.log('[Pi Browser] 检测到 Pi SDK,且当前未登录,开始自动登录...'); try { const auth = await Pi.authenticate(['payments']); window.currentPiUser = { uid: auth.user.uid, username: auth.user.username, accessToken: auth.accessToken }; localStorage.setItem('clanchain_piuser', JSON.stringify(window.currentPiUser)); console.log('[Pi Browser] 自动登录成功:', window.currentPiUser.username, window.currentPiUser.uid); // 登录成功后,从后端同步数据 if (typeof window.syncPlayerAndFamilyFromBackend === 'function') { console.log('[Pi Browser] 开始调用 syncPlayerAndFamilyFromBackend...'); await window.syncPlayerAndFamilyFromBackend(); console.log('[Pi Browser] 后端同步完成,currentFamily:', window.currentFamily); } } catch (e) { console.error('[Pi Browser] 自动登录失败:', e); } } else { console.log('[自动登录检查] 跳过自动登录:', { hasPiSDK: typeof Pi !== 'undefined', hasUser: !!window.currentPiUser }); } }); // Pi 用户信息(登录后挂载到 window.currentPiUser) // 页面加载时隐藏游戏主界面,等待登录 (暂时禁用) /*document.addEventListener('DOMContentLoaded', function() { // 隐藏所有页面内容,等登录后再显示 const familyContent = document.getElementById('family-page-content'); const familyTreeLayer = document.querySelector('.family-tree-layer'); const badgesLayer = document.getElementById('badgesLayer'); if (familyContent) familyContent.style.display = 'none'; if (familyTreeLayer) familyTreeLayer.style.display = 'none'; if (badgesLayer) badgesLayer.style.display = 'none'; });*/ // 记录已处理过的 paymentId(防止重复 approve) const _handledPayments = new Set(); // Pi 登录函数 async function doPiLogin() { const statusEl = document.getElementById('loginStatus'); const reAuthHint = document.getElementById('reAuthHint'); const loginBtn = document.getElementById('piLoginBtn'); // 调试:确认点击事件触发 statusEl.textContent = '⏳ 正在调用 Pi 登录...'; loginBtn.textContent = '登录中...'; loginBtn.disabled = true; if (reAuthHint) reAuthHint.style.display = 'none'; try { const auth = await Pi.authenticate(['payments'], async (incomplete) => { if (!incomplete) return; const pid = incomplete.identifier; if (incomplete.transaction && incomplete.transaction.txid) { // 已上链,补完 complete try { await fetch(`${API_BASE}/api/pi/payment/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId: pid, txid: incomplete.transaction.txid, uid: incomplete.user_uid }) }); } catch(e) { console.error('补完支付失败:', e); } } else { // 未上链,重新 approve(去重) if (_handledPayments.has(pid)) return; _handledPayments.add(pid); try { await fetch(`${API_BASE}/api/pi/payment/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId: pid, uid: incomplete.user_uid, offering: incomplete.metadata?.type || 'unknown' }) }); } catch(e) { console.error('重新审批失败:', e); } } }); // 登录成功 window.currentPiUser = { uid: auth.user.uid, username: auth.user.username, accessToken: auth.accessToken }; console.log('Pi 登录成功:', window.currentPiUser.username, window.currentPiUser.uid); // 保存 Pi 用户信息到 localStorage localStorage.setItem('clanchain_piuser', JSON.stringify(window.currentPiUser)); console.log('[登录] Pi 用户信息已保存到 localStorage'); // 隐藏登录覆盖层 document.getElementById('loginOverlay').style.display = 'none'; // 显示游戏界面 const familyContent = document.getElementById('family-page-content'); const familyTreeLayer = document.querySelector('.family-tree-layer'); const badgesLayer = document.getElementById('badgesLayer'); if (familyContent) familyContent.style.display = ''; // ===== 登录成功后,从后端同步玩家+家族数据并刷新 UI ===== console.log('[登录] 开始从后端同步数据...'); if (typeof window.syncPlayerAndFamilyFromBackend === 'function') { await window.syncPlayerAndFamilyFromBackend(); console.log('[登录] 后端同步完成,刷新 UI...'); // 同步完成后,根据 currentFamily 决定进入哪个页面 const userData = window.userData || {}; const currentFamily = window.currentFamily || null; if (currentFamily) { if (typeof window.renderBadges === 'function') window.renderBadges(); if (typeof window.updateStats === 'function') window.updateStats(); if (typeof window.renderTreeRedPacketButton === 'function') window.renderTreeRedPacketButton(); if (typeof window.startRedPacketPolling === 'function') window.startRedPacketPolling(); if (typeof window.switchPage === 'function') window.switchPage(window.PAGE?.HOME); } else { if (typeof window.switchPage === 'function') window.switchPage(window.PAGE?.LANDING); } } if (familyTreeLayer) familyTreeLayer.style.display = ''; if (badgesLayer) badgesLayer.style.display = ''; // ===== Phase 3: 从后端加载玩家档案 ===== try { if (typeof ClanPlayer !== 'undefined') { let remoteProfile = await ClanPlayer.fetchProfile(window.currentPiUser.uid, window.currentPiUser.username); // 检查是否是新用户(后端返回空数据) let isNewUser = false; if (remoteProfile) { const totalMaterials = Object.values(remoteProfile.materials || {}).reduce((a, b) => a + b, 0); const totalBadges = Object.values(remoteProfile.badges || {}).reduce((a, b) => a + b, 0); if (remoteProfile.virtue === 0 && totalMaterials === 0 && totalBadges === 0) { isNewUser = true; console.log('[登录] 检测到新用户,准备上传本地数据'); } } if (isNewUser && remoteProfile) { // 新用户:先将本地数据上传到后端 console.log('[登录] 上传本地数据到后端...'); await ClanPlayer.syncToBackend(window.currentPiUser.uid, userData); // 重新从后端获取档案(此时后端已有数据) remoteProfile = await ClanPlayer.fetchProfile(window.currentPiUser.uid, window.currentPiUser.username); } if (remoteProfile && typeof userData !== 'undefined') { const merged = ClanPlayer.mergeProfile(userData, remoteProfile); // 只更新 UID 和用户名,保留其他本地数据 merged.uid = window.currentPiUser.uid; merged.username = window.currentPiUser.username; Object.assign(userData, merged); if (typeof saveData === 'function') saveData(); console.log('[Phase3] 玩家档案已从后端加载并合并'); // 如果后端有家族记录,加载家族详情 if (merged.familyId && typeof ClanFamily !== 'undefined') { const familyDetail = await ClanFamily.fetchDetail(merged.familyId); if (familyDetail && typeof familiesDB !== 'undefined') { // 后端家族结构需转换为本地格式 if (familyDetail.treeData === undefined) { familyDetail.treeData = { memberCount: familyDetail.memberCount || 1, growthValue: familyDetail.growthValue || 0, totalVirtue: familyDetail.virtuePool || 0 }; } familiesDB[merged.familyId] = familyDetail; if (typeof saveData === 'function') saveData(); } } } // 刷新游戏界面 if (typeof initData === 'function') initData(); else if (typeof updateStats === 'function') updateStats(); } } catch(profileErr) { console.warn('[Phase3] 档案加载失败,继续使用本地数据:', profileErr); } // 更新个人信息显示 const nicknameEl = document.getElementById('personal-nickname'); const uidEl = document.getElementById('personal-uid'); if (nicknameEl) nicknameEl.textContent = auth.user.username || '族人'; if (uidEl) uidEl.textContent = 'UID: ' + auth.user.uid; // 尝试从后端加载 Pi 余额 try { const res = await fetch(`${API_BASE}/api/tk/balance/${window.currentPiUser.uid}`); const data = await res.json(); const piDisplay = document.getElementById('personal-pi'); if (piDisplay) piDisplay.textContent = (data.balance || 0).toLocaleString(); const claimBalance = document.getElementById('claim-pi-balance'); if (claimBalance) claimBalance.textContent = (data.balance || 10) + ' Pi'; } catch(e) { console.warn('获取余额失败(后端不可用时使用本地数据)', e); } } catch (err) { console.error('Pi 登录失败:', err); const errorMsg = err.message || err.toString() || ''; // 检测权限错误 if (errorMsg.includes('payments') || errorMsg.includes('scope')) { statusEl.textContent = '登录失败:缺少支付权限'; if (reAuthHint) reAuthHint.style.display = 'block'; loginBtn.textContent = '🔄 重新登录'; } else if (errorMsg.includes('not in Pi Browser') || errorMsg.includes('browser')) { statusEl.textContent = '请在 Pi Browser 中打开本应用'; loginBtn.textContent = '🔐 用 Pi 账号登录'; } else { statusEl.textContent = '登录失败,请重试'; loginBtn.textContent = '🔐 用 Pi 账号登录'; } loginBtn.disabled = false; } } // 绑定登录按钮事件 - 根据文档加载状态决定执行方式 function bindLoginButton() { const piLoginBtn = document.getElementById('piLoginBtn'); if (piLoginBtn) { piLoginBtn.onclick = doPiLogin; // 在页面上显示调试信息(方便手机端查看) setTimeout(() => { const statusEl = document.getElementById('loginStatus'); if (statusEl) statusEl.textContent = '✓ 登录按钮已绑定,可以点击登录'; }, 500); } else { setTimeout(() => { const statusEl = document.getElementById('loginStatus'); if (statusEl) statusEl.textContent = '❌ 错误:找不到登录按钮'; }, 500); } } // 如果 DOM 已加载,直接绑定;否则等待 DOMContentLoaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bindLoginButton); } else { bindLoginButton(); } /* ===================================================== Pi 支付 - 认领家族(0.1 Pi) ===================================================== */ window.doPiPayForClaim = async function(familyName) { // Pi SDK 未加载(非 Pi Browser 环境)→ 本地开发降级 if (typeof Pi === 'undefined') { console.warn('[doPiPayForClaim] Pi SDK 未加载,本地开发模式'); // 开发模式下,调用后端接口创建家族 try { const API = window.ClanAPI; if (API) { // 先重置离线模式,强制尝试连接后端 API.resetOffline(); if (!API.isOffline()) { // 优先使用 userData.uid(本地数据),getPiUser 可能返回 null const uid = window.userData?.uid || 'user_pi_uid'; const username = window.userData?.nickname || '用户名'; const claimResult = await API.post('/api/family/claim', { uid: uid, username: username, surname: familyName, familyName: familyName + '氏宗祠', intro: '', paymentId: 'dev_mode' }); if (claimResult && claimResult.success) { console.log('[开发模式] 后端创建家族成功:', claimResult.familyId); window._pendingClaimFamilyId = claimResult.familyId; return true; } else { console.error('[开发模式] 后端创建家族失败:', claimResult?.message); // 即使后端失败,也返回 true 允许本地创建(降级模式) console.warn('[开发模式] 降级:仅创建本地家族'); return true; } } else { // 纯离线模式 console.log('[离线模式] 仅创建本地家族'); return true; } } else { // 纯离线模式(ClanAPI 不存在) console.log('[离线模式] 仅创建本地家族'); return true; } } catch (e) { console.error('[开发模式] 调用后端接口失败:', e); alert(`调用后端接口失败: ${e.message}`); return false; } } // if (!currentPiUser) { alert('请先登录 Pi 账号'); return false; } try { return await new Promise((resolve) => { Pi.createPayment({ amount: 0.1, memo: `认领家族:${familyName}氏宗祠`, metadata: { type: 'claim_family', familyName: familyName, uid: window.currentPiUser ? window.currentPiUser.uid : 'dev' } }, { onReadyForServerApproval: async (paymentId) => { try { await fetch(`${API_BASE}/api/pi/payment/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, uid: window.currentPiUser ? window.currentPiUser.uid : 'dev', offering: 'claim_family', familyName }) }); } catch(e) { console.warn('认领审批请求失败:', e); } }, onReadyForServerCompletion: async (paymentId, txid) => { // 先通知 Pi 后端支付完成 try { await fetch(`${API_BASE}/api/pi/payment/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, txid, uid: window.currentPiUser ? window.currentPiUser.uid : 'dev' }) }); } catch(e) { console.warn('认领完成请求失败:', e); } // ===== Phase 3.4: 写入家族到后端 ===== if (window.currentPiUser && typeof ClanFamily !== 'undefined') { try { const claimResult = await ClanFamily.claimToBackend({ uid: window.currentPiUser.uid, username: window.currentPiUser.username, surname: familyName, familyName: familyName + '氏宗祠', paymentId: paymentId }); if (claimResult && claimResult.success) { console.log('[Phase3.4] 家族认领写入后端成功:', claimResult.familyId); // 将后端分配的 familyId 写入本地 window._pendingClaimFamilyId = claimResult.familyId; } else if (claimResult && !claimResult.offline) { console.warn('[Phase3.4] 后端认领返回失败:', claimResult.message); } } catch(claimErr) { console.warn('[Phase3.4] 后端认领写入异常:', claimErr); } } resolve(true); }, onCancel: () => resolve(false), onError: (err) => { console.error('认领支付错误:', err); resolve(false); } }); }); } catch(e) { console.error('Pi 支付异常:', e); return false; } }; /* ===================================================== Pi 支付 - 供品祭拜(可扩展) ===================================================== */ window.doPiPayForOffering = async function(amount, offeringName, metadata) { // Pi SDK 未加载(非 Pi Browser 环境)→ 本地开发降级 if (typeof Pi === 'undefined') { console.warn('[doPiPayForOffering] Pi SDK 未加载,本地开发模式直接放行'); return true; } // if (!currentPiUser) { alert('请先登录 Pi 账号'); return false; } try { return await new Promise((resolve) => { Pi.createPayment({ amount: amount, memo: `供奉:${offeringName}`, metadata: metadata || { type: 'offering', name: offeringName } }, { onReadyForServerApproval: async (paymentId) => { try { await fetch(`${API_BASE}/api/pi/payment/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, uid: window.currentPiUser.uid, offering: metadata?.type || 'offering' }) }); } catch(e) { console.warn('供品审批失败:', e); } }, onReadyForServerCompletion: async (paymentId, txid) => { try { await fetch(`${API_BASE}/api/pi/payment/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, txid, uid: window.currentPiUser.uid }) }); } catch(e) { console.warn('供品完成请求失败:', e); } resolve(true); }, onCancel: () => resolve(false), onError: (err) => { console.error('供品支付错误:', err); resolve(false); } }); }); } catch(e) { console.error('Pi 支付异常:', e); return false; } }; /* ===================================================== Pi 支付 - 充值功能 ===================================================== */ window.doPiPayForTopUp = async function(amount) { const debugInfo = document.getElementById('debug-info'); const log = (msg) => { console.log(msg); if (debugInfo) { debugInfo.style.display = 'block'; debugInfo.innerHTML += msg + '
'; } }; log('[doPiPayForTopUp] ========== 开始充值 =========='); log('[doPiPayForTopUp] 充值金额: ' + amount); log('[doPiPayForTopUp] typeof Pi: ' + typeof Pi); log('[doPiPayForTopUp] window.currentPiUser: ' + JSON.stringify(window.currentPiUser)); // Pi SDK 未加载(非 Pi Browser 环境)→ 本地开发降级 if (typeof Pi === 'undefined') { log('[doPiPayForTopUp] Pi SDK 未加载,本地开发模式直接放行'); return true; } // 检查用户登录 if (!window.currentPiUser) { log('[doPiPayForTopUp] 用户未登录,window.currentPiUser 为空'); alert('请先登录 Pi 账号'); return false; } // 关键修复:重新请求 payments 权限 log('[doPiPayForTopUp] 重新请求 payments 权限...'); try { const auth = await Pi.authenticate(['payments']); window.currentPiUser = { uid: auth.user.uid, username: auth.user.username, accessToken: auth.accessToken }; localStorage.setItem('clanchain_piuser', JSON.stringify(window.currentPiUser)); log('[doPiPayForTopUp] 权限请求成功,已更新 accessToken'); } catch (e) { log('[doPiPayForTopUp] 权限请求失败: ' + e.message); alert('无法获取支付权限: ' + e.message); return false; } log('[doPiPayForTopUp] 用户已登录,准备调用 Pi.createPayment'); try { return await new Promise((resolve) => { log('[doPiPayForTopUp] 调用 Pi.createPayment...'); Pi.createPayment({ amount: amount, memo: `ClanChain 充值 ${amount} Pi`, metadata: { type: 'topup', uid: window.currentPiUser.uid } }, { onReadyForServerApproval: async (paymentId) => { log('[doPiPayForTopUp] onReadyForServerApproval: ' + paymentId); try { await fetch(`${API_BASE}/api/pi/payment/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, uid: window.currentPiUser.uid, offering: 'topup', amount: amount }) }); } catch (e) { log('[doPiPayForTopUp] 充值审批失败: ' + e.message); } }, onReadyForServerCompletion: async (paymentId, txid) => { log('[doPiPayForTopUp] onReadyForServerCompletion: ' + paymentId + ', ' + txid); try { const r = await fetch(`${API_BASE}/api/pi/payment/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId, txid, uid: window.currentPiUser.uid }) }); const result = await r.json(); log('[doPiPayForTopUp] complete 接口返回: ' + JSON.stringify(result)); } catch (e) { log('[doPiPayForTopUp] 完成请求失败: ' + e.message); } log('[doPiPayForTopUp] 支付成功,resolve(true),准备等待 2 秒...'); // 等待一下,确保后端处理完成 await new Promise(resolve => setTimeout(resolve, 2000)); log('[doPiPayForTopUp] 等待结束,resolve(true)'); resolve(true); }, onCancel: () => { log('[doPiPayForTopUp] onCancel 被触发'); resolve(false); }, onError: (err) => { log('[doPiPayForTopUp] onError 被触发: ' + JSON.stringify(err)); resolve(false); } }); }); } catch (e) { log('[doPiPayForTopUp] Pi 支付异常: ' + e.message); return false; } }; /* ===================================================== 获取当前 Pi 用户 ===================================================== */ window.getPiUser = function() { return window.currentPiUser; };