@zzp123/mcp-zentao 1.2.1 → 1.3.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/dist/index.js +45 -0
- package/package.json +7 -4
- package/scripts/MCP-INTEGRATION.md +298 -0
- package/scripts/README.md +315 -0
- package/scripts/upload-clipboard-image.js +200 -0
- package/scripts/upload-clipboard-image.ps1 +128 -0
- package/scripts/upload-clipboard-image.py +196 -0
- package/scripts/upload.bat.example +41 -0
package/dist/index.js
CHANGED
|
@@ -989,6 +989,51 @@ server.tool("uploadFile", {
|
|
|
989
989
|
}
|
|
990
990
|
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
991
991
|
});
|
|
992
|
+
server.tool("uploadImageFromClipboard", {
|
|
993
|
+
base64Data: z.string(),
|
|
994
|
+
filename: z.string().optional(),
|
|
995
|
+
uid: z.string().optional()
|
|
996
|
+
}, async ({ base64Data, filename, uid }) => {
|
|
997
|
+
if (!zentaoApi)
|
|
998
|
+
throw new Error("Please initialize Zentao API first");
|
|
999
|
+
const fs = await import('fs');
|
|
1000
|
+
const path = await import('path');
|
|
1001
|
+
// 创建 img 文件夹(如果不存在)
|
|
1002
|
+
const imgDir = path.join(process.cwd(), 'img');
|
|
1003
|
+
if (!fs.existsSync(imgDir)) {
|
|
1004
|
+
fs.mkdirSync(imgDir, { recursive: true });
|
|
1005
|
+
}
|
|
1006
|
+
let fileBuffer;
|
|
1007
|
+
let finalFilename;
|
|
1008
|
+
// 处理 base64 数据
|
|
1009
|
+
const matches = base64Data.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
1010
|
+
if (matches) {
|
|
1011
|
+
const ext = matches[1];
|
|
1012
|
+
const data = matches[2];
|
|
1013
|
+
fileBuffer = Buffer.from(data, 'base64');
|
|
1014
|
+
finalFilename = filename || `clipboard_${Date.now()}.${ext}`;
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
// 直接的 base64 数据,没有 data URL 前缀
|
|
1018
|
+
fileBuffer = Buffer.from(base64Data, 'base64');
|
|
1019
|
+
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1020
|
+
}
|
|
1021
|
+
// 保存到 img 文件夹
|
|
1022
|
+
const savedPath = path.join(imgDir, finalFilename);
|
|
1023
|
+
fs.writeFileSync(savedPath, fileBuffer);
|
|
1024
|
+
// 上传到禅道
|
|
1025
|
+
const result = await zentaoApi.uploadFile({
|
|
1026
|
+
file: fileBuffer,
|
|
1027
|
+
filename: finalFilename,
|
|
1028
|
+
uid
|
|
1029
|
+
});
|
|
1030
|
+
const response = {
|
|
1031
|
+
upload: result,
|
|
1032
|
+
savedPath: savedPath,
|
|
1033
|
+
message: `图片已保存到本地并上传到禅道`
|
|
1034
|
+
};
|
|
1035
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1036
|
+
});
|
|
992
1037
|
server.tool("downloadFile", {
|
|
993
1038
|
fileId: z.number(),
|
|
994
1039
|
savePath: z.string()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zzp123/mcp-zentao",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "禅道项目管理系统的高级API集成包,提供任务管理、Bug跟踪等功能的完整封装,专为Cursor IDE设计的MCP扩展",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"README.md",
|
|
14
|
-
"json-args.js"
|
|
14
|
+
"json-args.js",
|
|
15
|
+
"scripts"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
17
18
|
"build": "tsc",
|
|
@@ -21,7 +22,9 @@
|
|
|
21
22
|
"test:watch": "jest --watch",
|
|
22
23
|
"test:manual": "ts-node test/manual-test.ts",
|
|
23
24
|
"prepublishOnly": "npm run test && npm run build",
|
|
24
|
-
"start": "node json-args.js"
|
|
25
|
+
"start": "node json-args.js",
|
|
26
|
+
"upload:clipboard": "node scripts/upload-clipboard-image.js",
|
|
27
|
+
"upload:clipboard:py": "python scripts/upload-clipboard-image.py"
|
|
25
28
|
},
|
|
26
29
|
"keywords": [
|
|
27
30
|
"mcp",
|
|
@@ -42,7 +45,7 @@
|
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
47
|
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
45
|
-
"@zzp123/mcp-zentao": "^1.2.
|
|
48
|
+
"@zzp123/mcp-zentao": "^1.2.1",
|
|
46
49
|
"axios": "^1.6.7",
|
|
47
50
|
"chalk": "^4.1.2",
|
|
48
51
|
"form-data": "^4.0.4",
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# MCP 集成说明
|
|
2
|
+
|
|
3
|
+
## 🔗 与 MCP 工具联动
|
|
4
|
+
|
|
5
|
+
本项目提供了两种方式来上传剪贴板图片到禅道:
|
|
6
|
+
|
|
7
|
+
### 方式1: 通过 MCP 工具(在 Claude Code 中使用)
|
|
8
|
+
|
|
9
|
+
**适用场景:** 在 Claude Code 对话中直接上传图片
|
|
10
|
+
|
|
11
|
+
**已有的 MCP 工具:**
|
|
12
|
+
|
|
13
|
+
1. **uploadImageFromClipboard** - 专门用于上传剪贴板图片
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"base64Data": "data:image/png;base64,...",
|
|
17
|
+
"filename": "optional-name.png",
|
|
18
|
+
"uid": "optional-uid"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. **uploadFile** - 通用文件上传(支持路径或 base64)
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"filePath": "/path/to/file.png",
|
|
26
|
+
"base64Data": "data:image/png;base64,...",
|
|
27
|
+
"filename": "optional-name.png",
|
|
28
|
+
"uid": "optional-uid"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**使用方法:**
|
|
33
|
+
|
|
34
|
+
在 Claude Code 中:
|
|
35
|
+
```
|
|
36
|
+
你:复制一张图片后,对 Claude 说:
|
|
37
|
+
"上传这张图片到禅道"
|
|
38
|
+
|
|
39
|
+
Claude 会自动:
|
|
40
|
+
1. 获取剪贴板中的图片 base64 数据
|
|
41
|
+
2. 调用 uploadImageFromClipboard 工具
|
|
42
|
+
3. 保存图片到 ./img/ 文件夹
|
|
43
|
+
4. 上传到禅道
|
|
44
|
+
5. 返回文件 ID 和 URL
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 方式2: 通过命令行脚本(独立使用)
|
|
50
|
+
|
|
51
|
+
**适用场景:** 在终端/命令行中快速上传图片,不需要启动 Claude Code
|
|
52
|
+
|
|
53
|
+
**提供的脚本:**
|
|
54
|
+
|
|
55
|
+
1. **Node.js 脚本** - `scripts/upload-clipboard-image.js`
|
|
56
|
+
2. **PowerShell 脚本** - `scripts/upload-clipboard-image.ps1`
|
|
57
|
+
3. **Python 脚本** - `scripts/upload-clipboard-image.py`
|
|
58
|
+
|
|
59
|
+
**使用方法:**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 方式1: 使用 npm scripts
|
|
63
|
+
npm run upload:clipboard
|
|
64
|
+
|
|
65
|
+
# 方式2: 直接运行脚本
|
|
66
|
+
node scripts/upload-clipboard-image.js
|
|
67
|
+
python scripts/upload-clipboard-image.py
|
|
68
|
+
.\scripts\upload-clipboard-image.ps1 -ZentaoUrl "..." -Username "..." -Password "..."
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🔄 两种方式的区别
|
|
74
|
+
|
|
75
|
+
| 对比项 | MCP 工具 | 命令行脚本 |
|
|
76
|
+
|--------|---------|-----------|
|
|
77
|
+
| **使用场景** | 在 Claude Code 对话中 | 独立在终端使用 |
|
|
78
|
+
| **启动方式** | 告诉 Claude 上传图片 | 运行命令 |
|
|
79
|
+
| **配置方式** | MCP 工具自动获取配置 | 需要设置环境变量 |
|
|
80
|
+
| **图片来源** | Claude 自动获取剪贴板 | 脚本读取剪贴板 |
|
|
81
|
+
| **适合人群** | 使用 Claude Code 的用户 | 喜欢命令行的开发者 |
|
|
82
|
+
| **优势** | 无缝集成,对话式操作 | 快速、独立、可自动化 |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 💡 推荐使用场景
|
|
87
|
+
|
|
88
|
+
### 场景1: 在编写需求/Bug时上传截图
|
|
89
|
+
|
|
90
|
+
**使用 MCP 工具:**
|
|
91
|
+
```
|
|
92
|
+
你:我要创建一个Bug
|
|
93
|
+
Claude:好的,请提供Bug的详细信息
|
|
94
|
+
你:[复制截图] 上传这张截图
|
|
95
|
+
Claude:✓ 图片已上传,文件ID: 123, URL: /zentao/file-read-123.png
|
|
96
|
+
你:创建Bug,标题是"登录页面样式错误",把刚才的图片加到描述里
|
|
97
|
+
Claude:✓ Bug已创建,ID: 456
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 场景2: 批量处理图片
|
|
101
|
+
|
|
102
|
+
**使用命令行脚本:**
|
|
103
|
+
```bash
|
|
104
|
+
# 创建一个循环脚本
|
|
105
|
+
while true; do
|
|
106
|
+
echo "按任意键上传当前剪贴板图片..."
|
|
107
|
+
read
|
|
108
|
+
npm run upload:clipboard
|
|
109
|
+
done
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 场景3: 与其他工具集成
|
|
113
|
+
|
|
114
|
+
**使用命令行脚本:**
|
|
115
|
+
```bash
|
|
116
|
+
# 截图后自动上传
|
|
117
|
+
# Mac Automator / Windows Task Scheduler
|
|
118
|
+
|
|
119
|
+
# 1. 截图工具保存图片
|
|
120
|
+
screencapture -c
|
|
121
|
+
|
|
122
|
+
# 2. 自动上传
|
|
123
|
+
npm run upload:clipboard
|
|
124
|
+
|
|
125
|
+
# 3. 将URL复制到剪贴板
|
|
126
|
+
# (脚本可修改为返回URL到剪贴板)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🛠️ 高级集成
|
|
132
|
+
|
|
133
|
+
### 1. 添加为 MCP 子命令
|
|
134
|
+
|
|
135
|
+
可以创建一个 MCP 工具来调用命令行脚本:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// 在 src/index.ts 中添加
|
|
139
|
+
server.tool("uploadClipboardViaCLI", {
|
|
140
|
+
method: z.enum(['node', 'python', 'powershell']).optional()
|
|
141
|
+
}, async ({ method = 'node' }) => {
|
|
142
|
+
const { exec } = await import('child_process');
|
|
143
|
+
const { promisify } = await import('util');
|
|
144
|
+
const execAsync = promisify(exec);
|
|
145
|
+
|
|
146
|
+
const command = method === 'python'
|
|
147
|
+
? 'python scripts/upload-clipboard-image.py'
|
|
148
|
+
: method === 'powershell'
|
|
149
|
+
? 'powershell -File scripts/upload-clipboard-image.ps1 ...'
|
|
150
|
+
: 'node scripts/upload-clipboard-image.js';
|
|
151
|
+
|
|
152
|
+
const { stdout, stderr } = await execAsync(command);
|
|
153
|
+
return { content: [{ type: "text", text: stdout || stderr }] };
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2. 创建快捷命令
|
|
158
|
+
|
|
159
|
+
**package.json:**
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"scripts": {
|
|
163
|
+
"zentao:upload": "npm run upload:clipboard",
|
|
164
|
+
"zentao:upload:watch": "nodemon --watch clipboard --exec 'npm run upload:clipboard'"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 3. 集成到 Git Hooks
|
|
170
|
+
|
|
171
|
+
**.git/hooks/pre-commit:**
|
|
172
|
+
```bash
|
|
173
|
+
#!/bin/bash
|
|
174
|
+
# 在提交前自动上传截图到禅道
|
|
175
|
+
|
|
176
|
+
if git diff --cached --name-only | grep -q "screenshots/"; then
|
|
177
|
+
echo "检测到新截图,自动上传到禅道..."
|
|
178
|
+
npm run upload:clipboard
|
|
179
|
+
fi
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 4. VS Code Task 集成
|
|
183
|
+
|
|
184
|
+
**.vscode/tasks.json:**
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"version": "2.0.0",
|
|
188
|
+
"tasks": [
|
|
189
|
+
{
|
|
190
|
+
"label": "Upload Clipboard to Zentao",
|
|
191
|
+
"type": "npm",
|
|
192
|
+
"script": "upload:clipboard",
|
|
193
|
+
"problemMatcher": [],
|
|
194
|
+
"presentation": {
|
|
195
|
+
"reveal": "always",
|
|
196
|
+
"panel": "new"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
然后使用 `Ctrl+Shift+B` 选择任务运行。
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 📊 完整工作流示例
|
|
208
|
+
|
|
209
|
+
### 工作流1: 使用 MCP(推荐)
|
|
210
|
+
|
|
211
|
+
```mermaid
|
|
212
|
+
graph LR
|
|
213
|
+
A[复制图片] --> B[告诉Claude上传]
|
|
214
|
+
B --> C[Claude调用MCP工具]
|
|
215
|
+
C --> D[自动保存到img/]
|
|
216
|
+
D --> E[上传到禅道]
|
|
217
|
+
E --> F[返回ID和URL]
|
|
218
|
+
F --> G[在对话中使用]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**优点:**
|
|
222
|
+
- ✅ 无需离开对话界面
|
|
223
|
+
- ✅ 自动化程度高
|
|
224
|
+
- ✅ 与其他操作串联
|
|
225
|
+
- ✅ 无需配置环境变量
|
|
226
|
+
|
|
227
|
+
### 工作流2: 使用命令行
|
|
228
|
+
|
|
229
|
+
```mermaid
|
|
230
|
+
graph LR
|
|
231
|
+
A[复制图片] --> B[运行脚本]
|
|
232
|
+
B --> C[读取剪贴板]
|
|
233
|
+
C --> D[保存到img/]
|
|
234
|
+
D --> E[上传到禅道]
|
|
235
|
+
E --> F[显示结果]
|
|
236
|
+
F --> G[手动复制URL]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**优点:**
|
|
240
|
+
- ✅ 快速独立
|
|
241
|
+
- ✅ 可自动化
|
|
242
|
+
- ✅ 可集成到其他工具
|
|
243
|
+
- ✅ 不依赖 Claude Code
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 🎯 最佳实践
|
|
248
|
+
|
|
249
|
+
### 1. 选择合适的方式
|
|
250
|
+
|
|
251
|
+
- **日常使用** → MCP 工具(在 Claude Code 中)
|
|
252
|
+
- **批量处理** → 命令行脚本
|
|
253
|
+
- **自动化集成** → 命令行脚本 + 系统工具
|
|
254
|
+
|
|
255
|
+
### 2. 配置管理
|
|
256
|
+
|
|
257
|
+
**MCP 方式:**
|
|
258
|
+
- 使用 `.zentao/config.json` 存储配置
|
|
259
|
+
- 通过 `initZentao` 工具初始化
|
|
260
|
+
|
|
261
|
+
**命令行方式:**
|
|
262
|
+
- 创建 `.env` 文件
|
|
263
|
+
- 或创建 `upload.bat` / `upload.sh` 包含配置
|
|
264
|
+
- 不要将密码提交到版本控制
|
|
265
|
+
|
|
266
|
+
### 3. 图片管理
|
|
267
|
+
|
|
268
|
+
- 所有图片统一保存到 `./img/` 文件夹
|
|
269
|
+
- 文件名格式:`clipboard_时间戳.png` 或 `image_时间戳.png`
|
|
270
|
+
- 可在 `.gitignore` 中添加 `img/` 避免提交
|
|
271
|
+
|
|
272
|
+
### 4. 错误处理
|
|
273
|
+
|
|
274
|
+
- 检查剪贴板是否有图片
|
|
275
|
+
- 验证禅道连接
|
|
276
|
+
- 记录上传日志
|
|
277
|
+
- 自动重试机制(可选)
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 📚 相关文档
|
|
282
|
+
|
|
283
|
+
- [脚本使用说明](./README.md)
|
|
284
|
+
- [MCP 工具列表](../README.md#api-tools)
|
|
285
|
+
- [禅道API文档](../禅道API文档.md)
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 🤝 贡献
|
|
290
|
+
|
|
291
|
+
如果您有更好的集成方案,欢迎提交 PR!
|
|
292
|
+
|
|
293
|
+
可能的改进方向:
|
|
294
|
+
- 监听剪贴板变化自动上传
|
|
295
|
+
- 支持拖拽上传
|
|
296
|
+
- 批量上传多张图片
|
|
297
|
+
- 图片压缩/优化
|
|
298
|
+
- 上传历史记录
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# 剪贴板图片上传脚本
|
|
2
|
+
|
|
3
|
+
提供三种方案来获取剪贴板图片并上传到禅道系统。
|
|
4
|
+
|
|
5
|
+
## 📋 方案对比
|
|
6
|
+
|
|
7
|
+
| 方案 | 平台 | 优点 | 缺点 |
|
|
8
|
+
|-----|------|------|------|
|
|
9
|
+
| Node.js | 跨平台 | 与项目集成,无需额外配置 | 需要Node环境 |
|
|
10
|
+
| PowerShell | Windows | 原生支持,无需安装依赖 | 仅Windows |
|
|
11
|
+
| Python | 跨平台 | 简单易用,代码清晰 | 需要Python环境 |
|
|
12
|
+
|
|
13
|
+
## 🚀 快速开始
|
|
14
|
+
|
|
15
|
+
### 方案1: Node.js 脚本(推荐)
|
|
16
|
+
|
|
17
|
+
**优点:** 跨平台,与项目无缝集成
|
|
18
|
+
|
|
19
|
+
**依赖:**
|
|
20
|
+
```bash
|
|
21
|
+
# 安装 clipboardy(可选,用于跨平台剪贴板支持)
|
|
22
|
+
npm install -g clipboardy
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**使用方法:**
|
|
26
|
+
|
|
27
|
+
**Windows:**
|
|
28
|
+
```batch
|
|
29
|
+
set ZENTAO_URL=https://your-zentao-url
|
|
30
|
+
set ZENTAO_USERNAME=your-username
|
|
31
|
+
set ZENTAO_PASSWORD=your-password
|
|
32
|
+
set ZENTAO_UID=optional-uid
|
|
33
|
+
|
|
34
|
+
node scripts/upload-clipboard-image.js
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Mac/Linux:**
|
|
38
|
+
```bash
|
|
39
|
+
export ZENTAO_URL=https://your-zentao-url
|
|
40
|
+
export ZENTAO_USERNAME=your-username
|
|
41
|
+
export ZENTAO_PASSWORD=your-password
|
|
42
|
+
export ZENTAO_UID=optional-uid
|
|
43
|
+
|
|
44
|
+
node scripts/upload-clipboard-image.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**特性:**
|
|
48
|
+
- ✅ 自动检测操作系统
|
|
49
|
+
- ✅ Windows: 使用 PowerShell 读取剪贴板
|
|
50
|
+
- ✅ macOS: 使用 pngpaste (需要安装: `brew install pngpaste`)
|
|
51
|
+
- ✅ Linux: 使用 xclip (需要安装: `sudo apt-get install xclip`)
|
|
52
|
+
- ✅ 自动保存到 img 文件夹
|
|
53
|
+
- ✅ 返回禅道文件ID和URL
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### 方案2: PowerShell 脚本(Windows专用)
|
|
58
|
+
|
|
59
|
+
**优点:** 原生支持,无需安装额外依赖
|
|
60
|
+
|
|
61
|
+
**使用方法:**
|
|
62
|
+
```powershell
|
|
63
|
+
.\scripts\upload-clipboard-image.ps1 `
|
|
64
|
+
-ZentaoUrl "https://your-zentao-url" `
|
|
65
|
+
-Username "your-username" `
|
|
66
|
+
-Password "your-password" `
|
|
67
|
+
-Uid "optional-uid"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**或者创建批处理文件 `upload.bat`:**
|
|
71
|
+
```batch
|
|
72
|
+
@echo off
|
|
73
|
+
powershell -ExecutionPolicy Bypass -File "%~dp0scripts\upload-clipboard-image.ps1" ^
|
|
74
|
+
-ZentaoUrl "https://your-zentao-url" ^
|
|
75
|
+
-Username "your-username" ^
|
|
76
|
+
-Password "your-password"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**特性:**
|
|
80
|
+
- ✅ Windows原生支持
|
|
81
|
+
- ✅ 无需安装额外工具
|
|
82
|
+
- ✅ 支持所有图片格式
|
|
83
|
+
- ✅ 自动转换为PNG
|
|
84
|
+
- ✅ 显示图片尺寸
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 方案3: Python 脚本(跨平台)
|
|
89
|
+
|
|
90
|
+
**优点:** 简单易用,代码清晰
|
|
91
|
+
|
|
92
|
+
**依赖安装:**
|
|
93
|
+
```bash
|
|
94
|
+
pip install pillow requests
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**使用方法:**
|
|
98
|
+
|
|
99
|
+
**Windows:**
|
|
100
|
+
```batch
|
|
101
|
+
set ZENTAO_URL=https://your-zentao-url
|
|
102
|
+
set ZENTAO_USERNAME=your-username
|
|
103
|
+
set ZENTAO_PASSWORD=your-password
|
|
104
|
+
set ZENTAO_UID=optional-uid
|
|
105
|
+
|
|
106
|
+
python scripts/upload-clipboard-image.py
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Mac/Linux:**
|
|
110
|
+
```bash
|
|
111
|
+
export ZENTAO_URL=https://your-zentao-url
|
|
112
|
+
export ZENTAO_USERNAME=your-username
|
|
113
|
+
export ZENTAO_PASSWORD=your-password
|
|
114
|
+
export ZENTAO_UID=optional-uid
|
|
115
|
+
|
|
116
|
+
python scripts/upload-clipboard-image.py
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**特性:**
|
|
120
|
+
- ✅ 跨平台支持
|
|
121
|
+
- ✅ 使用 PIL/Pillow 处理图片
|
|
122
|
+
- ✅ 支持剪贴板中的图片或图片文件路径
|
|
123
|
+
- ✅ 显示图片尺寸
|
|
124
|
+
- ✅ 详细的错误提示
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 📝 环境变量说明
|
|
129
|
+
|
|
130
|
+
| 变量名 | 必需 | 说明 | 示例 |
|
|
131
|
+
|--------|------|------|------|
|
|
132
|
+
| ZENTAO_URL | 是 | 禅道系统URL | https://zentao.example.com |
|
|
133
|
+
| ZENTAO_USERNAME | 是 | 登录用户名 | admin |
|
|
134
|
+
| ZENTAO_PASSWORD | 是 | 登录密码 | your-password |
|
|
135
|
+
| ZENTAO_UID | 否 | 富文本编辑器关联ID | editor-123 |
|
|
136
|
+
|
|
137
|
+
## 💡 使用技巧
|
|
138
|
+
|
|
139
|
+
### 1. 创建快捷命令
|
|
140
|
+
|
|
141
|
+
**Windows (创建 `upload.bat`):**
|
|
142
|
+
```batch
|
|
143
|
+
@echo off
|
|
144
|
+
REM 设置禅道配置
|
|
145
|
+
set ZENTAO_URL=https://your-zentao-url
|
|
146
|
+
set ZENTAO_USERNAME=your-username
|
|
147
|
+
set ZENTAO_PASSWORD=your-password
|
|
148
|
+
|
|
149
|
+
REM 运行上传脚本
|
|
150
|
+
node "%~dp0scripts\upload-clipboard-image.js"
|
|
151
|
+
pause
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Mac/Linux (创建 `upload.sh`):**
|
|
155
|
+
```bash
|
|
156
|
+
#!/bin/bash
|
|
157
|
+
# 设置禅道配置
|
|
158
|
+
export ZENTAO_URL=https://your-zentao-url
|
|
159
|
+
export ZENTAO_USERNAME=your-username
|
|
160
|
+
export ZENTAO_PASSWORD=your-password
|
|
161
|
+
|
|
162
|
+
# 运行上传脚本
|
|
163
|
+
node "$(dirname "$0")/scripts/upload-clipboard-image.js"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
然后:
|
|
167
|
+
```bash
|
|
168
|
+
chmod +x upload.sh
|
|
169
|
+
./upload.sh
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2. 配置文件方式
|
|
173
|
+
|
|
174
|
+
创建 `.env` 文件:
|
|
175
|
+
```env
|
|
176
|
+
ZENTAO_URL=https://your-zentao-url
|
|
177
|
+
ZENTAO_USERNAME=your-username
|
|
178
|
+
ZENTAO_PASSWORD=your-password
|
|
179
|
+
ZENTAO_UID=
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
然后加载环境变量:
|
|
183
|
+
```bash
|
|
184
|
+
# Linux/Mac
|
|
185
|
+
source .env
|
|
186
|
+
|
|
187
|
+
# Windows (使用 PowerShell)
|
|
188
|
+
Get-Content .env | ForEach-Object {
|
|
189
|
+
$name, $value = $_.split('=')
|
|
190
|
+
Set-Content env:\$name $value
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 3. 快捷键绑定
|
|
195
|
+
|
|
196
|
+
可以将脚本绑定到系统快捷键,实现:
|
|
197
|
+
1. 截图/复制图片
|
|
198
|
+
2. 按快捷键
|
|
199
|
+
3. 自动上传到禅道
|
|
200
|
+
|
|
201
|
+
**Windows AutoHotkey 示例:**
|
|
202
|
+
```ahk
|
|
203
|
+
; Ctrl+Shift+U 上传剪贴板图片
|
|
204
|
+
^+u::
|
|
205
|
+
Run, cmd /c "cd /d C:\path\to\project && upload.bat"
|
|
206
|
+
return
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Mac Automator 示例:**
|
|
210
|
+
1. 打开 Automator
|
|
211
|
+
2. 创建"快速操作"
|
|
212
|
+
3. 添加"运行Shell脚本"
|
|
213
|
+
4. 输入上传命令
|
|
214
|
+
5. 在系统偏好设置中设置快捷键
|
|
215
|
+
|
|
216
|
+
## 🔍 故障排查
|
|
217
|
+
|
|
218
|
+
### Node.js 方案
|
|
219
|
+
|
|
220
|
+
**问题:** `Error: Cannot find module 'form-data'`
|
|
221
|
+
```bash
|
|
222
|
+
npm install
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**问题:** macOS 提示 `pngpaste: command not found`
|
|
226
|
+
```bash
|
|
227
|
+
brew install pngpaste
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**问题:** Linux 提示 `xclip: command not found`
|
|
231
|
+
```bash
|
|
232
|
+
# Ubuntu/Debian
|
|
233
|
+
sudo apt-get install xclip
|
|
234
|
+
|
|
235
|
+
# CentOS/RHEL
|
|
236
|
+
sudo yum install xclip
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### PowerShell 方案
|
|
240
|
+
|
|
241
|
+
**问题:** 脚本无法运行
|
|
242
|
+
```powershell
|
|
243
|
+
# 临时允许脚本执行
|
|
244
|
+
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
|
245
|
+
|
|
246
|
+
# 或使用 -ExecutionPolicy 参数
|
|
247
|
+
powershell -ExecutionPolicy Bypass -File upload-clipboard-image.ps1
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Python 方案
|
|
251
|
+
|
|
252
|
+
**问题:** `ModuleNotFoundError: No module named 'PIL'`
|
|
253
|
+
```bash
|
|
254
|
+
pip install pillow
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**问题:** `ModuleNotFoundError: No module named 'requests'`
|
|
258
|
+
```bash
|
|
259
|
+
pip install requests
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 📸 完整工作流示例
|
|
263
|
+
|
|
264
|
+
1. **截图或复制图片**
|
|
265
|
+
- Windows: Win+Shift+S 或 PrintScreen
|
|
266
|
+
- Mac: Cmd+Shift+Ctrl+4
|
|
267
|
+
- 或从任意应用复制图片
|
|
268
|
+
|
|
269
|
+
2. **运行上传脚本**
|
|
270
|
+
```bash
|
|
271
|
+
# 使用你选择的方案
|
|
272
|
+
node scripts/upload-clipboard-image.js
|
|
273
|
+
# 或
|
|
274
|
+
.\scripts\upload-clipboard-image.ps1 -ZentaoUrl ... -Username ... -Password ...
|
|
275
|
+
# 或
|
|
276
|
+
python scripts/upload-clipboard-image.py
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
3. **获取结果**
|
|
280
|
+
```
|
|
281
|
+
========================================
|
|
282
|
+
✓ 上传成功!
|
|
283
|
+
========================================
|
|
284
|
+
文件ID: 123
|
|
285
|
+
访问URL: /zentao/file-read-123.png
|
|
286
|
+
本地路径: C:\project\img\clipboard_1234567890.png
|
|
287
|
+
========================================
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
4. **在禅道中使用**
|
|
291
|
+
- 复制返回的URL
|
|
292
|
+
- 在需求/Bug/任务描述中插入图片
|
|
293
|
+
- 使用 `` 或直接粘贴URL
|
|
294
|
+
|
|
295
|
+
## 🎯 高级用法
|
|
296
|
+
|
|
297
|
+
### 批量上传
|
|
298
|
+
|
|
299
|
+
创建一个循环脚本,持续监听剪贴板变化并自动上传。
|
|
300
|
+
|
|
301
|
+
### 与富文本编辑器集成
|
|
302
|
+
|
|
303
|
+
设置 `ZENTAO_UID` 环境变量,将上传的图片关联到特定的编辑器会话。
|
|
304
|
+
|
|
305
|
+
### 自定义保存路径
|
|
306
|
+
|
|
307
|
+
修改脚本中的 `imgDir` 变量,将图片保存到自定义位置。
|
|
308
|
+
|
|
309
|
+
## 📄 许可证
|
|
310
|
+
|
|
311
|
+
MIT
|
|
312
|
+
|
|
313
|
+
## 🤝 贡献
|
|
314
|
+
|
|
315
|
+
欢迎提交 Issue 和 Pull Request!
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 从剪贴板获取图片并上传到禅道
|
|
5
|
+
* 使用方法: node upload-clipboard-image.js
|
|
6
|
+
*
|
|
7
|
+
* 环境变量:
|
|
8
|
+
* - ZENTAO_URL: 禅道系统URL
|
|
9
|
+
* - ZENTAO_USERNAME: 用户名
|
|
10
|
+
* - ZENTAO_PASSWORD: 密码
|
|
11
|
+
* - ZENTAO_UID: 可选的UID参数
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import clipboardy from 'clipboardy';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname } from 'path';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// 动态导入 ZentaoAPI
|
|
24
|
+
async function uploadClipboardImage() {
|
|
25
|
+
try {
|
|
26
|
+
console.log('正在读取剪贴板内容...');
|
|
27
|
+
|
|
28
|
+
// 检查环境变量
|
|
29
|
+
const zentaoUrl = process.env.ZENTAO_URL;
|
|
30
|
+
const username = process.env.ZENTAO_USERNAME;
|
|
31
|
+
const password = process.env.ZENTAO_PASSWORD;
|
|
32
|
+
const uid = process.env.ZENTAO_UID;
|
|
33
|
+
|
|
34
|
+
if (!zentaoUrl || !username || !password) {
|
|
35
|
+
console.error('错误: 请设置环境变量 ZENTAO_URL, ZENTAO_USERNAME, ZENTAO_PASSWORD');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error('Windows示例:');
|
|
38
|
+
console.error(' set ZENTAO_URL=https://your-zentao-url');
|
|
39
|
+
console.error(' set ZENTAO_USERNAME=your-username');
|
|
40
|
+
console.error(' set ZENTAO_PASSWORD=your-password');
|
|
41
|
+
console.error(' node upload-clipboard-image.js');
|
|
42
|
+
console.error('');
|
|
43
|
+
console.error('Mac/Linux示例:');
|
|
44
|
+
console.error(' export ZENTAO_URL=https://your-zentao-url');
|
|
45
|
+
console.error(' export ZENTAO_USERNAME=your-username');
|
|
46
|
+
console.error(' export ZENTAO_PASSWORD=your-password');
|
|
47
|
+
console.error(' node upload-clipboard-image.js');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 尝试读取剪贴板图片
|
|
52
|
+
let imageData;
|
|
53
|
+
|
|
54
|
+
// Windows: 使用 PowerShell 读取剪贴板图片
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
const { execSync } = await import('child_process');
|
|
57
|
+
try {
|
|
58
|
+
const psScript = `
|
|
59
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
60
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
61
|
+
if ($img -ne $null) {
|
|
62
|
+
$ms = New-Object System.IO.MemoryStream
|
|
63
|
+
$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
64
|
+
[Convert]::ToBase64String($ms.ToArray())
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
const base64 = execSync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, {
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
maxBuffer: 10 * 1024 * 1024
|
|
70
|
+
}).trim();
|
|
71
|
+
|
|
72
|
+
if (base64) {
|
|
73
|
+
imageData = `data:image/png;base64,${base64}`;
|
|
74
|
+
console.log('✓ 从剪贴板读取图片成功 (Windows)');
|
|
75
|
+
} else {
|
|
76
|
+
console.error('错误: 剪贴板中没有图片');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('错误: 无法读取剪贴板图片', error.message);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// macOS: 使用 pngpaste
|
|
85
|
+
else if (process.platform === 'darwin') {
|
|
86
|
+
const { execSync } = await import('child_process');
|
|
87
|
+
try {
|
|
88
|
+
// 检查是否安装了 pngpaste
|
|
89
|
+
try {
|
|
90
|
+
execSync('which pngpaste', { stdio: 'ignore' });
|
|
91
|
+
} catch {
|
|
92
|
+
console.error('错误: 请先安装 pngpaste');
|
|
93
|
+
console.error('运行: brew install pngpaste');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const tempFile = path.join(__dirname, 'temp_clipboard.png');
|
|
98
|
+
execSync(`pngpaste "${tempFile}"`);
|
|
99
|
+
const buffer = fs.readFileSync(tempFile);
|
|
100
|
+
const base64 = buffer.toString('base64');
|
|
101
|
+
imageData = `data:image/png;base64,${base64}`;
|
|
102
|
+
fs.unlinkSync(tempFile);
|
|
103
|
+
console.log('✓ 从剪贴板读取图片成功 (macOS)');
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('错误: 剪贴板中没有图片或读取失败', error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Linux: 使用 xclip
|
|
110
|
+
else {
|
|
111
|
+
const { execSync } = await import('child_process');
|
|
112
|
+
try {
|
|
113
|
+
// 检查是否安装了 xclip
|
|
114
|
+
try {
|
|
115
|
+
execSync('which xclip', { stdio: 'ignore' });
|
|
116
|
+
} catch {
|
|
117
|
+
console.error('错误: 请先安装 xclip');
|
|
118
|
+
console.error('运行: sudo apt-get install xclip (Ubuntu/Debian)');
|
|
119
|
+
console.error('或: sudo yum install xclip (CentOS/RHEL)');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
124
|
+
maxBuffer: 10 * 1024 * 1024
|
|
125
|
+
});
|
|
126
|
+
const base64 = buffer.toString('base64');
|
|
127
|
+
imageData = `data:image/png;base64,${base64}`;
|
|
128
|
+
console.log('✓ 从剪贴板读取图片成功 (Linux)');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('错误: 剪贴板中没有图片或读取失败', error.message);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 导入 ZentaoAPI
|
|
136
|
+
const modulePath = path.join(__dirname, '..', 'dist', 'api', 'zentaoApi.js');
|
|
137
|
+
const { ZentaoAPI } = await import(modulePath);
|
|
138
|
+
|
|
139
|
+
// 创建 API 实例
|
|
140
|
+
console.log('正在连接禅道系统...');
|
|
141
|
+
const api = new ZentaoAPI({
|
|
142
|
+
url: zentaoUrl,
|
|
143
|
+
username: username,
|
|
144
|
+
password: password,
|
|
145
|
+
apiVersion: 'v1'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// 创建 img 文件夹
|
|
149
|
+
const imgDir = path.join(process.cwd(), 'img');
|
|
150
|
+
if (!fs.existsSync(imgDir)) {
|
|
151
|
+
fs.mkdirSync(imgDir, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 解析 base64 数据
|
|
155
|
+
const matches = imageData.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
156
|
+
if (!matches) {
|
|
157
|
+
console.error('错误: 无效的图片数据');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const ext = matches[1];
|
|
162
|
+
const base64Data = matches[2];
|
|
163
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
164
|
+
const filename = `clipboard_${Date.now()}.${ext}`;
|
|
165
|
+
|
|
166
|
+
// 保存到本地
|
|
167
|
+
const savedPath = path.join(imgDir, filename);
|
|
168
|
+
fs.writeFileSync(savedPath, buffer);
|
|
169
|
+
console.log(`✓ 图片已保存到: ${savedPath}`);
|
|
170
|
+
|
|
171
|
+
// 上传到禅道
|
|
172
|
+
console.log('正在上传到禅道...');
|
|
173
|
+
const result = await api.uploadFile({
|
|
174
|
+
file: buffer,
|
|
175
|
+
filename: filename,
|
|
176
|
+
uid: uid
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('========================================');
|
|
181
|
+
console.log('✓ 上传成功!');
|
|
182
|
+
console.log('========================================');
|
|
183
|
+
console.log('文件ID:', result.id);
|
|
184
|
+
console.log('访问URL:', result.url);
|
|
185
|
+
console.log('本地路径:', savedPath);
|
|
186
|
+
console.log('========================================');
|
|
187
|
+
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('');
|
|
190
|
+
console.error('========================================');
|
|
191
|
+
console.error('✗ 上传失败');
|
|
192
|
+
console.error('========================================');
|
|
193
|
+
console.error('错误信息:', error.message);
|
|
194
|
+
console.error('========================================');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 运行
|
|
200
|
+
uploadClipboardImage();
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# 从剪贴板获取图片并上传到禅道
|
|
2
|
+
# 使用方法: .\upload-clipboard-image.ps1
|
|
3
|
+
#
|
|
4
|
+
# 参数:
|
|
5
|
+
# -ZentaoUrl: 禅道系统URL
|
|
6
|
+
# -Username: 用户名
|
|
7
|
+
# -Password: 密码
|
|
8
|
+
# -Uid: 可选的UID参数
|
|
9
|
+
|
|
10
|
+
param(
|
|
11
|
+
[Parameter(Mandatory=$true)]
|
|
12
|
+
[string]$ZentaoUrl,
|
|
13
|
+
|
|
14
|
+
[Parameter(Mandatory=$true)]
|
|
15
|
+
[string]$Username,
|
|
16
|
+
|
|
17
|
+
[Parameter(Mandatory=$true)]
|
|
18
|
+
[string]$Password,
|
|
19
|
+
|
|
20
|
+
[Parameter(Mandatory=$false)]
|
|
21
|
+
[string]$Uid = ""
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 添加必要的程序集
|
|
25
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
26
|
+
Add-Type -AssemblyName System.Drawing
|
|
27
|
+
|
|
28
|
+
Write-Host "正在读取剪贴板图片..." -ForegroundColor Cyan
|
|
29
|
+
|
|
30
|
+
# 从剪贴板获取图片
|
|
31
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
32
|
+
|
|
33
|
+
if ($img -eq $null) {
|
|
34
|
+
Write-Host "错误: 剪贴板中没有图片" -ForegroundColor Red
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Write-Host "✓ 成功读取剪贴板图片 ($($img.Width)x$($img.Height))" -ForegroundColor Green
|
|
39
|
+
|
|
40
|
+
# 转换为 base64
|
|
41
|
+
$ms = New-Object System.IO.MemoryStream
|
|
42
|
+
$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
43
|
+
$base64 = [Convert]::ToBase64String($ms.ToArray())
|
|
44
|
+
$ms.Close()
|
|
45
|
+
|
|
46
|
+
Write-Host "✓ 图片已转换为 base64" -ForegroundColor Green
|
|
47
|
+
|
|
48
|
+
# 创建 img 文件夹
|
|
49
|
+
$imgDir = Join-Path (Get-Location) "img"
|
|
50
|
+
if (-not (Test-Path $imgDir)) {
|
|
51
|
+
New-Item -ItemType Directory -Path $imgDir | Out-Null
|
|
52
|
+
Write-Host "✓ 创建 img 文件夹: $imgDir" -ForegroundColor Green
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# 保存到本地
|
|
56
|
+
$filename = "clipboard_$([DateTimeOffset]::Now.ToUnixTimeMilliseconds()).png"
|
|
57
|
+
$localPath = Join-Path $imgDir $filename
|
|
58
|
+
$bytes = [Convert]::FromBase64String($base64)
|
|
59
|
+
[System.IO.File]::WriteAllBytes($localPath, $bytes)
|
|
60
|
+
Write-Host "✓ 图片已保存到: $localPath" -ForegroundColor Green
|
|
61
|
+
|
|
62
|
+
# 获取 Token
|
|
63
|
+
Write-Host "正在连接禅道系统..." -ForegroundColor Cyan
|
|
64
|
+
$passwordMd5 = [System.BitConverter]::ToString(
|
|
65
|
+
[System.Security.Cryptography.MD5]::Create().ComputeHash(
|
|
66
|
+
[System.Text.Encoding]::UTF8.GetBytes($Password)
|
|
67
|
+
)
|
|
68
|
+
).Replace("-", "").ToLower()
|
|
69
|
+
|
|
70
|
+
$tokenUrl = "$ZentaoUrl/zentao/api.php/v1/tokens"
|
|
71
|
+
$tokenBody = @{
|
|
72
|
+
account = $Username
|
|
73
|
+
password = $passwordMd5
|
|
74
|
+
} | ConvertTo-Json
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $tokenBody -ContentType "application/json"
|
|
78
|
+
$token = $tokenResponse.token
|
|
79
|
+
Write-Host "✓ 成功获取 Token" -ForegroundColor Green
|
|
80
|
+
} catch {
|
|
81
|
+
Write-Host "错误: 无法获取 Token - $($_.Exception.Message)" -ForegroundColor Red
|
|
82
|
+
exit 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# 上传文件
|
|
86
|
+
Write-Host "正在上传到禅道..." -ForegroundColor Cyan
|
|
87
|
+
|
|
88
|
+
$uploadUrl = "$ZentaoUrl/zentao/api.php/v1/files"
|
|
89
|
+
if ($Uid) {
|
|
90
|
+
$uploadUrl += "?uid=$Uid"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# 创建 multipart/form-data
|
|
94
|
+
$boundary = [System.Guid]::NewGuid().ToString()
|
|
95
|
+
$LF = "`r`n"
|
|
96
|
+
|
|
97
|
+
$bodyLines = (
|
|
98
|
+
"--$boundary",
|
|
99
|
+
"Content-Disposition: form-data; name=`"imgFile`"; filename=`"$filename`"",
|
|
100
|
+
"Content-Type: image/png$LF",
|
|
101
|
+
[System.Text.Encoding]::GetEncoding("iso-8859-1").GetString($bytes),
|
|
102
|
+
"--$boundary--$LF"
|
|
103
|
+
) -join $LF
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
$response = Invoke-RestMethod -Uri $uploadUrl -Method Post `
|
|
107
|
+
-Headers @{ Token = $token } `
|
|
108
|
+
-ContentType "multipart/form-data; boundary=$boundary" `
|
|
109
|
+
-Body $bodyLines
|
|
110
|
+
|
|
111
|
+
Write-Host ""
|
|
112
|
+
Write-Host "========================================" -ForegroundColor Green
|
|
113
|
+
Write-Host "✓ 上传成功!" -ForegroundColor Green
|
|
114
|
+
Write-Host "========================================" -ForegroundColor Green
|
|
115
|
+
Write-Host "文件ID: $($response.id)"
|
|
116
|
+
Write-Host "访问URL: $($response.url)"
|
|
117
|
+
Write-Host "本地路径: $localPath"
|
|
118
|
+
Write-Host "========================================" -ForegroundColor Green
|
|
119
|
+
|
|
120
|
+
} catch {
|
|
121
|
+
Write-Host ""
|
|
122
|
+
Write-Host "========================================" -ForegroundColor Red
|
|
123
|
+
Write-Host "✗ 上传失败" -ForegroundColor Red
|
|
124
|
+
Write-Host "========================================" -ForegroundColor Red
|
|
125
|
+
Write-Host "错误信息: $($_.Exception.Message)" -ForegroundColor Red
|
|
126
|
+
Write-Host "========================================" -ForegroundColor Red
|
|
127
|
+
exit 1
|
|
128
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
从剪贴板获取图片并上传到禅道
|
|
4
|
+
|
|
5
|
+
依赖安装:
|
|
6
|
+
pip install pillow requests
|
|
7
|
+
|
|
8
|
+
使用方法:
|
|
9
|
+
python upload-clipboard-image.py
|
|
10
|
+
|
|
11
|
+
环境变量:
|
|
12
|
+
ZENTAO_URL: 禅道系统URL
|
|
13
|
+
ZENTAO_USERNAME: 用户名
|
|
14
|
+
ZENTAO_PASSWORD: 密码
|
|
15
|
+
ZENTAO_UID: 可选的UID参数
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import hashlib
|
|
21
|
+
import requests
|
|
22
|
+
import base64
|
|
23
|
+
from io import BytesIO
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from PIL import ImageGrab, Image
|
|
29
|
+
except ImportError:
|
|
30
|
+
print("错误: 请先安装 Pillow 库")
|
|
31
|
+
print("运行: pip install pillow")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_clipboard_image():
|
|
36
|
+
"""从剪贴板获取图片"""
|
|
37
|
+
print("正在读取剪贴板图片...")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# 尝试获取剪贴板图片
|
|
41
|
+
img = ImageGrab.grabclipboard()
|
|
42
|
+
|
|
43
|
+
if img is None:
|
|
44
|
+
print("错误: 剪贴板中没有图片")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
# 如果是文件路径列表,读取第一个图片文件
|
|
48
|
+
if isinstance(img, list):
|
|
49
|
+
img_path = img[0]
|
|
50
|
+
if os.path.isfile(img_path):
|
|
51
|
+
img = Image.open(img_path)
|
|
52
|
+
else:
|
|
53
|
+
print(f"错误: 无法打开文件 {img_path}")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
print(f"✓ 成功读取剪贴板图片 ({img.width}x{img.height})")
|
|
57
|
+
return img
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"错误: 无法读取剪贴板图片 - {e}")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def image_to_base64(img):
|
|
65
|
+
"""将图片转换为 base64"""
|
|
66
|
+
buffer = BytesIO()
|
|
67
|
+
img.save(buffer, format='PNG')
|
|
68
|
+
img_bytes = buffer.getvalue()
|
|
69
|
+
return base64.b64encode(img_bytes).decode('utf-8'), img_bytes
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_zentao_token(url, username, password):
|
|
73
|
+
"""获取禅道 Token"""
|
|
74
|
+
print("正在连接禅道系统...")
|
|
75
|
+
|
|
76
|
+
# MD5 加密密码
|
|
77
|
+
password_md5 = hashlib.md5(password.encode()).hexdigest()
|
|
78
|
+
|
|
79
|
+
token_url = f"{url}/zentao/api.php/v1/tokens"
|
|
80
|
+
data = {
|
|
81
|
+
"account": username,
|
|
82
|
+
"password": password_md5
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
response = requests.post(token_url, json=data)
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
token = response.json().get('token')
|
|
89
|
+
|
|
90
|
+
if not token:
|
|
91
|
+
print("错误: 无法获取 Token")
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
|
|
94
|
+
print("✓ 成功获取 Token")
|
|
95
|
+
return token
|
|
96
|
+
|
|
97
|
+
except requests.exceptions.RequestException as e:
|
|
98
|
+
print(f"错误: 无法连接禅道系统 - {e}")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def upload_to_zentao(url, token, img_bytes, filename, uid=None):
|
|
103
|
+
"""上传图片到禅道"""
|
|
104
|
+
print("正在上传到禅道...")
|
|
105
|
+
|
|
106
|
+
upload_url = f"{url}/zentao/api.php/v1/files"
|
|
107
|
+
if uid:
|
|
108
|
+
upload_url += f"?uid={uid}"
|
|
109
|
+
|
|
110
|
+
headers = {
|
|
111
|
+
"Token": token
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
files = {
|
|
115
|
+
'imgFile': (filename, img_bytes, 'image/png')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
response = requests.post(upload_url, headers=headers, files=files)
|
|
120
|
+
response.raise_for_status()
|
|
121
|
+
result = response.json()
|
|
122
|
+
|
|
123
|
+
print("")
|
|
124
|
+
print("=" * 40)
|
|
125
|
+
print("✓ 上传成功!")
|
|
126
|
+
print("=" * 40)
|
|
127
|
+
print(f"文件ID: {result.get('id')}")
|
|
128
|
+
print(f"访问URL: {result.get('url')}")
|
|
129
|
+
print("=" * 40)
|
|
130
|
+
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
except requests.exceptions.RequestException as e:
|
|
134
|
+
print("")
|
|
135
|
+
print("=" * 40)
|
|
136
|
+
print("✗ 上传失败")
|
|
137
|
+
print("=" * 40)
|
|
138
|
+
print(f"错误信息: {e}")
|
|
139
|
+
print("=" * 40)
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main():
|
|
144
|
+
# 检查环境变量
|
|
145
|
+
zentao_url = os.environ.get('ZENTAO_URL')
|
|
146
|
+
username = os.environ.get('ZENTAO_USERNAME')
|
|
147
|
+
password = os.environ.get('ZENTAO_PASSWORD')
|
|
148
|
+
uid = os.environ.get('ZENTAO_UID')
|
|
149
|
+
|
|
150
|
+
if not all([zentao_url, username, password]):
|
|
151
|
+
print("错误: 请设置环境变量 ZENTAO_URL, ZENTAO_USERNAME, ZENTAO_PASSWORD")
|
|
152
|
+
print("")
|
|
153
|
+
print("Windows示例:")
|
|
154
|
+
print(" set ZENTAO_URL=https://your-zentao-url")
|
|
155
|
+
print(" set ZENTAO_USERNAME=your-username")
|
|
156
|
+
print(" set ZENTAO_PASSWORD=your-password")
|
|
157
|
+
print(" python upload-clipboard-image.py")
|
|
158
|
+
print("")
|
|
159
|
+
print("Mac/Linux示例:")
|
|
160
|
+
print(" export ZENTAO_URL=https://your-zentao-url")
|
|
161
|
+
print(" export ZENTAO_USERNAME=your-username")
|
|
162
|
+
print(" export ZENTAO_PASSWORD=your-password")
|
|
163
|
+
print(" python upload-clipboard-image.py")
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
# 获取剪贴板图片
|
|
167
|
+
img = get_clipboard_image()
|
|
168
|
+
|
|
169
|
+
# 转换为 base64
|
|
170
|
+
base64_str, img_bytes = image_to_base64(img)
|
|
171
|
+
print("✓ 图片已转换为 base64")
|
|
172
|
+
|
|
173
|
+
# 创建 img 文件夹
|
|
174
|
+
img_dir = Path.cwd() / 'img'
|
|
175
|
+
img_dir.mkdir(exist_ok=True)
|
|
176
|
+
|
|
177
|
+
# 保存到本地
|
|
178
|
+
timestamp = int(datetime.now().timestamp() * 1000)
|
|
179
|
+
filename = f"clipboard_{timestamp}.png"
|
|
180
|
+
local_path = img_dir / filename
|
|
181
|
+
|
|
182
|
+
with open(local_path, 'wb') as f:
|
|
183
|
+
f.write(img_bytes)
|
|
184
|
+
|
|
185
|
+
print(f"✓ 图片已保存到: {local_path}")
|
|
186
|
+
|
|
187
|
+
# 获取 Token
|
|
188
|
+
token = get_zentao_token(zentao_url, username, password)
|
|
189
|
+
|
|
190
|
+
# 上传到禅道
|
|
191
|
+
result = upload_to_zentao(zentao_url, token, img_bytes, filename, uid)
|
|
192
|
+
print(f"本地路径: {local_path}")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == '__main__':
|
|
196
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM ========================================
|
|
3
|
+
REM 剪贴板图片上传到禅道 - 配置示例
|
|
4
|
+
REM ========================================
|
|
5
|
+
REM
|
|
6
|
+
REM 使用方法:
|
|
7
|
+
REM 1. 复制此文件为 upload.bat
|
|
8
|
+
REM 2. 修改下面的配置信息
|
|
9
|
+
REM 3. 双击运行或在命令行运行
|
|
10
|
+
REM
|
|
11
|
+
|
|
12
|
+
REM ========================================
|
|
13
|
+
REM 配置区域 - 请修改为你的禅道信息
|
|
14
|
+
REM ========================================
|
|
15
|
+
|
|
16
|
+
set ZENTAO_URL=https://your-zentao-url
|
|
17
|
+
set ZENTAO_USERNAME=your-username
|
|
18
|
+
set ZENTAO_PASSWORD=your-password
|
|
19
|
+
set ZENTAO_UID=
|
|
20
|
+
|
|
21
|
+
REM ========================================
|
|
22
|
+
REM 选择上传方式
|
|
23
|
+
REM ========================================
|
|
24
|
+
|
|
25
|
+
REM 方式1: 使用 Node.js (推荐)
|
|
26
|
+
node "%~dp0upload-clipboard-image.js"
|
|
27
|
+
|
|
28
|
+
REM 方式2: 使用 PowerShell (Windows)
|
|
29
|
+
REM powershell -ExecutionPolicy Bypass -File "%~dp0upload-clipboard-image.ps1" ^
|
|
30
|
+
REM -ZentaoUrl "%ZENTAO_URL%" ^
|
|
31
|
+
REM -Username "%ZENTAO_USERNAME%" ^
|
|
32
|
+
REM -Password "%ZENTAO_PASSWORD%" ^
|
|
33
|
+
REM -Uid "%ZENTAO_UID%"
|
|
34
|
+
|
|
35
|
+
REM 方式3: 使用 Python
|
|
36
|
+
REM python "%~dp0upload-clipboard-image.py"
|
|
37
|
+
|
|
38
|
+
REM ========================================
|
|
39
|
+
REM 按任意键退出
|
|
40
|
+
REM ========================================
|
|
41
|
+
pause
|