Compare commits
6 Commits
main
...
c6d41d18b3
| Author | SHA1 | Date | |
|---|---|---|---|
| c6d41d18b3 | |||
| 6cd75e7599 | |||
|
|
231019fcd1 | ||
|
|
788f79dd76 | ||
| 06020aa084 | |||
| 07cac667ae |
66
deploy.sh
66
deploy.sh
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# 部署脚本:打包并通过 SSH 将构建产物同步到远程目录
|
||||
# 用法: ./deploy.sh <服务器IP> <SSH端口> <登录账户> <登录密码> [sudo]
|
||||
# 示例: ./deploy.sh 192.168.1.100 22 root mypassword
|
||||
# 若远程目录无写权限,加第5个参数 sudo:先传到 /tmp,再通过 sudo 拷到目标(会用到同一密码作为 sudo 密码)
|
||||
# 示例: ./deploy.sh 192.168.1.100 22 myuser mypassword sudo
|
||||
|
||||
if [ $# -lt 4 ]; then
|
||||
echo "用法: $0 <服务器IP> <SSH端口> <登录账户> <登录密码> [sudo]"
|
||||
echo "示例: $0 192.168.1.100 22 root mypassword"
|
||||
echo "远程目录无写权限时加第5参数: $0 <IP> <端口> <用户> <密码> sudo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HOST="$1"
|
||||
PORT="$2"
|
||||
USER="$3"
|
||||
PASSWORD="$4"
|
||||
USE_SUDO="${5:-}"
|
||||
REMOTE_DIR="/root/caddy/site/tool"
|
||||
REMOTE_TMP="/tmp/tool_deploy_$$"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DIST_DIR="${SCRIPT_DIR}/dist"
|
||||
SSH_OPTS="-p ${PORT} -o StrictHostKeyChecking=accept-new -T"
|
||||
|
||||
echo ">>> 正在打包..."
|
||||
cd "$SCRIPT_DIR"
|
||||
npm run build
|
||||
|
||||
if [ ! -d "$DIST_DIR" ]; then
|
||||
echo "错误: 构建产物目录 dist 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 sshpass 是否可用(用于非交互式传入密码)
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
echo "未找到 sshpass,请先安装:"
|
||||
echo " macOS: brew install sshpass"
|
||||
echo " Ubuntu: sudo apt-get install sshpass"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export SSHPASS="$PASSWORD"
|
||||
|
||||
if [ "$USE_SUDO" = "sudo" ]; then
|
||||
echo ">>> 正在同步到远程临时目录 ${REMOTE_TMP}"
|
||||
sshpass -e rsync -avz --delete \
|
||||
-e "ssh ${SSH_OPTS}" \
|
||||
"${DIST_DIR}/" \
|
||||
"${USER}@${HOST}:${REMOTE_TMP}/"
|
||||
echo ">>> 正在用 sudo 拷贝到目标目录 ${REMOTE_DIR}"
|
||||
# 只执行一次 sudo(读一次密码),在子 shell 里完成 rsync 与清理,避免第二次 sudo 无密码
|
||||
printf '%s\n' "$PASSWORD" | sshpass -e ssh ${SSH_OPTS} "${USER}@${HOST}" \
|
||||
"sudo -S sh -c 'rsync -avz --delete ${REMOTE_TMP}/ ${REMOTE_DIR}/ && rm -rf ${REMOTE_TMP}'"
|
||||
else
|
||||
echo ">>> 正在通过 SSH 同步到 ${USER}@${HOST}:${PORT} -> ${REMOTE_DIR}"
|
||||
sshpass -e rsync -avz --delete \
|
||||
-e "ssh ${SSH_OPTS}" \
|
||||
"${DIST_DIR}/" \
|
||||
"${USER}@${HOST}:${REMOTE_DIR}/"
|
||||
fi
|
||||
|
||||
unset SSHPASS
|
||||
echo ">>> 部署完成"
|
||||
@@ -2,7 +2,6 @@
|
||||
"name": "toolbox",
|
||||
"version": "1.0.0",
|
||||
"description": "A Vue-based toolbox application",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -11,7 +10,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"fflate": "^0.8.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.5"
|
||||
|
||||
@@ -257,13 +257,7 @@ const currentColor = computed(() => {
|
||||
return `rgb(${rgb.value.r}, ${rgb.value.g}, ${rgb.value.b})`
|
||||
})
|
||||
|
||||
/**
|
||||
* RGB转十六进制
|
||||
* @param {number} r - 红色值 (0-255)
|
||||
* @param {number} g - 绿色值 (0-255)
|
||||
* @param {number} b - 蓝色值 (0-255)
|
||||
* @returns {string} 十六进制颜色值(不含#号)
|
||||
*/
|
||||
// RGB转十六进制
|
||||
function rgbToHex(r, g, b) {
|
||||
const toHex = (n) => {
|
||||
const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16)
|
||||
@@ -272,13 +266,7 @@ function rgbToHex(r, g, b) {
|
||||
return (toHex(r) + toHex(g) + toHex(b)).toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB转HSL
|
||||
* @param {number} r - 红色值 (0-255)
|
||||
* @param {number} g - 绿色值 (0-255)
|
||||
* @param {number} b - 蓝色值 (0-255)
|
||||
* @returns {{h: number, s: number, l: number}} HSL对象
|
||||
*/
|
||||
// RGB转HSL
|
||||
function rgbToHsl(r, g, b) {
|
||||
r /= 255
|
||||
g /= 255
|
||||
@@ -314,11 +302,7 @@ function rgbToHsl(r, g, b) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制转RGB
|
||||
* @param {string} hex - 十六进制颜色值(不含#号)
|
||||
* @returns {{r: number, g: number, b: number}|null} RGB对象或null
|
||||
*/
|
||||
// 十六进制转RGB
|
||||
function hexToRgb(hex) {
|
||||
const result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
@@ -330,13 +314,7 @@ function hexToRgb(hex) {
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
* HSL转RGB
|
||||
* @param {number} h - 色相 (0-360)
|
||||
* @param {number} s - 饱和度 (0-100)
|
||||
* @param {number} l - 亮度 (0-100)
|
||||
* @returns {{r: number, g: number, b: number}} RGB对象
|
||||
*/
|
||||
// HSL转RGB
|
||||
function hslToRgb(h, s, l) {
|
||||
h /= 360
|
||||
s /= 100
|
||||
@@ -590,6 +568,7 @@ async function copyRgb() {
|
||||
await navigator.clipboard.writeText(rgbText)
|
||||
showToast('RGB已复制到剪贴板', 'info', 2000)
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err)
|
||||
showToast('复制失败:' + err.message)
|
||||
}
|
||||
}
|
||||
@@ -672,6 +651,7 @@ async function pasteRgb() {
|
||||
const text = await navigator.clipboard.readText()
|
||||
processPastedText(text, 'rgb')
|
||||
} catch (err) {
|
||||
console.error('粘贴失败:', err)
|
||||
// Clipboard API 失败,使用备用方法
|
||||
currentPasteType.value = 'rgb'
|
||||
pasteInputValue.value = ''
|
||||
@@ -702,6 +682,7 @@ async function copyHex() {
|
||||
await navigator.clipboard.writeText(hexText)
|
||||
showToast('十六进制已复制到剪贴板', 'info', 2000)
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err)
|
||||
showToast('复制失败:' + err.message)
|
||||
}
|
||||
}
|
||||
@@ -714,6 +695,7 @@ async function pasteHex() {
|
||||
const text = await navigator.clipboard.readText()
|
||||
processPastedText(text, 'hex')
|
||||
} catch (err) {
|
||||
console.error('粘贴失败:', err)
|
||||
// Clipboard API 失败,使用备用方法
|
||||
currentPasteType.value = 'hex'
|
||||
pasteInputValue.value = ''
|
||||
@@ -744,6 +726,7 @@ async function copyHsl() {
|
||||
await navigator.clipboard.writeText(hslText)
|
||||
showToast('HSL已复制到剪贴板', 'info', 2000)
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err)
|
||||
showToast('复制失败:' + err.message)
|
||||
}
|
||||
}
|
||||
@@ -756,6 +739,7 @@ async function pasteHsl() {
|
||||
const text = await navigator.clipboard.readText()
|
||||
processPastedText(text, 'hsl')
|
||||
} catch (err) {
|
||||
console.error('粘贴失败:', err)
|
||||
// Clipboard API 失败,使用备用方法
|
||||
currentPasteType.value = 'hsl'
|
||||
pasteInputValue.value = ''
|
||||
@@ -795,10 +779,7 @@ function handlePasteEvent(event) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前颜色到历史记录
|
||||
* 自动去重,最多保存50条记录
|
||||
*/
|
||||
// 保存到历史记录
|
||||
function saveToHistory() {
|
||||
const colorData = {
|
||||
rgb: `rgb(${rgb.value.r}, ${rgb.value.g}, ${rgb.value.b})`,
|
||||
@@ -822,7 +803,7 @@ function saveToHistory() {
|
||||
history = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取历史记录失败,忽略错误
|
||||
console.error('读取历史记录失败', e)
|
||||
}
|
||||
|
||||
// 检查是否与最后一条历史记录相同
|
||||
@@ -847,13 +828,11 @@ function saveToHistory() {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
|
||||
loadHistoryList()
|
||||
} catch (e) {
|
||||
// 保存历史记录失败,忽略错误
|
||||
console.error('保存历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从localStorage加载历史记录列表
|
||||
*/
|
||||
// 加载历史记录列表
|
||||
function loadHistoryList() {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
@@ -861,15 +840,12 @@ function loadHistoryList() {
|
||||
historyList.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载历史记录失败,重置为空数组
|
||||
console.error('加载历史记录失败', e)
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载历史记录到编辑器
|
||||
* @param {Object} colorData - 颜色数据对象
|
||||
*/
|
||||
// 加载历史记录
|
||||
function loadHistory(colorData) {
|
||||
updating.value = true
|
||||
rgb.value = { ...colorData.rgbValues }
|
||||
@@ -884,11 +860,7 @@ function toggleSidebar() {
|
||||
sidebarOpen.value = !sidebarOpen.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间戳为相对时间或日期字符串
|
||||
* @param {number} timestamp - 时间戳(毫秒)
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
// 格式化时间
|
||||
function formatTime(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const now = new Date()
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<!-- 左侧输入框 -->
|
||||
<div class="input-panel" :style="{ width: leftPanelWidth + '%' }">
|
||||
<div class="panel-header">
|
||||
<span class="panel-label">文本 A <span class="size-limit">(最大 {{ maxInputLabel }})</span></span>
|
||||
<span class="panel-label">文本 A</span>
|
||||
<div class="panel-actions">
|
||||
<button @click="copyToClipboard('left')" class="icon-btn" title="复制">
|
||||
<i class="far fa-copy"></i>
|
||||
@@ -139,7 +139,7 @@
|
||||
<!-- 右侧输入框 -->
|
||||
<div class="input-panel" :style="{ width: rightPanelWidth + '%' }">
|
||||
<div class="panel-header">
|
||||
<span class="panel-label">文本 B <span class="size-limit">(最大 {{ maxInputLabel }})</span></span>
|
||||
<span class="panel-label">文本 B</span>
|
||||
<div class="panel-actions">
|
||||
<button @click="copyToClipboard('right')" class="icon-btn" title="复制">
|
||||
<i class="far fa-copy"></i>
|
||||
@@ -276,10 +276,7 @@
|
||||
<script setup>
|
||||
import {ref, computed, watch, onMounted, onUnmounted} from 'vue'
|
||||
|
||||
// JSON对比模式:最大 2MB(受算法复杂度限制,O(n*m) 动态规划)
|
||||
// 文本对比模式:最大 5MB(算法复杂度较低)
|
||||
const MAX_INPUT_BYTES_JSON = 2 * 1024 * 1024 // 2MB for JSON comparison
|
||||
const MAX_INPUT_BYTES_TEXT = 5 * 1024 * 1024 // 5MB for text comparison
|
||||
const MAX_INPUT_BYTES = 500 * 1024 // 最大 500KB
|
||||
|
||||
const leftText = ref('')
|
||||
const rightText = ref('')
|
||||
@@ -314,15 +311,6 @@ const leftFullscreenResultRef = ref(null)
|
||||
const rightFullscreenResultRef = ref(null)
|
||||
const isScrolling = ref(false)
|
||||
|
||||
// 计算当前模式的最大输入限制
|
||||
const maxInputBytes = computed(() => {
|
||||
return compareMode.value === 'json' ? MAX_INPUT_BYTES_JSON : MAX_INPUT_BYTES_TEXT
|
||||
})
|
||||
|
||||
const maxInputLabel = computed(() => {
|
||||
return compareMode.value === 'json' ? '2MB' : '5MB'
|
||||
})
|
||||
|
||||
// 提示消息
|
||||
const toastMessage = ref('')
|
||||
const toastType = ref('error')
|
||||
@@ -365,12 +353,9 @@ const truncateToMaxBytes = (str, maxBytes) => {
|
||||
// 应用输入大小限制,超出则截断并提示
|
||||
const applyInputLimit = (side) => {
|
||||
const ref = side === 'left' ? leftText : rightText
|
||||
const maxBytes = compareMode.value === 'json' ? MAX_INPUT_BYTES_JSON : MAX_INPUT_BYTES_TEXT
|
||||
const maxBytesLabel = compareMode.value === 'json' ? '2MB' : '5MB'
|
||||
|
||||
if (getByteLength(ref.value) <= maxBytes) return
|
||||
ref.value = truncateToMaxBytes(ref.value, maxBytes)
|
||||
showToast(`内容已超过 ${maxBytesLabel} 限制,已自动截断`, 'info', 3000)
|
||||
if (getByteLength(ref.value) <= MAX_INPUT_BYTES) return
|
||||
ref.value = truncateToMaxBytes(ref.value, MAX_INPUT_BYTES)
|
||||
showToast(`内容已超过 500KB 限制,已自动截断`, 'info', 3000)
|
||||
if (side === 'left') updateLeftLineCount()
|
||||
else updateRightLineCount()
|
||||
}
|
||||
@@ -446,12 +431,9 @@ const pasteFromClipboard = async (side) => {
|
||||
try {
|
||||
let text = await navigator.clipboard.readText()
|
||||
if (text.trim()) {
|
||||
const maxBytes = compareMode.value === 'json' ? MAX_INPUT_BYTES_JSON : MAX_INPUT_BYTES_TEXT
|
||||
const maxBytesLabel = compareMode.value === 'json' ? '2MB' : '5MB'
|
||||
|
||||
if (getByteLength(text) > maxBytes) {
|
||||
text = truncateToMaxBytes(text, maxBytes)
|
||||
showToast(`粘贴内容已超过 ${maxBytesLabel} 限制,已自动截断`, 'info', 3000)
|
||||
if (getByteLength(text) > MAX_INPUT_BYTES) {
|
||||
text = truncateToMaxBytes(text, MAX_INPUT_BYTES)
|
||||
showToast('粘贴内容已超过 500KB 限制,已自动截断', 'info', 3000)
|
||||
}
|
||||
if (side === 'left') {
|
||||
leftText.value = text
|
||||
@@ -1289,6 +1271,7 @@ const compareListsIgnoreOrder = (listA, listB, ignoreOrder = false) => {
|
||||
|
||||
// 对比List(数组)- 寻找最佳匹配使相似度最高
|
||||
const compareLists = (listA, listB, ignoreOrder = false) => {
|
||||
|
||||
const n = listA.length
|
||||
const m = listB.length
|
||||
|
||||
@@ -1710,9 +1693,11 @@ const compareJson = (textA, textB) => {
|
||||
|
||||
// 执行节点对比,传递忽略列表顺序的参数
|
||||
const comparison = compareJsonNodes(jsonA, jsonB, ignoreListOrder.value)
|
||||
console.log(comparison)
|
||||
|
||||
// 转换为展示格式
|
||||
const displayResult = convertComparisonToDisplay(comparison, 0)
|
||||
console.log(displayResult)
|
||||
|
||||
// 计算统计信息
|
||||
const stats = calculateStats(comparison)
|
||||
@@ -1755,48 +1740,6 @@ const performCompare = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查输入大小限制
|
||||
const maxBytes = compareMode.value === 'json' ? MAX_INPUT_BYTES_JSON : MAX_INPUT_BYTES_TEXT
|
||||
const maxBytesLabel = compareMode.value === 'json' ? '2MB' : '5MB'
|
||||
const leftBytes = getByteLength(leftText.value)
|
||||
const rightBytes = getByteLength(rightText.value)
|
||||
|
||||
if (leftBytes > maxBytes || rightBytes > maxBytes) {
|
||||
showToast(`输入内容超过 ${maxBytesLabel} 限制,请减小输入大小`, 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
// JSON对比模式:检查是否可能因复杂度过高导致性能问题
|
||||
if (compareMode.value === 'json') {
|
||||
try {
|
||||
const jsonA = JSON.parse(leftText.value)
|
||||
const jsonB = JSON.parse(rightText.value)
|
||||
|
||||
// 检查是否有大型数组(可能影响性能)
|
||||
const checkLargeArray = (obj, depth = 0) => {
|
||||
if (depth > 10) return false // 防止无限递归
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length > 1000) return true
|
||||
// 检查嵌套数组
|
||||
for (const item of obj.slice(0, 10)) {
|
||||
if (checkLargeArray(item, depth + 1)) return true
|
||||
}
|
||||
} else if (obj && typeof obj === 'object') {
|
||||
for (const key in obj) {
|
||||
if (checkLargeArray(obj[key], depth + 1)) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (checkLargeArray(jsonA) || checkLargeArray(jsonB)) {
|
||||
showToast('检测到大型数组,对比可能需要较长时间,请耐心等待...', 'info', 5000)
|
||||
}
|
||||
} catch (e) {
|
||||
// JSON解析失败会在下面处理
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (compareMode.value === 'json') {
|
||||
compareResult.value = compareJson(leftText.value, rightText.value)
|
||||
@@ -1835,6 +1778,77 @@ const copyResult = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const compareTest = () => {
|
||||
let total = {
|
||||
left: [],
|
||||
right: [],
|
||||
stats: {same: 0, insert: 0, delete: 0, modify: 0}
|
||||
}
|
||||
const cases = [
|
||||
["[]","[\"a\"]"],
|
||||
["[\"a\"]", "[]"],
|
||||
["[[\"a\",\"b\"]]", "[]"],
|
||||
["[]","[\"a\",\"b\"]"],
|
||||
["[]","[[\"a\",\"b\"]]"],
|
||||
["{}","{\"a\": {\"c\":\"d\"}}"],
|
||||
["{}", "{\"a\":\"b\"}"],
|
||||
["[\"a\",\"b\"]", "[]"],
|
||||
["{\"a\": {\"c\":\"d\"}}", "[]"],
|
||||
["{\"a\":\"b\"}", "{}"],
|
||||
["{\"a\":\"a\",\"b\":\"b\",\"d\":null}", "{\"a\":\"a\",\"c\":\"c\"}"],
|
||||
["[\"a\",\"b\",null]", "[\"a\",\"c\"]"],
|
||||
["[{\"a\":\"a\"},{\"b\":\"b\"},null]", "[{\"a\":\"a\"},{\"c\":\"c\"}]"],
|
||||
["{\"a\":\"a\",\"b\":\"b\",\"c\":null}", "{\"a\":\"a\",\"c\":\"c\"}"],
|
||||
["[{\"c\":\"d\"}]","[[\"c\",\"d\"]]"],
|
||||
["{\"a\": {\"c\":\"d\"}}","{\"a\": [\"c\",\"d\"]}"],
|
||||
["{\"a\": {\"c\":\"d\"}}","[\"c\",\"d\"]"],
|
||||
["{\"a\": {\"c\":\"d\"}}","{\"a\": null}"],
|
||||
["{\"a\": null}","{\"a\": {\"c\":\"d\"}}"],
|
||||
["{\"a\": {\"c\":\"d\"}}","{}"],
|
||||
["{\"a\":\"a1\"}", "{\"b\":\"b2\"}"],
|
||||
["{\"a\":{\"c\":\"d\",\"e\":\"f\"}}", "{\"b\":{\"c\":\"d\",\"m\":\"l\"}}"],
|
||||
["{\"a\":{\"c\":\"d\",\"e\":\"f\"}}", "{\"a\":{\"c\":\"d\",\"e\":\"l\"}}"],
|
||||
["{\"a\": {\"c\":\"d\"}}", "{\"a\":[\"c\",\"d\"]}"],
|
||||
["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"c\":\"b1\"},{\"a\":\"a1\"}]"],
|
||||
["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"b\":\"b1\"},{\"a\":\"a1\"}]"],
|
||||
["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"c\":\"b1\"},{\"a\":\"a1\"}]"],
|
||||
["{\"a\":{\"c\":\"d\",\"e\":\"f\"},\"c\":\"a\",\"d\":\"e\",\"g\":[\"a\",\"b\",\"c\"],\"h\":[\"a\"],\"i\":[{\"a\":\"a1\"},{\"b\":\"b2\"},{\"c\":\"b1\"}]}", "{\"b\":{\"c\":\"d\",\"m\":\"l\"},\"c\":\"a\",\"d\":\"f\",\"g\":[\"a\",\"c\",\"b\"],\"h\":[\"b\",\"a\"],\"i\":[{\"b\":\"b1\"},{\"a\":\"a1\"},{\"d\":\"b1\"}]}"],
|
||||
["[[[\"a\",\"b\"],[]]]","[[[\"a\",\"b\",\"c\"],[\"b\"]],[\"c\"]]"]
|
||||
]
|
||||
for (let item of cases) {
|
||||
console.log("=================================================")
|
||||
console.log(item)
|
||||
console.log("=================================================")
|
||||
let res = compareJson(item[0], item[1])
|
||||
total.left.push({type: 'same', content: ''}, {
|
||||
type: 'same',
|
||||
content: '====================================='
|
||||
}, {type: 'same', content: ''}, {type: 'same', content: item[0]}, {type: 'same', content: ''}, {
|
||||
type: 'same',
|
||||
content: '-----------------------------------'
|
||||
})
|
||||
total.right.push({type: 'same', content: ''}, {
|
||||
type: 'same',
|
||||
content: '====================================='
|
||||
}, {type: 'same', content: ''}, {type: 'same', content: item[1]}, {type: 'same', content: ''}, {
|
||||
type: 'same',
|
||||
content: '-----------------------------------'
|
||||
})
|
||||
total.left.push({type: 'same', content: JSON.stringify(res.stats)}, {
|
||||
type: 'same',
|
||||
content: '-----------------------------------'
|
||||
})
|
||||
total.right.push({type: 'same', content: JSON.stringify(res.stats)}, {
|
||||
type: 'same',
|
||||
content: '-----------------------------------'
|
||||
})
|
||||
total.left.push(...res.left)
|
||||
total.right.push(...res.right)
|
||||
}
|
||||
compareResult.value = total
|
||||
}
|
||||
|
||||
// 切换侧栏
|
||||
const toggleSidebar = () => {
|
||||
sidebarOpen.value = !sidebarOpen.value
|
||||
@@ -1898,7 +1912,7 @@ const saveToHistory = () => {
|
||||
history = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取历史记录失败,忽略错误
|
||||
console.error('读取历史记录失败', e)
|
||||
}
|
||||
|
||||
// 检查是否与最新记录相同,如果相同则不保存
|
||||
@@ -1926,7 +1940,7 @@ const saveToHistory = () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
|
||||
loadHistoryList()
|
||||
} catch (e) {
|
||||
// 保存历史记录失败,忽略错误
|
||||
console.error('保存历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1938,7 +1952,7 @@ const loadHistoryList = () => {
|
||||
historyList.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载历史记录失败,忽略错误
|
||||
console.error('加载历史记录失败', e)
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
@@ -1959,6 +1973,7 @@ onMounted(() => {
|
||||
updateLeftLineCount()
|
||||
updateRightLineCount()
|
||||
loadHistoryList()
|
||||
// compareTest()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -2452,13 +2467,6 @@ onUnmounted(() => {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.panel-label .size-limit {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
color: #999999;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
item.encodingType === 'base64' ? 'Base64' :
|
||||
item.encodingType === 'url' ? 'URL' :
|
||||
item.encodingType === 'unicode' ? 'Unicode' :
|
||||
item.encodingType === 'zlib' ? 'Zlib' :
|
||||
item.encodingType
|
||||
}}</span>
|
||||
<span class="history-time">{{ formatTime(item.time) }}</span>
|
||||
@@ -78,13 +77,6 @@
|
||||
>
|
||||
Unicode
|
||||
</button>
|
||||
<button
|
||||
@click="encodingType = 'zlib'"
|
||||
:class="['type-btn', { active: encodingType === 'zlib' }]"
|
||||
title="Zlib 压缩/解压"
|
||||
>
|
||||
Zlib
|
||||
</button>
|
||||
</div>
|
||||
<button @click="copyInputToClipboard" class="toolbar-icon-btn" title="复制">
|
||||
<i class="far fa-copy"></i>
|
||||
@@ -159,11 +151,10 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { zlibSync, decompressSync } from 'fflate'
|
||||
|
||||
const inputText = ref('')
|
||||
const outputText = ref('')
|
||||
const encodingType = ref('base64') // 'base64'、'url'、'unicode' 或 'zlib'
|
||||
const encodingType = ref('base64') // 'base64'、'url' 或 'unicode'
|
||||
const leftPanelWidth = ref(50)
|
||||
const rightPanelWidth = ref(50)
|
||||
const isResizing = ref(false)
|
||||
@@ -228,26 +219,6 @@ watch(() => outputText.value, () => {
|
||||
updateOutputLineCount()
|
||||
})
|
||||
|
||||
// Zlib: Uint8Array -> Base64(用于显示/复制)
|
||||
const bytesToBase64 = (bytes) => {
|
||||
let binary = ''
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i])
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
|
||||
// Zlib: Base64 -> Uint8Array(自动去除空白字符)
|
||||
const base64ToBytes = (str) => {
|
||||
const clean = str.replace(/\s/g, '')
|
||||
const binary = atob(clean)
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
// Unicode 编码
|
||||
const encodeUnicode = (text) => {
|
||||
let result = ''
|
||||
@@ -350,11 +321,6 @@ const encode = () => {
|
||||
result = encodeURIComponent(inputText.value)
|
||||
} else if (encodingType.value === 'unicode') {
|
||||
result = encodeUnicode(inputText.value)
|
||||
} else if (encodingType.value === 'zlib') {
|
||||
const utf8 = new TextEncoder().encode(inputText.value)
|
||||
// 使用 zlib 格式,与 Java Deflater/Inflater 默认格式兼容
|
||||
const compressed = zlibSync(utf8)
|
||||
result = bytesToBase64(compressed)
|
||||
}
|
||||
outputText.value = result
|
||||
|
||||
@@ -386,11 +352,6 @@ const decode = () => {
|
||||
result = decodeURIComponent(inputText.value)
|
||||
} else if (encodingType.value === 'unicode') {
|
||||
result = decodeUnicode(inputText.value)
|
||||
} else if (encodingType.value === 'zlib') {
|
||||
const compressed = base64ToBytes(inputText.value.trim())
|
||||
// decompressSync 自动识别 zlib / raw deflate / gzip,可解 Java Deflater 输出
|
||||
const decompressed = decompressSync(compressed)
|
||||
result = new TextDecoder().decode(decompressed)
|
||||
}
|
||||
outputText.value = result
|
||||
|
||||
@@ -407,7 +368,6 @@ const decode = () => {
|
||||
const typeName = encodingType.value === 'base64' ? 'Base64' :
|
||||
encodingType.value === 'url' ? 'URL' :
|
||||
encodingType.value === 'unicode' ? 'Unicode' :
|
||||
encodingType.value === 'zlib' ? 'Zlib' :
|
||||
encodingType.value
|
||||
showToast(`解码失败:请检查输入是否为有效的${typeName}编码字符串`)
|
||||
outputText.value = ''
|
||||
@@ -524,7 +484,7 @@ const saveToHistory = (item) => {
|
||||
history = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取历史记录失败,忽略错误
|
||||
console.error('读取历史记录失败', e)
|
||||
}
|
||||
|
||||
// 避免重复保存相同的记录
|
||||
@@ -550,7 +510,7 @@ const saveToHistory = (item) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
|
||||
loadHistoryList()
|
||||
} catch (e) {
|
||||
// 保存历史记录失败,忽略错误
|
||||
console.error('保存历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +522,7 @@ const loadHistoryList = () => {
|
||||
historyList.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载历史记录失败,重置为空数组
|
||||
console.error('加载历史记录失败', e)
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<div class="hero-section">
|
||||
<h2 class="hero-title">
|
||||
今天是{{ `${new Date().getFullYear()}年${new Date().getMonth() + 1}月${new Date().getDate()}日` }}
|
||||
</h2>
|
||||
<p id="jinrishici-sentence" class="hero-subtitle"></p>
|
||||
<h2 class="hero-title">今天是{{`${new Date().getFullYear()}年${new Date().getMonth()+1}月${new Date().getDate()}日`}}</h2>
|
||||
<p class="hero-subtitle">凭君莫话封侯事,一将功成万骨枯。</p>
|
||||
</div>
|
||||
<div class="tools-grid">
|
||||
<router-link
|
||||
@@ -24,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const tools = ref([
|
||||
{
|
||||
@@ -63,14 +61,6 @@ const tools = ref([
|
||||
description: '颜色格式转换'
|
||||
}
|
||||
])
|
||||
onMounted(() => {
|
||||
let words_script = document.createElement('script');
|
||||
words_script.charset = "utf-8";
|
||||
words_script.src= "https://sdk.jinrishici.com/v2/browser/jinrishici.js";
|
||||
document.body.appendChild(words_script);
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -177,6 +167,9 @@ onMounted(() => {
|
||||
.tool-card {
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="left-panel" :style="{ width: leftPanelWidth + '%' }">
|
||||
<div class="panel-toolbar">
|
||||
<div class="view-tabs">
|
||||
<button class="view-tab active">编辑器 <span class="size-limit">(最大 5MB)</span></button>
|
||||
<button class="view-tab active">编辑器</button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<button @click="copyToClipboard" class="toolbar-icon-btn" title="复制">
|
||||
@@ -199,13 +199,6 @@
|
||||
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
|
||||
import JsonTreeNode from '../components/JsonTreeNode.vue'
|
||||
|
||||
// 最大输入限制:5MB(JSON格式化工具的限制)
|
||||
// 主要考虑因素:
|
||||
// 1. JSON.parse 可以处理更大的 JSON(10-50MB)
|
||||
// 2. DOM 渲染是主要瓶颈:大型 JSON 会创建大量 DOM 节点
|
||||
// 3. 路径遍历和展开/折叠状态管理也会消耗内存
|
||||
const MAX_INPUT_BYTES = 5 * 1024 * 1024 // 5MB
|
||||
|
||||
const inputJson = ref('')
|
||||
const sidebarOpen = ref(false)
|
||||
const leftPanelWidth = ref(50)
|
||||
@@ -609,7 +602,7 @@ const handleJsonPathInput = () => {
|
||||
}
|
||||
} catch (e) {
|
||||
matchedPaths.value.clear()
|
||||
// JSONPath 解析错误,忽略
|
||||
console.error('JSONPath 解析错误:', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +631,7 @@ const loadJsonPathHistory = () => {
|
||||
jsonPathHistory.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载 JSONPath 历史记录失败,重置为空数组
|
||||
console.error('加载 JSONPath 历史记录失败', e)
|
||||
jsonPathHistory.value = []
|
||||
}
|
||||
}
|
||||
@@ -667,7 +660,7 @@ const saveJsonPathHistory = (jsonPath) => {
|
||||
try {
|
||||
localStorage.setItem(JSONPATH_HISTORY_KEY, JSON.stringify(jsonPathHistory.value))
|
||||
} catch (e) {
|
||||
// 保存 JSONPath 历史记录失败,忽略错误
|
||||
console.error('保存 JSONPath 历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,28 +699,8 @@ const copyMatchedResults = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字符串 UTF-8 字节长度
|
||||
const getByteLength = (str) => new TextEncoder().encode(str).length
|
||||
|
||||
// 将字符串截断到最大字节数(避免切断多字节字符)
|
||||
const truncateToMaxBytes = (str, maxBytes) => {
|
||||
if (getByteLength(str) <= maxBytes) return str
|
||||
let end = str.length
|
||||
while (end > 0 && getByteLength(str.slice(0, end)) > maxBytes) end--
|
||||
return str.slice(0, end)
|
||||
}
|
||||
|
||||
// 应用输入大小限制,超出则截断并提示
|
||||
const applyInputLimit = () => {
|
||||
if (getByteLength(inputJson.value) <= MAX_INPUT_BYTES) return
|
||||
inputJson.value = truncateToMaxBytes(inputJson.value, MAX_INPUT_BYTES)
|
||||
showToast('内容已超过 5MB 限制,已自动截断', 'info', 3000)
|
||||
updateLineCount()
|
||||
}
|
||||
|
||||
// 更新行号
|
||||
const updateLineCount = () => {
|
||||
applyInputLimit()
|
||||
if (inputJson.value) {
|
||||
lineCount.value = inputJson.value.split('\n').length
|
||||
} else {
|
||||
@@ -876,21 +849,12 @@ const getAllPaths = (obj, prefix = 'root') => {
|
||||
|
||||
// 监听输入变化,实时更新树形结构
|
||||
watch(inputJson, () => {
|
||||
// 先应用大小限制
|
||||
applyInputLimit()
|
||||
|
||||
updateLineCount()
|
||||
// 使用nextTick确保DOM更新后再调整高度
|
||||
setTimeout(() => {
|
||||
adjustTextareaHeight()
|
||||
}, 0)
|
||||
if (inputJson.value.trim()) {
|
||||
// 检查大小,如果超过限制则不解析(避免性能问题)
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
treeLineCount.value = 1
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(inputJson.value)
|
||||
expandedNodes.value.clear()
|
||||
@@ -915,24 +879,9 @@ const formatJson = () => {
|
||||
showToast('请输入JSON数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查输入大小
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
showToast(`输入内容超过 5MB 限制,无法格式化`, 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(inputJson.value)
|
||||
const formatted = JSON.stringify(parsed, null, 2)
|
||||
|
||||
// 检查格式化后的大小
|
||||
if (getByteLength(formatted) > MAX_INPUT_BYTES) {
|
||||
showToast('格式化后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
inputJson.value = formatted
|
||||
inputJson.value = JSON.stringify(parsed, null, 2)
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
showToast('格式化成功', 'info', 2000)
|
||||
@@ -947,24 +896,9 @@ const minifyJson = () => {
|
||||
showToast('请输入JSON数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查输入大小
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
showToast(`输入内容超过 5MB 限制,无法压缩`, 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(inputJson.value)
|
||||
const minified = JSON.stringify(parsed)
|
||||
|
||||
// 检查压缩后的大小(压缩后应该更小,但为了安全还是检查)
|
||||
if (getByteLength(minified) > MAX_INPUT_BYTES) {
|
||||
showToast('压缩后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
inputJson.value = minified
|
||||
inputJson.value = JSON.stringify(parsed)
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
showToast('压缩成功', 'info', 2000)
|
||||
@@ -979,13 +913,6 @@ const escapeJson = () => {
|
||||
showToast('请输入JSON数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查输入大小
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
showToast(`输入内容超过 5MB 限制,无法转义`, 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let jsonToEscape = inputJson.value.trim()
|
||||
|
||||
@@ -1039,20 +966,8 @@ const escapeJson = () => {
|
||||
// 不是JSON,保持原样
|
||||
}
|
||||
// 添加引号并转义
|
||||
const escaped = JSON.stringify(jsonToEscape)
|
||||
// 检查转义后的大小
|
||||
if (getByteLength(escaped) > MAX_INPUT_BYTES) {
|
||||
showToast('转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
inputJson.value = JSON.stringify(jsonToEscape)
|
||||
}
|
||||
inputJson.value = escaped
|
||||
}
|
||||
}
|
||||
|
||||
// 最后检查一次大小(防止前面的分支没有检查)
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
showToast('转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
updateLineCount()
|
||||
@@ -1069,13 +984,6 @@ const unescapeJson = () => {
|
||||
showToast('请输入JSON数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查输入大小
|
||||
if (getByteLength(inputJson.value) > MAX_INPUT_BYTES) {
|
||||
showToast(`输入内容超过 5MB 限制,无法取消转义`, 'error', 4000)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let jsonToParse = inputJson.value.trim()
|
||||
|
||||
@@ -1119,24 +1027,13 @@ const unescapeJson = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(unescaped)
|
||||
// 如果解析成功,自动格式化
|
||||
const formatted = JSON.stringify(parsed, null, 2)
|
||||
// 检查格式化后的大小
|
||||
if (getByteLength(formatted) > MAX_INPUT_BYTES) {
|
||||
showToast('取消转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
inputJson.value = formatted
|
||||
inputJson.value = JSON.stringify(parsed, null, 2)
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
showToast('取消转义并格式化成功', 'info', 2000)
|
||||
return
|
||||
} catch (e) {
|
||||
// 如果解析失败,说明只是普通字符串,保持原样
|
||||
// 检查字符串大小
|
||||
if (getByteLength(unescaped) > MAX_INPUT_BYTES) {
|
||||
showToast('取消转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
inputJson.value = unescaped
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
@@ -1147,25 +1044,13 @@ const unescapeJson = () => {
|
||||
|
||||
// 如果取消转义后是对象或数组,自动格式化
|
||||
if (typeof unescaped === 'object' && unescaped !== null) {
|
||||
const formatted = JSON.stringify(unescaped, null, 2)
|
||||
// 检查格式化后的大小
|
||||
if (getByteLength(formatted) > MAX_INPUT_BYTES) {
|
||||
showToast('取消转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
inputJson.value = formatted
|
||||
inputJson.value = JSON.stringify(unescaped, null, 2)
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
showToast('取消转义并格式化成功', 'info', 2000)
|
||||
} else {
|
||||
// 其他类型(数字、布尔值等),转换为字符串
|
||||
const result = String(unescaped)
|
||||
// 检查结果大小
|
||||
if (getByteLength(result) > MAX_INPUT_BYTES) {
|
||||
showToast('取消转义后的内容超过 5MB 限制,无法显示', 'error', 4000)
|
||||
return
|
||||
}
|
||||
inputJson.value = result
|
||||
inputJson.value = String(unescaped)
|
||||
updateLineCount()
|
||||
resetEditorScroll()
|
||||
showToast('取消转义成功', 'info', 2000)
|
||||
@@ -1194,13 +1079,8 @@ const pasteFromClipboard = async () => {
|
||||
// 优先使用现代 Clipboard API(需要 HTTPS 或 localhost)
|
||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||
try {
|
||||
let text = await navigator.clipboard.readText()
|
||||
const text = await navigator.clipboard.readText()
|
||||
if (text.trim()) {
|
||||
// 检查大小限制
|
||||
if (getByteLength(text) > MAX_INPUT_BYTES) {
|
||||
text = truncateToMaxBytes(text, MAX_INPUT_BYTES)
|
||||
showToast('粘贴内容已超过 5MB 限制,已自动截断', 'info', 3000)
|
||||
}
|
||||
inputJson.value = text
|
||||
updateLineCount()
|
||||
// 粘贴后不重置滚动位置,保持在当前位置
|
||||
@@ -1249,16 +1129,6 @@ const handlePaste = async (event) => {
|
||||
const pastedText = event.clipboardData?.getData('text') || ''
|
||||
|
||||
if (pastedText.trim()) {
|
||||
// 检查大小限制
|
||||
if (getByteLength(pastedText) > MAX_INPUT_BYTES) {
|
||||
event.preventDefault()
|
||||
const truncated = truncateToMaxBytes(pastedText, MAX_INPUT_BYTES)
|
||||
inputJson.value = truncated
|
||||
showToast('粘贴内容已超过 5MB 限制,已自动截断', 'info', 3000)
|
||||
updateLineCount()
|
||||
return
|
||||
}
|
||||
|
||||
// 等待下一个tick,确保inputJson已更新
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
@@ -1293,7 +1163,7 @@ const saveToHistory = (json) => {
|
||||
history = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取历史记录失败,忽略错误
|
||||
console.error('读取历史记录失败', e)
|
||||
}
|
||||
|
||||
// 添加到开头
|
||||
@@ -1309,7 +1179,7 @@ const saveToHistory = (json) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
|
||||
loadHistoryList()
|
||||
} catch (e) {
|
||||
// 保存历史记录失败,忽略错误
|
||||
console.error('保存历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,7 +1191,7 @@ const loadHistoryList = () => {
|
||||
historyList.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载历史记录失败,重置为空数组
|
||||
console.error('加载历史记录失败', e)
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
@@ -1767,13 +1637,6 @@ onUnmounted(() => {
|
||||
border-bottom-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.view-tab .size-limit {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.7;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
|
||||
@@ -221,7 +221,7 @@ const saveToHistory = (text) => {
|
||||
history = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 读取历史记录失败,忽略错误
|
||||
console.error('读取历史记录失败', e)
|
||||
}
|
||||
|
||||
// 避免重复保存相同的记录
|
||||
@@ -243,7 +243,7 @@ const saveToHistory = (text) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
|
||||
loadHistoryList()
|
||||
} catch (e) {
|
||||
// 保存历史记录失败,忽略错误
|
||||
console.error('保存历史记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ const loadHistoryList = () => {
|
||||
historyList.value = JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
// 加载历史记录失败,重置为空数组
|
||||
console.error('加载历史记录失败', e)
|
||||
historyList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user