/** * PFP特征模态框处理器 * 显示来自pfp.json的官方特征数据 */ class PFPFeatureModal { constructor() { this.pfpLoader = null; this.modal = null; this.shedingJiRanges = []; this.allPFPData = null; } /** * 初始化模态框 */ async init() { try { // 加载PFP数据(使用现有的PFPLoader) if (typeof PFPLoader !== 'undefined') { this.pfpLoader = new PFPLoader(); this.allPFPData = await this.pfpLoader.loadAllPFP(); } else { // 如果PFPLoader不可用,直接从API加载 await this.loadPFPDataDirectly(); } // 加载设定集配置 await this.loadShedingJiConfig(); // 初始化Bootstrap模态框 const modalElement = document.getElementById('pfpFeatureModal'); if (modalElement) { this.modal = new bootstrap.Modal(modalElement); } // console.debug('✓ PFP特征模态框初始化完成'); } catch (error) { console.error('PFP特征模态框初始化失败:', error); } } /** * 直接从API加载PFP数据 */ async loadPFPDataDirectly() { try { const response = await fetch('https://h5.shuziwenbo.cn/json/shpfp/pfp.json'); this.allPFPData = await response.json(); // console.debug('✓ PFP数据加载完成'); } catch (error) { console.error('加载PFP数据失败:', error); this.allPFPData = {}; } } /** * 加载设定集配置 */ async loadShedingJiConfig() { try { const response = await fetch('/api/v1/pfp/config/sheding-ji'); const result = await response.json(); if (result.success && result.data) { this.shedingJiRanges = result.data.ranges; // console.debug('✓ 设定集配置加载完成'); } } catch (error) { console.error('加载设定集配置失败:', error); this.shedingJiRanges = [[1, 100], [9901, 10000]]; // 使用默认值 } } /** * 判断编号是否为设定集 * 支持两种格式:具体编号列表 [16, 33, 65, ...] 或范围列表 [[2, 100], [9901, 10000]] */ isShedingJi(pfpNumber) { if (!pfpNumber || !this.shedingJiRanges) { return false; } // 检查是否为具体编号列表格式 if (this.shedingJiRanges.length > 0 && typeof this.shedingJiRanges[0] === 'number') { return this.shedingJiRanges.includes(pfpNumber); } // 范围列表格式 for (const range of this.shedingJiRanges) { if (Array.isArray(range) && range.length === 2) { const [start, end] = range; if (pfpNumber >= start && pfpNumber <= end) { return true; } } } return false; } /** * 显示特征数据 * @param {number} pfpNumber - PFP编号 * @param {object} ocrResult - OCR识别结果(可选) */ async showFeatures(pfpNumber, ocrResult = null) { if (!this.modal) { console.error('模态框未初始化'); return; } // 更新模态框标题 document.getElementById('pfpNumber').textContent = pfpNumber; // 显示/隐藏设定集徽章 const badge = document.getElementById('pfpShedingJiBadge'); if (badge) { badge.style.display = this.isShedingJi(pfpNumber) ? 'inline' : 'none'; } // 获取PFP数据(支持 {data: {...}} 或直接 {...} 格式) const pfpDataSource = this.allPFPData ? (this.allPFPData.data || this.allPFPData) : null; const pfpData = pfpDataSource ? pfpDataSource[pfpNumber] : null; // 渲染特征内容 let html = ''; let dataSource = ''; // 优先级: OCR结果 > 官方特征数据 if (ocrResult && Array.isArray(ocrResult) && ocrResult.length > 0) { html = this.renderOCRFeatures(ocrResult); dataSource = 'OCR识别'; } else if (pfpData && pfpData.features) { html = this.renderOfficialFeatures(pfpData); dataSource = '官方数据'; } else { html = '
未找到该编号的特征数据
'; dataSource = '无'; } // 添加数据来源标签 if (dataSource !== '无') { html += `
数据来源: ${dataSource}
`; } // 更新模态框内容 document.getElementById('pfpFeatureContent').innerHTML = html; // 显示模态框 this.modal.show(); } /** * 渲染官方特征数据 */ renderOfficialFeatures(pfpData) { let html = ''; // 显示特征列表 if (pfpData.features) { const features = this.formatFeatures(pfpData.features); html += '
'; features.forEach(feature => { html += `
${feature.category || '特征'}
${feature.name || feature.value}
${feature.rarity ? `${feature.rarity}` : ''}
`; }); html += '
'; } // 显示名称 if (pfpData.name) { html += `
名称: ${pfpData.name}
`; } return html; } /** * 渲染OCR特征数据 */ renderOCRFeatures(ocrResult) { let html = '
'; ocrResult.forEach(item => { html += `
${item.text || item}
${item.confidence ? `置信度: ${(item.confidence * 100).toFixed(0)}%` : ''}
`; }); html += '
'; return html; } /** * 格式化特征数据 * 支持字符串格式: "背景_天球:1.98%,服饰_戌狗套装:0.81%,..." */ formatFeatures(features) { if (!features) return []; // 如果features是字符串(官方格式),解析它 if (typeof features === 'string') { return features.split(',').map(item => { const colonIndex = item.indexOf(':'); if (colonIndex === -1) return null; const keyPart = item.substring(0, colonIndex); const rarity = item.substring(colonIndex + 1); const underscoreIndex = keyPart.indexOf('_'); if (underscoreIndex === -1) return null; return { category: keyPart.substring(0, underscoreIndex), name: keyPart.substring(underscoreIndex + 1), rarity: rarity }; }).filter(item => item !== null); } // 如果features是对象,转换为数组 if (typeof features === 'object' && !Array.isArray(features)) { return Object.entries(features).map(([key, value]) => ({ category: key, value: value, name: value })); } return features; } /** * 解析官方特征字符串为对象 * @param {string} featuresStr - 特征字符串 "背景_天球:1.98%,服饰_戌狗套装:0.81%,..." * @returns {object} 特征对象 {"背景": "天球", "服饰": "戌狗套装", ...} */ parseOfficialFeatures(featuresStr) { const traits = {}; if (!featuresStr) return traits; const items = featuresStr.split(','); for (const item of items) { const colonIndex = item.indexOf(':'); if (colonIndex === -1) continue; const keyPart = item.substring(0, colonIndex); const underscoreIndex = keyPart.indexOf('_'); if (underscoreIndex === -1) continue; const category = keyPart.substring(0, underscoreIndex); const value = keyPart.substring(underscoreIndex + 1); traits[category] = value; } return traits; } /** * 根据特征数据反推PFP编号 * @param {object} traitsInfo - OCR识别的特征信息 {"背景": "天球", "服饰": "戌狗套装", ...} * @returns {object|null} 匹配结果 {pfpNumber, matchScore, isShedingJi} 或 null */ matchPfpNumberByTraits(traitsInfo) { if (!traitsInfo || !this.allPFPData || Object.keys(traitsInfo).length === 0) { return null; } let bestMatch = null; let bestScore = 0; const minMatchThreshold = 3; // 至少匹配3个特征才认为有效 // 获取PFP数据(支持 {data: {...}} 或直接 {...} 格式) const pfpData = this.allPFPData.data || this.allPFPData; // 遍历所有PFP数据 for (const [pfpNum, data] of Object.entries(pfpData)) { if (!data.features) continue; // 解析官方特征字符串 const officialTraits = this.parseOfficialFeatures(data.features); // 计算匹配分数 let matchCount = 0; for (const [category, value] of Object.entries(traitsInfo)) { if (officialTraits[category] === value) { matchCount++; } } // 更新最佳匹配 if (matchCount > bestScore && matchCount >= minMatchThreshold) { bestScore = matchCount; const num = parseInt(pfpNum); bestMatch = { pfpNumber: num, matchScore: matchCount, totalTraits: Object.keys(traitsInfo).length, matchRatio: (matchCount / Object.keys(traitsInfo).length * 100).toFixed(1), isShedingJi: this.isShedingJi(num), officialData: data }; } } return bestMatch; } } // 全局实例 let pfpFeatureModal = null; // 页面加载完成后初始化 $(document).ready(async function() { pfpFeatureModal = new PFPFeatureModal(); await pfpFeatureModal.init(); });