工具完成
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,404 @@
<template>
<div class="space-y-6">
<!-- 主内容区 -->
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- 左侧面板 - 常用示例和选项 -->
<div class="lg:col-span-1 space-y-6">
<!-- 常用示例 -->
<div class="card p-4">
<h2 class="text-md font-medium text-primary mb-4">常用示例</h2>
<div class="space-y-2">
<button
v-for="(example, index) in examples"
:key="index"
class="text-left w-full px-3 py-2 rounded-md text-sm text-secondary hover:bg-hover transition-colors"
@click="() => applyExample(example)"
>
{{ example.name }}
</button>
</div>
</div>
<!-- 正则选项 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-md font-medium text-primary">选项</h2>
<button
class="text-tertiary hover:text-error transition-colors text-sm"
@click="clearAll"
>
<FontAwesomeIcon :icon="['fas', 'trash']" class="mr-1" />
清空
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm text-secondary mb-2">标志位</label>
<div class="flex flex-wrap gap-2">
<button
:class="flags.includes('g') ? 'btn-primary text-xs' : 'btn-secondary text-xs'"
@click="() => toggleFlag('g')"
>
g (全局)
</button>
<button
:class="flags.includes('i') ? 'btn-primary text-xs' : 'btn-secondary text-xs'"
@click="() => toggleFlag('i')"
>
i (忽略大小写)
</button>
<button
:class="flags.includes('m') ? 'btn-primary text-xs' : 'btn-secondary text-xs'"
@click="() => toggleFlag('m')"
>
m (多行)
</button>
<button
:class="flags.includes('s') ? 'btn-primary text-xs' : 'btn-secondary text-xs'"
@click="() => toggleFlag('s')"
>
s (单行)
</button>
</div>
</div>
<div>
<label class="flex items-center text-sm text-secondary">
<input
v-model="showGroups"
type="checkbox"
class="mr-2"
/>
显示捕获组
</label>
</div>
</div>
</div>
</div>
<!-- 右侧面板 - 测试区域 -->
<div class="lg:col-span-3 space-y-6">
<!-- 正则表达式输入 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-md font-medium text-primary">正则表达式</h2>
<button
class="text-tertiary hover:text-primary transition-colors text-sm"
@click="copyRegex"
:disabled="!regexString"
>
<FontAwesomeIcon :icon="copied ? ['fas', 'check'] : ['fas', 'copy']" class="mr-1" />
{{ copied ? '已复制' : '复制' }}
</button>
</div>
<div class="relative">
<div class="absolute left-3 top-[13px] text-tertiary">/</div>
<input
v-model="regexString"
type="text"
placeholder="输入正则表达式..."
class="input-field pl-7 pr-14"
/>
<div class="absolute right-14 top-[13px] text-tertiary">/</div>
<input
v-model="flags"
type="text"
placeholder="flags"
class="absolute right-3 top-[13px] w-8 bg-transparent border-none outline-none text-tertiary"
/>
</div>
<div v-if="regexError" class="mt-2 text-sm text-error flex items-center gap-2">
<FontAwesomeIcon :icon="['fas', 'exclamation-triangle']" />
<span>{{ regexError }}</span>
</div>
</div>
<!-- 测试输入 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-md font-medium text-primary">测试文本</h2>
<div class="text-sm text-secondary">
字符数: <span class="text-primary">{{ testString.length }}</span>
</div>
</div>
<textarea
v-model="testString"
placeholder="输入要测试的文本..."
class="textarea-field min-h-[150px] w-full resize-y"
/>
</div>
<!-- 匹配结果 -->
<div class="card p-4">
<div class="flex items-center justify-between mb-4">
<h2 class="text-md font-medium text-primary">匹配结果</h2>
<div class="text-sm text-secondary">
匹配数量: <span class="text-primary">{{ matchCount }}</span>
</div>
</div>
<div v-if="testString" class="space-y-4">
<!-- 高亮显示的匹配文本 -->
<div class="bg-block rounded-md p-4 whitespace-pre-wrap font-mono text-sm">
<div v-if="matchCount > 0">
<div class="mb-3 text-tertiary text-xs flex items-center justify-between">
<span>
找到 <span class="text-primary font-medium">{{ matchCount }}</span> 个匹配项
</span>
<span class="text-tertiary text-xs">
原文长度: {{ testString.length }} 字符
</span>
</div>
<!-- 使用 v-html 显示高亮结果但要确保安全 -->
<div v-html="highlightedText" class="break-all"></div>
</div>
<span v-else class="text-tertiary">无匹配项</span>
</div>
<!-- 捕获组详情 -->
<div v-if="showGroups && matchCount > 0">
<h3 class="text-sm font-medium text-primary mb-2">捕获组详情</h3>
<div class="space-y-2">
<div
v-for="(match, index) in matches"
:key="index"
class="bg-block rounded-md p-3"
>
<div class="text-xs text-tertiary mb-2">
匹配 #{{ index + 1 }} (位置: {{ match.index }})
</div>
<div class="space-y-1">
<div class="flex items-start gap-2">
<span class="text-xs text-tertiary min-w-[40px]">完整:</span>
<code class="text-sm text-primary bg-primary/10 px-1 rounded break-all">
{{ match[0] || '' }}
</code>
</div>
<div
v-for="group in Math.max(0, match.length - 1)"
:key="group"
class="flex items-start gap-2"
>
<span class="text-xs text-tertiary min-w-[40px]"> {{ group }}:</span>
<code class="text-sm text-primary bg-primary/10 px-1 rounded break-all">
{{ match[group] || '(空)' }}
</code>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="flex items-center justify-center p-4 text-tertiary">
<FontAwesomeIcon :icon="['fas', 'info-circle']" class="mr-2" />
请输入测试文本
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
// 定义匹配结果类型
interface MatchResult extends RegExpExecArray {
index: number
}
// 响应式状态
const regexString = ref('')
const flags = ref('g')
const testString = ref('')
const matches = ref<MatchResult[]>([])
const matchCount = ref(0)
const showGroups = ref(true)
const regexError = ref<string | null>(null)
const copied = ref(false)
// 常用正则表达式示例
const examples = [
{
name: '邮箱地址',
pattern: '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}',
flags: 'g',
testText: 'test@example.com, invalid-email, another.email@domain.co.uk'
},
{
name: '手机号码',
pattern: '1[3-9]\\d{9}',
flags: 'g',
testText: '我的手机号是13812345678她的是15987654321座机010-12345678'
},
{
name: 'URL地址',
pattern: 'https?://[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*)',
flags: 'g',
testText: '访问 https://www.example.com 或 http://test.org/path?query=1'
},
{
name: 'IP地址',
pattern: '\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b',
flags: 'g',
testText: '服务器IP: 192.168.1.1, 公网IP: 8.8.8.8, 错误格式: 999.999.999.999'
},
{
name: '中文字符',
pattern: '[\\u4e00-\\u9fa5]',
flags: 'g',
testText: 'Hello 世界! This is 中文 mixed with English.'
}
]
// HTML转义函数
const escapeHtml = (text: string): string => {
if (!text) return ''
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
// 计算高亮文本
const highlightedText = computed(() => {
if (!testString.value || matchCount.value === 0) {
return escapeHtml(testString.value)
}
let result = ''
let lastIndex = 0
// 按索引顺序排序匹配项
const sortedMatches = [...matches.value].sort((a, b) => a.index - b.index)
// 遍历每个匹配项
sortedMatches.forEach(match => {
// 添加匹配前的文本
result += escapeHtml(testString.value.substring(lastIndex, match.index))
// 添加高亮的匹配内容
result += `<span style="background-color:rgba(var(--color-primary), 0.3); color:rgb(var(--color-primary)); font-weight:bold; padding:0 4px; border-radius:3px;">${escapeHtml(match[0])}</span>`
// 更新lastIndex
lastIndex = match.index + match[0].length
})
// 添加最后一个匹配后的文本
if (lastIndex < testString.value.length) {
result += escapeHtml(testString.value.substring(lastIndex))
}
return result
})
// 测试正则表达式
const testRegex = () => {
if (!regexString.value || !testString.value) {
matches.value = []
matchCount.value = 0
regexError.value = null
return
}
try {
// 验证正则表达式是否有效
new RegExp(regexString.value, flags.value)
regexError.value = null
if (flags.value.includes('g')) {
// 获取所有匹配
const allMatches: MatchResult[] = []
let match: RegExpExecArray | null
const regexWithGroups = new RegExp(regexString.value, flags.value)
// 收集所有匹配和捕获组
while ((match = regexWithGroups.exec(testString.value)) !== null) {
allMatches.push(match as MatchResult)
// 防止无限循环如果匹配长度为0手动增加索引
if (match.index === regexWithGroups.lastIndex) {
regexWithGroups.lastIndex++
}
}
matches.value = allMatches
matchCount.value = allMatches.length
} else {
// 单次匹配模式
const regexWithoutG = new RegExp(regexString.value, flags.value.replace('g', ''))
const execMatch = regexWithoutG.exec(testString.value)
if (execMatch) {
matches.value = [execMatch as MatchResult]
matchCount.value = 1
} else {
matches.value = []
matchCount.value = 0
}
}
} catch (error) {
console.error('正则表达式错误:', error)
regexError.value = (error as Error).message
matches.value = []
matchCount.value = 0
}
}
// 复制正则表达式
const copyRegex = async () => {
if (!regexString.value) return
try {
const regexText = `/${regexString.value}/${flags.value}`
await navigator.clipboard.writeText(regexText)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (error) {
console.error('复制失败:', error)
}
}
// 应用示例
const applyExample = (example: { pattern: string; flags: string; testText: string }) => {
regexString.value = example.pattern
flags.value = example.flags
testString.value = example.testText
}
// 清空所有内容
const clearAll = () => {
regexString.value = ''
flags.value = 'g'
testString.value = ''
matches.value = []
matchCount.value = 0
regexError.value = null
}
// 切换标志位
const toggleFlag = (flag: string) => {
if (flags.value.includes(flag)) {
flags.value = flags.value.replace(flag, '')
} else {
flags.value = flags.value + flag
}
}
// 监听输入变化,自动测试
watch([regexString, flags, testString, showGroups], () => {
testRegex()
}, { immediate: true })
</script>