forked from zguiy/utils
工具完成
This commit is contained in:
289
src/components/tools/QrcodeGenerator.vue
Normal file
289
src/components/tools/QrcodeGenerator.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 工具栏 -->
|
||||
<div class="card p-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
@click="generateQRCode"
|
||||
:disabled="!qrText.trim() || isGenerating"
|
||||
class="btn-primary"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
:icon="isGenerating ? ['fas', 'spinner'] : ['fas', 'qrcode']"
|
||||
:class="['mr-2', isGenerating && 'animate-spin']"
|
||||
/>
|
||||
{{ t('tools.qrcode_generator.generate') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="downloadQRCode"
|
||||
:disabled="!qrCodeDataUrl"
|
||||
class="btn-secondary"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'download']" class="mr-2" />
|
||||
{{ t('tools.qrcode_generator.download') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="clearAll"
|
||||
class="btn-secondary"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'trash']" class="mr-2" />
|
||||
{{ t('tools.qrcode_generator.clear') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- 输入配置区域 -->
|
||||
<div class="space-y-6">
|
||||
<!-- 文本输入 -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-lg font-semibold text-primary mb-3">{{ t('tools.qrcode_generator.text_input') }}</h3>
|
||||
<textarea
|
||||
v-model="qrText"
|
||||
:placeholder="t('tools.qrcode_generator.placeholder')"
|
||||
class="textarea-field h-32"
|
||||
@input="handleTextChange"
|
||||
/>
|
||||
<div class="text-sm text-secondary mt-2">
|
||||
字符数: {{ qrText.length }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置选项 -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-lg font-semibold text-primary mb-3">{{ t('tools.qrcode_generator.settings') }}</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 尺寸 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">
|
||||
{{ t('tools.qrcode_generator.size') }}: {{ qrSize }}px
|
||||
</label>
|
||||
<input
|
||||
v-model="qrSize"
|
||||
type="range"
|
||||
min="100"
|
||||
max="800"
|
||||
step="50"
|
||||
class="w-full"
|
||||
>
|
||||
<div class="flex justify-between text-xs text-tertiary mt-1">
|
||||
<span>100px</span>
|
||||
<span>800px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 容错级别 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">
|
||||
{{ t('tools.qrcode_generator.error_level') }}
|
||||
</label>
|
||||
<select v-model="errorLevel" class="select-field">
|
||||
<option value="L">低 (L) - 7%</option>
|
||||
<option value="M">中 (M) - 15%</option>
|
||||
<option value="Q">中高 (Q) - 25%</option>
|
||||
<option value="H">高 (H) - 30%</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 前景色 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">
|
||||
{{ t('tools.qrcode_generator.foreground_color') }}
|
||||
</label>
|
||||
<div class="flex space-x-2">
|
||||
<input
|
||||
v-model="foregroundColor"
|
||||
type="color"
|
||||
class="w-12 h-10 rounded border border-primary border-opacity-20"
|
||||
>
|
||||
<input
|
||||
v-model="foregroundColor"
|
||||
type="text"
|
||||
class="input-field flex-1"
|
||||
placeholder="#000000"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景色 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">
|
||||
{{ t('tools.qrcode_generator.background_color') }}
|
||||
</label>
|
||||
<div class="flex space-x-2">
|
||||
<input
|
||||
v-model="backgroundColor"
|
||||
type="color"
|
||||
class="w-12 h-10 rounded border border-primary border-opacity-20"
|
||||
>
|
||||
<input
|
||||
v-model="backgroundColor"
|
||||
type="text"
|
||||
class="input-field flex-1"
|
||||
placeholder="#FFFFFF"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览区域 -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-lg font-semibold text-primary mb-3">{{ t('tools.qrcode_generator.preview') }}</h3>
|
||||
|
||||
<div class="flex justify-center items-center min-h-[300px]">
|
||||
<div v-if="qrCodeDataUrl" class="text-center">
|
||||
<img
|
||||
:src="qrCodeDataUrl"
|
||||
:alt="t('tools.qrcode_generator.qr_code')"
|
||||
class="mx-auto mb-4 rounded border border-primary border-opacity-20"
|
||||
:style="{ maxWidth: '100%', height: 'auto' }"
|
||||
>
|
||||
<div class="text-sm text-secondary">
|
||||
{{ qrSize }}x{{ qrSize }}px
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="isGenerating" class="text-center">
|
||||
<FontAwesomeIcon :icon="['fas', 'spinner']" class="text-4xl text-primary animate-spin mb-4" />
|
||||
<div class="text-secondary">{{ t('tools.qrcode_generator.generating') }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center">
|
||||
<FontAwesomeIcon :icon="['fas', 'qrcode']" class="text-6xl text-tertiary mb-4" />
|
||||
<div class="text-secondary">{{ t('tools.qrcode_generator.no_preview') }}</div>
|
||||
</div>
|
||||
</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, watch } from 'vue'
|
||||
import { useLanguage } from '@/composables/useLanguage'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
const { t } = useLanguage()
|
||||
|
||||
// 响应式状态
|
||||
const qrText = ref('')
|
||||
const qrSize = ref(300)
|
||||
const errorLevel = ref('M')
|
||||
const foregroundColor = ref('#000000')
|
||||
const backgroundColor = ref('#FFFFFF')
|
||||
const qrCodeDataUrl = ref('')
|
||||
const isGenerating = ref(false)
|
||||
const statusMessage = ref('')
|
||||
const statusType = ref<'success' | 'error'>('success')
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async () => {
|
||||
if (!qrText.value.trim()) {
|
||||
showStatus('请输入要生成二维码的内容', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
isGenerating.value = true
|
||||
statusMessage.value = ''
|
||||
|
||||
try {
|
||||
const options = {
|
||||
width: qrSize.value,
|
||||
height: qrSize.value,
|
||||
errorCorrectionLevel: errorLevel.value as 'L' | 'M' | 'Q' | 'H',
|
||||
color: {
|
||||
dark: foregroundColor.value,
|
||||
light: backgroundColor.value
|
||||
},
|
||||
margin: 2
|
||||
}
|
||||
|
||||
const dataUrl = await QRCode.toDataURL(qrText.value, options)
|
||||
qrCodeDataUrl.value = dataUrl
|
||||
showStatus('二维码生成成功', 'success')
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
showStatus('生成二维码失败: ' + (error instanceof Error ? error.message : '未知错误'), 'error')
|
||||
qrCodeDataUrl.value = ''
|
||||
} finally {
|
||||
isGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 下载二维码
|
||||
const downloadQRCode = () => {
|
||||
if (!qrCodeDataUrl.value) return
|
||||
|
||||
try {
|
||||
const link = document.createElement('a')
|
||||
link.download = `qrcode-${Date.now()}.png`
|
||||
link.href = qrCodeDataUrl.value
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
showStatus('二维码下载成功', 'success')
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error)
|
||||
showStatus('下载失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有内容
|
||||
const clearAll = () => {
|
||||
qrText.value = ''
|
||||
qrCodeDataUrl.value = ''
|
||||
statusMessage.value = ''
|
||||
}
|
||||
|
||||
// 处理文本变化
|
||||
const handleTextChange = () => {
|
||||
// 文本变化时清除状态
|
||||
if (statusMessage.value) {
|
||||
statusMessage.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 显示状态消息
|
||||
const showStatus = (message: string, type: 'success' | 'error') => {
|
||||
statusMessage.value = message
|
||||
statusType.value = type
|
||||
setTimeout(() => {
|
||||
statusMessage.value = ''
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// 防抖重新生成
|
||||
let regenerateTimer: number | null = null
|
||||
const debouncedRegenerate = () => {
|
||||
if (regenerateTimer) {
|
||||
clearTimeout(regenerateTimer)
|
||||
}
|
||||
regenerateTimer = window.setTimeout(() => {
|
||||
if (qrCodeDataUrl.value && qrText.value.trim()) {
|
||||
generateQRCode()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 监听配置变化,自动重新生成
|
||||
watch([qrSize, errorLevel, foregroundColor, backgroundColor], debouncedRegenerate)
|
||||
</script>
|
||||
Reference in New Issue
Block a user