// 商店页面主逻辑 // 负责资源加载、搜索、分类筛选等功能 (function () { 'use strict'; class StoreView { constructor() { this.resources = []; this.filteredResources = []; this.currentCategory = ''; this.searchQuery = ''; this.itemTemplate = null; this.currentFps = 8; this.frameUrls = []; this.currentFrame = 0; this.itemAnimations = new Map(); // 存储每个 item 的动画数据 this.isUserLoggedIn = false; // 本地登录状态缓存 this.init(); } async init() { // 加载 item 模板 await this.loadItemTemplate(); // 绑定事件 this.bindEvents(); // 加载分类 await this.loadCategories(); // 加载资源 await this.loadResources(); } async loadItemTemplate() { try { // 使用相对于当前页面的路径 // store.html 位于 page/store/store.html,item.html 位于同一目录 const response = await fetch('./item.html'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const html = await response.text(); this.itemTemplate = html; } catch (error) { console.error('[StoreView] 加载模板失败:', error); // 如果相对路径失败,尝试绝对路径 try { const response = await fetch('/page/store/item.html'); if (response.ok) { const html = await response.text(); this.itemTemplate = html; } else { throw new Error('绝对路径也失败'); } } catch (fallbackError) { console.error('[StoreView] 备用路径也失败:', fallbackError); this.itemTemplate = '
加载失败
'; } } } async loadCategories() { try { const response = await fetch('http://localhost:3000/api/store/categories'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success && data.categories) { const categoryBar = document.getElementById('categoryBar'); if (categoryBar) { // 保留"全部"按钮,添加其他分类 const allButton = categoryBar.querySelector('.category-item[data-category=""]'); categoryBar.innerHTML = ''; if (allButton) { categoryBar.appendChild(allButton); } // 添加动态分类按钮 data.categories.forEach(category => { const button = document.createElement('button'); button.className = 'category-item'; button.dataset.category = category.name; // 使用文件夹名称作为分类名称 button.textContent = category.name; categoryBar.appendChild(button); }); } } } catch (error) { console.error('[StoreView] 加载分类失败:', error); // 如果加载失败,保持默认的"全部"按钮 } } bindEvents() { // 监听登录状态变化 window.addEventListener('message', (event) => { if (event.data && event.data.type === 'login-success' && event.data.user) { this.isUserLoggedIn = true; } else if (event.data && event.data.type === 'logout') { this.isUserLoggedIn = false; } }); // 搜索栏 const searchInput = document.getElementById('searchInput'); const searchButton = document.getElementById('searchButton'); if (searchInput) { searchInput.addEventListener('input', (e) => { this.searchQuery = e.target.value.trim(); this.filterResources(); }); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.filterResources(); } }); } if (searchButton) { searchButton.addEventListener('click', () => { this.filterResources(); }); } // 分类栏 const categoryItems = document.querySelectorAll('.category-item'); categoryItems.forEach(item => { item.addEventListener('click', () => { // 移除所有 active 类 categoryItems.forEach(i => i.classList.remove('active')); // 添加 active 类到当前项 item.classList.add('active'); // 更新当前分类 this.currentCategory = item.dataset.category || ''; // 重新加载资源 this.loadResources(); }); }); // 预览弹窗关闭 const previewClose = document.getElementById('previewClose'); const previewModal = document.getElementById('previewModal'); if (previewClose) { previewClose.addEventListener('click', () => { this.stopAnimation(); if (previewModal) { previewModal.style.display = 'none'; } }); } if (previewModal) { previewModal.addEventListener('click', (e) => { if (e.target === previewModal) { this.stopAnimation(); previewModal.style.display = 'none'; } }); } // 帧率滑块 const fpsSlider = document.getElementById('fpsSlider'); const fpsDisplay = document.getElementById('fpsDisplay'); if (fpsSlider && fpsDisplay) { fpsSlider.addEventListener('input', (e) => { const fps = parseInt(e.target.value); this.currentFps = fps; fpsDisplay.textContent = `${fps} FPS`; // 如果动画正在播放,重新启动以应用新帧率 if (this.animationInterval && this.frameUrls.length > 0) { const previewImage = document.getElementById('previewAnimationImage'); if (previewImage) { this.stopAnimation(); this.startAnimation(previewImage, this.frameUrls); } } }); } // 使用事件委托处理动态添加的按钮 const resourcesGrid = document.getElementById('resourcesGrid'); if (resourcesGrid) { // 购买按钮点击 resourcesGrid.addEventListener('click', (e) => { console.log('[StoreView] 点击事件触发,目标:', e.target); if (e.target.closest('.item-buy-button')) { const button = e.target.closest('.item-buy-button'); const path = button.dataset.resourcePath; console.log('[StoreView] 点击购买按钮,路径:', path); // 检查是否已登录 if (!this.isLoggedIn()) { console.log('[StoreView] 未登录,跳转登录页'); // 显示登录界面 if (window.parent !== window) { window.parent.postMessage({ type: 'navigation', page: 'login' }, '*'); } return; } console.log('[StoreView] 已登录,调用 handleBuy'); this.handleBuy(path); } }); } } async loadResources() { this.showLoading(true); this.hideEmpty(); try { const params = new URLSearchParams(); if (this.currentCategory) { params.append('category', this.currentCategory); } if (this.searchQuery) { params.append('search', this.searchQuery); } const response = await fetch(`http://localhost:3000/api/store/resources?${params.toString()}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success) { this.resources = data.resources || []; // 使用服务器返回的价格,如果没有则默认为0 this.resources.forEach(resource => { if (resource.points === undefined || resource.points === null) { resource.points = 0; } }); this.filteredResources = this.resources; this.renderResources(); } else { throw new Error(data.error || '加载资源失败'); } } catch (error) { console.error('[StoreView] 加载资源失败:', error); this.showGlobalAlert('加载资源失败: ' + error.message); this.resources = []; this.filteredResources = []; this.renderResources(); } finally { this.showLoading(false); } } filterResources() { // 重新加载资源(服务器端筛选) this.loadResources(); } renderResources() { const grid = document.getElementById('resourcesGrid'); if (!grid) return; if (this.filteredResources.length === 0) { grid.innerHTML = ''; this.showEmpty(); return; } this.hideEmpty(); // 计算列数 const columnWidth = 200; const gap = 16; const containerWidth = grid.offsetWidth || grid.clientWidth || window.innerWidth; const columnCount = Math.max(1, Math.floor((containerWidth + gap) / (columnWidth + gap))); // 创建列容器 grid.innerHTML = ''; const masonryContainer = document.createElement('div'); masonryContainer.className = 'resources-grid-masonry'; const columns = []; const columnHeights = []; for (let i = 0; i < columnCount; i++) { const column = document.createElement('div'); column.className = 'masonry-column'; masonryContainer.appendChild(column); columns.push(column); columnHeights.push(0); } grid.appendChild(masonryContainer); // 创建临时容器来测量项目高度 const tempContainer = document.createElement('div'); tempContainer.style.position = 'absolute'; tempContainer.style.visibility = 'hidden'; tempContainer.style.width = `${columnWidth}px`; tempContainer.style.top = '-9999px'; tempContainer.style.left = '-9999px'; document.body.appendChild(tempContainer); // 渲染所有项目到临时容器并测量 const itemsData = this.filteredResources.map(resource => { const itemHtml = this.renderItem(resource); tempContainer.innerHTML = itemHtml; const itemElement = tempContainer.querySelector('.store-item'); const height = itemElement ? itemElement.offsetHeight : 0; return { html: itemHtml, height }; }); // 将项目分配到最短的列 itemsData.forEach(({ html, height }) => { // 找到最短的列 const shortestColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); // 添加到最短的列 columns[shortestColumnIndex].insertAdjacentHTML('beforeend', html); // 更新列高度 columnHeights[shortestColumnIndex] += height + gap; }); // 清理临时容器 document.body.removeChild(tempContainer); // 为每个 item 绑定鼠标事件和 FPS 控制 const allItems = masonryContainer.querySelectorAll('.store-item'); allItems.forEach(item => { const category = item.dataset.category; const folder = item.dataset.folder; const previewImage = item.querySelector('.item-preview-image'); const fpsSlider = item.querySelector('.item-fps-slider'); const fpsDisplay = item.querySelector('.item-fps-display'); if (category && folder && previewImage) { // 存储当前 FPS(每个 item 独立) let currentFps = 8; // FPS 滑块事件 if (fpsSlider && fpsDisplay) { fpsSlider.addEventListener('input', (e) => { const fps = parseInt(e.target.value); currentFps = fps; fpsDisplay.textContent = `${fps} FPS`; // 如果动画正在播放,重新启动以应用新帧率 if (previewImage._animationInterval) { this.stopItemAnimation(previewImage); this.startItemAnimation(previewImage, category, folder, fps); } }); // 阻止事件冒泡,避免触发 item 的 mouseleave fpsSlider.addEventListener('mousedown', (e) => { e.stopPropagation(); }); } // 鼠标进入时播放动画 item.addEventListener('mouseenter', () => { this.startItemAnimation(previewImage, category, folder, currentFps); }); // 鼠标离开时停止动画 item.addEventListener('mouseleave', () => { this.stopItemAnimation(previewImage); }); } }); // 使用 resize observer 监听窗口大小变化 if (!this.resizeObserver) { this.resizeObserver = new ResizeObserver(() => { this.renderResources(); }); this.resizeObserver.observe(grid); } // 检查每个资源是否已存在(如果已登录),只更新按钮状态,不重新渲染 if (this.isLoggedIn()) { this.checkResourcesOwnership(this.filteredResources).then(() => { // 只更新按钮状态,不重新渲染整个列表 this.updateButtonStates(); }); } } // 更新单个资源的按钮状态 updateSingleButtonState(resourcePath) { const item = document.querySelector(`[data-resource-path="${CSS.escape(resourcePath)}"]`); if (!item) { console.warn('[StoreView] 未找到资源项:', resourcePath); return; } const resource = this.resources.find(r => r.path === resourcePath); if (!resource) { console.warn('[StoreView] 未找到资源对象:', resourcePath); return; } const button = item.querySelector('.item-buy-button'); const priceEl = item.querySelector('.item-price'); if (button) { const isFree = resource.points === 0; const isOwned = resource.isOwned || false; if (isFree) { // 免费资源:显示"免费",按钮"添加" button.textContent = '添加'; button.classList.remove('item-button-added'); if (priceEl) priceEl.textContent = '免费'; } else if (isOwned) { // 付费且已购买:显示价格,按钮"添加"(绿色) button.textContent = '添加'; button.classList.add('item-button-added'); if (priceEl) priceEl.textContent = `${resource.points} Ani币`; } else { // 付费且未购买:显示价格,按钮"购买" button.textContent = '购买'; button.classList.remove('item-button-added'); if (priceEl) priceEl.textContent = `${resource.points} Ani币`; } console.log('[StoreView] 单个按钮状态已更新:', resourcePath, 'isOwned:', isOwned); } } // 更新按钮状态(不重新渲染整个列表) updateButtonStates() { console.log('[StoreView] updateButtonStates 开始,资源列表:', this.resources.map(r => ({path: r.path, isOwned: r.isOwned, points: r.points}))); const allItems = document.querySelectorAll('.store-item'); console.log('[StoreView] 找到', allItems.length, '个 store-item 元素'); allItems.forEach(item => { const path = item.dataset.resourcePath; const resource = this.resources.find(r => r.path === path); if (resource) { const button = item.querySelector('.item-buy-button'); const priceEl = item.querySelector('.item-price'); if (button) { const isFree = resource.points === 0; const isOwned = resource.isOwned || false; if (isFree) { // 免费资源:显示"免费",按钮"添加" button.textContent = '添加'; button.classList.remove('item-button-added'); if (priceEl) priceEl.textContent = '免费'; } else if (isOwned) { // 付费且已购买:显示价格,按钮"添加"(绿色) button.textContent = '添加'; button.classList.add('item-button-added'); if (priceEl) priceEl.textContent = `${resource.points} Ani币`; } else { // 付费且未购买:显示价格,按钮"购买" button.textContent = '购买'; button.classList.remove('item-button-added'); if (priceEl) priceEl.textContent = `${resource.points} Ani币`; } } } }); } renderItem(resource) { if (!this.itemTemplate) { return '
模板未加载
'; } // 使用已保存的点数(在加载资源时已生成) const points = resource.points !== undefined ? resource.points : 0; // 根据点数和资源状态设置按钮文字和类 const isFree = points === 0; const isOwned = resource.isOwned || false; let buttonText = '添加'; let buttonClass = ''; let priceText = points === 0 ? '免费' : `${points} Ani币`; if (isFree) { // 免费资源:显示"免费",按钮"添加" buttonText = '添加'; buttonClass = ''; } else if (isOwned) { // 付费且已购买:显示价格,按钮"添加"(绿色) buttonText = '添加'; buttonClass = 'item-button-added'; } else { // 付费且未购买:显示价格,按钮"购买" buttonText = '购买'; buttonClass = ''; } // 先替换data-points中的{{points}}为原始points值 let html = this.itemTemplate .replace(/data-points="\{\{points\}\}"/g, `data-points="${points}"`) .replace(/\{\{name\}\}/g, this.escapeHtml(resource.name)) .replace(/\{\{category\}\}/g, this.escapeHtml(resource.category)) .replace(/\{\{categoryDir\}\}/g, this.escapeHtml(resource.categoryDir)) .replace(/\{\{previewUrl\}\}/g, resource.previewUrl || '') .replace(/\{\{frameCount\}\}/g, resource.frameCount || 0) .replace(/\{\{path\}\}/g, this.escapeHtml(resource.path)) .replace(/\{\{buttonText\}\}/g, buttonText) .replace(/\{\{buttonClass\}\}/g, buttonClass); // 最后替换价格显示中的{{points}}为priceText html = html.replace(/\{\{points\}\}/g, priceText); return html; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } async playAnimation(category, folder) { const previewModal = document.getElementById('previewModal'); const previewImage = document.getElementById('previewAnimationImage'); const previewTitle = document.getElementById('previewTitle'); if (!previewModal || !previewImage) return; // 显示弹窗 previewModal.style.display = 'flex'; if (previewTitle) { previewTitle.textContent = `动画预览: ${folder}`; } // 加载帧列表 try { const response = await fetch( `http://localhost:3000/api/store/frames?category=${encodeURIComponent(category)}&folder=${encodeURIComponent(folder)}` ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success && data.frameUrls && data.frameUrls.length > 0) { // 保存帧URLs this.frameUrls = data.frameUrls; // 开始播放动画 this.startAnimation(previewImage, data.frameUrls); } else { throw new Error('没有可用的帧'); } } catch (error) { console.error('[StoreView] 播放动画失败:', error); this.showGlobalAlert('播放动画失败: ' + error.message); previewModal.style.display = 'none'; } } startAnimation(imgElement, frameUrls) { // 停止之前的动画 this.stopAnimation(); this.currentFrame = 0; const fps = this.currentFps; const interval = 1000 / fps; // 预加载所有帧 const images = []; let loadedCount = 0; frameUrls.forEach((url, index) => { const img = new Image(); img.onload = () => { loadedCount++; if (loadedCount === frameUrls.length) { // 所有帧加载完成,开始播放 this.animationInterval = setInterval(() => { this.currentFrame = (this.currentFrame + 1) % frameUrls.length; imgElement.src = frameUrls[this.currentFrame]; }, interval); } }; img.onerror = () => { loadedCount++; if (loadedCount === frameUrls.length) { this.animationInterval = setInterval(() => { this.currentFrame = (this.currentFrame + 1) % frameUrls.length; imgElement.src = frameUrls[this.currentFrame]; }, interval); } }; img.src = url; images.push(img); }); // 立即显示第一帧 if (frameUrls.length > 0) { this.currentFrame = 0; imgElement.src = frameUrls[0]; } } stopAnimation() { if (this.animationInterval) { clearInterval(this.animationInterval); this.animationInterval = null; } } async startItemAnimation(imgElement, category, folder, fps = 8) { // 如果已经有动画在播放,先停止 this.stopItemAnimation(imgElement); // 检查是否已缓存帧数据 const cacheKey = `${category}/${folder}`; let frameUrls = this.itemAnimations.get(cacheKey); if (!frameUrls) { // 加载帧列表 try { const response = await fetch( `http://localhost:3000/api/store/frames?category=${encodeURIComponent(category)}&folder=${encodeURIComponent(folder)}` ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success && data.frameUrls && data.frameUrls.length > 0) { frameUrls = data.frameUrls; this.itemAnimations.set(cacheKey, frameUrls); } else { return; // 没有可用的帧 } } catch (error) { console.error('[StoreView] 加载帧失败:', error); return; } } // 开始播放动画 let currentFrame = 0; const interval = 1000 / fps; // 立即显示第一帧 if (frameUrls.length > 0) { imgElement.src = frameUrls[0]; } // 存储动画 interval const animationInterval = setInterval(() => { currentFrame = (currentFrame + 1) % frameUrls.length; imgElement.src = frameUrls[currentFrame]; }, interval); // 将 interval 和 fps 存储到 imgElement 上 imgElement._animationInterval = animationInterval; imgElement._currentFps = fps; } stopItemAnimation(imgElement) { if (imgElement._animationInterval) { clearInterval(imgElement._animationInterval); imgElement._animationInterval = null; } } isLoggedIn() { // 首先检查本地缓存 if (this.isUserLoggedIn) { return true; } // 如果本地缓存为 false,尝试从导航栏检查(作为备用方案) try { const navigationFrame = window.parent.document.getElementById('navigationFrame'); if (navigationFrame && navigationFrame.contentWindow) { const navDoc = navigationFrame.contentDocument || navigationFrame.contentWindow.document; const userAvatarContainer = navDoc.getElementById('userAvatarContainer'); // 如果用户头像容器存在且显示,说明已登录 if (userAvatarContainer) { const computedStyle = navDoc.defaultView.getComputedStyle(userAvatarContainer); const isLoggedIn = computedStyle.display !== 'none'; // 更新本地缓存 this.isUserLoggedIn = isLoggedIn; return isLoggedIn; } } } catch (error) { // 跨域或无法访问时,使用本地缓存 console.warn('[StoreView] 无法检查登录状态,使用本地缓存:', error); } return this.isUserLoggedIn; } // 检查资源是否已存在 async checkResourcesOwnership(resources) { if (!this.isLoggedIn()) { return; } const username = this.getCurrentUsername(); if (!username) { return; } // 批量检查资源 const checkPromises = resources.map(async (resource) => { try { const response = await fetch(`/api/pay/check-resource?username=${encodeURIComponent(username)}&resourcePath=${encodeURIComponent(resource.path)}`); const result = await response.json(); if (result.success) { resource.isOwned = result.exists; } } catch (error) { console.error(`[StoreView] 检查资源 ${resource.path} 失败:`, error); } }); await Promise.all(checkPromises); } getCurrentUsername() { try { const loginDataStr = localStorage.getItem('loginData'); if (!loginDataStr) { return null; } const loginData = JSON.parse(loginDataStr); const now = Date.now(); // 检查是否过期 if (now >= loginData.expireTime) { localStorage.removeItem('loginData'); return null; } return loginData.user ? loginData.user.username : null; } catch (error) { console.error('[StoreView] 获取用户名失败:', error); return null; } } async handleBuy(path) { console.log('[StoreView] handleBuy 被调用,path:', path); // 获取资源信息 const resource = this.resources.find(r => r.path === path); console.log('[StoreView] 找到资源:', resource); if (!resource) { this.showGlobalAlert('资源不存在'); return; } // 获取点数(从渲染的 item 中获取,或使用资源的点数) const itemElement = document.querySelector(`[data-resource-path="${path}"]`); let points = resource.points; if (itemElement) { const pointsEl = itemElement.querySelector('.item-price'); if (pointsEl) { const pointsText = pointsEl.textContent.replace('Ani币', '').trim(); points = parseInt(pointsText) || points; } const dataPoints = itemElement.querySelector('.item-buy-button')?.dataset.points; if (dataPoints) { points = parseInt(dataPoints) || points; } } // 检查是否登录 const username = this.getCurrentUsername(); if (!username) { if (window.parent && window.parent !== window) { window.parent.postMessage({ type: 'navigation', page: 'login' }, '*'); } return; } // 如果是0点,直接添加 if (points === 0) { try { const response = await fetch('/api/pay/purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username, resourcePath: resource.path, categoryDir: resource.categoryDir, itemName: resource.name, points: 0 }) }); const result = await response.json(); if (result.success) { // 标记为已拥有 resource.isOwned = true; // 立即更新单个按钮状态 this.updateSingleButtonState(resource.path); // 同时更新所有按钮状态(确保一致性) this.updateButtonStates(); if (window.parent && window.parent.HintView) { window.parent.HintView.success('添加成功!文件已添加到网盘', 3000); } // 通知父窗口刷新网盘 if (window.parent && window.parent !== window) { window.parent.postMessage({ type: 'refresh-disk' }, '*'); } } else { if (window.parent && window.parent.HintView) { window.parent.HintView.error(result.message || '添加失败', 3000); } } } catch (error) { console.error('[StoreView] 添加资源失败:', error); if (window.parent && window.parent.HintView) { window.parent.HintView.error('添加失败,请稍后重试', 3000); } } return; } // 检查用户点数 console.log('[StoreView] 检查用户点数,用户名:', username, '资源价格:', points); try { const pointsResponse = await fetch(`/api/user/points?username=${encodeURIComponent(username)}`); console.log('[StoreView] 点数请求响应:', pointsResponse.status); if (!pointsResponse.ok) { throw new Error(`HTTP error! status: ${pointsResponse.status}`); } const pointsResult = await pointsResponse.json(); console.log('[StoreView] 点数结果:', pointsResult); if (!pointsResult.success) { throw new Error(pointsResult.message || '获取点数失败'); } const userPoints = pointsResult.points || 0; console.log('[StoreView] 用户点数:', userPoints, '需要点数:', points); if (userPoints >= points) { // 点数充足,弹出确认对话框 console.log('[StoreView] 点数充足,准备弹出确认对话框'); console.log('[StoreView] window.parent.GlobalConfirm 存在:', !!(window.parent && window.parent.GlobalConfirm)); let confirmed = false; if (window.parent && window.parent.GlobalConfirm) { // GlobalConfirm.show 返回 Promise confirmed = await window.parent.GlobalConfirm.show( `确定要花费 ${points} Ani币购买 ${resource.name} 吗?` ); console.log('[StoreView] 用户选择:', confirmed); } else { // 降级使用原生 confirm console.log('[StoreView] GlobalConfirm 不可用,使用原生 confirm'); confirmed = confirm(`确定要花费 ${points} Ani币购买 ${resource.name} 吗?`); } if (confirmed) { // 确认购买 try { console.log('[StoreView] 发送购买请求...'); const response = await fetch('/api/pay/purchase', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username, resourcePath: resource.path, categoryDir: resource.categoryDir, itemName: resource.name, points: points }) }); const result = await response.json(); console.log('[StoreView] 购买结果:', result); if (result.success) { // 标记为已拥有 resource.isOwned = true; console.log('[StoreView] 购买成功,资源已标记为已拥有:', resource.path, 'isOwned:', resource.isOwned); // 立即更新单个按钮状态 this.updateSingleButtonState(resource.path); // 同时更新所有按钮状态(确保一致性) this.updateButtonStates(); if (window.parent && window.parent.HintView) { window.parent.HintView.success(`购买成功!已扣除 ${points} Ani币,文件已添加到网盘`, 3000); } // 通知父窗口刷新点数和网盘 if (window.parent && window.parent !== window) { window.parent.postMessage({ type: 'refresh-points' }, '*'); window.parent.postMessage({ type: 'refresh-disk' }, '*'); } } else { if (window.parent && window.parent.HintView) { window.parent.HintView.error(result.message || '购买失败', 3000); } } } catch (error) { console.error('[StoreView] 购买失败:', error); if (window.parent && window.parent.HintView) { window.parent.HintView.error('购买失败,请稍后重试', 3000); } } } } else { // 点数不足,弹出充值窗口 if (window.parent && window.parent !== window) { window.parent.postMessage({ type: 'open-recharge-view', needPoints: points, currentPoints: userPoints }, '*'); } } } catch (error) { console.error('[StoreView] 检查点数失败:', error); if (window.parent && window.parent.HintView) { window.parent.HintView.error('检查点数失败,请稍后重试', 3000); } } } showLoading(show) { const loadingState = document.getElementById('loadingState'); if (loadingState) { loadingState.style.display = show ? 'flex' : 'none'; } } showEmpty() { const emptyState = document.getElementById('emptyState'); if (emptyState) { emptyState.style.display = 'flex'; } } hideEmpty() { const emptyState = document.getElementById('emptyState'); if (emptyState) { emptyState.style.display = 'none'; } } showGlobalAlert(message) { if (window.parent && window.parent.postMessage) { window.parent.postMessage({ type: 'global-alert', text: message, duration: 3000 }, '*'); } else { alert(message); } } } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { new StoreView(); }); } else { new StoreView(); } })();