nv-basic-bw 1.0.12 → 1.0.15
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/nv-basci-bw.js +23 -23
- package/attr.js +1 -1
- package/calc.js +144 -0
- package/cls-accessor.js +1 -1
- package/code.js +191 -0
- package/com.sh +1 -0
- package/const.js +48 -0
- package/dnld.js +171 -0
- package/ele.js +365 -0
- package/index.js +21 -595
- package/limit.js +11 -1
- package/nd.js +390 -0
- package/package.json +2 -2
- package/ui.js +219 -1
- package/util.js +75 -0
package/nd.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
const {is_async} = require("./util");
|
|
2
|
+
|
|
3
|
+
const get_depth = (node) => {
|
|
4
|
+
let depth = 0;
|
|
5
|
+
while (node && node.parentNode) {
|
|
6
|
+
node = node.parentNode;
|
|
7
|
+
depth++;
|
|
8
|
+
}
|
|
9
|
+
return depth;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const get_ance = (node,which)=>{
|
|
13
|
+
if(which >0) {
|
|
14
|
+
let c = 1;
|
|
15
|
+
while(true) {
|
|
16
|
+
let pr = node.parentNode;
|
|
17
|
+
if(c===which) {
|
|
18
|
+
return pr;
|
|
19
|
+
} else {
|
|
20
|
+
node = pr;
|
|
21
|
+
++c;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
return node;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const get_rt = (node) => {
|
|
30
|
+
while (node.parentNode) {
|
|
31
|
+
node = node.parentNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return node;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const is_topo_leaf = (nd)=> nd.firstChild===null || nd.firstChild===undefined;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const get_drmost = (node,is_leaf=is_topo_leaf) => {
|
|
42
|
+
let rel_depth = 0;
|
|
43
|
+
while (true) {
|
|
44
|
+
if(!is_leaf(node)) {
|
|
45
|
+
node = node.lastChild;
|
|
46
|
+
++rel_depth;
|
|
47
|
+
} else {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return [node,rel_depth];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const get_dlmost = (node,is_leaf=is_topo_leaf)=>{
|
|
55
|
+
let rel_depth = 0;
|
|
56
|
+
while (true) {
|
|
57
|
+
if(!is_leaf(node)) {
|
|
58
|
+
node = node.firstChild;
|
|
59
|
+
++rel_depth;
|
|
60
|
+
} else {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [node,rel_depth];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const find_rb_of_fst_ance_has_rb = (node, rel_depth) => {
|
|
70
|
+
// 1️⃣ 当前节点有 nextSibling
|
|
71
|
+
if (node.nextSibling) {
|
|
72
|
+
return [node.nextSibling, rel_depth];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2️⃣ 沿父节点向上查找
|
|
76
|
+
let current = node.parentNode;
|
|
77
|
+
let depth = rel_depth;
|
|
78
|
+
|
|
79
|
+
while (current) {
|
|
80
|
+
depth -= 1; // 向上走一层
|
|
81
|
+
|
|
82
|
+
if (current.nextSibling) {
|
|
83
|
+
return [current.nextSibling, depth];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
current = current.parentNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3️⃣ 已到根节点,仍然没有
|
|
90
|
+
return [null,-1];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const editonly_sdfs_for_each = async(rt,cb=(el,rel_depth)=>{},is_leaf=is_topo_leaf)=> {
|
|
94
|
+
let [drmost,ignore] = get_drmost(rt,is_leaf)
|
|
95
|
+
let curr = rt;
|
|
96
|
+
let should_await = is_async(cb);
|
|
97
|
+
let rel_depth =0;
|
|
98
|
+
while(true) {
|
|
99
|
+
let should_break = false;
|
|
100
|
+
if(should_await) {
|
|
101
|
+
should_break = await cb(curr,rel_depth);
|
|
102
|
+
} else {
|
|
103
|
+
should_break = cb(curr,rel_depth);
|
|
104
|
+
}
|
|
105
|
+
if(should_break||curr=== drmost) {return;} else {
|
|
106
|
+
if(!is_leaf(curr)) {
|
|
107
|
+
++rel_depth;
|
|
108
|
+
curr = curr.firstChild;
|
|
109
|
+
} else {
|
|
110
|
+
let pair = find_rb_of_fst_ance_has_rb(curr,rel_depth);
|
|
111
|
+
curr = pair[0];
|
|
112
|
+
if(curr) {
|
|
113
|
+
rel_depth = pair[1];
|
|
114
|
+
} else {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const find_lb_of_fst_ance_has_lb = (node, rel_depth) => {
|
|
124
|
+
// 1️⃣ 当前节点有 nextSibling
|
|
125
|
+
if (node.prevSibling) {
|
|
126
|
+
return [node.prevSibling, rel_depth];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2️⃣ 沿父节点向上查找
|
|
130
|
+
let current = node.parentNode;
|
|
131
|
+
let depth = rel_depth;
|
|
132
|
+
|
|
133
|
+
while (current) {
|
|
134
|
+
depth -= 1; // 向上走一层
|
|
135
|
+
|
|
136
|
+
if (current.prevSibling) {
|
|
137
|
+
return [current.prevSibling, depth];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
current = current.parentNode;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3️⃣ 已到根节点,仍然没有
|
|
144
|
+
return [null,-1];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const editonly_rsdfs_for_each = async(rt,cb=(el,rel_depth)=>{},is_leaf=is_topo_leaf)=> {
|
|
148
|
+
let [dlmost,ignore] = get_dlmost(rt,is_leaf)
|
|
149
|
+
let curr = rt;
|
|
150
|
+
let should_await = is_async(cb);
|
|
151
|
+
let rel_depth =0;
|
|
152
|
+
while(true) {
|
|
153
|
+
let should_break = false;
|
|
154
|
+
if(should_await) {
|
|
155
|
+
should_break = await cb(curr,rel_depth);
|
|
156
|
+
} else {
|
|
157
|
+
should_break = cb(curr,rel_depth);
|
|
158
|
+
}
|
|
159
|
+
if(should_break||curr=== dlmost) {return;} else {
|
|
160
|
+
if(!is_leaf(curr)) {
|
|
161
|
+
++rel_depth;
|
|
162
|
+
curr = curr.lastChild;
|
|
163
|
+
} else {
|
|
164
|
+
let pair = find_lb_of_fst_ance_has_lb(curr,rel_depth);
|
|
165
|
+
curr = pair[0];
|
|
166
|
+
if(curr) {
|
|
167
|
+
rel_depth = pair[1];
|
|
168
|
+
} else {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const editonly_edfs_for_each = async(rt,cb=(el,rel_depth)=>{},is_leaf=is_topo_leaf)=> {
|
|
178
|
+
let [curr,rel_depth] = get_dlmost(rt,is_leaf)
|
|
179
|
+
let should_await = is_async(cb);
|
|
180
|
+
while(true) {
|
|
181
|
+
let should_break = false;
|
|
182
|
+
if(should_await) {
|
|
183
|
+
should_break = await cb(curr,rel_depth);
|
|
184
|
+
} else {
|
|
185
|
+
should_break = cb(curr,rel_depth);
|
|
186
|
+
}
|
|
187
|
+
if(should_break|| curr === rt) {return;} else {
|
|
188
|
+
let rb = curr.nextSibling;
|
|
189
|
+
if(rb) {
|
|
190
|
+
let pair = get_dlmost(rb,is_leaf);
|
|
191
|
+
rel_depth+= pair[1];
|
|
192
|
+
curr = pair[0];
|
|
193
|
+
} else {
|
|
194
|
+
let pr = curr.parentNode;
|
|
195
|
+
if(pr) {
|
|
196
|
+
--rel_depth;
|
|
197
|
+
curr = pr;
|
|
198
|
+
} else {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const editonly_redfs_for_each = async(rt,cb=(el,rel_depth)=>{},is_leaf=is_topo_leaf)=> {
|
|
207
|
+
let [curr,rel_depth] = get_drmost(rt,is_leaf)
|
|
208
|
+
let should_await = is_async(cb);
|
|
209
|
+
while(true) {
|
|
210
|
+
let should_break = false;
|
|
211
|
+
if(should_await) {
|
|
212
|
+
should_break = await cb(curr,rel_depth);
|
|
213
|
+
} else {
|
|
214
|
+
should_break = cb(curr,rel_depth);
|
|
215
|
+
}
|
|
216
|
+
if(should_break || curr === rt) {return;} else {
|
|
217
|
+
let lb = curr.prevSibling;
|
|
218
|
+
if(lb) {
|
|
219
|
+
let pair = get_drmost(lb,is_leaf);
|
|
220
|
+
rel_depth+= pair[1];
|
|
221
|
+
curr = pair[0];
|
|
222
|
+
} else {
|
|
223
|
+
let pr = curr.parentNode;
|
|
224
|
+
if(pr) {
|
|
225
|
+
--rel_depth;
|
|
226
|
+
curr = pr;
|
|
227
|
+
} else {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const visit = async(
|
|
236
|
+
rt,
|
|
237
|
+
exit_cb = (el,rel_depth)=>{},
|
|
238
|
+
enter_cb = (el,rel_depth)=>{},
|
|
239
|
+
is_leaf=is_topo_leaf,
|
|
240
|
+
) => {
|
|
241
|
+
let curr = rt;
|
|
242
|
+
let state = "open";
|
|
243
|
+
let cbs = {
|
|
244
|
+
"open": [enter_cb, is_async(enter_cb)],
|
|
245
|
+
"clos": [exit_cb, is_async(exit_cb) ]
|
|
246
|
+
};
|
|
247
|
+
let rel_depth = 0;
|
|
248
|
+
while(true) {
|
|
249
|
+
let should_break = false;
|
|
250
|
+
let [cb,should_await] = cbs[state];
|
|
251
|
+
if(state === "open") {
|
|
252
|
+
// open 状态 先执行用户回调
|
|
253
|
+
if(should_await) {should_break = await cb(curr,rel_depth);} else{should_break = cb(curr,rel_depth);}
|
|
254
|
+
// 然后再取下一个节点: 用户回调如果修改了树结构,拿到的是修改后的
|
|
255
|
+
if(should_break) {return;} else {
|
|
256
|
+
if(!is_leaf(curr)) {
|
|
257
|
+
curr=curr.firstChild; ++rel_depth;
|
|
258
|
+
} else {
|
|
259
|
+
state = "clos";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
//事先拿出nxt,因为用户可能改变树结构
|
|
264
|
+
let nxt;
|
|
265
|
+
let nxt_rel_depth;
|
|
266
|
+
let nxt_state;
|
|
267
|
+
let rb = curr.nextSibling;
|
|
268
|
+
if(rb) {
|
|
269
|
+
nxt_state = "open";
|
|
270
|
+
nxt = rb;
|
|
271
|
+
nxt_rel_depth = rel_depth;
|
|
272
|
+
} else {
|
|
273
|
+
nxt_state = "clos";
|
|
274
|
+
nxt = curr.parentNode;
|
|
275
|
+
nxt_rel_depth = rel_depth -1;
|
|
276
|
+
}
|
|
277
|
+
if(should_await) {should_break = await cb(curr,rel_depth);} else{should_break = cb(curr,rel_depth);}
|
|
278
|
+
if(should_break) {return;} else {
|
|
279
|
+
if(curr !== rt) {
|
|
280
|
+
state = nxt_state;curr = nxt;rel_depth = nxt_rel_depth;
|
|
281
|
+
} else {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
const rvisit = async(
|
|
291
|
+
rt,
|
|
292
|
+
exit_cb = (el,rel_depth)=>{},
|
|
293
|
+
enter_cb = (el,rel_depth)=>{},
|
|
294
|
+
is_leaf=is_topo_leaf,
|
|
295
|
+
) => {
|
|
296
|
+
let curr = rt;
|
|
297
|
+
let state = "open";
|
|
298
|
+
let cbs = {
|
|
299
|
+
"open": [enter_cb, is_async(enter_cb)],
|
|
300
|
+
"clos": [exit_cb, is_async(exit_cb) ]
|
|
301
|
+
};
|
|
302
|
+
let rel_depth = 0;
|
|
303
|
+
while(true) {
|
|
304
|
+
let should_break = false;
|
|
305
|
+
let [cb,should_await] = cbs[state];
|
|
306
|
+
if(state === "open") {
|
|
307
|
+
// open 状态 先执行用户回调
|
|
308
|
+
if(should_await) {should_break = await cb(curr,rel_depth);} else{should_break = cb(curr,rel_depth);}
|
|
309
|
+
// 然后再取下一个节点: 用户回调如果修改了树结构,拿到的是修改后的
|
|
310
|
+
if(should_break) {return;} else {
|
|
311
|
+
if(!is_leaf(curr)) {
|
|
312
|
+
curr=curr.lastChild;; ++rel_depth;
|
|
313
|
+
} else {
|
|
314
|
+
state = "clos";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
//事先拿出nxt,因为用户可能改变树结构
|
|
319
|
+
let nxt;
|
|
320
|
+
let nxt_rel_depth;
|
|
321
|
+
let nxt_state;
|
|
322
|
+
let lb = curr.prevSibling;
|
|
323
|
+
if(lb) {
|
|
324
|
+
nxt_state = "open";
|
|
325
|
+
nxt = lb;
|
|
326
|
+
nxt_rel_depth = rel_depth;
|
|
327
|
+
} else {
|
|
328
|
+
nxt_state = "clos";
|
|
329
|
+
nxt = curr.parentNode;
|
|
330
|
+
nxt_rel_depth = rel_depth -1;
|
|
331
|
+
}
|
|
332
|
+
if(should_await) {should_break = await cb(curr,rel_depth);} else{should_break = cb(curr,rel_depth);}
|
|
333
|
+
if(should_break) {return;} else {
|
|
334
|
+
if(curr !== rt) {
|
|
335
|
+
state = nxt_state;curr = nxt;rel_depth = nxt_rel_depth;
|
|
336
|
+
} else {
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
get_depth,
|
|
346
|
+
get_ance,
|
|
347
|
+
get_rt,
|
|
348
|
+
is_topo_leaf,
|
|
349
|
+
get_drmost,
|
|
350
|
+
get_dlmost,
|
|
351
|
+
find_rb_of_fst_ance_has_rb,
|
|
352
|
+
editonly_sdfs_for_each,
|
|
353
|
+
find_lb_of_fst_ance_has_lb,
|
|
354
|
+
editonly_rsdfs_for_each,
|
|
355
|
+
editonly_edfs_for_each,
|
|
356
|
+
editonly_redfs_for_each,
|
|
357
|
+
visit,
|
|
358
|
+
rvisit,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/*
|
|
362
|
+
visit(
|
|
363
|
+
document,
|
|
364
|
+
(el,rel_depth)=>{
|
|
365
|
+
let t = el.nodeType;
|
|
366
|
+
switch(t) {
|
|
367
|
+
case document.ELEMENT_NODE :{console.log(" ".repeat(rel_depth)+"</"+el.tagName.toLowerCase()+">");break;}
|
|
368
|
+
case document.TEXT_NODE :{console.log(" ".repeat(rel_depth)+el.textContent);break;}
|
|
369
|
+
case document.COMMENT_NODE :{console.log(" ".repeat(rel_depth)+"<!--" +el.data + "-->");break;}
|
|
370
|
+
case document.ATTRIBUTE_NODE: {break;}
|
|
371
|
+
case document.CDATA_SECTION_NODE : {break;}
|
|
372
|
+
case document.ENTITY_REFERENCE_NODE: {break;}
|
|
373
|
+
case document.ENTITY_NODE : {break;}
|
|
374
|
+
case document.PROCESSING_INSTRUCTION_NODE: {break;}
|
|
375
|
+
case document.DOCUMENT_NODE: {break;}
|
|
376
|
+
case document.DOCUMENT_TYPE_NODE: {break;}
|
|
377
|
+
case document.DOCUMENT_FRAGMENT_NODE: {break;}
|
|
378
|
+
case document.NOTATION_NODE : {break;}
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
(el,rel_depth)=>{
|
|
382
|
+
let t = el.nodeType;
|
|
383
|
+
switch(t) {
|
|
384
|
+
case document.ELEMENT_NODE :{
|
|
385
|
+
console.log(" ".repeat(rel_depth)+"<"+el.tagName.toLowerCase()+">");break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
)
|
|
390
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nv-basic-bw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"license": "ISC",
|
|
10
10
|
"description": "",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"nvison": "^1.
|
|
12
|
+
"nvison-bw": "^1.0.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
package/ui.js
CHANGED
|
@@ -182,6 +182,221 @@ const simple_confirm_cancel = async (title, cb = async (type, el) => { console.l
|
|
|
182
182
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
////
|
|
186
|
+
/**
|
|
187
|
+
* UI 层:弹出预览遮罩
|
|
188
|
+
*/
|
|
189
|
+
/**
|
|
190
|
+
* 显示预览弹窗,支持拖拽、保存和关闭
|
|
191
|
+
* @param {Promise|string|HTMLImageElement} imgData
|
|
192
|
+
*/
|
|
193
|
+
const show_img_modal = async (imgData) => {
|
|
194
|
+
// 1. 基础遮罩容器
|
|
195
|
+
const mask = document.createElement('div');
|
|
196
|
+
Object.assign(mask.style, {
|
|
197
|
+
position: 'fixed', inset: '0', backgroundColor: 'rgba(0,0,0,0.85)',
|
|
198
|
+
display: 'flex', justifyContent: 'center', alignItems: 'center',
|
|
199
|
+
zIndex: '100000', backdropFilter: 'blur(4px)', overflow: 'hidden'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const data = await imgData;
|
|
203
|
+
let img = (data instanceof HTMLElement) ? data : new Image();
|
|
204
|
+
if (typeof data === "string") img.src = data;
|
|
205
|
+
|
|
206
|
+
Object.assign(img.style, {
|
|
207
|
+
maxWidth: '90%', maxHeight: '90%', objectFit: 'contain',
|
|
208
|
+
border: '1px solid #555', boxShadow: '0 8px 30px rgba(0,0,0,0.5)',
|
|
209
|
+
backgroundColor: '#fff'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// --- 创建组合控件容器 ---
|
|
213
|
+
const panel = document.createElement('div');
|
|
214
|
+
Object.assign(panel.style, {
|
|
215
|
+
position: 'fixed', top: '30px', right: '30px',
|
|
216
|
+
display: 'flex', height: '40px', background: '#222',
|
|
217
|
+
borderRadius: '20px', boxShadow: '0 4px 15px rgba(0,0,0,0.8)',
|
|
218
|
+
zIndex: '100001', overflow: 'hidden', border: '1px solid #444'
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 1. 拖拽手柄
|
|
222
|
+
const dragHandle = document.createElement('div');
|
|
223
|
+
dragHandle.innerHTML = '⠿';
|
|
224
|
+
Object.assign(dragHandle.style, {
|
|
225
|
+
padding: '0 15px', cursor: 'move', backgroundColor: '#333',
|
|
226
|
+
height: '100%', display: 'flex', alignItems: 'center', color: '#888'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// 2. 保存按钮 (调用 savePick)
|
|
230
|
+
const saveBtn = document.createElement('div');
|
|
231
|
+
saveBtn.innerText = '保存截图';
|
|
232
|
+
Object.assign(saveBtn.style, {
|
|
233
|
+
padding: '0 15px', cursor: 'pointer', height: '100%',
|
|
234
|
+
display: 'flex', alignItems: 'center', fontSize: '13px',
|
|
235
|
+
fontWeight: 'bold', backgroundColor: '#2f54eb', color: '#fff',
|
|
236
|
+
borderRight: '1px solid #444'
|
|
237
|
+
});
|
|
238
|
+
saveBtn.onclick = async() => {
|
|
239
|
+
const handle = await window.showSaveFilePicker({
|
|
240
|
+
suggestedName: `screenshot_${new Date().getTime()}.png`,
|
|
241
|
+
types: [{
|
|
242
|
+
description: 'PNG Image',
|
|
243
|
+
accept: { 'image/png': ['.png'] },
|
|
244
|
+
}],
|
|
245
|
+
});
|
|
246
|
+
const response = await fetch(img.src);
|
|
247
|
+
const blob = await response.blob();
|
|
248
|
+
const writable = await handle.createWritable();
|
|
249
|
+
await writable.write(blob);
|
|
250
|
+
await writable.close();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// 3. 关闭按钮
|
|
254
|
+
const closeBtn = document.createElement('div');
|
|
255
|
+
closeBtn.innerText = '关闭';
|
|
256
|
+
Object.assign(closeBtn.style, {
|
|
257
|
+
padding: '0 15px', cursor: 'pointer', height: '100%',
|
|
258
|
+
display: 'flex', alignItems: 'center', fontSize: '13px',
|
|
259
|
+
fontWeight: 'bold', backgroundColor: '#ff4d4f', color: '#fff'
|
|
260
|
+
});
|
|
261
|
+
closeBtn.onclick = () => {
|
|
262
|
+
mask.remove();
|
|
263
|
+
panel.remove();
|
|
264
|
+
window.onmousemove = null;
|
|
265
|
+
window.onmouseup = null;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// --- 拖拽逻辑 ---
|
|
269
|
+
let isDragging = false;
|
|
270
|
+
let offset = { x: 0, y: 0 };
|
|
271
|
+
dragHandle.onmousedown = (e) => {
|
|
272
|
+
isDragging = true;
|
|
273
|
+
offset.x = e.clientX - panel.offsetLeft;
|
|
274
|
+
offset.y = e.clientY - panel.offsetTop;
|
|
275
|
+
};
|
|
276
|
+
window.onmousemove = (e) => {
|
|
277
|
+
if (!isDragging) return;
|
|
278
|
+
panel.style.left = (e.clientX - offset.x) + 'px';
|
|
279
|
+
panel.style.top = (e.clientY - offset.y) + 'px';
|
|
280
|
+
panel.style.right = 'auto';
|
|
281
|
+
};
|
|
282
|
+
window.onmouseup = () => isDragging = false;
|
|
283
|
+
|
|
284
|
+
// 组装并渲染
|
|
285
|
+
panel.append(dragHandle, saveBtn, closeBtn);
|
|
286
|
+
mask.appendChild(img);
|
|
287
|
+
document.body.append(mask, panel);
|
|
288
|
+
}
|
|
289
|
+
////
|
|
290
|
+
/**
|
|
291
|
+
* 格式转换核心函数
|
|
292
|
+
*/
|
|
293
|
+
const convert_canvas = async (canvas, format) => {
|
|
294
|
+
if (format === 'b64') return canvas.toDataURL('image/png');
|
|
295
|
+
let blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
|
296
|
+
switch (format) {
|
|
297
|
+
case 'url': return URL.createObjectURL(blob);
|
|
298
|
+
case 'u8a': return new Uint8Array(await blob.arrayBuffer());
|
|
299
|
+
case 'ab' : return await blob.arrayBuffer();
|
|
300
|
+
case 'img': {
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
const img = new Image();
|
|
303
|
+
const url = URL.createObjectURL(blob);
|
|
304
|
+
img.onload = () => {
|
|
305
|
+
// 只要图片加载进内存,就可以释放临时的 Blob URL 了
|
|
306
|
+
URL.revokeObjectURL(url);
|
|
307
|
+
resolve(img);
|
|
308
|
+
};
|
|
309
|
+
img.onerror = reject;
|
|
310
|
+
img.src = url;
|
|
311
|
+
img.crossOrigin = 'anonymous';
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
default : return blob;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
////
|
|
319
|
+
/**
|
|
320
|
+
* 最终完善版:带高亮反馈和异常处理的元素截图
|
|
321
|
+
*/
|
|
322
|
+
const ele2img = async (element, format = undefined) => {
|
|
323
|
+
// 检查元素合法性
|
|
324
|
+
if (!element || !(element instanceof HTMLElement)) {
|
|
325
|
+
console.error("无效的元素对象");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 1. 记录原始样式并开启高亮反馈
|
|
330
|
+
const originalOutline = element.style.outline;
|
|
331
|
+
const originalTransition = element.style.transition;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// 瞬间高亮目标元素,给用户视觉确认
|
|
335
|
+
element.style.transition = "outline 0.2s ease";
|
|
336
|
+
element.style.outline = "4px solid #2f54eb";
|
|
337
|
+
element.style.outlineOffset = "2px";
|
|
338
|
+
|
|
339
|
+
// 2. 确保元素在视口中央
|
|
340
|
+
element.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
341
|
+
|
|
342
|
+
// 3. 启动捕获 (提示:需在弹窗中选择“当前标签页”)
|
|
343
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
344
|
+
video: { displaySurface: "browser" },
|
|
345
|
+
preferCurrentTab: true
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const video = document.createElement('video');
|
|
349
|
+
video.srcObject = stream;
|
|
350
|
+
await video.play();
|
|
351
|
+
|
|
352
|
+
// --- 坐标与比例计算 ---
|
|
353
|
+
const scaleX = video.videoWidth / window.innerWidth;
|
|
354
|
+
const scaleY = video.videoHeight / window.innerHeight;
|
|
355
|
+
const rect = element.getBoundingClientRect();
|
|
356
|
+
|
|
357
|
+
const canvas = document.createElement('canvas');
|
|
358
|
+
canvas.width = rect.width * scaleX;
|
|
359
|
+
canvas.height = rect.height * scaleY;
|
|
360
|
+
|
|
361
|
+
const ctx = canvas.getContext('2d');
|
|
362
|
+
ctx.drawImage(
|
|
363
|
+
video,
|
|
364
|
+
rect.left * scaleX, rect.top * scaleY,
|
|
365
|
+
rect.width * scaleX, rect.height * scaleY,
|
|
366
|
+
0, 0,
|
|
367
|
+
canvas.width, canvas.height
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// 停止流
|
|
371
|
+
stream.getTracks().forEach(track => track.stop());
|
|
372
|
+
|
|
373
|
+
// 4. 恢复元素原始样式
|
|
374
|
+
element.style.outline = originalOutline;
|
|
375
|
+
element.style.transition = originalTransition;
|
|
376
|
+
|
|
377
|
+
// 5. 处理结果
|
|
378
|
+
if (format === undefined) {
|
|
379
|
+
const imgEl = await convert_canvas(canvas, "img");
|
|
380
|
+
show_img_modal(imgEl);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
return await convert_canvas(canvas, format);
|
|
384
|
+
|
|
385
|
+
} catch (err) {
|
|
386
|
+
// 恢复样式防止卡死在高亮状态
|
|
387
|
+
element.style.outline = originalOutline;
|
|
388
|
+
element.style.transition = originalTransition;
|
|
389
|
+
|
|
390
|
+
// 完善的错误分类捕获
|
|
391
|
+
if (err.name === 'NotAllowedError') {
|
|
392
|
+
console.warn("用户取消了屏幕共享授权或权限被拒绝");
|
|
393
|
+
} else if (err.name === 'NotFoundError') {
|
|
394
|
+
console.error("未找到可用的媒体流源");
|
|
395
|
+
} else {
|
|
396
|
+
console.error("截图发生意外错误:", err);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
185
400
|
|
|
186
401
|
|
|
187
402
|
module.exports = {
|
|
@@ -189,6 +404,9 @@ module.exports = {
|
|
|
189
404
|
creat_simple_alert,
|
|
190
405
|
simple_alert,
|
|
191
406
|
cancel_simple_alert,
|
|
192
|
-
simple_confirm_cancel
|
|
407
|
+
simple_confirm_cancel,
|
|
408
|
+
show_img_modal,
|
|
409
|
+
convert_canvas,
|
|
410
|
+
ele2img
|
|
193
411
|
};
|
|
194
412
|
|
package/util.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const is_async = (f)=>f.constructor.name === "AsyncFunction";
|
|
2
|
+
const is_url = (s)=>{
|
|
3
|
+
if (typeof s !== "string") return false;
|
|
4
|
+
s = s.trim();
|
|
5
|
+
if (s === "") return false;
|
|
6
|
+
|
|
7
|
+
// 快路径(你系统里最常见的)
|
|
8
|
+
// 绝对 URL / 协议相对 / 绝对路径 / 相对路径
|
|
9
|
+
if (
|
|
10
|
+
s.startsWith("http://") ||
|
|
11
|
+
s.startsWith("https://") ||
|
|
12
|
+
s.startsWith("ws://") ||
|
|
13
|
+
s.startsWith("wss://") ||
|
|
14
|
+
s.startsWith("//") ||
|
|
15
|
+
s.startsWith("/") ||
|
|
16
|
+
s.startsWith("./") ||
|
|
17
|
+
s.startsWith("../")
|
|
18
|
+
) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// data / blob / file / wasm 等(前端系统常见)
|
|
23
|
+
if (/^(data|blob|file|wasm):/i.test(s)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const fullfill_url = (url)=>{
|
|
30
|
+
if(
|
|
31
|
+
url.startsWith("http://") ||
|
|
32
|
+
url.startsWith("https://") ||
|
|
33
|
+
url.startsWith("ws://") ||
|
|
34
|
+
url.startsWith("wss://") ||
|
|
35
|
+
/^(data|blob|file|wasm):/i.test(url)
|
|
36
|
+
) {
|
|
37
|
+
return url;
|
|
38
|
+
} else if(
|
|
39
|
+
url.startsWith("//") || url.startsWith("/") || url.startsWith("./") || url.startsWith("../")
|
|
40
|
+
) {
|
|
41
|
+
// protocol-relative: //cdn/a.js
|
|
42
|
+
if (url.startsWith("//")) {
|
|
43
|
+
return window.location.protocol + url;
|
|
44
|
+
}
|
|
45
|
+
// absolute path: /a/b
|
|
46
|
+
if (url.startsWith("/")) {
|
|
47
|
+
return window.location.origin + url;
|
|
48
|
+
}
|
|
49
|
+
// relative path: ./a ../b foo/bar
|
|
50
|
+
return new URL(url, window.location.href).href;
|
|
51
|
+
} else {
|
|
52
|
+
return url
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const creat_dt_prefix = () => new Date().toISOString().replace(/[:.]/g, "-");
|
|
57
|
+
|
|
58
|
+
const CKRGX = /^(.*?)=["]?(.*)["]?$/;
|
|
59
|
+
const get_ck = (s=document.cookie)=>{
|
|
60
|
+
let arr = s.split("; ");
|
|
61
|
+
let d = {};
|
|
62
|
+
for(let e of arr) {
|
|
63
|
+
let grps = CKRGX.exec(e);
|
|
64
|
+
d[grps[1]] = grps[2];
|
|
65
|
+
}
|
|
66
|
+
return d;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
is_async,
|
|
71
|
+
is_url,
|
|
72
|
+
fullfill_url,
|
|
73
|
+
creat_dt_prefix,
|
|
74
|
+
get_ck
|
|
75
|
+
}
|