wnm168 commited on
Commit
538fec7
·
verified ·
1 Parent(s): c8bbbf0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1 -936
index.html CHANGED
@@ -1,936 +1 @@
1
- <!DOCTYPE html>
2
- <meta name="viewport" content="width=device-width, initial-scale=1" />
3
- <html>
4
-
5
- <head>
6
- <meta charset="UTF-8" />
7
- <title>Task</title>
8
- <link rel="stylesheet" type="text/css" href="https://www.unpkg.com/[email protected]/dist/css/bootstrap.min.css" />
9
- <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet" />
10
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/fonts.min.css" rel="stylesheet" />
11
- </head>
12
-
13
- <body>
14
- <div id="root"></div>
15
- <a id="back-to-top" href="#" class="btn btn-success btn-lg back-to-top" role="button"><i
16
- class="mdi mdi-arrow-up"></i></a>
17
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
18
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
19
- <script src="https://www.unpkg.com/[email protected]/dist/jquery.min.js"></script>
20
- <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
21
- <script src="https://www.unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
22
- <script src="https://unpkg.com/[email protected]/dist/react-bootstrap.min.js"></script>
23
- <script src="https://unpkg.com/[email protected]/dist/redux.min.js"></script>
24
- <script src="https://unpkg.com/[email protected]/umd/react-router-dom.min.js"></script>
25
- <script src="https://unpkg.com/[email protected]/babel.min.js"></script>
26
- <script src="https://unpkg.com/[email protected]/runtime.js"></script>
27
- <script src="https://cdn.bootcdn.net/ajax/libs/babel-polyfill/7.12.1/polyfill.min.js"></script>
28
- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.min.js"></script>
29
- <script src="layer/layer.js"></script>
30
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mitt.umd.min.js"></script>
31
-
32
- <style>
33
- .bi {
34
- display: inline-block;
35
- width: 1rem;
36
- height: 1rem;
37
- }
38
-
39
- /*
40
- * Sidebar
41
- */
42
- @media (min-width: 768px) {
43
- .sidebar {
44
- width: 100%;
45
- }
46
-
47
- .sidebar .offcanvas-lg {
48
- position: -webkit-sticky;
49
- position: sticky;
50
- top: 48px;
51
- }
52
-
53
- .navbar-search {
54
- display: block;
55
- }
56
- }
57
-
58
- .sidebar .nav-link {
59
- font-size: 0.875rem;
60
- font-weight: 500;
61
- }
62
-
63
- .sidebar .nav-link.active {
64
- color: #2470dc;
65
- }
66
-
67
- .sidebar-heading {
68
- font-size: 0.75rem;
69
- }
70
-
71
- /*
72
- * Navbar
73
- */
74
- .navbar {
75
- background-color: teal;
76
- }
77
-
78
- .navbar-brand {
79
- padding-top: 0.75rem;
80
- padding-bottom: 0.75rem;
81
- /* background-color: rgba(0, 0, 0, .25);
82
- box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); */
83
- }
84
-
85
- .navbar .form-control {
86
- padding: 0.75rem 1rem;
87
- }
88
-
89
- .bd-placeholder-img {
90
- font-size: 1.125rem;
91
- text-anchor: middle;
92
- -webkit-user-select: none;
93
- -moz-user-select: none;
94
- user-select: none;
95
- }
96
-
97
- @media (min-width: 768px) {
98
- .bd-placeholder-img-lg {
99
- font-size: 3.5rem;
100
- }
101
- }
102
-
103
- .b-example-divider {
104
- width: 100%;
105
- height: 3rem;
106
- background-color: rgba(0, 0, 0, 0.1);
107
- border: solid rgba(0, 0, 0, 0.15);
108
- border-width: 1px 0;
109
- box-shadow: inset 0 0.5em 1.5em rgba(0, 0, 0, 0.1),
110
- inset 0 0.125em 0.5em rgba(0, 0, 0, 0.15);
111
- }
112
-
113
- .b-example-vr {
114
- flex-shrink: 0;
115
- width: 1.5rem;
116
- height: 100vh;
117
- }
118
-
119
- .bi {
120
- vertical-align: -0.125em;
121
- fill: currentColor;
122
- }
123
-
124
- .nav-scroller {
125
- position: relative;
126
- z-index: 2;
127
- height: 2.75rem;
128
- overflow-y: hidden;
129
- }
130
-
131
- .nav-scroller .nav {
132
- display: flex;
133
- flex-wrap: nowrap;
134
- padding-bottom: 1rem;
135
- margin-top: -1px;
136
- overflow-x: auto;
137
- text-align: center;
138
- white-space: nowrap;
139
- -webkit-overflow-scrolling: touch;
140
- }
141
-
142
- .btn-bd-primary {
143
- --bd-violet-bg: #712cf9;
144
- --bd-violet-rgb: 112.520718, 44.062154, 249.437846;
145
-
146
- --bs-btn-font-weight: 600;
147
- --bs-btn-color: var(--bs-white);
148
- --bs-btn-bg: var(--bd-violet-bg);
149
- --bs-btn-border-color: var(--bd-violet-bg);
150
- --bs-btn-hover-color: var(--bs-white);
151
- --bs-btn-hover-bg: #6528e0;
152
- --bs-btn-hover-border-color: #6528e0;
153
- --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
154
- --bs-btn-active-color: var(--bs-btn-hover-color);
155
- --bs-btn-active-bg: #5a23c8;
156
- --bs-btn-active-border-color: #5a23c8;
157
- }
158
-
159
- .bd-mode-toggle {
160
- z-index: 1500;
161
- }
162
-
163
- .bd-mode-toggle .dropdown-menu .active .bi {
164
- display: block !important;
165
- }
166
-
167
- .back-to-top {
168
- position: fixed;
169
- bottom: 25px;
170
- right: 25px;
171
- display: none;
172
- }
173
-
174
- .leftsidebar {
175
- height: 100%;
176
- box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
177
- }
178
-
179
- @media (min-width: 768px) {
180
- .leftsidebar {
181
- min-width: 15%;
182
- }
183
- }
184
-
185
- @media (max-width: 768px) {
186
- .leftsidebar {
187
- max-width: 50%;
188
- }
189
- }
190
-
191
- .bg-teal {
192
- background-color: teal;
193
- }
194
- </style>
195
-
196
- <script type="text/babel" data-presets="react" data-type="module">
197
- //事件监听开始 通过修改localstorage实现跨页面事件监听
198
- const emitter = mitt();
199
- // 监听 localStorage 变化
200
- window.addEventListener("storage", (event) => {
201
- if (event.key === "event") {
202
- const { type, data } = JSON.parse(event.newValue);
203
- emitter.emit(type, data);
204
- }
205
- });
206
- // 封装 emit 方法
207
- const emitEvent = (type, data) => {
208
- // 触发本地事件
209
- emitter.emit(type, data);
210
- const randomString = Math.random()
211
- .toString(36)
212
- .substring(2, 10); // 生成一个随机字符串确保event每次的值不一样,如果一样会不触发事件
213
- const identity = `${Date.now()}-${randomString}`;
214
- // 存储到 localStorage,以便其他页面能够接收到
215
- localStorage.setItem(
216
- "event",
217
- JSON.stringify({ type, data, identity })
218
- );
219
- };
220
-
221
- // 封装 on 方法
222
- const onEvent = (type, callback) => {
223
- emitter.on(type, callback);
224
- };
225
-
226
- // 封装 off 方法
227
- const offEvent = (type, callback) => {
228
- emitter.off(type, callback);
229
- };
230
- //事件监听结束
231
-
232
-
233
- const { createStore, combineReducers } = Redux;
234
- // 从 localStorage 加载初始状态
235
- const loadStateFromLocalStorage = () => {
236
- try {
237
- const serializedState = localStorage.getItem('settings');
238
- if (serializedState === null) {
239
- return {}; // 默认值
240
- }
241
- return JSON.parse(serializedState);
242
- } catch (e) {
243
- console.error("Could not load state from localStorage:", e);
244
- return {}; // 默认值
245
- }
246
- };
247
- // 保存状态到 localStorage
248
- const saveStateToLocalStorage = (state) => {
249
- try {
250
- const serializedState = JSON.stringify(state);
251
- localStorage.setItem('settings', serializedState);
252
- } catch (e) {
253
- console.error("Could not save state to localStorage:", e);
254
- }
255
- };
256
-
257
- // 定义初始状态
258
- const initialSettingsState = loadStateFromLocalStorage();
259
-
260
- // 创建 settings Reducer
261
- function settingsReducer(state = initialSettingsState, action) {
262
- switch (action.type) {
263
- case 'SAVE_SETTING':
264
- return { ...state, ...action.payload };
265
- default:
266
- return state;
267
- }
268
- }
269
-
270
- // 合并 Reducer(如果有多个)
271
- const rootReducer = combineReducers({
272
- settings: settingsReducer,
273
- });
274
-
275
- // 创建 Redux Store
276
- const STORE = createStore(rootReducer);
277
-
278
- // 订阅 Store 的变化,并将状态保存到 localStorage
279
- STORE.subscribe(() => {
280
- saveStateToLocalStorage(STORE.getState().settings);
281
- });
282
-
283
-
284
- const bytesToSize = (bytes) => {
285
- if (bytes === 0) return '0 B';
286
- var k = 1024;
287
- sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
288
- i = Math.floor(Math.log(bytes) / Math.log(k));
289
- return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
290
- };
291
- const formatDate = (date) => {
292
- var d = new Date(date);
293
- var year = d.getFullYear();
294
- var month = d.getMonth() + 1;
295
- var day = d.getDate() < 10 ? '0' + d.getDate() : '' + d.getDate();
296
- var hour = d.getHours();
297
- var minutes = d.getMinutes();
298
- var seconds = d.getSeconds();
299
- return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds;
300
- };
301
-
302
-
303
- const { useState, useEffect, useRef } = React;
304
- const { HashRouter, Route, Link, Switch, useLocation, useParams } =
305
- ReactRouterDOM;
306
- const {
307
- Alert,
308
- Badge,
309
- Button,
310
- ButtonGroup,
311
- ButtonToolbar,
312
- Collapse,
313
- Col,
314
- Container,
315
- Form,
316
- Image,
317
- InputGroup,
318
- ListGroup,
319
- Modal,
320
- Nav,
321
- Navbar,
322
- NavDropdown,
323
- Offcanvas,
324
- Pagination,
325
- Row,
326
- Table,
327
- } = ReactBootstrap;
328
- //注意修改js文件后需要直接访问js以更新浏览器缓存
329
-
330
- // 表格组件
331
- const DataTable = ({ data, columns }) => {
332
- return (
333
- <Table responsive bordered>
334
- <thead>
335
- <tr className="text-center">
336
- {columns.map((column, index) => (
337
- <th key={index}>{column.title}</th>
338
- ))}
339
- </tr>
340
- </thead>
341
- <tbody>
342
- {data.map((row, rowIndex) => (
343
- <tr key={rowIndex} className="text-center">
344
- {columns.map((column, colIndex) => (
345
- <td key={colIndex}>
346
- {/* 调用渲染方法,如果没有定义,则直接显示数据 */}
347
- {column.render
348
- ? column.render(row)
349
- : row[column.dataIndex]}
350
- </td>
351
- ))}
352
- </tr>
353
- ))}
354
- </tbody>
355
- </Table>
356
- );
357
- };
358
- //分页组件
359
- const Paginate = (props) => {
360
- const page = props.page;
361
- const pageCount = Math.ceil(
362
- props.totalCount / props.itemsPerPage
363
- );
364
-
365
- const SelectItems = () => {
366
- const pageNumbers = Array.from(
367
- { length: pageCount },
368
- (_, i) => i + 1
369
- );
370
- return (
371
- <select
372
- className="form-select me-2"
373
- style={{ width: "auto" }}
374
- onChange={(e) => {
375
- props.onClick(parseInt(e.target.value));
376
- }}
377
- >
378
- {pageNumbers.map((number) => {
379
- const selected = number === page ? true : false;
380
- return (
381
- <option
382
- key={number}
383
- value={number}
384
- selected={selected}
385
- >
386
- {number}
387
- </option>
388
- );
389
- })}
390
- </select>
391
- );
392
- };
393
- return (
394
- <div className="d-flex justify-content-center align-items-baseline">
395
- <SelectItems />
396
- <span className="text-info me-2">
397
- {page}/{pageCount}
398
- </span>
399
- <Pagination>
400
- {pageCount > 1 && page > 1 && (
401
- <Pagination.First
402
- onClick={() => {
403
- props.onClick(1);
404
- }}
405
- />
406
- )}
407
- {pageCount > 1 && page > 1 && (
408
- <Pagination.Prev
409
- onClick={() => {
410
- props.onClick(page - 1);
411
- }}
412
- />
413
- )}
414
- {pageCount > 1 && page < pageCount && (
415
- <Pagination.Next
416
- onClick={() => {
417
- props.onClick(page + 1);
418
- }}
419
- />
420
- )}
421
- {pageCount > 1 && page < pageCount && (
422
- <Pagination.Last
423
- onClick={() => {
424
- props.onClick(pageCount);
425
- }}
426
- />
427
- )}
428
- </Pagination>
429
- </div>
430
- );
431
- };
432
- //图标组件
433
- const Icon = (props) => {
434
- return (
435
- <span
436
- onClick={props.onClick}
437
- className={`mdi mdi-${props.icon} fs-${props.size} ${props.className}`}
438
- ></span>
439
- );
440
- };
441
- //按钮图标组件
442
- const IconButton = (props) => {
443
- return (
444
- <Button
445
- variant="success"
446
- onClick={props.onClick}
447
- className={props.className}
448
- >
449
- <span
450
- className={`mdi mdi-${props.icon} fs-${props.iconSize} ${props.iconClassName}`}
451
- ></span>
452
- {props.text}
453
- </Button>
454
- );
455
- };
456
- //设置框
457
- const SettingModal = (props) => {
458
- const settings = [
459
- { "alist": [{ "label": "Alist地址", "key": "alist_host", "show": true }, { "label": "Alist令牌", "key": "alist_token", "show": false }] },
460
- { "github": [{ "label": "Actions地址", "key": "github_host", "show": true }, { "label": "Github令牌", "key": "github_token", "show": false }] }
461
- ]
462
- const [setting, setSetting] = useState({});
463
- // useEffect(() => {
464
- // localStorage.setItem('settings', JSON.stringify(setting));
465
- // }, [setting]);
466
-
467
- const loadSetting = () => {
468
- const storedSettings = STORE.getState().settings;
469
- if (storedSettings) {
470
- setSetting(storedSettings);
471
- }
472
- }
473
- const saveSetting = () => {
474
- STORE.dispatch({ type: 'SAVE_SETTING', payload: setting })
475
- //localStorage.setItem('settings', JSON.stringify(setting));
476
- }
477
- return (
478
- <Modal show={props.show} onHide={props.onHide} onShow={loadSetting}>
479
- <Modal.Header closeButton onHide={props.onHide}>
480
- <Modal.Title>设置</Modal.Title>
481
- </Modal.Header>
482
- <Modal.Body>
483
- <Form>
484
- <ListGroup>
485
- {settings.map((value, index) => {
486
- const key = Object.keys(value)[0];
487
- const items = value[key];
488
- return (<ListGroup.Item>
489
- {items.map((setting_item) => {
490
- return (
491
- <Form.Group as={Row} className="mb-3">
492
- <Form.Label column sm="3">
493
- {setting_item.label}
494
- </Form.Label>
495
- <Col sm="9">
496
- <Form.Control type={setting_item.show ? "input" : "password"} value={setting[setting_item.key]} name={setting_item.key} placeholder={setting_item.label} onChange={(e) => { setSetting({ ...setting, [setting_item.key]: e.target.value }) }} />
497
- </Col>
498
- </Form.Group>
499
- )
500
- })}
501
- </ListGroup.Item>)
502
- })}
503
- </ListGroup>
504
- </Form>
505
- </Modal.Body>
506
- <Modal.Footer className="justify-content-between">
507
- <Button
508
- variant="secondary"
509
- onClick={() => {
510
- props.onHide();
511
- }}
512
- >
513
- 关闭
514
- </Button>
515
- <Button
516
- variant="primary"
517
- onClick={() => {
518
- saveSetting();
519
- props.onHide();
520
- //props.onSave();
521
- }}
522
- >
523
- 保存
524
- </Button>
525
- </Modal.Footer>
526
- </Modal>
527
- );
528
- };
529
-
530
- //axios封装开始
531
- const useAxios = () => {
532
- const [response, setResponse] = useState(null);
533
- const [error, setError] = useState("");
534
- const [loading, setLoading] = useState(false);
535
-
536
- // Create an Axios instance
537
- const axiosInstance = axios.create({});
538
-
539
- // Set up request and response interceptors
540
- axiosInstance.interceptors.request.use(
541
- (config) => {
542
- // Log or modify request here
543
- //console.log("Sending request to:", config.url);
544
- return config;
545
- },
546
- (error) => {
547
- // Handle request error here
548
- return Promise.reject(error);
549
- }
550
- );
551
-
552
- axiosInstance.interceptors.response.use(
553
- (response) => {
554
- // Log or modify response here
555
- //console.log("Received response from:", response.config.url);
556
- return response;
557
- },
558
- (error) => {
559
- // Handle response error here
560
- return Promise.reject(error);
561
- }
562
- );
563
-
564
- useEffect(() => {
565
- const source = axios.CancelToken.source();
566
- return () => {
567
- // Cancel the request when the component unmounts
568
- source.cancel(
569
- "组件被卸载: 请求取消."
570
- );
571
- };
572
- }, []);
573
-
574
- // Making the API call with cancellation support
575
- const fetchData = async ({ url, method, data, headers }) => {
576
- setLoading(true);
577
- try {
578
- const result = await axiosInstance({
579
- url,
580
- method,
581
- headers: headers ? headers : {},
582
- data:
583
- method.toLowerCase() === "get"
584
- ? undefined
585
- : data,
586
- params:
587
- method.toLowerCase() === "get"
588
- ? data
589
- : undefined,
590
- cancelToken: axios.CancelToken.source().token,
591
- });
592
- setResponse(result.data);
593
- } catch (error) {
594
- if (axios.isCancel(error)) {
595
- console.log("Request cancelled", error.message);
596
- } else {
597
- setError(
598
- error.response
599
- ? error.response.data
600
- : error.message
601
- );
602
- }
603
- } finally {
604
- setLoading(false);
605
- }
606
- };
607
- return [response, error, loading, fetchData];
608
- };
609
- //axios封闭结束
610
-
611
- //API定义开始
612
- const getFiles = () => {
613
- const [response, error, loading, fetchData] = useAxios();
614
-
615
- const fetchDataByPage = async (setting, query) => {
616
- var host = setting.alist_host;
617
- if (!host.endsWith("/")) {
618
- host = host + '/'
619
- }
620
- fetchData({
621
- url: host + 'api/fs/list',
622
- method: "POST",
623
- data: query,
624
- headers: {
625
- 'Authorization': setting.alist_token,
626
- 'Content-Type': 'application/json'
627
- },
628
- });
629
- };
630
- return [response, error, loading, fetchDataByPage];
631
- };
632
- //API定义结束
633
-
634
- const Layout = ({ children }) => {
635
- useEffect(() => {
636
- // 组件挂载时执行的代码(相当于 componentDidMount)
637
- }, []); // 空数组表示只在挂载和卸载时执行
638
-
639
- const [showSideBar, setShowSideBar] = useState(false);
640
- const handleSidebarClose = () => setShowSideBar(false);
641
- const handleSidebarShow = () => setShowSideBar(true);
642
- const toggleSidebarShow = () => {
643
- setShowSideBar(!showSideBar);
644
- };
645
-
646
- const [setting, setSetting] = useState(false);
647
-
648
- return (
649
- <div className="pb-5">
650
- <header className="sticky-top">
651
- <Navbar expand="md">
652
- <Container fluid>
653
- <div>
654
- <Navbar.Toggle
655
- className="shadow-none border-0"
656
- onClick={handleSidebarShow}
657
- children={
658
- <Icon
659
- icon="menu"
660
- size="3"
661
- className="text-white"
662
- />
663
- }
664
- />
665
- <Navbar.Brand
666
- as={Link}
667
- to="/"
668
- className="text-white"
669
- >
670
- 离线管理
671
- </Navbar.Brand>
672
- </div>
673
- <div className="d-flex">
674
- <Button
675
- style={{
676
- backgroundColor: "transparent",
677
- }}
678
- className="nav-link btn"
679
- onClick={() => {
680
- setSetting(true)
681
- }}
682
- children={
683
- <Icon
684
- icon="dots-vertical"
685
- size="3"
686
- className="text-white"
687
- />
688
- }
689
- ></Button>
690
- <SettingModal
691
- show={setting}
692
- onHide={() => {
693
- setSetting(false);
694
- }}
695
- />
696
- </div>
697
- </Container>
698
- </Navbar>
699
- </header>
700
- <Container fluid>
701
- <Row style={{ minHeight: "100vh" }}>
702
- <Col
703
- md="2"
704
- lg="2"
705
- xl="2"
706
- className="ps-0 d-none d-md-block"
707
- >
708
- <Offcanvas
709
- className="leftsidebar h-100 bg-light"
710
- show={showSideBar}
711
- onHide={handleSidebarClose}
712
- placement="start"
713
- responsive="md"
714
- >
715
- <Offcanvas.Header
716
- className="py-2 border-bottom"
717
- closeButton
718
- >
719
- <Offcanvas.Title>
720
- 离线任务
721
- </Offcanvas.Title>
722
- </Offcanvas.Header>
723
- <Offcanvas.Body className="p-0">
724
- <Container fluid className="p-0">
725
- <Nav
726
- activeKey="1"
727
- className="flex-column"
728
- >
729
- <Nav.Link
730
- as={Link}
731
- className="nav-link text-dark"
732
- to="/"
733
- onClick={
734
- handleSidebarClose
735
- }
736
- >
737
- <Icon
738
- icon="plus"
739
- size="6"
740
- className="me-2"
741
- />
742
- 离线管理
743
- </Nav.Link>
744
- </Nav>
745
- </Container>
746
- </Offcanvas.Body>
747
- </Offcanvas>
748
- </Col>
749
-
750
- <Col xs="12" sm="12" md="10" lg="10" xl="10">
751
- <main>
752
- <Container fluid className="pt-2 px-0">
753
- {children}
754
- </Container>
755
- </main>
756
- </Col>
757
- </Row>
758
- </Container>
759
- </div>
760
- );
761
- };
762
- const Home = () => {
763
- const location = useLocation();
764
- const { id } = useParams();
765
- return (
766
- <div>
767
- <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
768
- <label className="fs-3">Home</label>
769
- <ButtonToolbar
770
- aria-label="文件列表"
771
- className="bg-teal rounded"
772
- >
773
- <ButtonGroup className="bg-teal">
774
- <IconButton
775
- onClick={() => {
776
- alert("test")
777
- }}
778
- text="刷新"
779
- className="bg-teal border-0"
780
- icon="reload"
781
- iconClassName="me-1 text-white"
782
- iconSize="6"
783
- />
784
- <IconButton
785
- onClick={() => {
786
- alert("hello");
787
- }}
788
- text="删除"
789
- className="bg-teal border-0"
790
- icon="delete-outline"
791
- iconClassName="me-1 text-white"
792
- iconSize="6"
793
- />
794
- </ButtonGroup>
795
- </ButtonToolbar>
796
- </div>
797
- <Container fluid className="p-2"></Container>
798
- </div>
799
- );
800
- };
801
-
802
- App = () => {
803
- const [open, setOpen] = useState(false);
804
- const [reload, setReload] = useState(false);
805
- const [response, error, loading, fetchDataByPage] = getFiles();
806
- const { folder } = useParams();
807
- const location = useLocation();
808
- const [path, setPath] = useState(location.pathname);
809
- const [page, setPage] = useState(1);
810
- const [query, setQuery] = useState({ "path": path, "password": "", "page": page, "per_page": 0, "refresh": true });
811
- const setting = STORE.getState().settings;
812
- const columns = [
813
- { title: "文件名称", dataIndex: "name" },
814
- { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(row.size)) },
815
- { title: "日期", dataIndex: "created", render: (row) => (formatDate(row.created)) },
816
- {
817
- title: "操作",
818
- dataIndex: "name",
819
- render: (row) => (
820
- row.is_dir ? <Nav.Link
821
- as={Link}
822
- className="nav-link text-dark"
823
- to={path + row.name + '/'}
824
- target="_blank"
825
- >
826
- <Icon
827
- icon="open-in-new"
828
- size="6"
829
- className="me-2"
830
- />
831
- </Nav.Link> :
832
- <Icon
833
- icon="download-outline"
834
- size="6"
835
- className="me-2"
836
- onClick={() => {
837
- alert(row.name);
838
- }}
839
- />
840
- ),
841
- },
842
- ];
843
-
844
- useEffect(() => {
845
- if (!setting.alist_token || setting.alist_token.length < 5) {
846
- layer.alert("请先正确配置Alsit的令牌", { icon: 5 });
847
- return
848
- }
849
- fetchDataByPage(setting, query);
850
- return () => { }
851
- }, [reload, query]);
852
-
853
-
854
- const forceUpdate = () => {
855
- setReload((pre) => !pre);
856
- };
857
-
858
- return (
859
- <div>
860
- {error && (
861
- <div className="text-center text-danger">
862
- {error}
863
- </div>
864
- )}
865
- {loading && (
866
- <div className="text-center text-success">
867
- 正在努力加载中......
868
- </div>
869
- )}
870
- <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
871
- <label className="fs-3">文件列表</label>
872
- <ButtonToolbar
873
- aria-label="功能区"
874
- className="bg-teal rounded"
875
- >
876
- <ButtonGroup className="bg-teal">
877
- <IconButton
878
- onClick={() => {
879
- emitEvent("test", { a: 'b' })
880
- }}
881
- text="刷新"
882
- className="bg-teal border-0"
883
- icon="reload"
884
- iconClassName="me-1 text-white"
885
- iconSize="6"
886
- />
887
- </ButtonGroup>
888
- </ButtonToolbar>
889
- </div>
890
- <Container fluid className="p-2">
891
- {response && (
892
- <DataTable data={response.data.content ? response.data.content : []} columns={columns} />
893
- )}
894
- </Container>
895
- </div>
896
- );
897
- };
898
-
899
- const container = document.getElementById("root");
900
- const root = ReactDOM.createRoot(container);
901
- root.render(
902
- <HashRouter>
903
- <Route path="/:path?">
904
- <Layout>
905
- <Switch>
906
- <Route path="/" exact component={App} />
907
- <Route path="/:folder?" component={App} />
908
- </Switch>
909
- </Layout>
910
- </Route>
911
- </HashRouter>
912
- );
913
-
914
- $(document).ready(function () {
915
- $(window).scroll(function () {
916
- if ($(this).scrollTop() > 50) {
917
- $("#back-to-top").fadeIn();
918
- } else {
919
- $("#back-to-top").fadeOut();
920
- }
921
- });
922
- // scroll body to 0px on click
923
- $("#back-to-top").click(function () {
924
- $("body,html").animate(
925
- {
926
- scrollTop: 0,
927
- },
928
- 400
929
- );
930
- return false;
931
- });
932
- });
933
- </script>
934
- </body>
935
-
936
- </html>
 
1
+ <!DOCTYPE html><meta name="viewport" content="width=device-width, initial-scale=1" /><html><head> <meta charset="UTF-8" /> <title>Task</title> <link rel="stylesheet" type="text/css" href="https://www.unpkg.com/[email protected]/dist/css/bootstrap.min.css" /> <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/fonts.min.css" rel="stylesheet" /></head><body> <div id="root"></div> <a id="back-to-top" href="#" class="btn btn-success btn-lg back-to-top" role="button"><i class="mdi mdi-arrow-up"></i></a> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://www.unpkg.com/[email protected]/dist/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script> <script src="https://www.unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/react-bootstrap.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/redux.min.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-router-dom.min.js"></script> <script src="https://unpkg.com/[email protected]/babel.min.js"></script> <script src="https://unpkg.com/[email protected]/runtime.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/babel-polyfill/7.12.1/polyfill.min.js"></script> <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script> <script src="layer/layer.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/8.17.1/ajv7.min.js"></script> <script src="https://unpkg.com/@tanstack/react-query@4/build/umd/index.production.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mitt.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script> <style> .bi { display: inline-block; width: 1rem; height: 1rem; } @media (min-width: 768px) { .sidebar { width: 100%; } .sidebar .offcanvas-lg { position: -webkit-sticky; position: sticky; top: 48px; } .navbar-search { display: block; } } .sidebar .nav-link { font-size: 0.875rem; font-weight: 500; } .sidebar .nav-link.active { color: #2470dc; } .sidebar-heading { font-size: 0.75rem; } .navbar { background-color: teal; } .navbar-brand { padding-top: 0.75rem; padding-bottom: 0.75rem; } .navbar .form-control { padding: 0.75rem 1rem; } .bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } .b-example-divider { width: 100%; height: 3rem; background-color: rgba(0, 0, 0, 0.1); border: solid rgba(0, 0, 0, 0.15); border-width: 1px 0; box-shadow: inset 0 0.5em 1.5em rgba(0, 0, 0, 0.1), inset 0 0.125em 0.5em rgba(0, 0, 0, 0.15); } .b-example-vr { flex-shrink: 0; width: 1.5rem; height: 100vh; } .bi { vertical-align: -0.125em; fill: currentColor; } .nav-scroller { position: relative; z-index: 2; height: 2.75rem; overflow-y: hidden; } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } .btn-bd-primary { --bd-violet-bg: #712cf9; --bd-violet-rgb: 112.520718, 44.062154, 249.437846; --bs-btn-font-weight: 600; --bs-btn-color: var(--bs-white); --bs-btn-bg: var(--bd-violet-bg); --bs-btn-border-color: var(--bd-violet-bg); --bs-btn-hover-color: var(--bs-white); --bs-btn-hover-bg: #6528e0; --bs-btn-hover-border-color: #6528e0; --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb); --bs-btn-active-color: var(--bs-btn-hover-color); --bs-btn-active-bg: #5a23c8; --bs-btn-active-border-color: #5a23c8; } .bd-mode-toggle { z-index: 1500; } .bd-mode-toggle .dropdown-menu .active .bi { display: block !important; } .back-to-top { position: fixed; bottom: 25px; right: 25px; display: none; } .leftsidebar { height: 100%; box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1); } @media (min-width: 768px) { .leftsidebar { min-width: 15%; } } @media (max-width: 768px) { .leftsidebar { max-width: 50%; } } .bg-teal { background-color: teal; } </style> <script type="text/babel" data-presets="react" data-type="module"> //事件监听开始 通过修改localstorage实现跨页面事件监听 const emitter = mitt(); // 监听 localStorage 变化 window.addEventListener("storage", (event) => { if (event.key === "event") { const { type, data } = JSON.parse(event.newValue); emitter.emit(type, data); } }); // 封装 emit 方法 const emitEvent = (type, data) => { // 触发本地事件 emitter.emit(type, data); const randomString = Math.random() .toString(36) .substring(2, 10); // 生成一个随机字符串确保event每次的值不一样,如果一样会不触发事件 const identity = `${Date.now()}-${randomString}`; // 存储到 localStorage,以便其他页面能够接收到 localStorage.setItem( "event", JSON.stringify({ type, data, identity }) ); }; // 封装 on 方法 const onEvent = (type, callback) => { emitter.on(type, callback); }; // 封装 off 方法 const offEvent = (type, callback) => { emitter.off(type, callback); }; //事件监听结束 var settingStorage = localforage.createInstance({ name: "setting", driver: localforage.LOCALSTORAGE }); // settingStorage.setItem("category", { name: 'test', id: 1 }); // settingStorage.getItem('category').then(function (value) { // console.log(value); // }).catch(function (err) { // console.log(err); // }); // settingStorage.getItem('category', function (err, value) { // console.log(value.name); // }); const { createStore, combineReducers } = Redux; // 从 localStorage 加载初始状态 const loadStateFromLocalStorage = () => { try { const serializedState = localStorage.getItem('settings'); if (serializedState === null) { return {}; // 默认值 } return JSON.parse(serializedState); } catch (e) { console.error("Could not load state from localStorage:", e); return {}; // 默认值 } }; // 保存状态到 localStorage const saveStateToLocalStorage = (state) => { try { const serializedState = JSON.stringify(state); localStorage.setItem('settings', serializedState); } catch (e) { console.error("Could not save state to localStorage:", e); } }; // 定义初始状态 const initialSettingsState = loadStateFromLocalStorage(); // 创建 settings Reducer function settingsReducer(state = initialSettingsState, action) { switch (action.type) { case 'SAVE_SETTING': return { ...state, ...action.payload }; default: return state; } } // 合并 Reducer(如果有多个) const rootReducer = combineReducers({ settings: settingsReducer, }); // 创建 Redux Store const STORE = createStore(rootReducer); // 订阅 Store 的变化,并将状态保存到 localStorage STORE.subscribe(() => { saveStateToLocalStorage(STORE.getState().settings); }); //数据校验 // var ajv = new ajv7.default() // const schema = { // type: "object", // properties: { // foo: { type: "integer" }, // bar: { type: "string" } // }, // required: ["foo"], // additionalProperties: false // } // const validate = ajv.compile(schema) // const data = { // foo: 1, // bar: "abc" // } // const valid = validate(data) // if (!valid) console.log(validate.errors) const bytesToSize = (bytes) => { if (bytes === 0) return '0 B'; var k = 1024; sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; i = Math.floor(Math.log(bytes) / Math.log(k)); return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; }; const formatDate = (date) => { var d = new Date(date); var year = d.getFullYear(); var month = d.getMonth() + 1; var day = d.getDate()< 10 ? '0' + d.getDate() : '' + d.getDate(); var hour = d.getHours(); var minutes = d.getMinutes(); var seconds = d.getSeconds(); return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds; }; let layerLoading = null; const showLoading = () => { const loadindex = layer.load(1); layerLoading = loadindex; } const hideLoading = () => { layer.close(layerLoading); } const { useState, useEffect, useRef } = React; const { HashRouter, Route, Link, Switch, useLocation, useParams } = ReactRouterDOM; const { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider } = ReactQuery; const queryClient = new QueryClient() const { Alert, Badge, Button, ButtonGroup, ButtonToolbar, Collapse, Col, Container, Form, Image, InputGroup, ListGroup, Modal, Nav, Navbar, NavDropdown, Offcanvas, Pagination, Row, Table, } = ReactBootstrap; //注意修改js文件后需要直接访问js以更新浏览器缓存 // 表格组件 const DataTable = ({ data, columns }) => { return ( <Table responsive bordered> <thead> <tr className="text-center"> {columns.map((column, index) => ( <th key={index}>{column.title}</th> ))} </tr> </thead> <tbody> {data.map((row, rowIndex) => ( <tr key={rowIndex} className="text-center"> {columns.map((column, colIndex) => ( <td key={colIndex}> {} {column.render ? column.render(row) : row[column.dataIndex]} </td> ))} </tr> ))} </tbody> </Table> ); }; //分页组件 const Paginate = (props) => { const page = props.page; const pageCount = Math.ceil( props.totalCount / props.itemsPerPage ); const SelectItems = () => { const pageNumbers = Array.from( { length: pageCount }, (_, i) => i + 1 ); return ( <select className="form-select me-2" style={{ width: "auto" }} onChange={(e) => { props.onClick(parseInt(e.target.value)); }} > {pageNumbers.map((number) => { const selected = number === page ? true : false; return ( <option key={number} value={number} selected={selected} > {number} </option> ); })} </select> ); }; return ( <div className="d-flex justify-content-center align-items-baseline"> <SelectItems /> <span className="text-info me-2"> {page}/{pageCount} </span> <Pagination> {pageCount > 1 && page > 1 && ( <Pagination.First onClick={() => { props.onClick(1); }} /> )} {pageCount > 1 && page > 1 && ( <Pagination.Prev onClick={() => { props.onClick(page - 1); }} /> )} {pageCount > 1 && page< pageCount && ( <Pagination.Next onClick={() => { props.onClick(page + 1); }} /> )} {pageCount > 1 && page< pageCount && ( <Pagination.Last onClick={() => { props.onClick(pageCount); }} /> )} </Pagination> </div> ); }; //图标组件 const Icon = (props) => { return ( <span onClick={props.onClick} className={`mdi mdi-${props.icon} fs-${props.size} ${props.className}`} ></span> ); }; //按钮图标组件 const IconButton = (props) => { return ( <Button variant="success" onClick={props.onClick} className={props.className} > <span className={`mdi mdi-${props.icon} fs-${props.iconSize} ${props.iconClassName}`} ></span> {props.text} </Button> ); }; //设置框 const SettingModal = (props) => { const settings = [ { "alist": [{ "label": "Alist地址", "key": "alist_host", "show": true }, { "label": "Alist令牌", "key": "alist_token", "show": false }] }, { "github": [{ "label": "Actions地址", "key": "github_host", "show": true }, { "label": "Github令牌", "key": "github_token", "show": false }] }, { "directus": [{ "label": "Directus地址", "key": "directus_host", "show": true }, { "label": "Directus令牌", "key": "directus_token", "show": false }] } ] const [setting, setSetting] = useState({}); // useEffect(() => { // localStorage.setItem('settings', JSON.stringify(setting)); // }, [setting]); const loadSetting = () => { const storedSettings = STORE.getState().settings; if (storedSettings) { setSetting(storedSettings); } } const saveSetting = () => { STORE.dispatch({ type: 'SAVE_SETTING', payload: setting }) //localStorage.setItem('settings', JSON.stringify(setting)); } return ( <Modal show={props.show} onHide={props.onHide} onShow={loadSetting}> <Modal.Header closeButton onHide={props.onHide}> <Modal.Title>设置</Modal.Title> </Modal.Header> <Modal.Body> <Form> <ListGroup> {settings.map((value, index) => { const key = Object.keys(value)[0]; const items = value[key]; return (<ListGroup.Item> {items.map((setting_item) => { return ( <Form.Group as={Row} className="mb-3"> <Form.Label column sm="3"> {setting_item.label} </Form.Label> <Col sm="9"> <Form.Control type={setting_item.show ? "input" : "password"} value={setting[setting_item.key]} name={setting_item.key} placeholder={setting_item.label} onChange={(e) => { setSetting({ ...setting, [setting_item.key]: e.target.value }) }} /> </Col> </Form.Group> ) })} </ListGroup.Item>) })} </ListGroup> </Form> </Modal.Body> <Modal.Footer className="justify-content-between"> <Button variant="secondary" onClick={() => { props.onHide(); }} > 关闭 </Button> <Button variant="primary" onClick={() => { saveSetting(); props.onHide(); //props.onSave(); }} > 保存 </Button> </Modal.Footer> </Modal> ); }; //axios封装开始 const useAxios = () => { const [response, setResponse] = useState(null); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); // Create an Axios instance const axiosInstance = axios.create({}); // Set up request and response interceptors axiosInstance.interceptors.request.use( (config) => { // Log or modify request here //console.log("Sending request to:", config.url); return config; }, (error) => { // Handle request error here return Promise.reject(error); } ); axiosInstance.interceptors.response.use( (response) => { // Log or modify response here //console.log("Received response from:", response.config.url); return response; }, (error) => { // Handle response error here return Promise.reject(error); } ); useEffect(() => { const source = axios.CancelToken.source(); return () => { // Cancel the request when the component unmounts source.cancel( "组件被卸载: 请求取消." ); }; }, []); // Making the API call with cancellation support const fetchData = async ({ url, method, data, headers }) => { setLoading(true); try { const result = await axiosInstance({ url, method, headers: headers ? headers : {}, data: method.toLowerCase() === "get" ? undefined : data, params: method.toLowerCase() === "get" ? data : undefined, cancelToken: axios.CancelToken.source().token, }); setResponse(result.data); } catch (error) { if (axios.isCancel(error)) { console.log("Request cancelled", error.message); } else { setError( error.response ? error.response.data : error.message ); } } finally { setLoading(false); } }; return [response, error, loading, fetchData]; }; //axios封闭结束 //API定义开始 const getFiles = () => { const [response, error, loading, fetchData] = useAxios(); const fetchDataByPage = async (setting, query) => { var host = setting.alist_host; if (!host.endsWith("/")) { host = host + '/' } fetchData({ url: host + 'api/fs/list', method: "POST", data: query, headers: { 'Authorization': setting.alist_token, 'Content-Type': 'application/json' }, }); }; return [response, error, loading, fetchDataByPage]; }; //API定义结束 const Layout = ({ children }) => { useEffect(() => { // 组件挂载时执行的代码(相当于 componentDidMount) }, []); // 空数组表示只在挂载和卸载时执行 const [showSideBar, setShowSideBar] = useState(false); const handleSidebarClose = () => setShowSideBar(false); const handleSidebarShow = () => setShowSideBar(true); const toggleSidebarShow = () => { setShowSideBar(!showSideBar); }; const [setting, setSetting] = useState(false); return ( <div className="pb-5"> <header className="sticky-top"> <Navbar expand="md"> <Container fluid> <div> <Navbar.Toggle className="shadow-none border-0" onClick={handleSidebarShow} children={ <Icon icon="menu" size="3" className="text-white" /> } /> <Navbar.Brand as={Link} to="/" className="text-white" > 离线管理 </Navbar.Brand> </div> <div className="d-flex"> <LocalTasks /> <Button style={{ backgroundColor: "transparent", }} className="nav-link btn" onClick={() => { setSetting(true) }} children={ <Icon icon="dots-vertical" size="3" className="text-white" /> } ></Button> <SettingModal show={setting} onHide={() => { setSetting(false); }} /> </div> </Container> </Navbar> </header> <Container fluid> <Row style={{ minHeight: "100vh" }}> <Col md="2" lg="2" xl="2" className="ps-0 d-none d-md-block" > <Offcanvas className="leftsidebar h-100 bg-light" show={showSideBar} onHide={handleSidebarClose} placement="start" responsive="md" > <Offcanvas.Header className="py-2 border-bottom" closeButton > <Offcanvas.Title> 离线任务 </Offcanvas.Title> </Offcanvas.Header> <Offcanvas.Body className="p-0"> <Container fluid className="p-0"> <Nav activeKey="1" className="flex-column" > <Nav.Link as={Link} className="nav-link text-dark" to="/" onClick={ handleSidebarClose } > <Icon icon="plus" size="6" className="me-2" /> 离线管理 </Nav.Link> </Nav> </Container> </Offcanvas.Body> </Offcanvas> </Col> <Col xs="12" sm="12" md="10" lg="10" xl="10"> <main> <Container fluid className="pt-2 px-0"> {children} </Container> </main> </Col> </Row> </Container> </div> ); }; const Home = () => { const location = useLocation(); const { id } = useParams(); return ( <div> <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light"> <label className="fs-3">Home</label> <ButtonToolbar aria-label="文件列表" className="bg-teal rounded" > <ButtonGroup className="bg-teal"> <IconButton onClick={() => { alert("test") }} text="刷新" className="bg-teal border-0" icon="reload" iconClassName="me-1 text-white" iconSize="6" /> <IconButton onClick={() => { alert("hello"); }} text="删除" className="bg-teal border-0" icon="delete-outline" iconClassName="me-1 text-white" iconSize="6" /> </ButtonGroup> </ButtonToolbar> </div> <Container fluid className="p-2"></Container> </div> ); }; const LocalTasks = () => { const [show, setShow] = useState(false); const handleClose = () => setShow(false); const handleShow = () => setShow(true); const [downloads, setDownloads] = useState([]) const [addDownloadObject, setAddDownloadObject] = useState({}) const setting = STORE.getState().settings; const columns = [ { title: "文件名称", dataIndex: "name" }, { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(row.size)) }, { title: "日期", dataIndex: "created", render: (row) => (formatDate(row.created)) }, { title: "操作", dataIndex: "name", render: (row) => ( <div> <Icon icon="delete-outline" size="6" className="me-2" onClick={() => { layer.confirm('确定删除?该操作无法撤销!!!', { icon: 3 }, function (index) { setDownloads( downloads.filter(a => a.name !== row.name ) ); layer.close(index); }, function () { }); }} /> <Icon icon="pencil-outline" size="6" className="me-2" onClick={() => { layer.prompt({ title: '输入文件名称,并确认', formType: 0, value: row.name, success: function (layero, index) { $("div[aria-modal]").eq(0).removeAttr("tabindex");//解决弹出窗的input无法获取焦点的问题 }, end: function (layero, index) { $("div[aria-modal]").eq(0).attr("tabindex", -1).focus();//再把焦点还回去 } }, function (value, index) { const newDownloads = downloads.map(downloadItem => { if (downloadItem.name === row.name) { return { ...downloadItem, name: value }; } return downloadItem; }); setDownloads(newDownloads); layer.close(index); }); }} /> </div> ), }, ]; const { mutateAsync: localTaskdMutation } = useMutation({ mutationKey: ["get-download"], mutationFn: async () => { showLoading(); var host = setting.directus_host; if (!host.endsWith("/")) { host = host + '/' } var url = host + 'items/task'; const tasks = downloads.map(task => { return { url: task.url + '##' + task.name } }) return await axios.post(url, tasks, { headers: { 'Authorization': "Bearer " + setting.directus_token, 'Content-Type': 'application/json' }, }) }, onSuccess: async (data, variables, context) => { hideLoading(); layer.msg('任务添加成功', { time: 2000, icon: 6 }); }, onError: () => { hideLoading(); layer.msg('任务添加失败', { time: 2000, icon: 5 }); } }) const addDowload = (fileinfo) => { const file = fileinfo.data.data; const download = { name: file.name, size: file.size, url: file.raw_url, created: file.created } setAddDownloadObject(download) } useEffect(() => { if (addDownloadObject && ('name' in addDownloadObject)) { setDownloads([...downloads, addDownloadObject]) setAddDownloadObject({}) } }, [addDownloadObject]); useEffect(() => { onEvent("addDownload", addDowload) settingStorage.getItem('downloads').then(function (value) { if (value) { setDownloads(value) } }).catch(function (err) { console.log(err) }); }, []); useEffect(() => { settingStorage.setItem('downloads', downloads) }, [downloads]); if (downloads.length > 0) { return ( <div> <Button style={{ backgroundColor: "transparent", }} className="nav-link btn" onClick={handleShow} children={ <span> <Icon icon="download" size="3" className="text-white" /> <Badge bg="danger" style={{ top: '-15px', left: '-10px' }}>{downloads.length}</Badge> </span> } ></Button> <Modal show={show} onHide={handleClose}> <Modal.Header closeButton> <Modal.Title>本地下载任务</Modal.Title> </Modal.Header> <Modal.Body> {downloads && ( <DataTable data={downloads ? downloads : []} columns={columns} /> )} </Modal.Body> <Modal.Footer className="justify-content-between"> <ButtonGroup> <Button variant="primary" onClick={async () => { await localTaskdMutation() }}> 添加转存 </Button> </ButtonGroup> <ButtonGroup> <Button variant="danger" onClick={() => { layer.confirm('确定删除?该操作无法撤销!!!', { icon: 3 }, function (index) { setDownloads([]); layer.close(index); }, function () { }); }}> 清空 </Button> <Button variant="primary" onClick={handleClose}> 关闭 </Button> </ButtonGroup> </Modal.Footer> </Modal> </div > ); } } App = () => { const [open, setOpen] = useState(false); const [reload, setReload] = useState(false); const [response, error, loading, fetchDataByPage] = getFiles(); const { folder } = useParams(); const location = useLocation(); const [path, setPath] = useState(location.pathname); const [page, setPage] = useState(1); const [query, setQuery] = useState({ "path": path, "password": "", "page": page, "per_page": 0, "refresh": true }); const setting = STORE.getState().settings; //const queryClient = useQueryClient() // Queries //const { data, error, isLoading, refetch } = useQuery({ // queryKey: ['test'], queryFn: () => axios.get("") //}) const { data: fileData, mutateAsync: downloadMutation } = useMutation({ mutationKey: ["get-download"], mutationFn: async (fileinfo) => { showLoading(); var host = setting.alist_host; if (!host.endsWith("/")) { host = host + '/' } var url = host + 'api/fs/get'; return await axios.post(url, fileinfo, { headers: { 'Authorization': setting.alist_token, 'Content-Type': 'application/json' }, }) }, onSuccess: async (data, variables, context) => { hideLoading(); }, onError: () => { hideLoading(); } }) useEffect(() => { if (fileData) { emitEvent("addDownload", fileData) } }, [fileData]); const columns = [ { title: "文件名称", dataIndex: "name" }, { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(row.size)) }, { title: "日期", dataIndex: "created", render: (row) => (formatDate(row.created)) }, { title: "操作", dataIndex: "name", render: (row) => ( row.is_dir ?<Nav.Link as={Link} className="nav-link text-dark" to={path + row.name + '/'} target="_blank" > <Icon icon="open-in-new" size="6" className="me-2" /> </Nav.Link> : <Icon icon="download-outline" size="6" className="me-2" onClick={async () => { let data = { "path": decodeURIComponent(path + row.name), "password": "" } await downloadMutation(data); }} /> ), }, ]; useEffect(() => { if (!setting.alist_token || setting.alist_token.length< 5) { layer.alert("请先正确配置Alsit的令牌", { icon: 5 }); return } fetchDataByPage(setting, query); return () => { } }, [reload, query]); const forceUpdate = () => { setReload((pre) => !pre); }; return ( <div> <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light"> <label className="fs-3">文件列表</label> <ButtonToolbar aria-label="功能区" className="bg-teal rounded" > <ButtonGroup className="bg-teal"> <IconButton onClick={() => { emitEvent("test", { a: 'b' }) }} text="刷新" className="bg-teal border-0" icon="reload" iconClassName="me-1 text-white" iconSize="6" /> </ButtonGroup> </ButtonToolbar> </div> <Container fluid className="p-2"> {error && ( <div className="text-center text-danger"> {error} </div> )} {(loading) && ( <div className="text-center text-success"> 正在努力加载中...... </div> )} {response && ( <DataTable data={response.data.content ? response.data.content : []} columns={columns} /> )} </Container> </div> ); }; const container = document.getElementById("root"); const root = ReactDOM.createRoot(container); root.render( <QueryClientProvider client={queryClient}> <HashRouter> <Route path="/:path?"> <Layout> <Switch> <Route path="/" exact component={App} /> <Route path="/:folder?" component={App} /> </Switch> </Layout> </Route> </HashRouter> </QueryClientProvider> ); $(document).ready(function () { $(window).scroll(function () { if ($(this).scrollTop() > 50) { $("#back-to-top").fadeIn(); } else { $("#back-to-top").fadeOut(); } }); // scroll body to 0px on click $("#back-to-top").click(function () { $("body,html").animate( { scrollTop: 0, }, 400 ); return false; }); }); </script></body></html>