desktop-tool-pl-qrcode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/package.json +45 -0
- package/src/QRCodePlugin.css +241 -0
- package/src/QRCodePlugin.tsx +214 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +10 -0
- package/src/utils/qrcode-generator.ts +40 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# desktop-tool-pl-qrcode
|
|
2
|
+
|
|
3
|
+
强大的二维码生成工具插件,适用于 Desktop Tool。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 📝 文本转二维码
|
|
8
|
+
- 🔗 链接转二维码
|
|
9
|
+
- 🎨 自定义颜色和样式
|
|
10
|
+
- 📥 下载为 PNG 图片
|
|
11
|
+
- ⚙️ 多种容错率选择
|
|
12
|
+
- 📐 自定义尺寸和边距
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
通过 Desktop Tool 的插件市场搜索 `qrcode` 或 `desktop-tool-pl-qrcode` 安装。
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
1. 打开插件
|
|
21
|
+
2. 选择输入类型(文本/链接)
|
|
22
|
+
3. 输入内容
|
|
23
|
+
4. 调整样式(可选)
|
|
24
|
+
5. 点击下载保存
|
|
25
|
+
|
|
26
|
+
## 开发
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 安装依赖
|
|
30
|
+
npm install
|
|
31
|
+
|
|
32
|
+
# 开发模式
|
|
33
|
+
npm run dev
|
|
34
|
+
|
|
35
|
+
# 构建
|
|
36
|
+
npm run build
|
|
37
|
+
|
|
38
|
+
# 发布到 npm
|
|
39
|
+
npm publish
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 技术栈
|
|
43
|
+
|
|
44
|
+
- React 18
|
|
45
|
+
- TypeScript
|
|
46
|
+
- Vite
|
|
47
|
+
- qrcode
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "desktop-tool-pl-qrcode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "强大的二维码生成工具",
|
|
5
|
+
"keywords": ["desktop-tool-plugin", "qrcode", "二维码"],
|
|
6
|
+
"author": "Desktop Tool",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"desktop-tool": {
|
|
19
|
+
"id": "com.desktop-tool.plugin.qrcode",
|
|
20
|
+
"entry": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^18.0.0",
|
|
24
|
+
"react-dom": "^18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"qrcode": "^1.5.3"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/qrcode": "^1.5.5",
|
|
31
|
+
"@types/react": "^18.2.0",
|
|
32
|
+
"@types/react-dom": "^18.2.0",
|
|
33
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
34
|
+
"typescript": "^5.0.0",
|
|
35
|
+
"vite": "^4.3.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite",
|
|
39
|
+
"build": "tsc && vite build",
|
|
40
|
+
"build:types": "tsc",
|
|
41
|
+
"build:lib": "vite build",
|
|
42
|
+
"preview": "vite preview",
|
|
43
|
+
"prepublishOnly": "npm run build"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
.qrcode-plugin {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: 20px;
|
|
5
|
+
padding: 20px;
|
|
6
|
+
max-width: 900px;
|
|
7
|
+
margin: 0 auto;
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.qrcode-header {
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
align-items: center;
|
|
15
|
+
margin-bottom: 10px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.qrcode-header h2 {
|
|
19
|
+
margin: 0;
|
|
20
|
+
font-size: 24px;
|
|
21
|
+
color: #333;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.qrcode-window-controls {
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.qrcode-window-controls button {
|
|
30
|
+
width: 32px;
|
|
31
|
+
height: 32px;
|
|
32
|
+
border: none;
|
|
33
|
+
background: #f5f5f5;
|
|
34
|
+
border-radius: 6px;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
transition: background 0.2s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.qrcode-window-controls button:hover {
|
|
41
|
+
background: #e0e0e0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.qrcode-tabs {
|
|
45
|
+
display: flex;
|
|
46
|
+
gap: 8px;
|
|
47
|
+
border-bottom: 2px solid #e0e0e0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.qrcode-tab {
|
|
51
|
+
padding: 10px 20px;
|
|
52
|
+
background: none;
|
|
53
|
+
border: none;
|
|
54
|
+
border-bottom: 2px solid transparent;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
font-size: 15px;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
color: #666;
|
|
59
|
+
transition: all 0.2s;
|
|
60
|
+
margin-bottom: -2px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.qrcode-tab:hover {
|
|
64
|
+
color: #0066CC;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.qrcode-tab.active {
|
|
68
|
+
color: #0066CC;
|
|
69
|
+
border-bottom-color: #0066CC;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.qrcode-content {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: 20px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.qrcode-input-section {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.qrcode-input-section label {
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
color: #333;
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.qrcode-textarea {
|
|
91
|
+
width: 100%;
|
|
92
|
+
padding: 12px;
|
|
93
|
+
border: 1px solid #ddd;
|
|
94
|
+
border-radius: 8px;
|
|
95
|
+
font-size: 14px;
|
|
96
|
+
font-family: inherit;
|
|
97
|
+
resize: vertical;
|
|
98
|
+
transition: border-color 0.2s;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.qrcode-textarea:focus {
|
|
102
|
+
outline: none;
|
|
103
|
+
border-color: #0066CC;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.qrcode-error {
|
|
107
|
+
padding: 12px 16px;
|
|
108
|
+
background: #ffe6e6;
|
|
109
|
+
color: #dc3545;
|
|
110
|
+
border-radius: 8px;
|
|
111
|
+
border: 1px solid #ffc9c9;
|
|
112
|
+
font-size: 14px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.qrcode-preview-section {
|
|
116
|
+
display: flex;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.qrcode-preview-card {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 16px;
|
|
125
|
+
padding: 24px;
|
|
126
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
127
|
+
border-radius: 16px;
|
|
128
|
+
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.qrcode-image {
|
|
132
|
+
max-width: 100%;
|
|
133
|
+
height: auto;
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
background: white;
|
|
136
|
+
padding: 16px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.qrcode-download-btn {
|
|
140
|
+
padding: 12px 24px;
|
|
141
|
+
background: white;
|
|
142
|
+
color: #667eea;
|
|
143
|
+
border: none;
|
|
144
|
+
border-radius: 8px;
|
|
145
|
+
font-size: 15px;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
transition: all 0.2s;
|
|
149
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.qrcode-download-btn:hover {
|
|
153
|
+
transform: translateY(-2px);
|
|
154
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.qrcode-settings {
|
|
158
|
+
padding: 20px;
|
|
159
|
+
background: #f9f9f9;
|
|
160
|
+
border-radius: 12px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.qrcode-settings h3 {
|
|
164
|
+
margin: 0 0 16px 0;
|
|
165
|
+
font-size: 16px;
|
|
166
|
+
color: #333;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.qrcode-settings-grid {
|
|
170
|
+
display: grid;
|
|
171
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
172
|
+
gap: 16px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.qrcode-setting-item {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
gap: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.qrcode-setting-item label {
|
|
182
|
+
font-size: 13px;
|
|
183
|
+
font-weight: 500;
|
|
184
|
+
color: #555;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.qrcode-slider {
|
|
188
|
+
width: 100%;
|
|
189
|
+
height: 6px;
|
|
190
|
+
border-radius: 3px;
|
|
191
|
+
background: #ddd;
|
|
192
|
+
outline: none;
|
|
193
|
+
-webkit-appearance: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.qrcode-slider::-webkit-slider-thumb {
|
|
197
|
+
-webkit-appearance: none;
|
|
198
|
+
appearance: none;
|
|
199
|
+
width: 18px;
|
|
200
|
+
height: 18px;
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
background: #0066CC;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
transition: all 0.2s;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.qrcode-slider::-webkit-slider-thumb:hover {
|
|
208
|
+
transform: scale(1.2);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.qrcode-slider::-moz-range-thumb {
|
|
212
|
+
width: 18px;
|
|
213
|
+
height: 18px;
|
|
214
|
+
border-radius: 50%;
|
|
215
|
+
background: #0066CC;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
border: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.qrcode-color-picker {
|
|
221
|
+
width: 60px;
|
|
222
|
+
height: 40px;
|
|
223
|
+
border: 1px solid #ddd;
|
|
224
|
+
border-radius: 6px;
|
|
225
|
+
cursor: pointer;
|
|
226
|
+
padding: 2px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.qrcode-select {
|
|
230
|
+
padding: 8px 12px;
|
|
231
|
+
border: 1px solid #ddd;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
font-size: 14px;
|
|
234
|
+
background: white;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.qrcode-select:focus {
|
|
239
|
+
outline: none;
|
|
240
|
+
border-color: #0066CC;
|
|
241
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { generateQRCode, downloadQRCode } from './utils/qrcode-generator';
|
|
3
|
+
import './QRCodePlugin.css';
|
|
4
|
+
|
|
5
|
+
interface QRCodePluginProps {
|
|
6
|
+
pluginId: string;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
onMinimize?: () => void;
|
|
9
|
+
onMaximize?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type TabType = 'text' | 'url';
|
|
13
|
+
|
|
14
|
+
type ErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H';
|
|
15
|
+
|
|
16
|
+
function QRCodePlugin({ onClose, onMinimize, onMaximize }: QRCodePluginProps) {
|
|
17
|
+
const [tab, setTab] = useState<TabType>('text');
|
|
18
|
+
const [input, setInput] = useState('');
|
|
19
|
+
const [qrCodeUrl, setQrCodeUrl] = useState<string>('');
|
|
20
|
+
const [error, setError] = useState<string>('');
|
|
21
|
+
|
|
22
|
+
const [settings, setSettings] = useState({
|
|
23
|
+
width: 300,
|
|
24
|
+
margin: 2,
|
|
25
|
+
color: '#000000',
|
|
26
|
+
bgColor: '#ffffff',
|
|
27
|
+
errorLevel: 'M' as ErrorCorrectionLevel
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 实时生成二维码
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (input.trim()) {
|
|
33
|
+
generateQR();
|
|
34
|
+
} else {
|
|
35
|
+
setQrCodeUrl('');
|
|
36
|
+
}
|
|
37
|
+
}, [input, settings, tab]);
|
|
38
|
+
|
|
39
|
+
const generateQR = async () => {
|
|
40
|
+
if (!input.trim()) {
|
|
41
|
+
setQrCodeUrl('');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setError('');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const text = tab === 'url' && !input.match(/^https?:\/\//)
|
|
49
|
+
? `https://${input}`
|
|
50
|
+
: input;
|
|
51
|
+
|
|
52
|
+
const result = await generateQRCode(text, {
|
|
53
|
+
width: settings.width,
|
|
54
|
+
margin: settings.margin,
|
|
55
|
+
color: { dark: settings.color, light: settings.bgColor },
|
|
56
|
+
errorCorrectionLevel: settings.errorLevel
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
setQrCodeUrl(result);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
setError((err as Error).message);
|
|
62
|
+
setQrCodeUrl('');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleDownload = () => {
|
|
67
|
+
if (qrCodeUrl) {
|
|
68
|
+
const timestamp = new Date().getTime();
|
|
69
|
+
downloadQRCode(qrCodeUrl, `qrcode-${timestamp}.png`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
74
|
+
setInput(e.target.value);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="qrcode-plugin">
|
|
79
|
+
<div className="qrcode-header">
|
|
80
|
+
<h2>📱 二维码生成器</h2>
|
|
81
|
+
{onClose && (
|
|
82
|
+
<div className="qrcode-window-controls">
|
|
83
|
+
<button onClick={onMinimize} title="最小化">─</button>
|
|
84
|
+
<button onClick={onMaximize} title="最大化">□</button>
|
|
85
|
+
<button onClick={onClose} title="关闭">✕</button>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="qrcode-tabs">
|
|
91
|
+
<button
|
|
92
|
+
className={`qrcode-tab ${tab === 'text' ? 'active' : ''}`}
|
|
93
|
+
onClick={() => setTab('text')}
|
|
94
|
+
>
|
|
95
|
+
文本
|
|
96
|
+
</button>
|
|
97
|
+
<button
|
|
98
|
+
className={`qrcode-tab ${tab === 'url' ? 'active' : ''}`}
|
|
99
|
+
onClick={() => setTab('url')}
|
|
100
|
+
>
|
|
101
|
+
链接
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="qrcode-content">
|
|
106
|
+
<div className="qrcode-input-section">
|
|
107
|
+
<label>
|
|
108
|
+
{tab === 'text' ? '输入文本内容' : '输入链接地址'}
|
|
109
|
+
</label>
|
|
110
|
+
<textarea
|
|
111
|
+
value={input}
|
|
112
|
+
onChange={handleInputChange}
|
|
113
|
+
placeholder={tab === 'text' ? '请输入要生成二维码的文本内容...' : '请输入 URL 地址...'}
|
|
114
|
+
className="qrcode-textarea"
|
|
115
|
+
rows={4}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{error && (
|
|
120
|
+
<div className="qrcode-error">
|
|
121
|
+
⚠️ {error}
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{qrCodeUrl && (
|
|
126
|
+
<div className="qrcode-preview-section">
|
|
127
|
+
<div className="qrcode-preview-card">
|
|
128
|
+
<img
|
|
129
|
+
src={qrCodeUrl}
|
|
130
|
+
alt="二维码"
|
|
131
|
+
className="qrcode-image"
|
|
132
|
+
style={{ width: `${settings.width}px` }}
|
|
133
|
+
/>
|
|
134
|
+
<button
|
|
135
|
+
className="qrcode-download-btn"
|
|
136
|
+
onClick={handleDownload}
|
|
137
|
+
>
|
|
138
|
+
📥 下载二维码
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
<div className="qrcode-settings">
|
|
145
|
+
<h3>⚙️ 设置</h3>
|
|
146
|
+
|
|
147
|
+
<div className="qrcode-settings-grid">
|
|
148
|
+
<div className="qrcode-setting-item">
|
|
149
|
+
<label>尺寸: {settings.width}px</label>
|
|
150
|
+
<input
|
|
151
|
+
type="range"
|
|
152
|
+
min="200"
|
|
153
|
+
max="600"
|
|
154
|
+
step="50"
|
|
155
|
+
value={settings.width}
|
|
156
|
+
onChange={(e) => setSettings({ ...settings, width: Number(e.target.value) })}
|
|
157
|
+
className="qrcode-slider"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div className="qrcode-setting-item">
|
|
162
|
+
<label>边距: {settings.margin}</label>
|
|
163
|
+
<input
|
|
164
|
+
type="range"
|
|
165
|
+
min="0"
|
|
166
|
+
max="4"
|
|
167
|
+
step="1"
|
|
168
|
+
value={settings.margin}
|
|
169
|
+
onChange={(e) => setSettings({ ...settings, margin: Number(e.target.value) })}
|
|
170
|
+
className="qrcode-slider"
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div className="qrcode-setting-item">
|
|
175
|
+
<label>前景色</label>
|
|
176
|
+
<input
|
|
177
|
+
type="color"
|
|
178
|
+
value={settings.color}
|
|
179
|
+
onChange={(e) => setSettings({ ...settings, color: e.target.value })}
|
|
180
|
+
className="qrcode-color-picker"
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div className="qrcode-setting-item">
|
|
185
|
+
<label>背景色</label>
|
|
186
|
+
<input
|
|
187
|
+
type="color"
|
|
188
|
+
value={settings.bgColor}
|
|
189
|
+
onChange={(e) => setSettings({ ...settings, bgColor: e.target.value })}
|
|
190
|
+
className="qrcode-color-picker"
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div className="qrcode-setting-item">
|
|
195
|
+
<label>容错率</label>
|
|
196
|
+
<select
|
|
197
|
+
value={settings.errorLevel}
|
|
198
|
+
onChange={(e) => setSettings({ ...settings, errorLevel: e.target.value as ErrorCorrectionLevel })}
|
|
199
|
+
className="qrcode-select"
|
|
200
|
+
>
|
|
201
|
+
<option value="L">L (7%)</option>
|
|
202
|
+
<option value="M">M (15%)</option>
|
|
203
|
+
<option value="Q">Q (25%)</option>
|
|
204
|
+
<option value="H">H (30%)</option>
|
|
205
|
+
</select>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default QRCodePlugin;
|
package/src/index.ts
ADDED
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import QRCode from 'qrcode';
|
|
2
|
+
|
|
3
|
+
export interface QRCodeOptions {
|
|
4
|
+
width?: number;
|
|
5
|
+
margin?: number;
|
|
6
|
+
color?: { dark: string; light: string };
|
|
7
|
+
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function generateQRCode(
|
|
11
|
+
text: string,
|
|
12
|
+
options: QRCodeOptions = {}
|
|
13
|
+
): Promise<string> {
|
|
14
|
+
const defaultOptions: QRCodeOptions = {
|
|
15
|
+
width: 300,
|
|
16
|
+
margin: 2,
|
|
17
|
+
color: { dark: '#000000', light: '#ffffff' },
|
|
18
|
+
errorCorrectionLevel: 'M',
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (!text || text.trim() === '') {
|
|
23
|
+
throw new Error('输入内容不能为空');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
return await QRCode.toDataURL(text, defaultOptions);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`生成二维码失败: ${(error as Error).message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function downloadQRCode(dataUrl: string, filename: string = 'qrcode.png'): void {
|
|
34
|
+
const link = document.createElement('a');
|
|
35
|
+
link.href = dataUrl;
|
|
36
|
+
link.download = filename;
|
|
37
|
+
document.body.appendChild(link);
|
|
38
|
+
link.click();
|
|
39
|
+
document.body.removeChild(link);
|
|
40
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"outDir": "./dist",
|
|
19
|
+
"emitDeclarationOnly": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"]
|
|
22
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
build: {
|
|
7
|
+
lib: {
|
|
8
|
+
entry: 'src/index.ts',
|
|
9
|
+
name: 'QRCodePlugin',
|
|
10
|
+
fileName: 'index',
|
|
11
|
+
formats: ['es']
|
|
12
|
+
},
|
|
13
|
+
rollupOptions: {
|
|
14
|
+
external: ['react', 'react-dom', 'qrcode'],
|
|
15
|
+
output: {
|
|
16
|
+
globals: {
|
|
17
|
+
react: 'React',
|
|
18
|
+
'react-dom': 'ReactDOM',
|
|
19
|
+
qrcode: 'QRCode'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|