1
0
This commit is contained in:
2025-04-14 16:35:05 +08:00
commit b76fb31890
8 changed files with 477 additions and 0 deletions

1
frontend/package.json Normal file
View File

@@ -0,0 +1 @@
{"name":"modern-qrcode-frontend","private":true,"version":"0.0.0","type":"module","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"dependencies":{"@element-plus/icons-vue":"^2.1.0","axios":"^1.6.0","element-plus":"^2.4.0","pinia":"^2.1.0","vue":"^3.3.0","vue-router":"^4.2.0"},"devDependencies":{"@vitejs/plugin-vue":"^4.5.0","sass":"^1.69.0","unplugin-auto-import":"^0.16.0","unplugin-vue-components":"^0.25.0","vite":"^5.0.0"}}

212
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,212 @@
<template>
<el-container class="app-container">
<el-header class="app-header">
<h1>现代化二维码生成器</h1>
</el-header>
<el-main>
<el-row :gutter="20" justify="center">
<el-col :span="12">
<el-card class="qr-form">
<el-form :model="form" label-position="top">
<el-form-item label="内容">
<el-input
v-model="form.text"
type="textarea"
:rows="3"
placeholder="请输入需要生成二维码的内容"
/>
</el-form-item>
<el-form-item label="尺寸">
<el-slider
v-model="form.size"
:min="100"
:max="800"
:step="50"
show-input
/>
</el-form-item>
<el-form-item label="错误纠正级别">
<el-select v-model="form.errorCorrection" class="w-full">
<el-option label="低 (L)" value="L" />
<el-option label="中 (M)" value="M" />
<el-option label="高 (Q)" value="Q" />
<el-option label="最高 (H)" value="H" />
</el-select>
</el-form-item>
<el-form-item label="Logo">
<el-upload
class="logo-uploader"
action="/api/upload"
:show-file-list="false"
:on-success="handleLogoSuccess"
>
<img v-if="form.logo" :src="form.logo" class="logo" />
<el-icon v-else class="logo-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="generateQRCode" :loading="loading">
生成二维码
</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-col :span="12" v-if="qrCodeUrl">
<el-card class="qr-preview">
<div class="qr-image-container">
<img :src="qrCodeUrl" alt="生成的二维码" />
<el-button type="success" @click="downloadQRCode">
下载二维码
</el-button>
</div>
</el-card>
</el-col>
</el-row>
</el-main>
<el-footer class="app-footer">
<p>基于开源项目 PHP QR Code 重构的现代化二维码生成工具</p>
</el-footer>
</el-container>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const form = reactive({
text: '',
size: 300,
errorCorrection: 'M',
logo: ''
})
const loading = ref(false)
const qrCodeUrl = ref('')
const generateQRCode = async () => {
if (!form.text) {
ElMessage.warning('请输入需要生成二维码的内容')
return
}
loading.value = true
try {
const response = await fetch('/api/qrcode/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(form)
})
if (response.ok) {
const blob = await response.blob()
qrCodeUrl.value = URL.createObjectURL(blob)
ElMessage.success('二维码生成成功')
} else {
throw new Error('生成失败')
}
} catch (error) {
ElMessage.error('二维码生成失败,请重试')
} finally {
loading.value = false
}
}
const handleLogoSuccess = (response) => {
form.logo = response.url
ElMessage.success('Logo上传成功')
}
const downloadQRCode = () => {
const link = document.createElement('a')
link.href = qrCodeUrl.value
link.download = 'qrcode.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
</script>
<style scoped>
.app-container {
min-height: 100vh;
background-color: #f5f7fa;
}
.app-header {
background-color: #409eff;
color: white;
text-align: center;
line-height: 60px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.app-footer {
text-align: center;
color: #909399;
padding: 20px 0;
}
.qr-form {
margin-top: 20px;
}
.qr-preview {
margin-top: 20px;
}
.qr-image-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.qr-image-container img {
max-width: 100%;
height: auto;
}
.logo-uploader {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: border-color 0.3s;
}
.logo-uploader:hover {
border-color: #409eff;
}
.logo-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
}
.logo {
width: 100px;
height: 100px;
display: block;
object-fit: contain;
}
.w-full {
width: 100%;
}
</style>

15
frontend/src/main.js Normal file
View File

@@ -0,0 +1,15 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')

25
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,25 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
}
}
}
})