init
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "toolbox",
|
"name": "toolbox",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||||
"vue": "^3.4.0",
|
"vue": "^3.4.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
@@ -453,6 +454,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==",
|
||||||
|
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||||
"vue": "^3.4.0",
|
"vue": "^3.4.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
@@ -17,4 +18,3 @@
|
|||||||
"vite": "^5.0.0"
|
"vite": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="json-tree-node">
|
<div class="json-tree-node" :class="{ 'is-matched': isMatched, 'is-filtered': isFiltered }">
|
||||||
<div
|
<div
|
||||||
class="node-line"
|
class="node-line"
|
||||||
:class="{ 'has-children': hasChildren, 'is-expanded': isExpanded }"
|
:class="{ 'has-children': hasChildren, 'is-expanded': isExpanded, 'is-matched': isMatched }"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
<span v-if="hasChildren" class="expand-icon">
|
<span v-if="hasChildren" class="expand-icon">
|
||||||
{{ isExpanded ? '▼' : '▶' }}
|
<i :class="isExpanded ? 'fas fa-chevron-down' : 'fas fa-chevron-right'"></i>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="expand-placeholder"></span>
|
<span v-else class="expand-placeholder"></span>
|
||||||
|
<span v-if="showPath && nodePath" class="node-path">{{ nodePath }}:</span>
|
||||||
<span class="node-key" v-if="key !== null">
|
<span class="node-key" v-if="key !== null">
|
||||||
<span class="key-name">{{ key }}</span>:
|
<span class="key-name" :class="{ 'matched': isMatched }">{{ key }}</span>:
|
||||||
</span>
|
</span>
|
||||||
<span class="node-value" :class="valueType">
|
<span class="node-value" :class="[valueType, { 'matched': isMatched }]">
|
||||||
<span v-if="valueType === 'string'">"{{ displayValue }}"</span>
|
<span v-if="valueType === 'string'">"{{ displayValue }}"</span>
|
||||||
<span v-else>{{ displayValue }}</span>
|
<span v-else>{{ displayValue }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasChildren && isExpanded" class="node-children">
|
<div v-if="hasChildren && isExpanded && shouldShowChildren" class="node-children">
|
||||||
<JsonTreeNode
|
<JsonTreeNode
|
||||||
v-for="(child, index) in children"
|
v-for="(child, index) in filteredChildren"
|
||||||
:key="index"
|
:key="index"
|
||||||
:data="child.value"
|
:data="child.value"
|
||||||
:key-name="child.key"
|
:key-name="child.key"
|
||||||
:path="child.path"
|
:path="child.path"
|
||||||
:expanded="expanded"
|
:expanded="expanded"
|
||||||
|
:matched-paths="matchedPaths"
|
||||||
|
:json-path-query="jsonPathQuery"
|
||||||
@toggle="$emit('toggle', $event)"
|
@toggle="$emit('toggle', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +53,22 @@ const props = defineProps({
|
|||||||
expanded: {
|
expanded: {
|
||||||
type: Set,
|
type: Set,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
matchedPaths: {
|
||||||
|
type: Set,
|
||||||
|
default: () => new Set()
|
||||||
|
},
|
||||||
|
jsonPathQuery: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
showPath: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
nodePath: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -86,6 +105,73 @@ const isExpanded = computed(() => {
|
|||||||
return props.expanded.has(currentPath.value)
|
return props.expanded.has(currentPath.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 判断当前节点是否匹配 JSONPath
|
||||||
|
const isMatched = computed(() => {
|
||||||
|
if (!props.jsonPathQuery || !props.jsonPathQuery.trim()) return false
|
||||||
|
return props.matchedPaths.has(currentPath.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是否有筛选
|
||||||
|
const hasFilter = computed(() => {
|
||||||
|
return props.jsonPathQuery && props.jsonPathQuery.trim() !== ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是否应该显示子节点
|
||||||
|
const shouldShowChildren = computed(() => {
|
||||||
|
if (!hasFilter.value) return true
|
||||||
|
// 如果节点匹配,显示子节点(展开匹配节点的内容)
|
||||||
|
if (isMatched.value) return true
|
||||||
|
// 如果当前节点是匹配节点的子节点,显示所有子节点
|
||||||
|
if (isChildOfMatched.value) return true
|
||||||
|
// 检查是否有子节点匹配
|
||||||
|
return hasMatchingChildren.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查是否有子节点匹配
|
||||||
|
const hasMatchingChildren = computed(() => {
|
||||||
|
if (!hasFilter.value) return true
|
||||||
|
if (!hasChildren.value) return false
|
||||||
|
|
||||||
|
const basePath = props.path === '' && props.keyName === null ? 'root' : currentPath.value
|
||||||
|
|
||||||
|
if (Array.isArray(props.data)) {
|
||||||
|
return props.data.some((item, index) => {
|
||||||
|
const childPath = `${basePath}[${index}]`
|
||||||
|
return props.matchedPaths.has(childPath) || checkChildrenMatch(item, childPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.data === 'object' && props.data !== null) {
|
||||||
|
return Object.keys(props.data).some(key => {
|
||||||
|
const childPath = basePath === 'root' ? `root.${key}` : `${basePath}.${key}`
|
||||||
|
return props.matchedPaths.has(childPath) || checkChildrenMatch(props.data[key], childPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 递归检查子节点是否匹配
|
||||||
|
const checkChildrenMatch = (obj, path) => {
|
||||||
|
if (props.matchedPaths.has(path)) return true
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.some((item, index) => {
|
||||||
|
const childPath = `${path}[${index}]`
|
||||||
|
return props.matchedPaths.has(childPath) || checkChildrenMatch(item, childPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'object' && obj !== null) {
|
||||||
|
return Object.keys(obj).some(key => {
|
||||||
|
const childPath = `${path}.${key}`
|
||||||
|
return props.matchedPaths.has(childPath) || checkChildrenMatch(obj[key], childPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const valueType = computed(() => {
|
const valueType = computed(() => {
|
||||||
if (props.data === null) return 'null'
|
if (props.data === null) return 'null'
|
||||||
if (Array.isArray(props.data)) return 'array'
|
if (Array.isArray(props.data)) return 'array'
|
||||||
@@ -132,6 +218,57 @@ const children = computed(() => {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 筛选后的子节点
|
||||||
|
const filteredChildren = computed(() => {
|
||||||
|
if (!hasFilter.value) return children.value
|
||||||
|
|
||||||
|
// 如果当前节点匹配,显示所有子节点(不进行过滤)
|
||||||
|
if (isMatched.value) return children.value
|
||||||
|
|
||||||
|
// 如果当前节点是匹配节点的子节点,显示所有子节点(不进行过滤)
|
||||||
|
if (isChildOfMatched.value) return children.value
|
||||||
|
|
||||||
|
// 否则,只显示匹配的子节点或其子节点匹配的子节点
|
||||||
|
return children.value.filter(child => {
|
||||||
|
const childPath = child.path === 'root'
|
||||||
|
? (typeof child.key === 'number' ? `root[${child.key}]` : `root.${child.key}`)
|
||||||
|
: (typeof child.key === 'number' ? `${child.path}[${child.key}]` : `${child.path}.${child.key}`)
|
||||||
|
|
||||||
|
// 如果子节点匹配,显示
|
||||||
|
if (props.matchedPaths.has(childPath)) return true
|
||||||
|
|
||||||
|
// 如果子节点的子节点匹配,也显示
|
||||||
|
return checkChildrenMatch(child.value, childPath)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查当前节点是否是匹配节点的子节点
|
||||||
|
const isChildOfMatched = computed(() => {
|
||||||
|
if (!hasFilter.value || isMatched.value) return false
|
||||||
|
|
||||||
|
// 检查当前路径是否以任何匹配路径开头(作为前缀)
|
||||||
|
for (const matchedPath of props.matchedPaths) {
|
||||||
|
if (matchedPath === 'root') continue
|
||||||
|
|
||||||
|
// 如果当前路径以匹配路径开头,且后面跟着 . 或 [,说明是子节点
|
||||||
|
const prefix = matchedPath + '.'
|
||||||
|
const prefixBracket = matchedPath + '['
|
||||||
|
if (currentPath.value.startsWith(prefix) || currentPath.value.startsWith(prefixBracket)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是否被筛选(有筛选但当前节点不匹配且没有匹配的子节点,且不是匹配节点的子节点)
|
||||||
|
const isFiltered = computed(() => {
|
||||||
|
if (!hasFilter.value) return false
|
||||||
|
if (isMatched.value) return false
|
||||||
|
if (isChildOfMatched.value) return false
|
||||||
|
return !hasMatchingChildren.value
|
||||||
|
})
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
if (hasChildren.value) {
|
if (hasChildren.value) {
|
||||||
emit('toggle', currentPath.value)
|
emit('toggle', currentPath.value)
|
||||||
@@ -171,12 +308,24 @@ const toggle = () => {
|
|||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expand-icon i {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.expand-placeholder {
|
.expand-placeholder {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-path {
|
||||||
|
color: #666666;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.node-key {
|
.node-key {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
@@ -221,4 +370,27 @@ const toggle = () => {
|
|||||||
border-left: 1px solid #e0e0e0;
|
border-left: 1px solid #e0e0e0;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 匹配的节点高亮样式 */
|
||||||
|
.json-tree-node.is-matched .node-line {
|
||||||
|
background: #fff9e6;
|
||||||
|
border-left: 3px solid #ff9800;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-tree-node.is-matched .key-name.matched {
|
||||||
|
color: #ff6f00;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-tree-node.is-matched .node-value.matched {
|
||||||
|
color: #ff6f00;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 被筛选掉的节点隐藏 */
|
||||||
|
.json-tree-node.is-filtered {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { createApp } from 'vue'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
// 引入 Font Awesome 6.4
|
||||||
|
import '@fortawesome/fontawesome-free/css/all.css'
|
||||||
|
|
||||||
createApp(App).use(router).mount('#app')
|
createApp(App).use(router).mount('#app')
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,12 @@
|
|||||||
<Transition name="toast">
|
<Transition name="toast">
|
||||||
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
||||||
<div class="toast-content">
|
<div class="toast-content">
|
||||||
<svg v-if="toastType === 'error'" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i v-if="toastType === 'error'" class="fas fa-circle-exclamation"></i>
|
||||||
<circle cx="8" cy="8" r="6" stroke-width="1.5"/>
|
<i v-else class="fas fa-circle-info"></i>
|
||||||
<path d="M8 5v3M8 11h.01" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<svg v-else width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
|
||||||
<circle cx="8" cy="8" r="6" stroke-width="1.5"/>
|
|
||||||
<path d="M8 4v1M8 7v4" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>{{ toastMessage }}</span>
|
<span>{{ toastMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor">
|
<i class="fas fa-xmark"></i>
|
||||||
<path d="M3 3l8 8M11 3l-8 8" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -60,8 +52,12 @@
|
|||||||
<div class="input-header">
|
<div class="input-header">
|
||||||
<label class="input-label">RGB</label>
|
<label class="input-label">RGB</label>
|
||||||
<div class="copy-paste-buttons">
|
<div class="copy-paste-buttons">
|
||||||
<button @click="copyRgb" class="copy-btn" title="复制RGB">复制</button>
|
<button @click="copyRgb" class="copy-btn" title="复制RGB">
|
||||||
<button @click="pasteRgb" class="paste-btn" title="粘贴RGB">粘贴</button>
|
<i class="far fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="pasteRgb" class="paste-btn" title="粘贴RGB">
|
||||||
|
<i class="far fa-paste"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rgb-inputs">
|
<div class="rgb-inputs">
|
||||||
@@ -109,8 +105,12 @@
|
|||||||
<div class="input-header">
|
<div class="input-header">
|
||||||
<label class="input-label">十六进制</label>
|
<label class="input-label">十六进制</label>
|
||||||
<div class="copy-paste-buttons">
|
<div class="copy-paste-buttons">
|
||||||
<button @click="copyHex" class="copy-btn" title="复制十六进制">复制</button>
|
<button @click="copyHex" class="copy-btn" title="复制十六进制">
|
||||||
<button @click="pasteHex" class="paste-btn" title="粘贴十六进制">粘贴</button>
|
<i class="far fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="pasteHex" class="paste-btn" title="粘贴十六进制">
|
||||||
|
<i class="far fa-paste"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hex-input-wrapper">
|
<div class="hex-input-wrapper">
|
||||||
@@ -131,8 +131,12 @@
|
|||||||
<div class="input-header">
|
<div class="input-header">
|
||||||
<label class="input-label">HSL</label>
|
<label class="input-label">HSL</label>
|
||||||
<div class="copy-paste-buttons">
|
<div class="copy-paste-buttons">
|
||||||
<button @click="copyHsl" class="copy-btn" title="复制HSL">复制</button>
|
<button @click="copyHsl" class="copy-btn" title="复制HSL">
|
||||||
<button @click="pasteHsl" class="paste-btn" title="粘贴HSL">粘贴</button>
|
<i class="far fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="pasteHsl" class="paste-btn" title="粘贴HSL">
|
||||||
|
<i class="far fa-paste"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hsl-inputs">
|
<div class="hsl-inputs">
|
||||||
@@ -822,20 +826,29 @@ loadHistoryList()
|
|||||||
|
|
||||||
.copy-paste-buttons {
|
.copy-paste-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn,
|
.copy-btn,
|
||||||
.paste-btn {
|
.paste-btn {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem;
|
||||||
border: 1px solid #d0d0d0;
|
border: 1px solid #d0d0d0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn i,
|
||||||
|
.paste-btn i {
|
||||||
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn:hover,
|
.copy-btn:hover,
|
||||||
@@ -1024,7 +1037,8 @@ loadHistoryList()
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-content svg {
|
.toast-content svg,
|
||||||
|
.toast-content i {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1065,8 +1079,10 @@ loadHistoryList()
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-close-btn svg {
|
.toast-close-btn svg,
|
||||||
|
.toast-close-btn i {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast动画 */
|
/* Toast动画 */
|
||||||
|
|||||||
@@ -4,20 +4,12 @@
|
|||||||
<Transition name="toast">
|
<Transition name="toast">
|
||||||
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
||||||
<div class="toast-content">
|
<div class="toast-content">
|
||||||
<svg v-if="toastType === 'error'" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i v-if="toastType === 'error'" class="fas fa-circle-exclamation"></i>
|
||||||
<circle cx="8" cy="8" r="6" stroke-width="1.5"/>
|
<i v-else class="fas fa-circle-info"></i>
|
||||||
<path d="M8 5v3M8 11h.01" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<svg v-else width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
|
||||||
<circle cx="8" cy="8" r="6" stroke-width="1.5"/>
|
|
||||||
<path d="M8 4v1M8 7v4" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>{{ toastMessage }}</span>
|
<span>{{ toastMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor">
|
<i class="fas fa-xmark"></i>
|
||||||
<path d="M3 3l8 8M11 3l-8 8" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -87,31 +79,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="copyInputToClipboard" class="toolbar-icon-btn" title="复制">
|
<button @click="copyInputToClipboard" class="toolbar-icon-btn" title="复制">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-copy"></i>
|
||||||
<rect x="5" y="5" width="8" height="8" rx="1" stroke-width="1.5"/>
|
|
||||||
<path d="M3 11V3a2 2 0 0 1 2-2h8" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
<button @click="pasteFromClipboard" class="toolbar-icon-btn" title="粘贴">
|
<button @click="pasteFromClipboard" class="toolbar-icon-btn" title="粘贴">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-paste"></i>
|
||||||
<path d="M5 7h6M5 10h4" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
<rect x="3" y="3" width="10" height="10" rx="1" stroke-width="1.5"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
<button @click="encode" class="toolbar-icon-btn" title="编码">
|
<button @click="encode" class="toolbar-icon-btn" title="编码">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="fa-solid fa-code"></i>
|
||||||
<path d="M4 6l4 4 4-4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
<button @click="decode" class="toolbar-icon-btn" title="解码">
|
<button @click="decode" class="toolbar-icon-btn" title="解码">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<svg viewBox="150 -50 1100 1250" xmlns="http://www.w3.org/2000/svg" width="17" height="17"><path d="M285.352637 0.003641h111.956538v114.687184h-111.956538v282.621991a110.682235 110.682235 0 0 1-33.313896 81.282266 110.682235 110.682235 0 0 1-81.282266 33.313896 110.682235 110.682235 0 0 1 81.282266 33.313897 110.682235 110.682235 0 0 1 33.313896 81.282266v282.621991h111.956538v114.687184h-111.956538a188.050574 188.050574 0 0 1-80.007964-40.049493 93.570179 93.570179 0 0 1-34.67922-74.637691v-226.643722a110.682235 110.682235 0 0 0-33.313896-81.282267 110.682235 110.682235 0 0 0-81.282267-33.313896H0v-111.956537h55.978269a110.682235 110.682235 0 0 0 81.282266-33.313897 110.682235 110.682235 0 0 0 33.313896-81.282266V114.690825A113.776969 113.776969 0 0 1 285.261616 0.003641z m794.61835 0a113.776969 113.776969 0 0 1 114.687184 114.687184v226.643722a113.776969 113.776969 0 0 0 114.687185 114.687184H1365.323624v111.956538h-55.978268a113.776969 113.776969 0 0 0-114.687185 114.687184v226.643722a113.776969 113.776969 0 0 1-114.687184 114.687184h-111.956537V909.309175h111.956537V626.687184a113.776969 113.776969 0 0 1 114.687184-114.687184 113.776969 113.776969 0 0 1-114.687184-114.687184V114.690825h-111.956537V0.003641h111.956537zM682.661812 682.665453a54.612945 54.612945 0 0 1 55.978269 55.978269 58.799937 58.799937 0 0 1-16.019797 41.323795 54.612945 54.612945 0 0 1-80.007965 0 58.799937 58.799937 0 0 1-16.019797-41.323795 54.612945 54.612945 0 0 1 55.978269-55.978269z m-226.643721 0a54.612945 54.612945 0 0 1 55.978268 55.978269 58.799937 58.799937 0 0 1-16.019797 41.323795 52.246384 52.246384 0 0 1-40.049493 17.294099 59.164024 59.164024 0 0 1-58.708916-58.708916 52.246384 52.246384 0 0 1 17.294099-40.049493 58.799937 58.799937 0 0 1 41.505839-15.837754z m453.287443 0a58.799937 58.799937 0 0 1 41.323795 16.019797 52.246384 52.246384 0 0 1 17.294099 40.049493 59.164024 59.164024 0 0 1-58.708916 58.708916 52.246384 52.246384 0 0 1-40.049493-17.294099 58.799937 58.799937 0 0 1-16.019797-41.323795 54.612945 54.612945 0 0 1 55.978269-55.978269z" fill="#666666" p-id="26339"></path></svg>
|
||||||
<path d="M4 10l4-4 4 4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
<button @click="clearAll" class="toolbar-icon-btn" title="清空">
|
<button @click="clearAll" class="toolbar-icon-btn" title="清空">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-trash-can"></i>
|
||||||
<path d="M3 3l10 10M3 13L13 3" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,10 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
<button @click="copyOutputToClipboard" class="toolbar-icon-btn" title="复制输出">
|
<button @click="copyOutputToClipboard" class="toolbar-icon-btn" title="复制输出">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-copy"></i>
|
||||||
<rect x="5" y="5" width="8" height="8" rx="1" stroke-width="1.5"/>
|
|
||||||
<path d="M3 11V3a2 2 0 0 1 2-2h8" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -641,7 +618,8 @@ onUnmounted(() => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-content svg {
|
.toast-content svg,
|
||||||
|
.toast-content i {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,8 +663,10 @@ onUnmounted(() => {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-close-btn svg {
|
.toast-close-btn svg,
|
||||||
|
.toast-close-btn i {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 浮层提示动画 */
|
/* 浮层提示动画 */
|
||||||
@@ -991,8 +971,10 @@ onUnmounted(() => {
|
|||||||
background: #e5e5e5;
|
background: #e5e5e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-icon-btn svg {
|
.toolbar-icon-btn svg,
|
||||||
|
.toolbar-icon-btn i {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-container {
|
.editor-container {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
<p class="tool-description">{{ tool.description }}</p>
|
<p class="tool-description">{{ tool.description }}</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<p class="icp-info">苏ICP备2022013040号-1</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -155,5 +158,18 @@ const tools = ref([
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem 0;
|
||||||
|
margin-top: 3rem;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icp-info {
|
||||||
|
color: #999999;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,7 @@
|
|||||||
class="input-field"
|
class="input-field"
|
||||||
/>
|
/>
|
||||||
<button @click="showDateTimePicker = true" class="calendar-btn" title="选择日期时间">
|
<button @click="showDateTimePicker = true" class="calendar-btn" title="选择日期时间">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-calendar"></i>
|
||||||
<rect x="2" y="3" width="12" height="11" rx="1" stroke-width="1.5"/>
|
|
||||||
<path d="M5 1v4M11 1v4M2 7h12" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 日期时间选择器组件 -->
|
<!-- 日期时间选择器组件 -->
|
||||||
@@ -58,10 +55,7 @@
|
|||||||
class="input-field readonly"
|
class="input-field readonly"
|
||||||
/>
|
/>
|
||||||
<button @click="copyToClipboard(timestampOutput)" class="copy-btn" title="复制">
|
<button @click="copyToClipboard(timestampOutput)" class="copy-btn" title="复制">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-copy"></i>
|
||||||
<rect x="5" y="5" width="8" height="8" rx="1" stroke-width="1.5"/>
|
|
||||||
<path d="M3 11V5a2 2 0 0 1 2-2h6" stroke-width="1.5"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,10 +79,7 @@
|
|||||||
class="input-field readonly"
|
class="input-field readonly"
|
||||||
/>
|
/>
|
||||||
<button @click="copyToClipboard(dateStringOutput)" class="copy-btn" title="复制">
|
<button @click="copyToClipboard(dateStringOutput)" class="copy-btn" title="复制">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i class="far fa-copy"></i>
|
||||||
<rect x="5" y="5" width="8" height="8" rx="1" stroke-width="1.5"/>
|
|
||||||
<path d="M3 11V5a2 2 0 0 1 2-2h6" stroke-width="1.5"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,10 +89,12 @@
|
|||||||
<div class="conversion-label">当前时间戳:</div>
|
<div class="conversion-label">当前时间戳:</div>
|
||||||
<div class="current-timestamp-controls">
|
<div class="current-timestamp-controls">
|
||||||
<span class="current-timestamp-value">{{ currentTimestampDisplay }}</span>
|
<span class="current-timestamp-value">{{ currentTimestampDisplay }}</span>
|
||||||
<button @click="togglePause" class="control-btn" :title="isPaused ? '继续' : '暂停'">
|
<button @click="togglePause" class="control-btn-icon" :title="isPaused ? '继续' : '暂停'">
|
||||||
{{ isPaused ? '▶' : 'II' }} {{ isPaused ? '继续' : '暂停' }}
|
<i :class="isPaused ? 'fas fa-play' : 'fas fa-pause'"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="resetData" class="control-btn-icon" title="重置数据">
|
||||||
|
<i class="fas fa-rotate-right"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="resetData" class="control-btn" title="重置数据">C 重置数据</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,19 +109,12 @@
|
|||||||
<Transition name="toast">
|
<Transition name="toast">
|
||||||
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
<div v-if="toastMessage" class="toast-notification" :class="toastType">
|
||||||
<div class="toast-content">
|
<div class="toast-content">
|
||||||
<svg v-if="toastType === 'error'" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
<i v-if="toastType === 'error'" class="fas fa-circle-exclamation"></i>
|
||||||
<circle cx="8" cy="8" r="6" stroke-width="1.5"/>
|
<i v-else class="fas fa-circle-check"></i>
|
||||||
<path d="M8 5v3M8 11h.01" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
<svg v-else width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor">
|
|
||||||
<path d="M13 4L6 11L3 8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
<span>{{ toastMessage }}</span>
|
<span>{{ toastMessage }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
<button @click="closeToast" class="toast-close-btn" title="关闭">
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor">
|
<i class="fas fa-xmark"></i>
|
||||||
<path d="M3 3l8 8M11 3l-8 8" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -617,7 +603,7 @@ onUnmounted(() => {
|
|||||||
.current-timestamp-controls {
|
.current-timestamp-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-timestamp-value {
|
.current-timestamp-value {
|
||||||
@@ -651,6 +637,33 @@ onUnmounted(() => {
|
|||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control-btn-icon {
|
||||||
|
padding: 0.375rem;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #d0d0d0;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #666666;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn-icon:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-color: #1a1a1a;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn-icon:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
@@ -789,7 +802,8 @@ onUnmounted(() => {
|
|||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-content svg {
|
.toast-content svg,
|
||||||
|
.toast-content i {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,16 +890,18 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.current-timestamp-controls {
|
.current-timestamp-controls {
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: stretch;
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-timestamp-value {
|
.current-timestamp-value {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn {
|
.control-btn-icon {
|
||||||
width: 100%;
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-notification {
|
.toast-notification {
|
||||||
|
|||||||
Reference in New Issue
Block a user