duqing2026 commited on
Commit
0763242
·
1 Parent(s): 0235003

Initial commit: Super QR Code Master

Browse files
Files changed (5) hide show
  1. Dockerfile +16 -0
  2. README.md +50 -5
  3. app.py +105 -0
  4. requirements.txt +4 -0
  5. templates/index.html +331 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Create a non-root user for HF Spaces
11
+ RUN useradd -m -u 1000 user
12
+ USER user
13
+
14
+ EXPOSE 7860
15
+
16
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md CHANGED
@@ -1,12 +1,57 @@
1
  ---
2
  title: Qr Code Master
3
- emoji: 📉
4
- colorFrom: green
5
- colorTo: blue
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
- short_description: 二维码大师
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Qr Code Master
3
+ emoji: 🏁
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  license: mit
9
+ short_description: 超级二维码工坊 - 专业免费的在线二维码生成器
10
  ---
11
 
12
+ # 超级二维码工坊 (QR Code Master)
13
+
14
+ 一个强大、免费、无广告的在线二维码生成工具。
15
+
16
+ ## 功能特点
17
+
18
+ - **多类型支持**:支持生成文本/链接、WiFi 连接码、电子名片 (VCard) 等多种格式。
19
+ - **个性化定制**:自由调节前景色、背景色。
20
+ - **Logo 嵌入**:支持上传 Logo 图片并自动居中嵌入到二维码中。
21
+ - **隐私安全**:所有生成过程在服务器内存中完成,不保存用户数据。
22
+ - **高清下载**:支持生成高清 PNG 图片。
23
+
24
+ ## 技术栈
25
+
26
+ - **后端**:Flask, Segno (二维码引擎), Pillow (图像处理)
27
+ - **前端**:Vue.js 3, Tailwind CSS
28
+ - **部署**:Docker, Hugging Face Spaces
29
+
30
+ ## 本地运行
31
+
32
+ 1. 克隆项目
33
+ ```bash
34
+ git clone https://github.com/yourusername/qr-code-master.git
35
+ cd qr-code-master
36
+ ```
37
+
38
+ 2. 安装依赖
39
+ ```bash
40
+ pip install -r requirements.txt
41
+ ```
42
+
43
+ 3. 运行应用
44
+ ```bash
45
+ python app.py
46
+ ```
47
+
48
+ 4. 访问
49
+ 打开浏览器访问 `http://localhost:7860`
50
+
51
+ ## Hugging Face Spaces 部署
52
+
53
+ 本项目已配置 Dockerfile,可直接部署至 Hugging Face Spaces (Docker Space)。
54
+
55
+ 1. 创建新的 Space。
56
+ 2. 选择 Docker SDK。
57
+ 3. 推送代码即可。
app.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ from flask import Flask, render_template, request, jsonify, send_file
5
+ import segno
6
+ from segno import helpers
7
+ from PIL import Image
8
+
9
+ app = Flask(__name__)
10
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload
11
+
12
+ def hex_to_rgb(hex_color):
13
+ hex_color = hex_color.lstrip('#')
14
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
15
+
16
+ @app.route('/')
17
+ def index():
18
+ return render_template('index.html')
19
+
20
+ @app.route('/api/generate', methods=['POST'])
21
+ def generate_qr():
22
+ try:
23
+ data = request.form
24
+ qr_type = data.get('type', 'text')
25
+ content = data.get('content', '')
26
+ fg_color = data.get('fg_color', '#000000')
27
+ bg_color = data.get('bg_color', '#ffffff')
28
+ scale = int(data.get('scale', 10))
29
+ border = int(data.get('border', 4))
30
+
31
+ # Handle colors (Segno expects hex or RGB tuple, but let's be safe)
32
+ # Segno handles hex strings well.
33
+
34
+ qr = None
35
+
36
+ # 1. Generate QR Object
37
+ if qr_type == 'wifi':
38
+ ssid = data.get('wifi_ssid')
39
+ password = data.get('wifi_password')
40
+ security = data.get('wifi_security', 'WPA')
41
+ hidden = data.get('wifi_hidden') == 'true'
42
+ qr = helpers.make_wifi(ssid=ssid, password=password, security=security, hidden=hidden)
43
+ elif qr_type == 'vcard':
44
+ name = data.get('vcard_name')
45
+ displayname = data.get('vcard_displayname')
46
+ email = data.get('vcard_email')
47
+ phone = data.get('vcard_phone')
48
+ url = data.get('vcard_url')
49
+ # Segno make_vcard is powerful
50
+ qr = helpers.make_vcard(name=name, displayname=displayname, email=email, phone=phone, url=url)
51
+ else:
52
+ # Default text/url
53
+ if not content:
54
+ return jsonify({'error': '内容不能为空'}), 400
55
+ qr = segno.make(content, error='h') # High error correction for logos
56
+
57
+ # 2. Convert to PIL Image
58
+ out = io.BytesIO()
59
+ qr.save(out, kind='png', scale=scale, border=border, dark=fg_color, light=bg_color)
60
+ out.seek(0)
61
+ img = Image.open(out).convert("RGBA")
62
+
63
+ # 3. Handle Logo
64
+ logo_file = request.files.get('logo')
65
+ if logo_file:
66
+ logo = Image.open(logo_file).convert("RGBA")
67
+
68
+ # Calculate logo size (e.g., 20% of QR width)
69
+ qr_width, qr_height = img.size
70
+ logo_max_size = int(qr_width * 0.25) # 25% coverage max
71
+
72
+ # Resize logo maintaining aspect ratio
73
+ logo.thumbnail((logo_max_size, logo_max_size), Image.Resampling.LANCZOS)
74
+
75
+ # Position centered
76
+ logo_pos = ((qr_width - logo.size[0]) // 2, (qr_height - logo.size[1]) // 2)
77
+
78
+ # Paste logo (with transparency support)
79
+ # Create a white background for logo if needed or just paste
80
+ # Usually better to have a small white border around logo for readability
81
+ # Let's create a white backing for the logo
82
+ logo_bg_size = (logo.size[0] + 4, logo.size[1] + 4) # 4px padding
83
+ logo_bg = Image.new('RGBA', logo_bg_size, bg_color)
84
+ logo_bg_pos = (logo_pos[0] - 2, logo_pos[1] - 2)
85
+
86
+ img.paste(logo_bg, logo_bg_pos, logo_bg) # Paste white square
87
+ img.paste(logo, logo_pos, logo) # Paste logo
88
+
89
+ # 4. Return as Base64
90
+ final_out = io.BytesIO()
91
+ img.save(final_out, format='PNG')
92
+ final_out.seek(0)
93
+ b64_data = base64.b64encode(final_out.getvalue()).decode()
94
+
95
+ return jsonify({
96
+ 'image': f'data:image/png;base64,{b64_data}',
97
+ 'filename': 'qrcode.png'
98
+ })
99
+
100
+ except Exception as e:
101
+ print(f"Error: {e}")
102
+ return jsonify({'error': str(e)}), 500
103
+
104
+ if __name__ == '__main__':
105
+ app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask==3.0.0
2
+ segno==1.6.1
3
+ Pillow==10.2.0
4
+ gunicorn==21.2.0
templates/index.html ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>超级二维码工坊 | QR Code Master</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
12
+ body { font-family: 'Noto Sans SC', sans-serif; }
13
+ .glass {
14
+ background: rgba(255, 255, 255, 0.9);
15
+ backdrop-filter: blur(10px);
16
+ border: 1px solid rgba(255, 255, 255, 0.2);
17
+ }
18
+ .color-input-wrapper {
19
+ position: relative;
20
+ overflow: hidden;
21
+ border-radius: 0.5rem;
22
+ border: 1px solid #e2e8f0;
23
+ display: flex;
24
+ align-items: center;
25
+ padding: 0.25rem;
26
+ }
27
+ .color-input-wrapper input[type="color"] {
28
+ border: none;
29
+ width: 30px;
30
+ height: 30px;
31
+ cursor: pointer;
32
+ background: none;
33
+ }
34
+ </style>
35
+ </head>
36
+ <body class="bg-gradient-to-br from-indigo-50 to-purple-50 min-h-screen text-slate-800">
37
+ <div id="app" class="container mx-auto px-4 py-8 max-w-5xl">
38
+
39
+ <!-- Header -->
40
+ <header class="text-center mb-10">
41
+ <h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-purple-600 mb-2">
42
+ <i class="fa-solid fa-qrcode mr-2"></i>超级二维码工坊
43
+ </h1>
44
+ <p class="text-slate-500">专业、免费、安全的在线二维码生成器</p>
45
+ </header>
46
+
47
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
48
+
49
+ <!-- Left: Configuration -->
50
+ <div class="lg:col-span-7 space-y-6">
51
+
52
+ <!-- Type Selection Tabs -->
53
+ <div class="bg-white rounded-xl shadow-sm p-2 flex space-x-2 border border-slate-100">
54
+ <button @click="currentType = 'text'"
55
+ :class="['flex-1 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center space-x-2', currentType === 'text' ? 'bg-indigo-600 text-white shadow-md' : 'text-slate-600 hover:bg-slate-50']">
56
+ <i class="fa-solid fa-link"></i> <span>文本/链接</span>
57
+ </button>
58
+ <button @click="currentType = 'wifi'"
59
+ :class="['flex-1 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center space-x-2', currentType === 'wifi' ? 'bg-indigo-600 text-white shadow-md' : 'text-slate-600 hover:bg-slate-50']">
60
+ <i class="fa-solid fa-wifi"></i> <span>WiFi</span>
61
+ </button>
62
+ <button @click="currentType = 'vcard'"
63
+ :class="['flex-1 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center space-x-2', currentType === 'vcard' ? 'bg-indigo-600 text-white shadow-md' : 'text-slate-600 hover:bg-slate-50']">
64
+ <i class="fa-solid fa-address-card"></i> <span>电子名片</span>
65
+ </button>
66
+ </div>
67
+
68
+ <!-- Input Form -->
69
+ <div class="bg-white rounded-2xl shadow-lg p-6 border border-slate-100">
70
+
71
+ <!-- Text/Link Inputs -->
72
+ <div v-if="currentType === 'text'" class="space-y-4">
73
+ <div>
74
+ <label class="block text-sm font-medium text-slate-700 mb-1">内容或链接</label>
75
+ <textarea v-model="form.content" rows="4"
76
+ class="w-full rounded-lg border-slate-200 focus:border-indigo-500 focus:ring focus:ring-indigo-200 transition-all p-3"
77
+ placeholder="输入文本或 https://..."></textarea>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- WiFi Inputs -->
82
+ <div v-if="currentType === 'wifi'" class="space-y-4">
83
+ <div>
84
+ <label class="block text-sm font-medium text-slate-700 mb-1">WiFi 名称 (SSID)</label>
85
+ <input type="text" v-model="form.wifi_ssid" class="w-full rounded-lg border-slate-200 p-2.5 focus:ring-indigo-200 focus:border-indigo-500">
86
+ </div>
87
+ <div>
88
+ <label class="block text-sm font-medium text-slate-700 mb-1">密码</label>
89
+ <input type="text" v-model="form.wifi_password" class="w-full rounded-lg border-slate-200 p-2.5 focus:ring-indigo-200 focus:border-indigo-500">
90
+ </div>
91
+ <div class="flex space-x-4">
92
+ <div class="flex-1">
93
+ <label class="block text-sm font-medium text-slate-700 mb-1">加密方式</label>
94
+ <select v-model="form.wifi_security" class="w-full rounded-lg border-slate-200 p-2.5">
95
+ <option value="WPA">WPA/WPA2</option>
96
+ <option value="WEP">WEP</option>
97
+ <option value="nopass">无密码</option>
98
+ </select>
99
+ </div>
100
+ <div class="flex items-center pt-6">
101
+ <label class="flex items-center space-x-2 cursor-pointer">
102
+ <input type="checkbox" v-model="form.wifi_hidden" class="rounded text-indigo-600 focus:ring-indigo-500">
103
+ <span class="text-sm text-slate-700">隐藏网络</span>
104
+ </label>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- VCard Inputs -->
110
+ <div v-if="currentType === 'vcard'" class="space-y-4">
111
+ <div class="grid grid-cols-2 gap-4">
112
+ <div>
113
+ <label class="block text-sm font-medium text-slate-700 mb-1">姓名</label>
114
+ <input type="text" v-model="form.vcard_name" class="w-full rounded-lg border-slate-200 p-2.5" placeholder="Lastname;Firstname">
115
+ </div>
116
+ <div>
117
+ <label class="block text-sm font-medium text-slate-700 mb-1">显示名称</label>
118
+ <input type="text" v-model="form.vcard_displayname" class="w-full rounded-lg border-slate-200 p-2.5" placeholder="张三">
119
+ </div>
120
+ </div>
121
+ <div class="grid grid-cols-2 gap-4">
122
+ <div>
123
+ <label class="block text-sm font-medium text-slate-700 mb-1">邮箱</label>
124
+ <input type="email" v-model="form.vcard_email" class="w-full rounded-lg border-slate-200 p-2.5">
125
+ </div>
126
+ <div>
127
+ <label class="block text-sm font-medium text-slate-700 mb-1">电话</label>
128
+ <input type="tel" v-model="form.vcard_phone" class="w-full rounded-lg border-slate-200 p-2.5">
129
+ </div>
130
+ </div>
131
+ <div>
132
+ <label class="block text-sm font-medium text-slate-700 mb-1">个人主页/公司网址</label>
133
+ <input type="url" v-model="form.vcard_url" class="w-full rounded-lg border-slate-200 p-2.5">
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- Customization Options -->
139
+ <div class="bg-white rounded-2xl shadow-lg p-6 border border-slate-100">
140
+ <h3 class="font-bold text-lg text-slate-800 mb-4 flex items-center">
141
+ <i class="fa-solid fa-palette text-indigo-500 mr-2"></i> 美化选项
142
+ </h3>
143
+
144
+ <div class="grid grid-cols-2 gap-6 mb-6">
145
+ <div>
146
+ <label class="block text-sm font-medium text-slate-700 mb-2">前景色</label>
147
+ <div class="color-input-wrapper">
148
+ <input type="color" v-model="form.fg_color">
149
+ <span class="ml-2 text-sm text-slate-500 font-mono" v-text="form.fg_color"></span>
150
+ </div>
151
+ </div>
152
+ <div>
153
+ <label class="block text-sm font-medium text-slate-700 mb-2">背景色</label>
154
+ <div class="color-input-wrapper">
155
+ <input type="color" v-model="form.bg_color">
156
+ <span class="ml-2 text-sm text-slate-500 font-mono" v-text="form.bg_color"></span>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <div class="mb-6">
162
+ <label class="block text-sm font-medium text-slate-700 mb-2">嵌入 Logo (可选)</label>
163
+ <div class="flex items-center space-x-4">
164
+ <label class="cursor-pointer bg-slate-50 border border-slate-200 hover:bg-slate-100 text-slate-600 px-4 py-2 rounded-lg transition-colors flex items-center">
165
+ <i class="fa-solid fa-cloud-upload-alt mr-2"></i> 选择图片
166
+ <input type="file" @change="handleLogoUpload" class="hidden" accept="image/*">
167
+ </label>
168
+ <span v-if="logoFileName" class="text-sm text-green-600 flex items-center">
169
+ <i class="fa-solid fa-check-circle mr-1"></i> <span v-text="logoFileName"></span>
170
+ <button @click="clearLogo" class="ml-2 text-slate-400 hover:text-red-500">
171
+ <i class="fa-solid fa-times"></i>
172
+ </button>
173
+ </span>
174
+ </div>
175
+ </div>
176
+
177
+ <button @click="generateQR" :disabled="loading"
178
+ class="w-full bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white font-bold py-3 rounded-xl shadow-lg transform transition hover:-translate-y-0.5 disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center">
179
+ <span v-if="loading"><i class="fa-solid fa-circle-notch fa-spin mr-2"></i> 生成中...</span>
180
+ <span v-else><i class="fa-solid fa-wand-magic-sparkles mr-2"></i> 立即生成二维码</span>
181
+ </button>
182
+ </div>
183
+ </div>
184
+
185
+ <!-- Right: Preview -->
186
+ <div class="lg:col-span-5">
187
+ <div class="bg-white rounded-2xl shadow-xl p-6 border border-slate-100 sticky top-8 text-center">
188
+ <h3 class="font-bold text-lg text-slate-800 mb-6">预览结果</h3>
189
+
190
+ <div class="aspect-square bg-slate-50 rounded-xl flex items-center justify-center border-2 border-dashed border-slate-200 mb-6 overflow-hidden relative group">
191
+ <div v-if="!resultImage && !loading" class="text-slate-400 flex flex-col items-center">
192
+ <i class="fa-solid fa-qrcode text-6xl mb-4 opacity-20"></i>
193
+ <p>点击生成预览</p>
194
+ </div>
195
+ <div v-else-if="loading" class="text-indigo-500">
196
+ <i class="fa-solid fa-circle-notch fa-spin text-4xl"></i>
197
+ </div>
198
+ <img v-else :src="resultImage" class="max-w-full max-h-full object-contain shadow-sm rounded-lg">
199
+ </div>
200
+
201
+ <div v-if="resultImage" class="space-y-3 animate-fade-in">
202
+ <a :href="resultImage" download="qrcode.png"
203
+ class="block w-full bg-slate-900 hover:bg-slate-800 text-white font-bold py-3 rounded-xl shadow-md transition-colors">
204
+ <i class="fa-solid fa-download mr-2"></i> 下载 PNG
205
+ </a>
206
+ <p class="text-xs text-slate-400">建议使用手机扫码测试后再打印</p>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- Tips -->
211
+ <div class="mt-6 bg-blue-50 rounded-xl p-4 text-sm text-blue-700 border border-blue-100">
212
+ <div class="flex items-start">
213
+ <i class="fa-solid fa-info-circle mt-0.5 mr-2"></i>
214
+ <div>
215
+ <p class="font-bold mb-1">使用小贴士</p>
216
+ <ul class="list-disc list-inside space-y-1 opacity-80">
217
+ <li>Logo 建议使用背景透明的 PNG 图片</li>
218
+ <li>若颜色太浅可能导致无法扫描,建议保持高对比度</li>
219
+ <li>WiFi 二维码可直接扫码连接网络</li>
220
+ </ul>
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ <script>
229
+ const { createApp, ref, reactive } = Vue;
230
+
231
+ createApp({
232
+ setup() {
233
+ const currentType = ref('text');
234
+ const loading = ref(false);
235
+ const resultImage = ref(null);
236
+ const logoFile = ref(null);
237
+ const logoFileName = ref('');
238
+
239
+ const form = reactive({
240
+ content: '',
241
+ wifi_ssid: '',
242
+ wifi_password: '',
243
+ wifi_security: 'WPA',
244
+ wifi_hidden: false,
245
+ vcard_name: '',
246
+ vcard_displayname: '',
247
+ vcard_email: '',
248
+ vcard_phone: '',
249
+ vcard_url: '',
250
+ fg_color: '#000000',
251
+ bg_color: '#ffffff',
252
+ scale: 10
253
+ });
254
+
255
+ const handleLogoUpload = (event) => {
256
+ const file = event.target.files[0];
257
+ if (file) {
258
+ logoFile.value = file;
259
+ logoFileName.value = file.name;
260
+ }
261
+ };
262
+
263
+ const clearLogo = () => {
264
+ logoFile.value = null;
265
+ logoFileName.value = '';
266
+ };
267
+
268
+ const generateQR = async () => {
269
+ loading.value = true;
270
+ resultImage.value = null;
271
+
272
+ try {
273
+ const formData = new FormData();
274
+ formData.append('type', currentType.value);
275
+ formData.append('fg_color', form.fg_color);
276
+ formData.append('bg_color', form.bg_color);
277
+ formData.append('scale', form.scale);
278
+
279
+ if (currentType.value === 'text') {
280
+ formData.append('content', form.content);
281
+ } else if (currentType.value === 'wifi') {
282
+ formData.append('wifi_ssid', form.wifi_ssid);
283
+ formData.append('wifi_password', form.wifi_password);
284
+ formData.append('wifi_security', form.wifi_security);
285
+ formData.append('wifi_hidden', form.wifi_hidden);
286
+ } else if (currentType.value === 'vcard') {
287
+ formData.append('vcard_name', form.vcard_name);
288
+ formData.append('vcard_displayname', form.vcard_displayname);
289
+ formData.append('vcard_email', form.vcard_email);
290
+ formData.append('vcard_phone', form.vcard_phone);
291
+ formData.append('vcard_url', form.vcard_url);
292
+ }
293
+
294
+ if (logoFile.value) {
295
+ formData.append('logo', logoFile.value);
296
+ }
297
+
298
+ const response = await fetch('/api/generate', {
299
+ method: 'POST',
300
+ body: formData
301
+ });
302
+
303
+ const data = await response.json();
304
+
305
+ if (response.ok) {
306
+ resultImage.value = data.image;
307
+ } else {
308
+ alert('生成失败: ' + (data.error || '未知错误'));
309
+ }
310
+ } catch (error) {
311
+ alert('请求错误: ' + error.message);
312
+ } finally {
313
+ loading.value = false;
314
+ }
315
+ };
316
+
317
+ return {
318
+ currentType,
319
+ form,
320
+ loading,
321
+ resultImage,
322
+ logoFileName,
323
+ handleLogoUpload,
324
+ clearLogo,
325
+ generateQR
326
+ };
327
+ }
328
+ }).mount('#app');
329
+ </script>
330
+ </body>
331
+ </html>