Ry Mofadongxue2
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>游戏存档数据实时对照工具</title> <!-- 引入Vue 2 --> <script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script> <!-- 引入Font Awesome --> <link href="https://unpkg.com/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <style> /* 基础样式 */ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #f3f4f6; color: #1f2937; line-height: 1.5; padding: 20px; min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } /* 头部样式 */ header { text-align: center; margin-bottom: 40px; } h1 { font-size: clamp(1.8rem, 4vw, 2.5rem); font-weight: 700; margin-bottom: 10px; color: #1e293b; } header p { color: #6b7280; font-size: 1.1rem; } /* 主内容区样式 */ main { display: flex; flex-direction: column; gap: 30px; } .card { background-color: white; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 20px; transition: box-shadow 0.3s ease; } .card:hover { box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .card-title { font-size: 1.25rem; font-weight: 600; display: flex; align-items: center; } .card-title i { margin-right: 8px; } .encrypted .card-title { color: #3b82f6; } .decrypted .card-title { color: #10b981; } .map-preview .card-title { color: #f59e0b; } /* 文本区域样式 */ textarea { width: 100%; min-height: 250px; padding: 15px; border: 1px solid #d1d5db; border-radius: 6px; font-family: Consolas, Monaco, monospace; font-size: 0.875rem; resize: both; overflow: auto; transition: all 0.3s ease; } textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25); } .decrypted textarea:focus { border-color: #10b981; box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25); } /* 按钮样式 */ .btn-group { display: flex; gap: 10px; } button { display: flex; align-items: center; gap: 5px; padding: 6px 12px; border: none; border-radius: 4px; font-size: 0.875rem; cursor: pointer; transition: all 0.3s ease; } .btn-primary { background-color: #3b82f6; color: white; } .btn-primary:hover { background-color: #2563eb; } .btn-secondary { background-color: #10b981; color: white; } .btn-secondary:hover { background-color: #059669; } .btn-neutral { background-color: #e5e7eb; color: #1f2937; } .btn-neutral:hover { background-color: #d1d5db; } /* 提示消息样式 */ .message { padding: 15px; border-radius: 6px; display: flex; align-items: center; gap: 8px; margin-top: 10px; } .message-success { background-color: #dcfce7; color: #166534; } .message-error { background-color: #fee2e2; color: #b91c1c; } /* 底部样式 */ footer { text-align: center; margin-top: 50px; color: #6b7280; font-size: 0.875rem; } /* 全屏模式样式 */ .fullscreen-container { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: #1a1a1a; padding: 20px; display: flex; flex-direction: column; } .fullscreen-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; color: white; } .fullscreen-textarea { flex: 1; width: 100%; min-height: auto !important; background-color: #2d2d2d; color: #f0f0f0; border: none; font-size: 1rem !important; } .fullscreen-textarea:focus { border-color: #10b981; box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25); } /* 地图预览样式 */ .map-preview-container { margin-top: 15px; } .map-grid { border-collapse: collapse; margin: 0 auto; } .map-grid td { width: 30px; height: 30px; border: 1px solid #d1d5db; text-align: center; vertical-align: middle; font-size: 10px; background-color: #f9fafb; } /* 提示框样式 */ .cell-tooltip { position: fixed; /* 改为fixed定位,相对于视口 */ background-color: rgba(0, 0, 0, 0.8); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; pointer-events: none; /* 让鼠标事件穿透提示框 */ z-index: 1000; transition: opacity 0.1s; white-space: nowrap; } /* 响应式调整 */ @media (max-width: 768px) { .card-header { flex-direction: column; align-items: flex-start; gap: 10px; } .btn-group { width: 100%; justify-content: flex-start; } .map-grid td { width: 20px; height: 20px; } } </style> </head> <body> <div id="app" class="container"> <header> <h1>游戏存档数据实时对照工具</h1> <p>加密数据与解密数据双向实时转换(含中英文翻译)</p> </header> <main> <!-- 加密数据区域 --> <div class="card encrypted"> <div class="card-header"> <h2 class="card-title"> <i class="fa fa-lock"></i>加密存档数据 </h2> <button @click="copyEncrypted" class="btn-primary"> <i class="fa fa-copy"></i> 复制 </button> </div> <textarea v-model="encryptedData" @input="encryptToDecrypt" placeholder="粘贴加密的游戏存档数据到这里..."></textarea> </div> <!-- 解密数据区域 --> <div class="card decrypted"> <div class="card-header"> <h2 class="card-title"> <i class="fa fa-unlock"></i>解密数据(中文) </h2> <div class="btn-group"> <button @click="toggleFullscreen" class="btn-neutral"> <i class="fa" :class="isFullscreen ? 'fa-compress' : 'fa-expand'"></i> {{ isFullscreen ? '退出全屏' : '全屏编辑' }} </button> <button @click="copyDecrypted" class="btn-secondary"> <i class="fa fa-copy"></i> 复制 </button> </div> </div> <textarea v-model="decryptedData" @input="decryptToEncrypt" placeholder="解密后的中文数据会显示在这里,也可以直接编辑...未知字符会显示为【原字符】"></textarea> </div> <!-- 地图预览区域 --> <div class="card map-preview"> <div class="card-header"> <h2 class="card-title"> <i class="fa fa-map"></i>楼层地图预览 </h2> <button @click="refreshMap" class="btn-neutral"> <i class="fa fa-refresh"></i> 刷新地图 </button> </div> <div v-if="mapData.length > 0" class="map-preview-container"> <!-- 浮动提示框 --> <div v-if="showTooltip" class="cell-tooltip" :style="{ left: tooltipX + 'px', top: tooltipY + 'px' }"> {{ tooltipValue }} </div> <table class="map-grid"> <tbody> <tr v-for="(row, rowIndex) in mapData" :key="rowIndex"> <td v-for="(cell, colIndex) in row" :key="colIndex" :style="{ backgroundColor: cell === '墙' ? 'black' : cell === '空' ? 'white' : 'gray', width: '30px', height: '30px', border: '1px solid #ccc', position: 'relative' }" @mouseenter="handleMouseEnter(cell, $event)" @mousemove="handleMouseMove($event)" @mouseleave="handleMouseLeave"> <!-- 单元格视觉显示逻辑(按 o 值区分) --> <template v-if="typeof cell === 'object'"> <!-- o===0 怪物(红色圆点) --> <template v-if="cell.o === 0"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '15px', height: '15px', borderRadius: '50%', backgroundColor: 'red', boxShadow: '0 0 3px rgba(255, 0, 0, 0.8)' }"></span> </template> <!-- o===1 装备宝箱(蓝色菱形) --> <template v-else-if="cell.o === 1"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%) rotate(45deg)', width: '14px', height: '14px', backgroundColor: '#1e90ff', boxShadow: '0 0 4px rgba(30, 144, 255, 0.8)', border: '1px solid rgba(255, 255, 255, 0.5)' }"></span> </template> <!-- o===3 金钱(金黄色圆形) --> <template v-else-if="cell.o === 3"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#ffd700', boxShadow: '0 0 3px rgba(255, 215, 0, 0.8)' }"></span> </template> <!-- o===5 书柜装饰(棕色矩形) --> <template v-else-if="cell.o === 5"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '18px', height: '10px', backgroundColor: '#8b4513', border: '1px solid #5d2f09', borderRadius: '2px' }"></span> </template> <!-- o===6 剧情触发点(亮紫色五角星,增强静态发光效果) --> <template v-else-if="cell.o === 6"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '18px', height: '18px', backgroundColor: '#e6ccff', /* 更明亮的浅紫色 */ clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)', boxShadow: '0 0 10px 3px rgba(230, 204, 255, 0.9), 0 0 15px 5px rgba(230, 204, 255, 0.6)', /* 强化静态发光 */ border: '1px solid rgba(255, 255, 255, 0.8)', /* 白色边框增加亮度 */ zIndex: 2 /* 确保在其他元素上方显示 */ }"></span> </template> <!-- o===7 出口(绿色三角形) --> <template v-else-if="cell.o === 7"> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 0, height: 0, borderLeft: '8px solid transparent', borderRight: '8px solid transparent', borderBottom: '16px solid #32cd32', boxShadow: '0 0 4px rgba(50, 205, 50, 0.8)' }"></span> </template> <!-- 未定义的 o 值(灰色问号) --> <template v-else> <span :style="{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', color: '#888', fontSize: '16px', fontWeight: 'bold' }">?</span> </template> </template> </td> </tr> </tbody> </table> </div> <div v-else class="message message-error"> <i class="fa fa-exclamation-circle"></i> <span>未找到楼层地砖数据或数据格式不正确</span> </div> </div> <!-- 状态提示 --> <div v-if="message" :class="'message message-' + messageType"> <i :class="messageType === 'success' ? 'fa fa-check-circle' : 'fa fa-exclamation-circle'"></i> <span>{{ message }}</span> </div> <!-- 全屏编辑容器 --> <div v-if="isFullscreen" class="fullscreen-container"> <div class="fullscreen-header"> <h2><i class="fa fa-unlock"></i> 解密数据全屏编辑</h2> <button @click="toggleFullscreen" class="btn-neutral"> <i class="fa fa-compress"></i> 退出全屏 </button> </div> <textarea v-model="fullscreenContent" @input="syncFullscreenChanges" class="fullscreen-textarea" placeholder="在此编辑解密数据..."></textarea> </div> </main> <footer> <p>游戏存档数据转换工具 © 2023</p> </footer> </div> <script> new Vue({ el: '#app', data() { return { // 加密和解密数据 encryptedData: '', decryptedData: '', // 存储"楼层地砖"所在行的内容 floorTilesData: '', // 解析后的地图数据(二维数组) mapData: [], showTooltip: false, tooltipValue: '', tooltipX: 0, tooltipY: 0, // 全屏相关数据 isFullscreen: false, fullscreenContent: '', // 消息提示 message: '', messageType: 'success', // 替换加密的密码对应表 cipherMap: { // 未破解字母 --begin "G": "★", "\"": "■", // 未破解字母 --end "]": "{", "[": "}", "z": ":", ".": "\"", ":": ",", ",": "[", "!": "]", "-": "-", " ": " ", "h": "a", "Q": "b", "L": "c", "y": "d", "1": "e", "l": "f", "X": "g", "s": "h", "Y": "i", "d": "j", "n": "k", "i": "l", "P": "m", "J": "n", "r": "o", "F": "p", "t": "q", "Z": "r", "V": "s", "f": "t", "w": "u", "#": "v", "I": "w", "u": "x", "C": "y", "e": "A", "v": "B", "O": "C", "T": "D", "E": "E", "j": "F", "q": "G", "@": "H", "5": "I", "o": "L", "b": "M", "4": "N", "N": "O", "K": "P", "c": "R", "7": "S", "p": "T", "k": "U", "g": "W", "}": "0", "3": "1", "S": "2", "0": "3", "B": "4", "D": "5", "9": "6", "a": "7", "8": "8", "W": "9", "{": ".", }, // 英文到汉字的对照表 englishToChineseMap: { // 基础属性 "shopsUnlocked": "已解锁商店", "items": "物品", "defense": "防御力", "bestiaryMonsterIDs": "图鉴怪物ID", "hpGemsThisRun": "本次获得的生命宝石", "goldMultiplier": "金币倍率", "storyProgress": "剧情进度", "successfulRuns": "成功运行次数", "attGems": "攻击宝石", "defEarth": "土系防御", "mpGemsThisRun": "本次获得的魔法宝石", "defIce": "冰系防御", "mpGems": "魔法宝石", "potionDuration": "药水持续时间", "maxMP": "最大魔法值", "attGemsThisRun": "本次获得的攻击宝石", "shopsFoundThisRun": "本次发现的商店", "hp": "当前生命值", "attack": "攻击力", "attWind": "风系攻击", "foundFloorObjects": "发现的楼层物品", "runsToBeatTheGame": "通关所需运行次数", "secretTheseFiveFloors": "这五层的秘密", "gamepadSelectedHotkeySlot": "手柄选中的快捷键槽", "creationTime": "创建时间", "numFloorObjects": "楼层物品数量", "snapshotData": "快照数据", "gold": "金币", "equippedItems": "已装备物品", "skillNodesUnlocked": "已解锁技能节点", "mp": "当前魔法值", "nextLevelAt": "下一等级所需经验", "level": "等级", "magGemCost": "魔法宝石成本", "defGems": "防御宝石", "attEarth": "土系攻击", "attIce": "冰系攻击", "skillGemCost": "技能宝石成本", "dodgeChance": "闪避几率", "hpGems": "生命宝石", "maxHP": "最大生命值", "magic": "魔法值", "defWind": "风系防御", "critChance": "暴击几率", "hotkeySlotItems": "快捷键槽物品", "dropBonus": "掉落加成", "defLight": "光系防御", "killChance": "击杀几率", "sellPercent": "出售百分比", "gameSwitches": "游戏开关", "hpGemCost": "生命宝石成本", "skillPoints": "技能点", "skillGems": "技能宝石", "attLight": "光系攻击", "hpRegen": "生命回复", "defGemCost": "防御宝石成本", "magGems": "魔法宝石", "attFire": "火系攻击", "attDark": "暗系攻击", "mpGemCost": "魔法宝石成本", "mpRegen": "魔法回复", "bleed": "流血效果", "attGemCost": "攻击宝石成本", "skillPointsPerLevel": "每级技能点", "defDark": "暗系防御", "defFire": "火系防御", "attackDelay": "攻击延迟", "critMultiplier": "暴击倍率", "potionEfficiency": "药水效率", "lifeDrain": "生命吸取", "exp": "经验值", "expMultiplier": "经验倍率", // 其他属性 "startPoint": "起始点", "museumItems": "博物馆物品", "deepestFloor": "最深楼层", "currentFloor": "当前楼层", "heroType": "英雄类型", "magGemsThisRun": "本次获得的魔法宝石", "runStartGold": "运行开始时的金币", "itemsEnchanted": "已附魔物品数量", "skillGemsThisRun": "本次获得的技能宝石", "floorTiles": "楼层地砖", "wingsFound": "已发现翅膀", "legacySavesChecked": "已检查旧存档", "activeEffects": "活跃效果", "exists": "是否存在", "sorcerorPhase": "巫师阶段", "potionsCrafted": "已制作药水数量", "runStartLevel": "运行开始时的等级", "defGemsThisRun": "本次获得的防御宝石", "canChangeHeroes": "是否可更换英雄", "shopItems": "商店物品", "hotkeyItems": "快捷键物品", "wingsSoldFloor": "翅膀出售楼层", "runStartFloor": "运行开始时的楼层", "gameMode": "游戏模式", "newGamePlusNum": "游戏周目", "secretRoomsFound": "已发现的秘密房间", "messageWins": "胜利消息", // 地图信息 "★": "空", "■": "墙", }, // 反向映射表(用于解密到加密) reverseCipherMap: {}, // 汉字到英文的反向对照表(用于中文编辑后重新加密) chineseToEnglishMap: {}, // 排序后的中文键列表(按长度排序,用于正确翻译) sortedChineseKeys: [] } }, created() { // 生成反向映射表 this.generateReverseMap(); // 生成中文到英文的反向对照表及排序后的键列表 this.generateChineseToEnglishMap(); }, methods: { // 生成反向映射表(加密映射的反向) generateReverseMap() { for (const [key, value] of Object.entries(this.cipherMap)) { this.reverseCipherMap[value] = key; } }, // 生成中文到英文的反向对照表及排序后的键列表 generateChineseToEnglishMap() { // 创建中文到英文的映射 for (const [english, chinese] of Object.entries(this.englishToChineseMap)) { this.chineseToEnglishMap[chinese] = english; } // 提取所有中文键并按长度降序排序(长词汇优先) this.sortedChineseKeys = Object.keys(this.chineseToEnglishMap) .sort((a, b) => b.length - a.length); }, // 将英文转换为中文 translateToChinese(text) { let result = text; // 先将较长的英文词汇翻译,避免被短词汇分割 const sortedEnglishEntries = Object.entries(this.englishToChineseMap) .sort((a, b) => b[0].length - a[0].length); // 遍历排序后的英文-中文映射表,替换所有匹配的英文为中文 for (const [english, chinese] of sortedEnglishEntries) { const regex = new RegExp(`${english}`, 'g'); result = result.replace(regex, chinese); } return result; }, // 将中文转换为英文(用于编辑后重新加密) translateToEnglish(text) { let result = text; // 使用排序后的中文键列表(长词汇优先)进行替换 for (const chinese of this.sortedChineseKeys) { const english = this.chineseToEnglishMap[chinese]; // 使用正则表达式确保匹配整个词汇 const regex = new RegExp(`${chinese}`, 'g'); result = result.replace(regex, english); } return result; }, // 处理格式化:遇到[时,在遇到]之前不换行,其他情况遇到{或}时换行 formatText(jsonStr) { let result = ''; let indentLevel = 0; // 缩进层级 let inString = false; // 是否在字符串中 let prevChar = ''; // 上一个字符 let currentValue = ''; // 当前值缓存 const fullWidthSpace = ' '; // 中文全角空格 let openBraces = 0; // 未闭合的"{"个数 let openBrackets = 0; // 未闭合的"["个数 // 用于生成缩进的辅助函数,使用全角空格 const getIndent = () => fullWidthSpace.repeat(indentLevel * 2); for (const char of jsonStr) { // 处理字符串边界(考虑转义的引号) if (char === '"' && prevChar !== '\\') { inString = !inString; result += char; prevChar = char; continue; } // 不在字符串中时才处理格式化 if (!inString) { // 缓存当前值字符 if (char !== '{' && char !== '}' && char !== '[' && char !== ']' && char !== ',' && char !== ':' && char.trim() !== '') { currentValue += char; } // 判断是否在数组内部 const inArray = openBrackets > 0; switch (char) { case '{': indentLevel++; openBraces++; if (currentValue) { result += currentValue; currentValue = ''; } result += char; // 数组内部不添加任何空格和换行,外部则换行缩进 if (!inArray) { result += '\n' + getIndent(); } break; case '[': if (currentValue) { result += currentValue; currentValue = ''; } result += char; indentLevel++; openBrackets++; break; case '}': indentLevel = Math.max(0, indentLevel - 1); openBraces = Math.max(0, openBraces - 1); if (currentValue) { result += currentValue; currentValue = ''; } // 数组内部不添加任何空格和换行,外部则换行缩进 if (!inArray) { result += '\n' + getIndent() + char; } else { result += char; } break; case ']': indentLevel = Math.max(0, indentLevel - 1); openBrackets = Math.max(0, openBrackets - 1); if (currentValue) { result += currentValue; currentValue = ''; } result += char; break; case ',': if (currentValue) { result += currentValue; currentValue = ''; } result += char; // 数组内部不添加任何空格和换行,外部则换行缩进 if (!inArray) { result += '\n' + getIndent(); } break; case ':': if (currentValue) { result += currentValue; currentValue = ''; } // 数组内部不添加空格,外部添加全角空格 result += ':' + (inArray ? '' : fullWidthSpace); break; case ' ': case '\n': case '\t': // 忽略原有的空白字符,但如果有缓存值,先添加 if (currentValue) { result += currentValue; currentValue = ''; } break; default: // 非特殊字符且不在字符串中,不直接添加,由缓存处理 break; } } else { // 在字符串中,直接添加字符 result += char; } prevChar = char; } // 添加最后可能剩余的缓存值 if (currentValue) { result += currentValue; } return result; }, // 提取"楼层地砖"所在行的内容 extractFloorTilesData(text) { // 查找包含"楼层地砖"的行 const lines = text.split('\n'); for (const line of lines) { if (line.includes('楼层地砖')) { // 提取并清理数据(去除前后空格和逗号) this.floorTilesData = line.trim().replace(/,$/, ''); // 提取后立即解析地图数据 this.parseFloorTilesData(); break; } } }, // 解析楼层地砖数据为二维数组 // 解析楼层地砖数据为二维数组,并处理全是"墙"的起始行和列 parseFloorTilesData() { // 重置地图数据 this.mapData = []; if (!this.floorTilesData) { return; } try { // 提取冒号后的数组部分 const colonIndex = this.floorTilesData.indexOf(':'); const dataPart = colonIndex !== -1 ? this.floorTilesData.slice(colonIndex + 1).trim() : ''; if (!dataPart) { throw new Error("未找到有效的数组数据"); } // 处理没有引号的字符串元素,给它们添加引号 let formattedData = dataPart.replace(/([^,\[\]{}:]+?)(?=[,\[\]}:])/g, (match) => { if (!/^\d+$/.test(match.trim()) && !/^["'].*["']$/.test(match.trim())) { return `"${match.trim()}"`; } return match; }); // 将处理后的字符串解析为JSON const parsedData = JSON.parse(formattedData); // 验证是否为二维数组 if (Array.isArray(parsedData) && parsedData.every(row => Array.isArray(row))) { // 处理全是"墙"的行和列 let processedData = [...parsedData]; // 移除全是"墙"的起始行 while (processedData.length > 0 && processedData[0].every(cell => cell === "墙")) { processedData.shift(); } // 移除全是"墙"的末尾行 while (processedData.length > 0 && processedData[processedData.length - 1].every(cell => cell === "墙")) { processedData.pop(); } // 关键修改:进行矩阵转置,将列转为行 if (processedData.length > 0) { const rows = processedData.length; const cols = processedData[0].length; // 初始化转置后的数组 let transposedData = []; for (let j = 0; j < cols; j++) { transposedData[j] = []; for (let i = 0; i < rows; i++) { // 原[i][j]变为新[j][i] transposedData[j][i] = processedData[i][j]; } } // 移除全是"墙"的起始行 while (transposedData.length > 0 && transposedData[0].every(cell => cell === "墙")) { transposedData.shift(); } // 移除全是"墙"的末尾行 while (transposedData.length > 0 && transposedData[transposedData.length - 1].every( cell => cell === "墙")) { transposedData.pop(); } // 新增:在最外层添加一层"墙" if (transposedData.length > 0) { const colsCount = transposedData[0].length; // 创建顶部和底部的墙行(全是"墙") const wallRow = Array(colsCount).fill("墙"); // 在顶部添加墙行 transposedData.unshift(wallRow); // 在底部添加墙行 transposedData.push(wallRow); // 在每行的首尾添加墙 transposedData = transposedData.map(row => { return ["墙", ...row, "墙"]; }); } this.mapData = transposedData; } else { this.mapData = processedData; } // 显示解析成功信息 const rowCount = this.mapData.length; const colCount = rowCount > 0 ? this.mapData[0].length : 0; this.showMessage(`地图解析成功,共 ${rowCount} 行 ${colCount} 列,已添加外层墙`, 'success'); } else { throw new Error("解析的数据不是二维数组"); } } catch (error) { console.error("解析楼层地砖数据失败:", error); this.showMessage(`地图解析失败: ${error.message}`, 'error'); this.mapData = []; } }, // 刷新地图预览 refreshMap() { this.parseFloorTilesData(); }, // 加密数据转解密数据:先字符映射,再翻译为中文,最后格式化 encryptToDecrypt() { // 1. 应用字符映射转换,未找到映射的字符用【】包裹 let result = ''; for (const char of this.encryptedData) { if (this.cipherMap.hasOwnProperty(char)) { result += this.cipherMap[char]; } else { console.log('char', char) // 对未知字符使用【】包裹 result += `【${char}】`; } } // 2. 将结果中的英文翻译为中文 const translatedResult = this.translateToChinese(result); // 3. 应用格式化规则 const formattedResult = this.formatText(translatedResult); // 4. 提取楼层地砖数据 this.extractFloorTilesData(formattedResult); // 避免循环更新 if (this.decryptedData !== formattedResult) { this.decryptedData = formattedResult; // 如果在全屏模式下,同步更新全屏内容 if (this.isFullscreen) { this.fullscreenContent = formattedResult; } } }, // 解密数据转加密数据:先将中文翻译为英文,移除格式,再字符反向映射 decryptToEncrypt() { this.fullscreenContent = this.decryptedData; // 1. 先将中文翻译回英文 const englishText = this.translateToEnglish(this.decryptedData); // 2. 移除所有换行符、制表符、中文空格 let textWithoutNewlines = englishText.replace(/[\n\r\t ]/g, ''); // 3. 处理【】包裹的未知字符,提取原始字符 // 使用正则表达式匹配【...】格式并提取内容 const unknownCharRegex = /【(.*?)】/g; textWithoutNewlines = textWithoutNewlines.replace(unknownCharRegex, (match, p1) => p1); // 4. 应用反向字符映射 let result = ''; for (const char of textWithoutNewlines) { if (this.reverseCipherMap.hasOwnProperty(char)) { result += this.reverseCipherMap[char]; } else { // 对于解密区新增的未知字符,直接保留 result += char; } } // 5. 更新楼层地砖数据 this.extractFloorTilesData(this.decryptedData); // 避免循环更新 if (this.encryptedData !== result) { this.encryptedData = result; } }, // 复制加密数据 copyEncrypted() { this.copyToClipboard(this.encryptedData, '加密数据已复制到剪贴板'); }, // 复制解密数据 copyDecrypted() { this.copyToClipboard(this.decryptedData, '解密数据已复制到剪贴板'); }, // 复制到剪贴板通用方法 copyToClipboard(text, successMsg) { if (!text) { this.showMessage('没有可复制的内容', 'error'); return; } navigator.clipboard.writeText(text) .then(() => { this.showMessage(successMsg, 'success'); }) .catch(err => { this.showMessage('复制失败,请手动复制', 'error'); console.error('复制失败:', err); }); }, // 显示消息提示 showMessage(text, type = 'success') { this.message = text; this.messageType = type; // 3秒后自动隐藏消息 setTimeout(() => { this.message = ''; }, 3000); }, // 切换全屏模式 toggleFullscreen() { if (!this.isFullscreen) { // 进入全屏模式前保存当前内容 this.fullscreenContent = this.decryptedData; this.isFullscreen = true; // 进入全屏后自动聚焦文本框 this.$nextTick(() => { document.querySelector('.fullscreen-textarea').focus(); }); this.showMessage('已进入全屏编辑模式', 'success'); } else { // 退出全屏模式时更新内容 this.decryptedData = this.fullscreenContent; this.decryptToEncrypt(); // 同步到加密区域 this.isFullscreen = false; this.showMessage('已退出全屏编辑模式', 'success'); } }, // 同步全屏模式下的编辑内容 syncFullscreenChanges() { this.decryptedData = this.fullscreenContent; // 1. 先将中文翻译回英文 const englishText = this.translateToEnglish(this.fullscreenContent); // 2. 移除所有换行符、制表符、中文空格 let textWithoutNewlines = englishText.replace(/[\n\r\t ]/g, ''); // 3. 处理【】包裹的未知字符,提取原始字符 const unknownCharRegex = /【(.*?)】/g; textWithoutNewlines = textWithoutNewlines.replace(unknownCharRegex, (match, p1) => p1); // 4. 应用反向字符映射 let result = ''; for (const char of textWithoutNewlines) { if (this.reverseCipherMap.hasOwnProperty(char)) { result += this.reverseCipherMap[char]; } else { // 对于解密区新增的未知字符,直接保留 result += char; } } // 5. 更新楼层地砖数据 this.extractFloorTilesData(this.fullscreenContent); // 避免循环更新 if (this.encryptedData !== result) { this.encryptedData = result; } }, // 鼠标进入单元格时显示提示 handleMouseEnter(cellValue, event) { this.showTooltip = true; this.updateTooltipPosition(event); // 根据cellValue的类型和属性设置提示文字 if (typeof cellValue === 'object' && cellValue !== null) { if ('o' in cellValue) { switch (cellValue.o) { case 0: // 处理存在p属性的情况(怪物) if ('p' in cellValue) { switch (cellValue.p) { case 266: this.tooltipValue = `老鼠[${JSON.stringify(cellValue)}]`; break; case 273: this.tooltipValue = `蝙蝠[${JSON.stringify(cellValue)}]`; break; default: this.tooltipValue = `未知怪物[${JSON.stringify(cellValue)}]`; } } else this.tooltipValue = `未知[${JSON.stringify(cellValue)}]`; break; case 1: this.tooltipValue = `装备宝箱[${JSON.stringify(cellValue)}]`; break; case 3: if ('g' in cellValue) { this.tooltipValue = `金钱: ${cellValue.g}[${JSON.stringify(cellValue)}]`; } else this.tooltipValue = `未知[${JSON.stringify(cellValue)}]`; break; case 5: this.tooltipValue = `书柜装饰[${JSON.stringify(cellValue)}]`; break; case 6: this.tooltipValue = `剧情触发点[${JSON.stringify(cellValue)}]`; break; case 7: this.tooltipValue = `出口[${JSON.stringify(cellValue)}]`; break; default: this.tooltipValue = `未知[${JSON.stringify(cellValue)}]`; break; } } else this.tooltipValue = `未知[${JSON.stringify(cellValue)}]`; } else { // 非JSON对象的情况(墙、空等) this.tooltipValue = cellValue; } }, // 鼠标在单元格内移动时更新提示位置 handleMouseMove(event) { this.updateTooltipPosition(event); }, // 鼠标离开单元格时隐藏提示 handleMouseLeave() { this.showTooltip = false; }, // 更新提示框位置,考虑滚动偏移 updateTooltipPosition(event) { // 使用clientX和clientY获取相对于视口的位置 // 这样即使页面滚动,提示框也能正确显示 this.tooltipX = event.clientX + 10; this.tooltipY = event.clientY + 10; // 可选:检测边界,确保提示框不会超出视口 const tooltipWidth = 100; // 估算提示框宽度 const tooltipHeight = 30; // 估算提示框高度 // 如果右侧超出视口,调整到左侧 if (this.tooltipX + tooltipWidth > window.innerWidth) { this.tooltipX = event.clientX - tooltipWidth - 10; } // 如果底部超出视口,调整到上方 if (this.tooltipY + tooltipHeight > window.innerHeight) { this.tooltipY = event.clientY - tooltipHeight - 10; } } } }); </script> </body> </html>





