new
This commit is contained in:
1
frontend/package.json
Normal file
1
frontend/package.json
Normal 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
212
frontend/src/App.vue
Normal 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
15
frontend/src/main.js
Normal 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
25
frontend/vite.config.js
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user