const app = getApp(); Page({ data: { searchKeyword: '', searchResults: [], center: null, reversedGenerations: [], siblings: [], peers: [], peerScrollLeft: 0, children: [], childrenScrollLeft: 0, loading: false, showDetail: false, detailMember: null }, onLoad: function () {}, onShow: function () { if (typeof this.getTabBar === 'function' && this.getTabBar()) { this.getTabBar().setData({ selected: 1 }); } }, onSearchInput: function (e) { this.setData({ searchKeyword: e.detail.value }); }, searchMember: function () { const kw = this.data.searchKeyword.trim(); if (!kw) { wx.showToast({ title: '请输入姓名', icon: 'none' }); return; } if (!app.globalData.isLoggedIn) { wx.showModal({ title: '请先登录', showCancel: false, success: () => { wx.navigateTo({ url: '/pages/login/login' }); } }); return; } const searchUrl = `${app.globalData.baseUrl}/api/members/search`; console.log('[Search] URL:', searchUrl); console.log('[Search] keyword:', kw); console.log('[Search] isLoggedIn:', app.globalData.isLoggedIn); console.log('[Search] token:', app.globalData.token ? app.globalData.token.substring(0, 10) + '...' : 'null'); wx.request({ url: searchUrl, method: 'GET', data: { keyword: kw }, header: { 'Authorization': `Bearer ${app.globalData.token}` }, success: (res) => { console.log('[Search] statusCode:', res.statusCode); console.log('[Search] data:', JSON.stringify(res.data)); if (res.data && res.data.success) { const list = res.data.data || []; if (list.length === 0) { wx.showToast({ title: '未找到相关成员', icon: 'none' }); } else if (list.length === 1) { // 唯一结果,直接展示世系 this.setData({ searchResults: [], searchKeyword: list[0].name, loading: true, center: null, reversedGenerations: [], siblings: [], children: [] }); this.loadLineage(list[0].id); } else { this.setData({ searchResults: list }); } } else { wx.showToast({ title: (res.data && res.data.message) || '搜索失败', icon: 'none' }); } }, fail: (err) => { console.error('[Search] 网络请求失败:', JSON.stringify(err)); wx.showToast({ title: '网络失败:' + (err.errMsg || ''), icon: 'none', duration: 3000 }); } }); }, selectMember: function (e) { const member = e.currentTarget.dataset.member; this.setData({ searchResults: [], searchKeyword: member.name, loading: true, center: null, reversedGenerations: [], siblings: [], children: [] }); this.loadLineage(member.id); }, loadLineage: function (memberId) { const lineageUrl = `${app.globalData.baseUrl}/api/lineage/${memberId}`; console.log('[Lineage] 请求URL:', lineageUrl); console.log('[Lineage] token:', app.globalData.token ? app.globalData.token.substring(0, 10) + '...' : 'null'); wx.showLoading({ title: '加载中...' }); wx.request({ url: lineageUrl, method: 'GET', header: { 'Authorization': `Bearer ${app.globalData.token}` }, success: (res) => { wx.hideLoading(); console.log('[Lineage] statusCode:', res.statusCode); console.log('[Lineage] success:', res.data && res.data.success); console.log('[Lineage] data keys:', res.data && res.data.data ? Object.keys(res.data.data) : 'none'); if (res.data && res.data.success) { const d = res.data.data; console.log('[Lineage] generations:', (d.generations || []).length); console.log('[Lineage] siblings:', (d.siblings || []).length); console.log('[Lineage] children:', (d.children || []).length); console.log('[Lineage] center:', JSON.stringify(d.center)); const center = d.center || null; const siblings = d.siblings || []; // 计算 rpx→px 比率,用于 scroll-left const sysInfo = wx.getSystemInfoSync(); const rpxRatio = sysInfo.windowWidth / 750; // 卡片宽200rpx + 间距16rpx = 216rpx/卡 // padding两端各275rpx,当祖先在index=K时 scrollLeft=K*216*ratio const cardUnit = 216 * rpxRatio; // 每代:祖先 + 兄弟合并排序,计算 scrollLeft const rawGenerations = (d.generations || []).slice().reverse(); const reversedGenerations = rawGenerations.map(gen => { const allPeers = [ { ...gen.ancestor, isAncestor: true }, ...(gen.siblings || []).map(s => ({ ...s, isAncestor: false })) ].sort((a, b) => { const oa = a.child_order || 1; const ob = b.child_order || 1; return oa !== ob ? oa - ob : a.id - b.id; }); const ancIdx = allPeers.findIndex(p => p.isAncestor); return { ...gen, allPeers, scrollLeft: ancIdx * cardUnit }; }); // 查询人物行:center + 兄弟合并排序,计算 scrollLeft let peers = []; let peerScrollLeft = 0; if (center) { const allPeers = [ { ...center, isCenter: true }, ...siblings.map(s => ({ ...s, isCenter: false })) ].sort((a, b) => { const oa = a.child_order || 1; const ob = b.child_order || 1; return oa !== ob ? oa - ob : a.id - b.id; }); peers = allPeers; const cIdx = allPeers.findIndex(p => p.isCenter); peerScrollLeft = cIdx * cardUnit; } // 子女行居中:将中间那个子女滚到屏幕中央 const childrenArr = d.children || []; const midChildIdx = Math.floor(childrenArr.length / 2); const childrenScrollLeft = midChildIdx * cardUnit; this.setData({ center, reversedGenerations, siblings, children: childrenArr, childrenScrollLeft, peers, peerScrollLeft, loading: false }); } else { console.error('[Lineage] 失败:', JSON.stringify(res.data)); wx.showToast({ title: (res.data && res.data.message) || '加载失败', icon: 'none' }); this.setData({ loading: false }); } }, fail: (err) => { wx.hideLoading(); console.error('[Lineage] 网络失败:', JSON.stringify(err)); wx.showToast({ title: '世系加载失败:' + (err.errMsg || ''), icon: 'none', duration: 3000 }); this.setData({ loading: false }); } }); }, clearSearch: function () { this.setData({ searchKeyword: '', searchResults: [], center: null, reversedGenerations: [], siblings: [], peers: [], peerScrollLeft: 0, children: [], childrenScrollLeft: 0, loading: false }); }, viewDetail: function (e) { const member = e.currentTarget.dataset.member; if (!member || !member.id) return; wx.request({ url: `${app.globalData.baseUrl}/api/members/${member.id}`, method: 'GET', header: { 'Authorization': `Bearer ${app.globalData.token}` }, success: (res) => { if (res.data && res.data.success) { this.setData({ showDetail: true, detailMember: res.data.data }); } } }); }, switchCenter: function () { if (!this.data.detailMember) return; const m = this.data.detailMember; this.setData({ showDetail: false, detailMember: null, searchKeyword: m.name, loading: true, center: null, reversedGenerations: [], siblings: [], children: [] }); this.loadLineage(m.id); }, closeDetail: function () { this.setData({ showDetail: false, detailMember: null }); }, stopProp: function () {} });