| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001 |
- // 商店页面主逻辑
- // 负责资源加载、搜索、分类筛选等功能
- (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 = '<div class="store-item">加载失败</div>';
- }
- }
- }
- 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 '<div class="store-item">模板未加载</div>';
- }
-
- // 使用已保存的点数(在加载资源时已生成)
- 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();
- }
- })();
|