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 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
@@ -0,0 +1,2 @@
1
+ export { default } from './QRCodePlugin';
2
+ export { manifest } from './manifest';
@@ -0,0 +1,10 @@
1
+ export const manifest = {
2
+ id: 'com.desktop-tool.plugin.qrcode',
3
+ name: '二维码生成器',
4
+ version: '1.0.0',
5
+ description: '强大的二维码生成工具,支持文本、链接等多种格式',
6
+ author: 'Desktop Tool',
7
+ icon: '📱',
8
+ category: '工具',
9
+ permissions: [] as string[]
10
+ };
@@ -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
+ });