ones-fetch 1.1.0 → 1.2.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 +8 -2
- package/bin/install.mjs +31 -0
- package/package.json +3 -2
- package/public/index.html +67 -21
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
npx ones-fetch
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
或者使用 npm 安装:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g ones-fetch
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
这会自动:
|
|
14
20
|
- 安装依赖(约 30MB,仅首次需要)
|
|
15
21
|
- 在桌面创建快捷方式
|
|
@@ -17,8 +23,8 @@ npx ones-fetch
|
|
|
17
23
|
|
|
18
24
|
**注意**:
|
|
19
25
|
- 依赖只需安装一次,后续启动无需重新安装
|
|
20
|
-
- 服务器会在浏览器关闭
|
|
21
|
-
-
|
|
26
|
+
- 服务器会在浏览器关闭 2 分钟后自动退出
|
|
27
|
+
- 解析任务时会显示实时进度(1/7)
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
package/bin/install.mjs
CHANGED
|
@@ -79,6 +79,37 @@ Categories=Utility;
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
async function main() {
|
|
82
|
+
// 检测是否是通过 npx 或 postinstall 运行
|
|
83
|
+
const isPostInstall = process.env.npm_lifecycle_event === 'postinstall';
|
|
84
|
+
|
|
85
|
+
if (isPostInstall) {
|
|
86
|
+
// postinstall 时只创建快捷方式,不安装依赖
|
|
87
|
+
console.log('ONES Fetch 安装后配置\n');
|
|
88
|
+
console.log('正在创建桌面快捷方式...');
|
|
89
|
+
try {
|
|
90
|
+
const os = platform();
|
|
91
|
+
if (os === 'win32') {
|
|
92
|
+
await createWindowsShortcut();
|
|
93
|
+
} else if (os === 'darwin') {
|
|
94
|
+
await createMacShortcut();
|
|
95
|
+
} else {
|
|
96
|
+
await createLinuxShortcut();
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('✗ 快捷方式创建失败:', err.message);
|
|
100
|
+
// 不退出,允许安装继续
|
|
101
|
+
}
|
|
102
|
+
console.log('\n✓ 配置完成!');
|
|
103
|
+
console.log('\n使用方法:');
|
|
104
|
+
console.log(' 1. 双击桌面上的 "ONES 采集工具" 图标');
|
|
105
|
+
console.log(' 2. 浏览器会自动打开工具页面');
|
|
106
|
+
console.log('\n或者在命令行运行:');
|
|
107
|
+
console.log(` cd ${projectRoot}`);
|
|
108
|
+
console.log(' npm start');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// npx 运行时的完整安装流程
|
|
82
113
|
console.log('ONES Fetch 安装程序\n');
|
|
83
114
|
|
|
84
115
|
// 安装依赖
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ones-fetch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ONES Fetch — Web app for recursive ONES subtask crawling",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/server.mjs",
|
|
11
|
-
"dev": "node --watch src/server.mjs"
|
|
11
|
+
"dev": "node --watch src/server.mjs",
|
|
12
|
+
"postinstall": "node bin/install.mjs"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"playwright-core": "^1.58.2"
|
package/public/index.html
CHANGED
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
.spinner { display: inline-block; width: 18px; height: 18px; border: 2px solid #e5e7eb; border-top-color: #6366f1; border-radius: 50%; animation: spin .7s linear infinite; vertical-align: middle; margin-right: 6px; }
|
|
50
50
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
51
51
|
.copied { background: #10b981 !important; }
|
|
52
|
+
.progress-bar { width: 100%; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden; margin-top: 8px; }
|
|
53
|
+
.progress-fill { height: 100%; background: linear-gradient(90deg, #6366f1, #8b5cf6); transition: width 0.3s ease; border-radius: 3px; }
|
|
54
|
+
.progress-text { font-size: 0.8125rem; color: #6b7280; margin-top: 4px; }
|
|
52
55
|
</style>
|
|
53
56
|
</head>
|
|
54
57
|
<body>
|
|
@@ -65,6 +68,12 @@
|
|
|
65
68
|
<span class="status" id="status"></span>
|
|
66
69
|
</div>
|
|
67
70
|
<div class="hint" id="authHint"></div>
|
|
71
|
+
<div id="progressContainer" style="display:none">
|
|
72
|
+
<div class="progress-bar">
|
|
73
|
+
<div class="progress-fill" id="progressFill" style="width:0%"></div>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="progress-text" id="progressText"></div>
|
|
76
|
+
</div>
|
|
68
77
|
<div class="preview-chips" id="chips"></div>
|
|
69
78
|
</div>
|
|
70
79
|
|
|
@@ -174,6 +183,22 @@ function renderChips(taskIds) {
|
|
|
174
183
|
el.innerHTML = taskIds.map(id => `<span class="chip">${id}</span>`).join('');
|
|
175
184
|
}
|
|
176
185
|
|
|
186
|
+
function updateProgress(current, total, message) {
|
|
187
|
+
const container = document.getElementById('progressContainer');
|
|
188
|
+
const fill = document.getElementById('progressFill');
|
|
189
|
+
const text = document.getElementById('progressText');
|
|
190
|
+
|
|
191
|
+
if (total === 0) {
|
|
192
|
+
container.style.display = 'none';
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
container.style.display = 'block';
|
|
197
|
+
const percent = Math.round((current / total) * 100);
|
|
198
|
+
fill.style.width = percent + '%';
|
|
199
|
+
text.textContent = `${message} (${current}/${total})`;
|
|
200
|
+
}
|
|
201
|
+
|
|
177
202
|
async function parse() {
|
|
178
203
|
const text = document.getElementById('input').value.trim();
|
|
179
204
|
if (!text) { setStatus('请先粘贴文本', true); return; }
|
|
@@ -185,40 +210,60 @@ async function parse() {
|
|
|
185
210
|
setStatus(`已提取 ${taskIds.length} 个任务 ID,正在抓取...`);
|
|
186
211
|
setAuthHint(context.baseUrl ? `已识别站点:${context.baseUrl}${context.teamId ? `,团队:${context.teamId}` : ''}` : '');
|
|
187
212
|
document.getElementById('parseBtn').disabled = true;
|
|
213
|
+
updateProgress(0, taskIds.length, '准备开始');
|
|
188
214
|
document.getElementById('tbody').innerHTML = '<tr><td colspan="5" class="loading"><span class="spinner"></span>加载中...</td></tr>';
|
|
189
215
|
document.getElementById('resultsSection').style.display = '';
|
|
190
216
|
|
|
191
217
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
// 逐个解析任务以显示进度
|
|
219
|
+
const allTasks = [];
|
|
220
|
+
const roots = [];
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < taskIds.length; i++) {
|
|
223
|
+
updateProgress(i + 1, taskIds.length, `正在解析任务 ${taskIds[i]}`);
|
|
224
|
+
|
|
225
|
+
const resp = await fetch('/api/crawl', {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
taskIds: [taskIds[i]],
|
|
230
|
+
baseUrl: authContext.baseUrl || context.baseUrl || undefined,
|
|
231
|
+
teamId: authContext.teamId || context.teamId || undefined
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
const data = await resp.json();
|
|
235
|
+
if (!resp.ok) {
|
|
236
|
+
if (data.error === 'auth_required' || data.error === 'token_expired') {
|
|
237
|
+
updateAuthContext(data);
|
|
238
|
+
showAuthButton(true, data.error === 'token_expired' ? '重新连接 ONES' : '连接 ONES');
|
|
239
|
+
setAuthHint(`将打开一个浏览器窗口用于登录 ONES,完成后会自动重试。${authContext.baseUrl ? ` 当前站点:${authContext.baseUrl}` : ''}`);
|
|
240
|
+
}
|
|
241
|
+
const msg = data.error === 'token_expired'
|
|
242
|
+
? '登录状态已过期,请点击”连接 ONES”重新登录'
|
|
243
|
+
: data.error === 'auth_required'
|
|
244
|
+
? '当前还没有可用的 ONES 凭据,请先连接 ONES'
|
|
245
|
+
: (data.detail ?? data.error ?? '抓取失败');
|
|
246
|
+
setStatus(msg, true);
|
|
247
|
+
document.getElementById('tbody').innerHTML = `<tr><td colspan=”5” class=”empty”>${msg}</td></tr>`;
|
|
248
|
+
updateProgress(0, 0, '');
|
|
249
|
+
return;
|
|
203
250
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
? '当前还没有可用的 ONES 凭据,请先连接 ONES'
|
|
208
|
-
: (data.detail ?? data.error ?? '抓取失败');
|
|
209
|
-
setStatus(msg, true);
|
|
210
|
-
document.getElementById('tbody').innerHTML = `<tr><td colspan="5" class="empty">${msg}</td></tr>`;
|
|
211
|
-
return;
|
|
251
|
+
|
|
252
|
+
if (data.roots) roots.push(...data.roots);
|
|
253
|
+
if (data.tasks) allTasks.push(...data.tasks);
|
|
212
254
|
}
|
|
255
|
+
|
|
213
256
|
showAuthButton(false);
|
|
214
|
-
updateAuthContext(
|
|
215
|
-
allTasks =
|
|
257
|
+
updateAuthContext({ baseUrl: authContext.baseUrl, teamId: authContext.teamId });
|
|
258
|
+
allTasks = allTasks;
|
|
216
259
|
collapsed = new Set();
|
|
217
260
|
renderTable();
|
|
218
261
|
setStatus(`共获取 ${allTasks.length} 条任务`);
|
|
262
|
+
updateProgress(0, 0, '');
|
|
219
263
|
document.getElementById('countLabel').textContent = `共 ${allTasks.length} 条`;
|
|
220
264
|
} catch (e) {
|
|
221
265
|
setStatus('网络错误:' + e.message, true);
|
|
266
|
+
updateProgress(0, 0, '');
|
|
222
267
|
} finally {
|
|
223
268
|
document.getElementById('parseBtn').disabled = false;
|
|
224
269
|
}
|
|
@@ -382,6 +427,7 @@ function clearAll() {
|
|
|
382
427
|
document.getElementById('status').textContent = '';
|
|
383
428
|
setAuthHint('');
|
|
384
429
|
showAuthButton(false);
|
|
430
|
+
updateProgress(0, 0, '');
|
|
385
431
|
authContext = { baseUrl: '', teamId: '' };
|
|
386
432
|
document.getElementById('resultsSection').style.display = 'none';
|
|
387
433
|
allTasks = [];
|