(function () { var VALID_TABS = ['cp', 'brother', 'sister']; var LAYER_BASE = './assets/yumi/layers/'; var THEMES = { cp: { bg: 'bg-cp.png', tab: 'tab-cp.png', card: 'card-cp.png', center: 'center-cp.png', level: 'level-cp.png', }, brother: { bg: 'bg-brother.png', tab: 'tab-brother.png', card: 'card-brother.png', center: 'center-brother.png', level: 'level-brother.png', }, sister: { bg: 'bg-sister.png', tab: 'tab-sister.png', card: 'card-sister.png', center: 'center-sister.png', level: 'level-sister.png', }, }; var DEFAULT_RELATION = { days: 999, togetherDays: 4, currentLevel: 1, nextLevel: 1, currentExp: 1000, nextExp: 200000, neededExp: 888, nextExpLevel: 2, progressPercent: 22.3, me: { avatar: '' }, partner: { avatar: '' }, }; var page = document.getElementById('yumiCpPage'); var backButton = document.getElementById('yumiBackButton'); var helpButton = document.getElementById('yumiHelpButton'); var tabButtons = Array.prototype.slice.call( document.querySelectorAll('.yumi-tab-button[data-tab]') ); var bgLayer = document.getElementById('yumiBgLayer'); var tabJewel = document.getElementById('yumiTabJewel'); var cardSkin = document.getElementById('yumiCardSkin'); var centerArt = document.getElementById('yumiCenterArt'); var levelBgs = Array.prototype.slice.call( document.querySelectorAll('.yumi-level-bg') ); var leftAvatar = document.getElementById('yumiLeftAvatar'); var rightAvatar = document.getElementById('yumiRightAvatar'); var daysValue = document.getElementById('yumiDaysValue'); var expAway = document.getElementById('yumiExpAway'); var currentLevel = document.getElementById('yumiCurrentLevel'); var nextLevel = document.getElementById('yumiNextLevel'); var progressFill = document.getElementById('yumiProgressFill'); var progressText = document.getElementById('yumiProgressText'); var timeText = document.getElementById('yumiTimeText'); var state = { activeTab: normalizeTab(readParam('tab')) || 'cp', data: clone(DEFAULT_RELATION), }; function clone(value) { return JSON.parse(JSON.stringify(value)); } function readParam(name) { var params = new URLSearchParams(window.location.search || ''); return params.get(name); } function normalizeTab(value) { var tab = String(value || '') .trim() .toLowerCase(); return VALID_TABS.indexOf(tab) >= 0 ? tab : ''; } function readNumber(value, fallback) { var number = Number(value); return Number.isFinite(number) ? number : fallback || 0; } function t(key, fallback) { if (window.HyAppI18n && window.HyAppI18n.t) { return window.HyAppI18n.t(key, fallback); } return fallback || key; } function formatNumber(value) { var number = Math.max(0, Math.floor(readNumber(value, 0))); return String(number).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function asset(name) { return LAYER_BASE + name; } function syncURL(tab) { if (!window.history || !window.history.replaceState) return; var url = new URL(window.location.href); url.searchParams.set('tab', tab); // tab 是当前视觉主题状态;replaceState 让刷新可复现,同时不把每次切换压入 App WebView 返回栈。 window.history.replaceState(null, '', url.toString()); } function setImage(node, fileName) { if (!node) return; var nextSrc = asset(fileName); if (node.getAttribute('src') !== nextSrc) node.setAttribute('src', nextSrc); } function renderAvatar(container, url) { if (!container) return; container.innerHTML = ''; if (!url) return; var image = document.createElement('img'); image.src = url; image.alt = ''; container.appendChild(image); } function normalizedRelation(raw) { var data = raw || {}; return { days: readNumber(data.days, DEFAULT_RELATION.days), togetherDays: readNumber( data.togetherDays || data.together_days, DEFAULT_RELATION.togetherDays ), currentLevel: readNumber( data.currentLevel || data.current_level || data.level, 1 ), nextLevel: readNumber(data.nextLevel || data.next_level, 1), currentExp: readNumber( data.currentExp || data.current_exp, DEFAULT_RELATION.currentExp ), nextExp: readNumber( data.nextExp || data.next_exp, DEFAULT_RELATION.nextExp ), neededExp: readNumber( data.neededExp || data.needed_exp, DEFAULT_RELATION.neededExp ), nextExpLevel: readNumber( data.nextExpLevel || data.next_exp_level, DEFAULT_RELATION.nextExpLevel ), progressPercent: Math.max( 0, Math.min( 100, readNumber( data.progressPercent || data.progress_percent, DEFAULT_RELATION.progressPercent ) ) ), me: { avatar: String( (data.me && data.me.avatar) || data.me_avatar || '' ), }, partner: { avatar: String( (data.partner && data.partner.avatar) || data.partner_avatar || '' ), }, }; } function renderCopy() { var data = state.data; daysValue.textContent = formatNumber(data.days); expAway.textContent = t( 'cpSpace.expAway', '{exp} Exp away from Lv. {level}' ) .replace('{exp}', formatNumber(data.neededExp)) .replace('{level}', formatNumber(data.nextExpLevel)); currentLevel.textContent = t( 'cpSpace.levelValue', 'Lv.{level}' ).replace('{level}', formatNumber(data.currentLevel)); nextLevel.textContent = t('cpSpace.levelValue', 'Lv.{level}').replace( '{level}', formatNumber(data.nextLevel) ); progressText.textContent = [ formatNumber(data.currentExp), formatNumber(data.nextExp), ].join('/'); timeText.textContent = t( 'cpSpace.timeTogether', 'Time spent together: {days} days' ).replace('{days}', formatNumber(data.togetherDays)); progressFill.style.width = Math.max(1, Math.min(233, (data.progressPercent / 100) * 233)) + 'px'; renderAvatar(leftAvatar, data.me.avatar); renderAvatar(rightAvatar, data.partner.avatar); } function setActiveTab(tab, options) { var nextTab = normalizeTab(tab) || 'cp'; var theme = THEMES[nextTab]; state.activeTab = nextTab; page.setAttribute('data-tab', nextTab); setImage(bgLayer, theme.bg); setImage(tabJewel, theme.tab); setImage(cardSkin, theme.card); setImage(centerArt, theme.center); levelBgs.forEach(function (node) { setImage(node, theme.level); }); tabButtons.forEach(function (button) { var selected = button.getAttribute('data-tab') === nextTab; button.setAttribute('aria-selected', selected ? 'true' : 'false'); }); renderCopy(); if (!options || options.syncURL !== false) syncURL(nextTab); } function applyExternalData(payload) { var data = payload || {}; var tabData = data[state.activeTab] || data.relation || data; state.data = normalizedRelation(tabData); renderCopy(); } function bindEvents() { tabButtons.forEach(function (button) { button.addEventListener('click', function () { setActiveTab(button.getAttribute('data-tab')); }); }); if (backButton) { backButton.addEventListener('click', function () { if (window.HyAppBridge && window.HyAppBridge.back) { window.HyAppBridge.back(); return; } // 无客户端 bridge 的浏览器验收环境仍需保留真实返回热区。 window.history.back(); }); } if (helpButton) { helpButton.addEventListener('click', function () { if (window.HyAppBridge && window.HyAppBridge.post) { window.HyAppBridge.post('cpSpaceHelpTap', { page: 'yumi-cp-space', }); } }); } window.addEventListener('hyapp:i18n-ready', renderCopy); window.addEventListener('hyapp:cp-space-yumi-data', function (event) { applyExternalData(event.detail || {}); }); } bindEvents(); if (window.HyAppCPSpaceYumiData) applyExternalData(window.HyAppCPSpaceYumiData); setActiveTab(state.activeTab, { syncURL: false }); if (window.HyAppBridge && window.HyAppBridge.ready) { // ready 只声明页面能力;真实关系数据由客户端事件或网关注入,避免 H5 自行判断关系状态。 window.HyAppBridge.ready({ page: 'yumi-cp-space' }); } })();