工具完成
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
2025-06-28 22:38:49 +08:00
parent 2c668fedd0
commit 8400dbfab9
60 changed files with 23197 additions and 144 deletions

View File

@ -0,0 +1,565 @@
<template>
<div class="space-y-6">
<!-- 工具栏 -->
<div class="card p-4">
<div class="flex flex-wrap gap-2">
<button
@click="formatCode"
:disabled="!inputCode.trim() || isFormatting"
class="btn-primary"
>
<FontAwesomeIcon
:icon="isFormatting ? ['fas', 'spinner'] : ['fas', 'code']"
:class="['mr-2', isFormatting && 'animate-spin']"
/>
{{ t('tools.code_formatter.format') }}
</button>
<button
@click="minifyCode"
:disabled="!inputCode.trim() || isFormatting"
class="btn-secondary"
>
<FontAwesomeIcon :icon="['fas', 'compress']" class="mr-2" />
{{ t('tools.code_formatter.minify') }}
</button>
<button
@click="copyToClipboard"
:disabled="!outputCode"
class="btn-secondary"
>
<FontAwesomeIcon
:icon="copied ? ['fas', 'check'] : ['fas', 'copy']"
:class="['mr-2', copied && 'text-success']"
/>
{{ t('tools.code_formatter.copy') }}
</button>
<button
@click="clearAll"
class="btn-secondary"
>
<FontAwesomeIcon :icon="['fas', 'trash']" class="mr-2" />
{{ t('tools.code_formatter.clear') }}
</button>
</div>
</div>
<!-- 语言选择和设置 -->
<div class="card p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-secondary mb-2">
{{ t('tools.code_formatter.language') }}
</label>
<select v-model="selectedLanguage" class="select-field" @change="handleLanguageChange">
<option v-for="lang in supportedLanguages" :key="lang.value" :value="lang.value">
{{ lang.label }}
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-secondary mb-2">
{{ t('tools.code_formatter.indent_size') }}
</label>
<select v-model="indentSize" class="select-field">
<option value="2">2 空格</option>
<option value="4">4 空格</option>
<option value="tab">制表符</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-secondary mb-2">
{{ t('tools.code_formatter.line_width') }}
</label>
<input
v-model="lineWidth"
type="number"
min="80"
max="200"
class="input-field"
placeholder="120"
>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 输入区域 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-primary">{{ t('tools.code_formatter.input') }}</h3>
<div class="flex space-x-2">
<button
@click="pasteFromClipboard"
class="p-2 rounded text-secondary hover:text-primary transition-colors"
title="粘贴"
>
<FontAwesomeIcon :icon="['fas', 'clipboard']" />
</button>
<button
@click="loadExample"
class="p-2 rounded text-secondary hover:text-primary transition-colors"
title="加载示例"
>
<FontAwesomeIcon :icon="['fas', 'file-code']" />
</button>
</div>
</div>
<textarea
v-model="inputCode"
:placeholder="getPlaceholder()"
class="textarea-field font-mono text-sm"
style="height: 500px; resize: vertical;"
@input="handleInputChange"
/>
<div class="text-sm text-secondary mt-2">
行数: {{ inputLines }} | 字符数: {{ inputCode.length }}
</div>
</div>
<!-- 输出区域 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-primary">{{ t('tools.code_formatter.output') }}</h3>
<div class="text-sm text-secondary">
{{ outputStats }}
</div>
</div>
<textarea
v-model="outputCode"
:placeholder="t('tools.code_formatter.output_placeholder')"
class="textarea-field font-mono text-sm"
style="height: 500px; resize: vertical;"
readonly
/>
<div v-if="outputCode" class="text-sm text-secondary mt-2">
行数: {{ outputLines }} | 字符数: {{ outputCode.length }}
</div>
</div>
</div>
<!-- 状态消息 -->
<div v-if="statusMessage" class="card p-4">
<div :class="[
'flex items-center space-x-2',
statusType === 'success' ? 'text-success' : 'text-error'
]">
<FontAwesomeIcon
:icon="statusType === 'success' ? ['fas', 'check'] : ['fas', 'times']"
/>
<span>{{ statusMessage }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useLanguage } from '@/composables/useLanguage'
const { t } = useLanguage()
// 响应式状态
const inputCode = ref('')
const outputCode = ref('')
const selectedLanguage = ref('javascript')
const indentSize = ref('2')
const lineWidth = ref(120)
const isFormatting = ref(false)
const copied = ref(false)
const statusMessage = ref('')
const statusType = ref<'success' | 'error'>('success')
// 支持的语言
const supportedLanguages = [
{ value: 'javascript', label: 'JavaScript' },
{ value: 'typescript', label: 'TypeScript' },
{ value: 'html', label: 'HTML' },
{ value: 'css', label: 'CSS' },
{ value: 'json', label: 'JSON' },
{ value: 'sql', label: 'SQL' },
{ value: 'xml', label: 'XML' },
{ value: 'yaml', label: 'YAML' },
{ value: 'markdown', label: 'Markdown' }
]
// 计算属性
const inputLines = computed(() => {
return inputCode.value.split('\n').length
})
const outputLines = computed(() => {
return outputCode.value.split('\n').length
})
const outputStats = computed(() => {
if (!outputCode.value) return ''
const originalSize = inputCode.value.length
const formattedSize = outputCode.value.length
const diff = formattedSize - originalSize
const diffText = diff > 0 ? `+${diff}` : diff.toString()
return `${diffText} 字符`
})
// 格式化代码
const formatCode = async () => {
if (!inputCode.value.trim()) {
showStatus('请输入要格式化的代码', 'error')
return
}
isFormatting.value = true
statusMessage.value = ''
try {
let formatted = ''
switch (selectedLanguage.value) {
case 'json':
formatted = formatJSON(inputCode.value)
break
case 'html':
formatted = formatHTML(inputCode.value)
break
case 'css':
formatted = formatCSS(inputCode.value)
break
case 'javascript':
case 'typescript':
formatted = formatJavaScript(inputCode.value)
break
case 'sql':
formatted = formatSQL(inputCode.value)
break
case 'xml':
formatted = formatXML(inputCode.value)
break
default:
formatted = formatGeneric(inputCode.value)
}
outputCode.value = formatted
showStatus('代码格式化成功', 'success')
} catch (error) {
console.error('格式化失败:', error)
showStatus('格式化失败: ' + (error instanceof Error ? error.message : '未知错误'), 'error')
} finally {
isFormatting.value = false
}
}
// 压缩代码
const minifyCode = () => {
if (!inputCode.value.trim()) {
showStatus('请输入要压缩的代码', 'error')
return
}
try {
let minified = ''
switch (selectedLanguage.value) {
case 'json':
const parsed = JSON.parse(inputCode.value)
minified = JSON.stringify(parsed)
break
case 'css':
minified = minifyCSS(inputCode.value)
break
case 'javascript':
minified = minifyJavaScript(inputCode.value)
break
default:
minified = inputCode.value.replace(/\s+/g, ' ').trim()
}
outputCode.value = minified
showStatus('代码压缩成功', 'success')
} catch (error) {
console.error('压缩失败:', error)
showStatus('压缩失败: ' + (error instanceof Error ? error.message : '未知错误'), 'error')
}
}
// JSON格式化
const formatJSON = (code: string): string => {
const parsed = JSON.parse(code)
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
return JSON.stringify(parsed, null, indent)
}
// HTML格式化
const formatHTML = (code: string): string => {
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
let level = 0
let formatted = ''
// 简单的HTML格式化逻辑
const lines = code.replace(/></g, '>\n<').split('\n')
for (let line of lines) {
line = line.trim()
if (!line) continue
if (line.startsWith('</')) {
level = Math.max(0, level - 1)
}
formatted += indent.repeat(level) + line + '\n'
if (line.startsWith('<') && !line.startsWith('</') && !line.endsWith('/>') && !line.includes('</')) {
level++
}
}
return formatted.trim()
}
// CSS格式化
const formatCSS = (code: string): string => {
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
return code
.replace(/\s*{\s*/g, ' {\n')
.replace(/;\s*/g, ';\n')
.replace(/\s*}\s*/g, '\n}\n')
.split('\n')
.map(line => {
line = line.trim()
if (!line) return ''
if (line.endsWith('{') || line.endsWith('}')) {
return line
}
return indent + line
})
.filter(line => line !== '')
.join('\n')
}
// JavaScript格式化
const formatJavaScript = (code: string): string => {
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
let level = 0
let formatted = ''
let inString = false
let stringChar = ''
for (let i = 0; i < code.length; i++) {
const char = code[i]
const prevChar = code[i - 1]
if ((char === '"' || char === "'") && prevChar !== '\\') {
if (!inString) {
inString = true
stringChar = char
} else if (char === stringChar) {
inString = false
stringChar = ''
}
}
if (!inString) {
if (char === '{') {
formatted += char + '\n' + indent.repeat(++level)
continue
} else if (char === '}') {
formatted = formatted.trimEnd() + '\n' + indent.repeat(--level) + char
if (code[i + 1] && code[i + 1] !== ';' && code[i + 1] !== ',' && code[i + 1] !== ')') {
formatted += '\n' + indent.repeat(level)
}
continue
} else if (char === ';') {
formatted += char
if (code[i + 1] && code[i + 1] !== '}') {
formatted += '\n' + indent.repeat(level)
}
continue
}
}
formatted += char
}
return formatted.trim()
}
// SQL格式化
const formatSQL = (code: string): string => {
const keywords = ['SELECT', 'FROM', 'WHERE', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ORDER BY', 'GROUP BY', 'HAVING', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP']
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
let formatted = code.toUpperCase()
keywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi')
formatted = formatted.replace(regex, `\n${keyword}`)
})
return formatted
.split('\n')
.map(line => line.trim())
.filter(line => line !== '')
.join('\n')
}
// XML格式化
const formatXML = (code: string): string => {
// 简单的XML格式化复用HTML格式化逻辑
return formatHTML(code)
}
// 通用格式化
const formatGeneric = (code: string): string => {
// 基本的缩进处理
const indent = indentSize.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSize.value))
let level = 0
return code.split('\n').map(line => {
line = line.trim()
if (!line) return ''
// 简单的括号缩进
const openBrackets = (line.match(/[{(\[]/g) || []).length
const closeBrackets = (line.match(/[})\]]/g) || []).length
if (closeBrackets > openBrackets) {
level = Math.max(0, level - (closeBrackets - openBrackets))
}
const formatted = indent.repeat(level) + line
if (openBrackets > closeBrackets) {
level += (openBrackets - closeBrackets)
}
return formatted
}).join('\n')
}
// CSS压缩
const minifyCSS = (code: string): string => {
return code
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除注释
.replace(/\s+/g, ' ') // 多个空格替换为单个
.replace(/;\s*}/g, '}') // 移除最后一个分号
.replace(/\s*{\s*/g, '{')
.replace(/;\s*/g, ';')
.replace(/:\s*/g, ':')
.trim()
}
// JavaScript压缩
const minifyJavaScript = (code: string): string => {
return code
.replace(/\/\/.*$/gm, '') // 移除单行注释
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释
.replace(/\s+/g, ' ') // 多个空格替换为单个
.replace(/\s*([{}();,])\s*/g, '$1') // 移除操作符周围的空格
.trim()
}
// 复制到剪贴板
const copyToClipboard = async () => {
if (!outputCode.value) return
try {
await navigator.clipboard.writeText(outputCode.value)
copied.value = true
showStatus('代码已复制到剪贴板', 'success')
setTimeout(() => {
copied.value = false
}, 2000)
} catch (error) {
console.error('复制失败:', error)
showStatus('复制失败', 'error')
}
}
// 从剪贴板粘贴
const pasteFromClipboard = async () => {
try {
const text = await navigator.clipboard.readText()
inputCode.value = text
handleInputChange()
} catch (error) {
console.error('粘贴失败:', error)
showStatus('粘贴失败', 'error')
}
}
// 加载示例代码
const loadExample = () => {
const examples: Record<string, string> = {
javascript: `function calculateSum(numbers) {
const result = numbers.reduce((sum, num) => {
return sum + num;
}, 0);
return result;
}
const data = [1, 2, 3, 4, 5];
console.log(calculateSum(data));`,
json: `{"name":"极速箱工具集","version":"1.0.0","tools":[{"id":1,"name":"代码格式化器","active":true},{"id":2,"name":"JSON格式化器","active":true}],"settings":{"theme":"dark","language":"zh-CN"}}`,
html: `<div class="container"><header><h1>标题</h1></header><main><section><p>这是一段文本</p></section></main></div>`,
css: `.container{display:flex;flex-direction:column;}.header{background-color:#333;color:white;padding:20px;}.main{flex:1;padding:20px;}`,
sql: `SELECT u.name, u.email, p.title FROM users u INNER JOIN posts p ON u.id = p.user_id WHERE u.active = 1 ORDER BY p.created_at DESC;`
}
inputCode.value = examples[selectedLanguage.value] || examples.javascript
}
// 清除所有内容
const clearAll = () => {
inputCode.value = ''
outputCode.value = ''
statusMessage.value = ''
}
// 处理输入变化
const handleInputChange = () => {
if (statusMessage.value) {
statusMessage.value = ''
}
}
// 处理语言变化
const handleLanguageChange = () => {
outputCode.value = ''
}
// 获取占位符文本
const getPlaceholder = (): string => {
const placeholders: Record<string, string> = {
javascript: '输入 JavaScript 代码...',
html: '输入 HTML 代码...',
css: '输入 CSS 代码...',
json: '输入 JSON 数据...',
sql: '输入 SQL 语句...'
}
return placeholders[selectedLanguage.value] || '输入代码...'
}
// 显示状态消息
const showStatus = (message: string, type: 'success' | 'error') => {
statusMessage.value = message
statusType.value = type
setTimeout(() => {
statusMessage.value = ''
}, 3000)
}
</script>