zxlwq commited on
Commit
eb288b8
·
verified ·
1 Parent(s): ea51004

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +2052 -0
templates/index.html ADDED
@@ -0,0 +1,2052 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/bootstrap@5.3.3/dist/css/bootstrap.min.css" />
9
+ <link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet" />
10
+ <link href="https://cdn.jsdelivr.net/npm/roboto-font@0.1.0/css/fonts.min.css" rel="stylesheet" />
11
+ <link href="//unpkg.com/layui@2.9.21/dist/css/layui.css" rel="stylesheet">
12
+ </head>
13
+
14
+ <body style="height:100%;">
15
+ <div id="root"></div>
16
+ <a id="back-to-top" href="#" class="btn btn-success btn-lg back-to-top" role="button"><i
17
+ class="mdi mdi-arrow-up"></i></a>
18
+ <script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
19
+ <script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
20
+ <script src="https://www.unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
22
+ <script src="https://www.unpkg.com/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script>
23
+ <script src="https://unpkg.com/react-bootstrap@2.10.7/dist/react-bootstrap.min.js"></script>
24
+ <script src="https://unpkg.com/redux@4.2.1/dist/redux.min.js"></script>
25
+ <script src="https://unpkg.com/react-router-dom@5.3.0/umd/react-router-dom.min.js"></script>
26
+ <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
27
+ <script src="https://unpkg.com/regenerator-runtime@0.14.1/runtime.js"></script>
28
+ <script src="https://cdn.bootcdn.net/ajax/libs/babel-polyfill/7.12.1/polyfill.min.js"></script>
29
+ <script src="https://unpkg.com/axios@1.7.9/dist/axios.min.js"></script>
30
+ <script src="//unpkg.com/layui@2.9.21/dist/layui.js"></script>
31
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/8.17.1/ajv7.min.js"></script>
32
+ <script src="https://unpkg.com/@tanstack/react-query@4.36.1/build/umd/index.production.js"></script>
33
+ <script src="https://cdn.jsdelivr.net/npm/mitt@3.0.1/dist/mitt.umd.min.js"></script>
34
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
35
+ <script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script>
36
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css" />
37
+
38
+ <style>
39
+ .bi {
40
+ display: inline-block;
41
+ width: 1rem;
42
+ height: 1rem;
43
+ }
44
+
45
+ /*
46
+ * Sidebar
47
+ */
48
+ @media (min-width: 768px) {
49
+ .sidebar {
50
+ width: 100%;
51
+ }
52
+
53
+ .sidebar .offcanvas-lg {
54
+ position: -webkit-sticky;
55
+ position: sticky;
56
+ top: 48px;
57
+ }
58
+
59
+ .navbar-search {
60
+ display: block;
61
+ }
62
+ }
63
+
64
+ .sidebar .nav-link {
65
+ font-size: 0.875rem;
66
+ font-weight: 500;
67
+ }
68
+
69
+ .sidebar .nav-link.active {
70
+ color: #2470dc;
71
+ }
72
+
73
+ .sidebar-heading {
74
+ font-size: 0.75rem;
75
+ }
76
+
77
+ /*
78
+ * Navbar
79
+ */
80
+ .navbar {
81
+ background-color: teal;
82
+ }
83
+
84
+ .navbar-brand {
85
+ padding-top: 0.75rem;
86
+ padding-bottom: 0.75rem;
87
+ /* background-color: rgba(0, 0, 0, .25);
88
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); */
89
+ }
90
+
91
+ .navbar .form-control {
92
+ padding: 0.75rem 1rem;
93
+ }
94
+
95
+ .bd-placeholder-img {
96
+ font-size: 1.125rem;
97
+ text-anchor: middle;
98
+ -webkit-user-select: none;
99
+ -moz-user-select: none;
100
+ user-select: none;
101
+ }
102
+
103
+ @media (min-width: 768px) {
104
+ .bd-placeholder-img-lg {
105
+ font-size: 3.5rem;
106
+ }
107
+ }
108
+
109
+ .b-example-divider {
110
+ width: 100%;
111
+ height: 3rem;
112
+ background-color: rgba(0, 0, 0, 0.1);
113
+ border: solid rgba(0, 0, 0, 0.15);
114
+ border-width: 1px 0;
115
+ box-shadow: inset 0 0.5em 1.5em rgba(0, 0, 0, 0.1),
116
+ inset 0 0.125em 0.5em rgba(0, 0, 0, 0.15);
117
+ }
118
+
119
+ .b-example-vr {
120
+ flex-shrink: 0;
121
+ width: 1.5rem;
122
+ height: 100vh;
123
+ }
124
+
125
+ .bi {
126
+ vertical-align: -0.125em;
127
+ fill: currentColor;
128
+ }
129
+
130
+ .nav-scroller {
131
+ position: relative;
132
+ z-index: 2;
133
+ height: 2.75rem;
134
+ overflow-y: hidden;
135
+ }
136
+
137
+ .nav-scroller .nav {
138
+ display: flex;
139
+ flex-wrap: nowrap;
140
+ padding-bottom: 1rem;
141
+ margin-top: -1px;
142
+ overflow-x: auto;
143
+ text-align: center;
144
+ white-space: nowrap;
145
+ -webkit-overflow-scrolling: touch;
146
+ }
147
+
148
+ .btn-bd-primary {
149
+ --bd-violet-bg: #712cf9;
150
+ --bd-violet-rgb: 112.520718, 44.062154, 249.437846;
151
+
152
+ --bs-btn-font-weight: 600;
153
+ --bs-btn-color: var(--bs-white);
154
+ --bs-btn-bg: var(--bd-violet-bg);
155
+ --bs-btn-border-color: var(--bd-violet-bg);
156
+ --bs-btn-hover-color: var(--bs-white);
157
+ --bs-btn-hover-bg: #6528e0;
158
+ --bs-btn-hover-border-color: #6528e0;
159
+ --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
160
+ --bs-btn-active-color: var(--bs-btn-hover-color);
161
+ --bs-btn-active-bg: #5a23c8;
162
+ --bs-btn-active-border-color: #5a23c8;
163
+ }
164
+
165
+ .bd-mode-toggle {
166
+ z-index: 1500;
167
+ }
168
+
169
+ .bd-mode-toggle .dropdown-menu .active .bi {
170
+ display: block !important;
171
+ }
172
+
173
+ .back-to-top {
174
+ position: fixed;
175
+ bottom: 25px;
176
+ right: 25px;
177
+ display: none;
178
+ }
179
+
180
+ .leftsidebar {
181
+ height: 100%;
182
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
183
+ }
184
+
185
+ @media (min-width: 768px) {
186
+ .leftsidebar {
187
+ min-width: 15%;
188
+ }
189
+ }
190
+
191
+ @media (max-width: 768px) {
192
+ .leftsidebar {
193
+ max-width: 50%;
194
+ }
195
+ }
196
+
197
+ .bg-teal {
198
+ background-color: teal;
199
+ }
200
+ </style>
201
+
202
+ <script type="text/babel" data-presets="react" data-type="module">
203
+ window.layer = layui.layer;
204
+ //事件监听开始 通过修改localstorage实现跨页面事件监听
205
+ const emitter = mitt();
206
+ // 监听 localStorage 变化
207
+ window.addEventListener("storage", (event) => {
208
+ if (event.key === "event") {
209
+ const { type, data } = JSON.parse(event.newValue);
210
+ emitter.emit(type, data);
211
+ }
212
+ });
213
+ // 封装 emit 方法
214
+ const emitEvent = (type, data) => {
215
+ // 触发本地事件
216
+ emitter.emit(type, data);
217
+ const randomString = Math.random()
218
+ .toString(36)
219
+ .substring(2, 10); // 生成一个随机字符串确保event每次的值不一样,如果一样会不触发事件
220
+ const identity = `${ Date.now() }-${ randomString }`;
221
+ // 存储到 localStorage,以便其他页面能够接收到
222
+ localStorage.setItem(
223
+ "event",
224
+ JSON.stringify({ type, data, identity })
225
+ );
226
+ };
227
+
228
+ // 封装 on 方法
229
+ const onEvent = (type, callback) => {
230
+ emitter.on(type, callback);
231
+ };
232
+
233
+ // 封装 off 方法
234
+ const offEvent = (type, callback) => {
235
+ emitter.off(type, callback);
236
+ };
237
+ //事件监听结束
238
+
239
+
240
+ Fancybox.bind("[data-fancybox]", {
241
+ Toolbar: {
242
+ display: {
243
+ right: ["slideshow", "download", "thumbs", "close"],
244
+ },
245
+ },
246
+ Images: {
247
+ initialSize: "fit",
248
+ }
249
+ });
250
+
251
+
252
+ var settingStorage = localforage.createInstance({
253
+ name: "setting",
254
+ driver: localforage.LOCALSTORAGE
255
+ });
256
+ // settingStorage.setItem("category", { name: 'test', id: 1 });
257
+ // settingStorage.getItem('category').then(function (value) {
258
+ // console.log(value);
259
+ // }).catch(function (err) {
260
+ // console.log(err);
261
+ // });
262
+ // settingStorage.getItem('category', function (err, value) {
263
+ // console.log(value.name);
264
+ // });
265
+
266
+
267
+ const { createStore, combineReducers } = Redux;
268
+ // 从 localStorage 加载初始状态
269
+ const loadStateFromLocalStorage = () => {
270
+ try {
271
+ const serializedState = localStorage.getItem('settings');
272
+ if (serializedState === null) {
273
+ return {}; // 默认值
274
+ }
275
+ return JSON.parse(serializedState);
276
+ } catch (e) {
277
+ console.error("Could not load state from localStorage:", e);
278
+ return {}; // 默认值
279
+ }
280
+ };
281
+ // 保存状态到 localStorage
282
+ const saveStateToLocalStorage = (state) => {
283
+ try {
284
+ const serializedState = JSON.stringify(state);
285
+ localStorage.setItem('settings', serializedState);
286
+ } catch (e) {
287
+ console.error("Could not save state to localStorage:", e);
288
+ }
289
+ };
290
+
291
+ // 定义初始状态
292
+ const initialSettingsState = loadStateFromLocalStorage();
293
+ // 创建 settings Reducer
294
+ function settingsReducer(state = initialSettingsState, action) {
295
+ switch (action.type) {
296
+ case 'SAVE_SETTING':
297
+ return { ...state, ...action.payload };
298
+ default:
299
+ return state;
300
+ }
301
+ }
302
+
303
+ // 合并 Reducer(如果有多个)
304
+ const rootReducer = combineReducers({
305
+ settings: settingsReducer,
306
+ });
307
+
308
+ // 创建 Redux Store
309
+ const STORE = createStore(rootReducer);
310
+
311
+ // 订阅 Store 的变化,并将状态保存到 localStorage
312
+ STORE.subscribe(() => {
313
+ saveStateToLocalStorage(STORE.getState().settings);
314
+ });
315
+
316
+
317
+ //数据校验
318
+ // var ajv = new ajv7.default()
319
+ // const schema = {
320
+ // type: "object",
321
+ // properties: {
322
+ // foo: { type: "integer" },
323
+ // bar: { type: "string" }
324
+ // },
325
+ // required: ["foo"],
326
+ // additionalProperties: false
327
+ // }
328
+
329
+ // const validate = ajv.compile(schema)
330
+
331
+ // const data = {
332
+ // foo: 1,
333
+ // bar: "abc"
334
+ // }
335
+ // const valid = validate(data)
336
+ // if (!valid) console.log(validate.errors)
337
+
338
+
339
+ const bytesToSize = (bytes) => {
340
+ if (bytes === 0) return '0 B';
341
+ var k = 1024;
342
+ sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
343
+ i = Math.floor(Math.log(bytes) / Math.log(k));
344
+ return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
345
+ };
346
+ const formatDate = (date) => {
347
+ var d = new Date(date);
348
+ var year = d.getFullYear();
349
+ var month = d.getMonth() + 1;
350
+ var day = d.getDate() < 10 ? '0' + d.getDate() : '' + d.getDate();
351
+ var hour = d.getHours();
352
+ var minutes = d.getMinutes();
353
+ var seconds = d.getSeconds();
354
+ return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds;
355
+ };
356
+
357
+ let layerLoading = null;
358
+
359
+ const showLoading = () => {
360
+ const loadindex = layer.load(1);
361
+ layerLoading = loadindex;
362
+ }
363
+
364
+ const hideLoading = () => {
365
+ layer.close(layerLoading);
366
+ }
367
+
368
+
369
+ const { useState, useEffect, useRef } = React;
370
+ const { HashRouter, Route, Link, Switch, useLocation, useParams } = ReactRouterDOM;
371
+ const { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider } = ReactQuery;
372
+ const queryClient = new QueryClient()
373
+ const {
374
+ Alert,
375
+ Badge,
376
+ Button,
377
+ ButtonGroup,
378
+ ButtonToolbar,
379
+ Card,
380
+ Collapse,
381
+ Col,
382
+ Container,
383
+ Dropdown,
384
+ Form,
385
+ Image,
386
+ InputGroup,
387
+ ListGroup,
388
+ Modal,
389
+ Nav,
390
+ Navbar,
391
+ NavDropdown,
392
+ Offcanvas,
393
+ Pagination,
394
+ Row,
395
+ Table,
396
+ } = ReactBootstrap;
397
+ //注意修改js文件后需要直接访问js以更新浏览器缓存
398
+
399
+ // 表格组件
400
+ const DataTable = ({ data, columns }) => {
401
+ return (
402
+ <Table responsive bordered>
403
+ <thead>
404
+ <tr className="text-center">
405
+ { columns.map((column, index) => (
406
+ <th key={ index }>{ column.title }</th>
407
+ )) }
408
+ </tr>
409
+ </thead>
410
+ <tbody>
411
+ { data.map((row, rowIndex) => (
412
+ <tr key={ rowIndex } className="text-center">
413
+ { columns.map((column, colIndex) => (
414
+ <td key={ colIndex }>
415
+ {/* 调用渲染方法,如果没有定义,则直接显示数据 */ }
416
+ { column.render
417
+ ? column.render(row)
418
+ : row[column.dataIndex] }
419
+ </td>
420
+ )) }
421
+ </tr>
422
+ )) }
423
+ </tbody>
424
+ </Table>
425
+ );
426
+ };
427
+ //分页组件
428
+ const Paginate = (props) => {
429
+ const page = props.page;
430
+ const pageCount = Math.ceil(
431
+ props.totalCount / props.itemsPerPage
432
+ );
433
+
434
+ const SelectItems = () => {
435
+ const pageNumbers = Array.from(
436
+ { length: pageCount },
437
+ (_, i) => i + 1
438
+ );
439
+ return (
440
+ <select
441
+ className="page-link border-0 h-100 py-0"
442
+ style={ { width: "auto" } }
443
+ onChange={ (e) => {
444
+ props.onClick(parseInt(e.target.value));
445
+ } }
446
+ >
447
+ { pageNumbers.map((number) => {
448
+ const selected = number === page ? true : false;
449
+ return (
450
+ <option
451
+ key={ number }
452
+ value={ number }
453
+ selected={ selected }
454
+ >
455
+ { number }
456
+ </option>
457
+ );
458
+ }) }
459
+ </select>
460
+ );
461
+ };
462
+ return (
463
+ <div className="d-flex justify-content-center align-items-baseline">
464
+
465
+ <Pagination>
466
+ { pageCount > 1 && page > 1 && (
467
+ <Pagination.First
468
+ onClick={ () => {
469
+ props.onClick(1);
470
+ } }
471
+ />
472
+ ) }
473
+ { pageCount > 1 && page > 1 && (
474
+ <Pagination.Prev
475
+ onClick={ () => {
476
+ props.onClick(page - 1);
477
+ } }
478
+ />
479
+ ) }
480
+ <Pagination.Item linkClassName="p-0 h-100 d-inline-block">
481
+ <SelectItems />
482
+ </Pagination.Item>
483
+ <Pagination.Item>
484
+ <span className="text-info">
485
+ { page }/{ pageCount }
486
+ </span>
487
+ </Pagination.Item>
488
+ { pageCount > 1 && page < pageCount && (
489
+ <Pagination.Next
490
+ onClick={ () => {
491
+ props.onClick(page + 1);
492
+ } }
493
+ />
494
+ ) }
495
+ { pageCount > 1 && page < pageCount && (
496
+ <Pagination.Last
497
+ onClick={ () => {
498
+ props.onClick(pageCount);
499
+ } }
500
+ />
501
+ ) }
502
+ </Pagination>
503
+ </div>
504
+ );
505
+ };
506
+ //图标组件
507
+ const Icon = (props) => {
508
+ return (
509
+ <span
510
+ onClick={ props.onClick }
511
+ className={ `mdi mdi-${ props.icon } fs-${ props.size } ${ props.className }` }
512
+ ></span>
513
+ );
514
+ };
515
+ //按钮图标组件
516
+ const IconButton = (props) => {
517
+ return (
518
+ <Button
519
+ variant="success"
520
+ onClick={ props.onClick }
521
+ className={ props.className }
522
+ >
523
+ <span
524
+ className={ `mdi mdi-${ props.icon } fs-${ props.iconSize } ${ props.iconClassName }` }
525
+ ></span>
526
+ { props.text }
527
+ </Button>
528
+ );
529
+ };
530
+ //video组件
531
+ const createCaption = (video) => {
532
+ var html = video.code + ' ' + video.title;
533
+ //html+="<a href='/tags'>test</a>";
534
+ video.tags.map(tag => {
535
+ html += tag;
536
+ })
537
+ return html;
538
+ };
539
+
540
+
541
+ const AsyncImage = (props) => {
542
+ const [loadedSrc, setLoadedSrc] = React.useState(null);
543
+ React.useEffect(() => {
544
+ setLoadedSrc(null);
545
+ if (props.src) {
546
+ const handleLoad = () => {
547
+ setLoadedSrc(props.src);
548
+ };
549
+ const image = document.createElement("img");
550
+ image.addEventListener('load', handleLoad);
551
+ image.src = props.src;
552
+ return () => {
553
+ image.removeEventListener('load', handleLoad);
554
+ };
555
+ }
556
+ }, [props.src]);
557
+ if (loadedSrc === props.src) {
558
+ return (
559
+ <img { ...props } />
560
+ );
561
+ }
562
+ return <img { ...props } src="https://placehold.co/600x400?text=Loading" />;
563
+ };
564
+
565
+ //设置框
566
+ const SettingModal = (props) => {
567
+ const settings = [
568
+ { "thunderx": [{ "label": "登陆令牌", "key": "secret_token", "show": false }, { "label": "代理地址", "key": "cf_proxy", "show": true }] },
569
+ { "github": [{ "label": "Actions地址", "key": "github_host", "show": true }, { "label": "Github令牌", "key": "github_token", "show": false }] },
570
+ { "directus": [{ "label": "Directus地址", "key": "directus_host", "show": true }, { "label": "Directus令牌", "key": "directus_token", "show": false }] }
571
+ ]
572
+ // const settings = [
573
+ // { "thunderx": [{ "label": "代理地址", "key": "cf_proxy", "show": true }, { "label": "登陆令牌", "key": "secret_token", "show": false }] },
574
+ // { "github": [{ "label": "Actions地址", "key": "github_host", "show": true }, { "label": "Github令牌", "key": "github_token", "show": false }] },
575
+ // { "directus": [{ "label": "Directus地址", "key": "directus_host", "show": true }, { "label": "Directus令牌", "key": "directus_token", "show": false }] }
576
+ // ]
577
+ const [setting, setSetting] = useState({});
578
+ // useEffect(() => {
579
+ // localStorage.setItem('settings', JSON.stringify(setting));
580
+ // }, [setting]);
581
+
582
+ const loadSetting = () => {
583
+ const storedSettings = STORE.getState().settings;
584
+ if (storedSettings) {
585
+ setSetting(storedSettings);
586
+ }
587
+ }
588
+ const saveSetting = () => {
589
+ STORE.dispatch({ type: 'SAVE_SETTING', payload: setting })
590
+ //localStorage.setItem('settings', JSON.stringify(setting));
591
+ }
592
+ return (
593
+ <Modal show={ props.show } onHide={ props.onHide } onShow={ loadSetting }>
594
+ <Modal.Header closeButton onHide={ props.onHide }>
595
+ <Modal.Title>设置</Modal.Title>
596
+ </Modal.Header>
597
+ <Modal.Body>
598
+ <Form>
599
+ <ListGroup>
600
+ { settings.map((value, index) => {
601
+ const key = Object.keys(value)[0];
602
+ const items = value[key];
603
+ return (<ListGroup.Item>
604
+ { items.map((setting_item) => {
605
+ return (
606
+ <Form.Group as={ Row } className="mb-3">
607
+ <Form.Label column sm="3">
608
+ { setting_item.label }
609
+ </Form.Label>
610
+ <Col sm="9">
611
+ <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 }) } } />
612
+ </Col>
613
+ </Form.Group>
614
+ )
615
+ }) }
616
+ </ListGroup.Item>)
617
+ }) }
618
+ </ListGroup>
619
+ </Form>
620
+ </Modal.Body>
621
+ <Modal.Footer className="justify-content-between">
622
+ <Button
623
+ variant="secondary"
624
+ onClick={ () => {
625
+ props.onHide();
626
+ } }
627
+ >
628
+ 关闭
629
+ </Button>
630
+ <Button
631
+ variant="primary"
632
+ onClick={ () => {
633
+ saveSetting();
634
+ props.onHide();
635
+ //props.onSave();
636
+ } }
637
+ >
638
+ 保存
639
+ </Button>
640
+ </Modal.Footer>
641
+ </Modal>
642
+ );
643
+ };
644
+
645
+ //axios封装开始
646
+ const useAxios = () => {
647
+ const [response, setResponse] = useState(null);
648
+ const [error, setError] = useState("");
649
+ const [loading, setLoading] = useState(false);
650
+
651
+ // Create an Axios instance
652
+ const axiosInstance = axios.create({});
653
+
654
+ // Set up request and response interceptors
655
+ axiosInstance.interceptors.request.use(
656
+ (config) => {
657
+ // Log or modify request here
658
+ //console.log("Sending request to:", config.url);
659
+ return config;
660
+ },
661
+ (error) => {
662
+ // Handle request error here
663
+ return Promise.reject(error);
664
+ }
665
+ );
666
+
667
+ axiosInstance.interceptors.response.use(
668
+ (response) => {
669
+ // Log or modify response here
670
+ //console.log("Received response from:", response.config.url);
671
+ return response;
672
+ },
673
+ (error) => {
674
+ // Handle response error here
675
+ return Promise.reject(error);
676
+ }
677
+ );
678
+
679
+ useEffect(() => {
680
+ const source = axios.CancelToken.source();
681
+ return () => {
682
+ // Cancel the request when the component unmounts
683
+ source.cancel(
684
+ "组件被卸载: 请求取消."
685
+ );
686
+ };
687
+ }, []);
688
+
689
+ // Making the API call with cancellation support
690
+ const fetchData = async ({ url, method, data, headers }) => {
691
+ setLoading(true);
692
+ try {
693
+ const result = await axiosInstance({
694
+ url,
695
+ method,
696
+ headers: headers ? headers : {},
697
+ data:
698
+ method.toLowerCase() === "get"
699
+ ? undefined
700
+ : data,
701
+ params:
702
+ method.toLowerCase() === "get"
703
+ ? data
704
+ : undefined,
705
+ cancelToken: axios.CancelToken.source().token,
706
+ });
707
+ setResponse(result.data);
708
+ } catch (error) {
709
+ if (axios.isCancel(error)) {
710
+ console.log("Request cancelled", error.message);
711
+ } else {
712
+ setError(
713
+ error.response
714
+ ? error.response.data
715
+ : error.message
716
+ );
717
+ }
718
+ } finally {
719
+ setLoading(false);
720
+ }
721
+ };
722
+ return [response, error, loading, fetchData];
723
+ };
724
+ //axios封闭结束
725
+
726
+ //分页hooks
727
+ const usePagination = () => {
728
+ const [pagination, setPagination] = useState({
729
+ pageSize: 36,
730
+ pageIndex: 1,
731
+ });
732
+ const { pageSize, pageIndex } = pagination;
733
+
734
+
735
+ return {
736
+ limit: pageSize,
737
+ onPaginationChange: setPagination,
738
+ pagination,
739
+ skip: pageSize * (pageIndex - 1),
740
+ };
741
+ }
742
+ //分页结束
743
+
744
+
745
+ //API定义开始
746
+ const getFiles = () => {
747
+ const [response, error, loading, fetchData] = useAxios();
748
+
749
+ const fetchDataByPage = async (setting, query) => {
750
+ fetchData({
751
+ url: '/files',
752
+ method: "POST",
753
+ data: query,
754
+ headers: {
755
+ 'Authorization': setting.secret_token,
756
+ 'Content-Type': 'application/json'
757
+ },
758
+ });
759
+ };
760
+ return [response, error, loading, fetchDataByPage];
761
+ };
762
+
763
+ const paginateLinksGet = async (page_token, keyword) => {
764
+ //const url = `/files?size=${limit}&page=${page}&kw=${keyword}`;
765
+ console.log("======fuck========");
766
+ const url = `/files`;
767
+
768
+ const { data } = await axios.post(url,
769
+ {
770
+ "size": 100,
771
+ "parent_id": "",
772
+ "next_page_token": page_token,
773
+ "additional_filters": {},
774
+ "additionalProp1": {}
775
+ },
776
+ {
777
+ headers: {
778
+ 'Authorization': setting.secret_token,
779
+ 'Content-Type': 'application/json'
780
+ },
781
+ })
782
+ return data
783
+ }
784
+
785
+ const paginateFavoritesGet = async (limit, page, keyword) => {
786
+ const url = `/favorites?size=${ limit }&page=${ page }&kw=${ keyword }`;
787
+ const { data } = await axios.get(url)
788
+ return data
789
+ }
790
+
791
+
792
+ const paginateTagLinksGet = async (limit, page, tag) => {
793
+ const url = `/tags?size=${ limit }&page=${ page }&tag=${ tag }`;
794
+ const { data } = await axios.get(url)
795
+ return data
796
+ }
797
+
798
+ const paginateTasksGet = async (limit, skip) => {
799
+ const setting = STORE.getState().settings;
800
+ const url = setting.directus_host + `items/task?limit=${ limit }&offset=${ skip }&meta[]=filter_count&sort[]=-id`;
801
+ const { data } = await axios.get(url, { headers: { Authorization: "Bearer " + setting.directus_token } })
802
+ return data
803
+ }
804
+
805
+
806
+ //API定义结束
807
+
808
+ const Layout = ({ children }) => {
809
+ useEffect(() => {
810
+ // 组件挂载时执行的代码(相当于 componentDidMount)
811
+ }, []); // 空数组表示只在挂载和卸载时执行
812
+
813
+ const [showSideBar, setShowSideBar] = useState(false);
814
+ const handleSidebarClose = () => setShowSideBar(false);
815
+ const handleSidebarShow = () => setShowSideBar(true);
816
+ const toggleSidebarShow = () => {
817
+ setShowSideBar(!showSideBar);
818
+ };
819
+
820
+ const [setting, setSetting] = useState(false);
821
+
822
+ return (
823
+ <div>
824
+ <header className="sticky-top">
825
+ <Navbar expand="md">
826
+ <Container fluid>
827
+ <div>
828
+ <Navbar.Toggle
829
+ className="shadow-none border-0"
830
+ onClick={ handleSidebarShow }
831
+ children={
832
+ <Icon
833
+ icon="menu"
834
+ size="3"
835
+ className="text-white"
836
+ />
837
+ }
838
+ />
839
+ <Navbar.Brand
840
+ as={ Link }
841
+ to="/"
842
+ className="text-white"
843
+ >
844
+ 文件列表
845
+ </Navbar.Brand>
846
+ </div>
847
+ <div className="d-flex">
848
+ <Offline />
849
+ <Tasks />
850
+ <LocalTasks />
851
+ <Button
852
+ style={ {
853
+ backgroundColor: "transparent",
854
+ } }
855
+ className="nav-link btn"
856
+ onClick={ () => {
857
+ setSetting(true)
858
+ } }
859
+ children={
860
+ <Icon
861
+ icon="dots-vertical"
862
+ size="3"
863
+ className="text-white"
864
+ />
865
+ }
866
+ ></Button>
867
+ <SettingModal
868
+ show={ setting }
869
+ onHide={ () => {
870
+ setSetting(false);
871
+ } }
872
+ />
873
+ </div>
874
+ </Container>
875
+ </Navbar>
876
+ </header>
877
+ <Container fluid>
878
+ <Row style={ { minHeight: "100vh" } }>
879
+ <Col
880
+ md="2"
881
+ lg="2"
882
+ xl="2"
883
+ className="ps-0 d-none d-md-block"
884
+ >
885
+ <Offcanvas
886
+ className="leftsidebar h-100 bg-light"
887
+ show={ showSideBar }
888
+ onHide={ handleSidebarClose }
889
+ placement="start"
890
+ responsive="md"
891
+ >
892
+ <Offcanvas.Header
893
+ className="py-2 border-bottom"
894
+ closeButton
895
+ >
896
+ <Offcanvas.Title>
897
+ 离线任务
898
+ </Offcanvas.Title>
899
+ </Offcanvas.Header>
900
+ <Offcanvas.Body className="p-0">
901
+ <Container fluid className="p-0">
902
+ <Nav
903
+ activeKey="1"
904
+ className="flex-column"
905
+ >
906
+ <Nav.Link
907
+ as={ Link }
908
+ className="nav-link text-dark"
909
+ to="/"
910
+ onClick={
911
+ handleSidebarClose
912
+ }
913
+ >
914
+ <Icon
915
+ icon="file"
916
+ size="6"
917
+ className="me-2"
918
+ />
919
+ 文件列表
920
+ </Nav.Link>
921
+ <Nav.Link
922
+ as={ Link }
923
+ className="nav-link text-dark"
924
+ to="/shares"
925
+ onClick={
926
+ handleSidebarClose
927
+ }
928
+ >
929
+ <Icon
930
+ icon="share-variant"
931
+ size="6"
932
+ className="me-2"
933
+ />
934
+ 分享列表
935
+ </Nav.Link>
936
+ </Nav>
937
+ </Container>
938
+ </Offcanvas.Body>
939
+ </Offcanvas>
940
+ </Col>
941
+
942
+ <Col xs="12" sm="12" md="10" lg="10" xl="10">
943
+ <main>
944
+ <Container fluid className="pt-2 px-0 pb-5">
945
+ { children }
946
+ </Container>
947
+ </main>
948
+ </Col>
949
+ </Row>
950
+ </Container>
951
+ </div>
952
+ );
953
+ };
954
+ const Home = () => {
955
+ const location = useLocation();
956
+ const { id } = useParams();
957
+ return (
958
+ <div>
959
+ <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
960
+ <label className="fs-3">Home</label>
961
+ <ButtonToolbar
962
+ aria-label="文件列表"
963
+ className="bg-teal rounded"
964
+ >
965
+ <ButtonGroup className="bg-teal">
966
+ <IconButton
967
+ onClick={ () => {
968
+ alert("test")
969
+ } }
970
+ text="刷新"
971
+ className="bg-teal border-0"
972
+ icon="reload"
973
+ iconClassName="me-1 text-white"
974
+ iconSize="6"
975
+ />
976
+ <IconButton
977
+ onClick={ () => {
978
+ alert("hello");
979
+ } }
980
+ text="删除"
981
+ className="bg-teal border-0"
982
+ icon="delete-outline"
983
+ iconClassName="me-1 text-white"
984
+ iconSize="6"
985
+ />
986
+ </ButtonGroup>
987
+ </ButtonToolbar>
988
+ </div>
989
+ <Container fluid className="p-2"></Container>
990
+ </div>
991
+ );
992
+ };
993
+
994
+ const Videos = () => {
995
+ const [reload, setReload] = useState(false);
996
+ const [pageToken, setPageToken] = useState('');
997
+ const [keyword, setKeyword] = useState("")
998
+ const [search, setSearch] = useState("")
999
+ const [videos, setVideos] = useState([])
1000
+ const setting = STORE.getState().settings;
1001
+ const { id } = useParams();
1002
+ const columns = [
1003
+ { title: "文件名称", dataIndex: "name" },
1004
+ { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(Number(row.size))) },
1005
+ { title: "日期", dataIndex: "created_time", render: (row) => (formatDate(row.created_time)) },
1006
+ {
1007
+ title: "操作",
1008
+ dataIndex: "name",
1009
+ render: (row) => (
1010
+ <div className="d-flex justify-content-center">
1011
+ {
1012
+ row.kind == "drive#folder" ? (<Nav.Link
1013
+ as={ Link }
1014
+ className="nav-link text-dark"
1015
+ to={ `/videos/${ row.id }` }
1016
+ target="_blank"
1017
+ >
1018
+ <Icon
1019
+ icon="open-in-new"
1020
+ size="6"
1021
+ className="me-2"
1022
+ />
1023
+ </Nav.Link>) :
1024
+ (<Icon
1025
+ icon="download-outline"
1026
+ size="6"
1027
+ className="me-2"
1028
+ onClick={ async () => {
1029
+ let data = { "id": row.id }
1030
+ await downloadMutation(data);
1031
+ } }
1032
+ />)
1033
+ }
1034
+
1035
+ <Icon
1036
+ icon="delete"
1037
+ size="6"
1038
+ className="me-2"
1039
+ onClick={ async () => {
1040
+ let data = { "id": row.id }
1041
+ await deleteMutation(data);
1042
+ } }
1043
+ />
1044
+
1045
+ <Icon
1046
+ icon="share-variant"
1047
+ size="6"
1048
+ className="me-2"
1049
+ onClick={ async () => {
1050
+ let data = { "id": row.id }
1051
+ await sharedMutation(data);
1052
+ } }
1053
+ />
1054
+ </div>
1055
+ ),
1056
+ },
1057
+ ];
1058
+ const authorization = 'Bearer ' + setting.secret_token;
1059
+ const { data: fileData, mutateAsync: downloadMutation } = useMutation({
1060
+ mutationKey: ["get-download"],
1061
+ mutationFn: async (fileinfo) => {
1062
+ showLoading();
1063
+ var url = '/files/' + fileinfo.id;
1064
+ return await axios.get(url, {
1065
+ headers: {
1066
+ 'Authorization': authorization,
1067
+ 'Content-Type': 'application/json'
1068
+ },
1069
+ })
1070
+ },
1071
+ onSuccess: async (data, variables, context) => {
1072
+ hideLoading();
1073
+ },
1074
+ onError: () => {
1075
+ hideLoading();
1076
+ }
1077
+ })
1078
+ const { data: shareData, mutateAsync: sharedMutation } = useMutation({
1079
+ mutationKey: ["create_share"],
1080
+ mutationFn: async (fileinfo) => {
1081
+ showLoading();
1082
+ var url = '/file_batch_share?need_password=false&expiration_days=-1';
1083
+ return await axios.post(url, [fileinfo.id], {
1084
+ headers: {
1085
+ 'Authorization': authorization,
1086
+ 'Content-Type': 'application/json'
1087
+ },
1088
+ })
1089
+ },
1090
+ onSuccess: async (data, variables, context) => {
1091
+ hideLoading();
1092
+ },
1093
+ onError: () => {
1094
+ hideLoading();
1095
+ }
1096
+ })
1097
+ const { data: deleteData, mutateAsync: deleteMutation } = useMutation({
1098
+ mutationKey: ["delete_file"],
1099
+ mutationFn: async (fileinfo) => {
1100
+ showLoading();
1101
+ var url = '/files/' + fileinfo.id;
1102
+ return await axios.delete(url, {
1103
+ headers: {
1104
+ 'Authorization': authorization,
1105
+ 'Content-Type': 'application/json'
1106
+ },
1107
+ })
1108
+ },
1109
+ onSuccess: async (data, variables, context) => {
1110
+ hideLoading();
1111
+ forceUpdate();
1112
+ },
1113
+ onError: () => {
1114
+ hideLoading();
1115
+ }
1116
+ })
1117
+
1118
+ const { data: linksData, mutateAsync: filesMutation, error: linksError, isPending: linksLoading } = useMutation({
1119
+ mutationKey: ["get-files", pageToken],
1120
+ mutationFn: async (query) => {
1121
+ showLoading();
1122
+ var url = '/files';
1123
+ return await axios.post(url, query, {
1124
+ headers: {
1125
+ 'Authorization': authorization,
1126
+ 'Content-Type': 'application/json'
1127
+ },
1128
+ })
1129
+ },
1130
+ onSuccess: async (data, variables, context) => {
1131
+ hideLoading();
1132
+ },
1133
+ onError: () => {
1134
+ hideLoading();
1135
+ }
1136
+ })
1137
+
1138
+ useEffect(() => {
1139
+ }, [pageToken, reload, search]);
1140
+ useEffect(() => {
1141
+ if (!setting.secret_token || setting.secret_token.length < 5) {
1142
+ layer.alert("请先正确配置登陆令牌,最少5位", { icon: 5 });
1143
+ return
1144
+ }
1145
+
1146
+ let data = {
1147
+ "size": 100,
1148
+ "parent_id": id,
1149
+ "next_page_token": pageToken,
1150
+ "additional_filters": {},
1151
+ "additionalProp1": {}
1152
+ }
1153
+
1154
+ filesMutation(data);
1155
+ }, []);
1156
+ useEffect(() => {
1157
+ if (linksData) {
1158
+ setPageToken(linksData.data.next_page_token)
1159
+ setVideos([...linksData.data.files])
1160
+ }
1161
+ }, [linksData]);
1162
+
1163
+
1164
+ useEffect(() => {
1165
+ if (fileData) {
1166
+ emitEvent("addDownload", fileData)
1167
+ }
1168
+ }, [fileData]);
1169
+
1170
+ useEffect(() => {
1171
+ if (deleteData) {
1172
+ layer.msg("删除完成,请刷新查看结果");
1173
+ }
1174
+ }, [deleteData]);
1175
+
1176
+ useEffect(() => {
1177
+ if (shareData) {
1178
+ layer.alert(shareData.data.share_url);
1179
+ }
1180
+ }, [shareData]);
1181
+
1182
+ const handleSearchClick = () => {
1183
+ setSearch(keyword)
1184
+ };
1185
+
1186
+
1187
+ const forceUpdate = () => {
1188
+ setReload((pre) => !pre);
1189
+ };
1190
+
1191
+ return (
1192
+ <div>
1193
+ <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
1194
+ <label className="fs-3">文件列表</label>
1195
+ <ButtonToolbar
1196
+ aria-label="文件列表"
1197
+ className="bg-teal rounded"
1198
+ >
1199
+ <ButtonGroup className="bg-teal">
1200
+ <IconButton
1201
+ onClick={ () => {
1202
+ forceUpdate();
1203
+ } }
1204
+ text="刷新"
1205
+ className="bg-teal border-0"
1206
+ icon="reload"
1207
+ iconClassName="me-1 text-white"
1208
+ iconSize="6"
1209
+ />
1210
+ </ButtonGroup>
1211
+ </ButtonToolbar>
1212
+ </div>
1213
+ { linksError && (
1214
+ <div className="text-center text-danger">
1215
+ 发生错误,请稍后重试!!!
1216
+ </div>
1217
+ ) }
1218
+
1219
+ <Container fluid className="p-2">
1220
+ <InputGroup className="mb-3">
1221
+ <Form.Control
1222
+ placeholder="关键词"
1223
+ aria-label="关键词"
1224
+ aria-describedby="关键词"
1225
+ onChange={ e => setKeyword(e.target.value) }
1226
+ />
1227
+ <Button variant="outline-secondary" id="button-addon2" onClick={ () => { handleSearchClick() } }>
1228
+ 搜索
1229
+ </Button>
1230
+ </InputGroup>
1231
+
1232
+ { (linksLoading) && (
1233
+ <Row>
1234
+ <Col xs={ 12 } className="py-2">
1235
+ <div className="text-center text-success">
1236
+ 正在努力加载中......
1237
+ </div>
1238
+ </Col>
1239
+ </Row>
1240
+ ) }
1241
+ { linksData && (
1242
+ <Row>
1243
+ <DataTable data={ videos ? videos : [] } columns={ columns } />
1244
+ </Row>
1245
+ ) }
1246
+
1247
+ </Container>
1248
+ </div>
1249
+ );
1250
+ };
1251
+
1252
+ const Shares = () => {
1253
+ const [reload, setReload] = useState(false);
1254
+ const [pageToken, setPageToken] = useState('');
1255
+ const [keyword, setKeyword] = useState("")
1256
+ const [search, setSearch] = useState("")
1257
+ const [videos, setVideos] = useState([])
1258
+ const setting = STORE.getState().settings;
1259
+ const { id } = useParams();
1260
+ const columns = [
1261
+ { title: "文件名称", dataIndex: "title" },
1262
+ { title: "分享ID", dataIndex: "share_id" },
1263
+ { title: "日期", dataIndex: "create_time", render: (row) => (formatDate(row.create_time)) },
1264
+ {
1265
+ title: "操作",
1266
+ dataIndex: "name",
1267
+ render: (row) => (
1268
+ <Icon
1269
+ icon="cancel"
1270
+ size="6"
1271
+ className="me-2"
1272
+ onClick={ async () => {
1273
+ let data = { "id": row.share_id }
1274
+ await deleteMutation(data);
1275
+ } }
1276
+ />
1277
+ ),
1278
+ },
1279
+ ];
1280
+ const authorization = 'Bearer ' + setting.secret_token;
1281
+ const { data: fileData, mutateAsync: deleteMutation } = useMutation({
1282
+ mutationKey: ["share_batch_delete"],
1283
+ mutationFn: async (fileinfo) => {
1284
+ showLoading();
1285
+ var url = '/share_batch_delete';
1286
+ return await axios.post(url, [fileinfo.id], {
1287
+ headers: {
1288
+ 'Authorization': authorization,
1289
+ 'Content-Type': 'application/json'
1290
+ },
1291
+ })
1292
+ },
1293
+ onSuccess: async (data, variables, context) => {
1294
+ hideLoading();
1295
+ forceUpdate();
1296
+ },
1297
+ onError: () => {
1298
+ hideLoading();
1299
+ }
1300
+ })
1301
+ const { data: linksData, mutateAsync: filesMutation, error: linksError, isPending: linksLoading } = useMutation({
1302
+ mutationKey: ["get_share_list", pageToken],
1303
+ mutationFn: async (query) => {
1304
+ showLoading();
1305
+ var url = '/get_share_list';
1306
+ return await axios.post(url, query, {
1307
+ headers: {
1308
+ 'Authorization': authorization,
1309
+ 'Content-Type': 'application/json'
1310
+ },
1311
+ })
1312
+ },
1313
+ onSuccess: async (data, variables, context) => {
1314
+ hideLoading();
1315
+ },
1316
+ onError: () => {
1317
+ hideLoading();
1318
+ }
1319
+ })
1320
+
1321
+ // useEffect(() => {
1322
+
1323
+ // }, [pageToken, reload]);
1324
+ useEffect(() => {
1325
+ if (!setting.secret_token || setting.secret_token.length < 5) {
1326
+ layer.alert("请先正确配置登陆令牌,最少5位", { icon: 5 });
1327
+ return
1328
+ }
1329
+
1330
+ let data = {
1331
+ "size": 100,
1332
+ "parent_id": id,
1333
+ "next_page_token": pageToken,
1334
+ "additional_filters": {},
1335
+ "additionalProp1": {}
1336
+ }
1337
+
1338
+ filesMutation(data);
1339
+ }, [pageToken, reload]);
1340
+ useEffect(() => {
1341
+ if (linksData) {
1342
+ setPageToken(linksData.data.next_page_token)
1343
+ setVideos([...linksData.data.data])
1344
+ }
1345
+ }, [linksData]);
1346
+
1347
+
1348
+ const forceUpdate = () => {
1349
+ setReload((pre) => !pre);
1350
+ };
1351
+
1352
+ return (
1353
+ <div>
1354
+ <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
1355
+ <label className="fs-3">分享列表</label>
1356
+ <ButtonToolbar
1357
+ aria-label="分享列表"
1358
+ className="bg-teal rounded"
1359
+ >
1360
+ <ButtonGroup className="bg-teal">
1361
+ <IconButton
1362
+ onClick={ () => {
1363
+ forceUpdate();
1364
+ } }
1365
+ text="刷新"
1366
+ className="bg-teal border-0"
1367
+ icon="reload"
1368
+ iconClassName="me-1 text-white"
1369
+ iconSize="6"
1370
+ />
1371
+ </ButtonGroup>
1372
+ </ButtonToolbar>
1373
+ </div>
1374
+ { linksError && (
1375
+ <div className="text-center text-danger">
1376
+ 发生错误,请稍后重试!!!
1377
+ </div>
1378
+ ) }
1379
+
1380
+ <Container fluid className="p-2">
1381
+ { (linksLoading) && (
1382
+ <Row>
1383
+ <Col xs={ 12 } className="py-2">
1384
+ <div className="text-center text-success">
1385
+ 正在努力加载中......
1386
+ </div>
1387
+ </Col>
1388
+ </Row>
1389
+ ) }
1390
+ { linksData && (
1391
+ <Row>
1392
+ <DataTable data={ videos ? videos : [] } columns={ columns } />
1393
+ </Row>
1394
+ ) }
1395
+
1396
+ </Container>
1397
+ </div>
1398
+ );
1399
+ };
1400
+
1401
+ const Offline = () => {
1402
+ const [selectedOption, setSelectedOption] = useState(1);
1403
+ const handleOptionChange = (event) => {
1404
+ setSelectedOption(event.target.value);
1405
+ };
1406
+
1407
+ const [linkValue, setLinkValue] = useState('');
1408
+ const handleLinkChange = (event) => {
1409
+ setLinkValue(event.target.value);
1410
+ };
1411
+
1412
+ const [show, setShow] = useState(false);
1413
+ const handleClose = () => setShow(false);
1414
+ const handleShow = () => setShow(true);
1415
+ const setting = STORE.getState().settings;
1416
+ const authorization = 'Bearer ' + setting.secret_token;
1417
+
1418
+ const { data: taskData, mutateAsync: offlineMutation, error: taskError, isPending: taskLoading } = useMutation({
1419
+ mutationKey: ["offfline-files"],
1420
+ mutationFn: async (query) => {
1421
+ showLoading();
1422
+ var url = query.url;
1423
+ return await axios.post(url, query.data, {
1424
+ headers: {
1425
+ 'Authorization': authorization,
1426
+ 'Content-Type': 'application/json'
1427
+ },
1428
+ })
1429
+ },
1430
+ onSuccess: async (data, variables, context) => {
1431
+ hideLoading();
1432
+ },
1433
+ onError: () => {
1434
+ hideLoading();
1435
+ }
1436
+ })
1437
+
1438
+ useEffect(() => {
1439
+ if (taskData) {
1440
+ layer.msg("添加完成,刷新查看");
1441
+ }
1442
+ }, [taskData]);
1443
+
1444
+
1445
+ return (
1446
+ <div>
1447
+ <Button
1448
+ style={ {
1449
+ backgroundColor: "transparent",
1450
+ } }
1451
+ className="nav-link btn mr-1"
1452
+ onClick={ handleShow }
1453
+ children={
1454
+ <span>
1455
+ <Icon
1456
+ icon="plus"
1457
+ size="3"
1458
+ className="text-white"
1459
+ />
1460
+ </span>
1461
+ }
1462
+ ></Button>
1463
+ <Modal show={ show } onHide={ handleClose }>
1464
+ <Modal.Header closeButton>
1465
+ <Modal.Title>添加离线</Modal.Title>
1466
+ </Modal.Header>
1467
+ <Modal.Body>
1468
+ <Form>
1469
+ <Form.Group className="mb-3" controlId="exampleForm.ControlInput1">
1470
+ <Form.Label>链接类型</Form.Label>
1471
+ <div>
1472
+ <Form.Check
1473
+ inline
1474
+ label="磁力"
1475
+ name="offline_type"
1476
+ type="radio"
1477
+ value={ 1 }
1478
+ checked={ selectedOption == 1 }
1479
+ onChange={ handleOptionChange }
1480
+ />
1481
+ <Form.Check
1482
+ inline
1483
+ label="分享"
1484
+ name="offline_type"
1485
+ type="radio"
1486
+ value={ 2 }
1487
+ checked={ selectedOption == 2 }
1488
+ onChange={ handleOptionChange }
1489
+ />
1490
+ </div>
1491
+ </Form.Group>
1492
+ <Form.Group
1493
+ className="mb-3"
1494
+ controlId="exampleForm.ControlTextarea1"
1495
+ >
1496
+ <Form.Label>链接地址</Form.Label>
1497
+ <Form.Control as="textarea" value={ linkValue }
1498
+ onChange={ handleLinkChange } rows={ 3 } />
1499
+ </Form.Group>
1500
+ </Form>
1501
+ </Modal.Body>
1502
+ <Modal.Footer>
1503
+ <Button variant="secondary" onClick={ handleClose }>
1504
+ 关闭
1505
+ </Button>
1506
+ <Button variant="primary" onClick={ async () => {
1507
+ var data = {};
1508
+ if (selectedOption == 1) {
1509
+ data = {
1510
+ url: "/offline",
1511
+ data: {
1512
+ "file_url": linkValue,
1513
+ "parent_id": "",
1514
+ "name": "",
1515
+ "additionalProp1": {}
1516
+ }
1517
+ }
1518
+ } else {
1519
+ data = {
1520
+ url: "/restore?share_id=" + linkValue,
1521
+ data: []
1522
+ }
1523
+ }
1524
+ await offlineMutation(data);
1525
+ } }>
1526
+ 保存
1527
+ </Button>
1528
+ </Modal.Footer>
1529
+ </Modal>
1530
+ </div>
1531
+ );
1532
+ }
1533
+ const Tasks = () => {
1534
+ const [show, setShow] = useState(false);
1535
+ const handleClose = () => setShow(false);
1536
+ const handleShow = () => setShow(true);
1537
+ const [reload, setReload] = useState(false);
1538
+ const { limit, onPaginationChange, skip, pagination } = usePagination();
1539
+ const [meta, setMeta] = useState({ filter_count: 0 })
1540
+ const [tasks, setTasks] = useState([])
1541
+ const { data: tasksData, refetch: tasksRefetch, isLoading: tasksLoading, error: tasksError } = useQuery({
1542
+ queryKey: ['get_paginate_tasks', limit, skip],
1543
+ queryFn: () => paginateTasksGet(limit, skip),
1544
+ enabled: show,
1545
+ })
1546
+
1547
+ useEffect(() => {
1548
+ //tasksRefetch()
1549
+ }, [pagination, reload]);
1550
+
1551
+ useEffect(() => {
1552
+ if (tasksData) {
1553
+ setMeta(tasksData.meta)
1554
+ setTasks([...tasksData.data])
1555
+ }
1556
+ }, [tasksData]);
1557
+
1558
+
1559
+ const forceUpdate = () => {
1560
+ setReload((pre) => !pre);
1561
+ };
1562
+
1563
+ return (
1564
+ <div>
1565
+ <Button
1566
+ style={ {
1567
+ backgroundColor: "transparent",
1568
+ } }
1569
+ className="nav-link btn"
1570
+ onClick={ handleShow }
1571
+ children={
1572
+ <span>
1573
+ <Icon
1574
+ icon="cloud-download-outline"
1575
+ size="3"
1576
+ className="text-white"
1577
+ />
1578
+ </span>
1579
+ }
1580
+ ></Button>
1581
+
1582
+
1583
+ <Modal show={ show } onHide={ handleClose }>
1584
+ <Modal.Header closeButton>
1585
+ <Modal.Title>远程下载任务</Modal.Title>
1586
+ </Modal.Header>
1587
+ <Modal.Body className="py-0">
1588
+ { tasksError && (
1589
+ <div className="text-center text-danger">
1590
+ 发生错误,请稍后重试!!!
1591
+ </div>
1592
+ ) }
1593
+ { (tasksLoading) && (
1594
+ <div className="text-center text-success">
1595
+ 正在努力加载中......
1596
+ </div>
1597
+ ) }
1598
+ <Container fluid className="p-2">
1599
+ <Row>
1600
+ <Col xs={ 12 }>
1601
+ <Paginate page={ pagination.pageIndex } onClick={ (i) => { onPaginationChange({ pageSize: 36, pageIndex: i }) } } itemsPerPage="36" totalCount={ meta.filter_count } />
1602
+ </Col>
1603
+ </Row>
1604
+
1605
+ <Row>
1606
+ <Col xs={ 12 }>
1607
+ <Table bordered hover>
1608
+ <thead>
1609
+ <tr>
1610
+ <th>#</th>
1611
+ <th>文件名</th>
1612
+ <th>状态</th>
1613
+ </tr>
1614
+ </thead>
1615
+ { tasksData && (
1616
+ <tbody>
1617
+ { tasks.map((task, index) => (
1618
+ <tr>
1619
+ <td>{ task.id }</td>
1620
+ <td>{ task.url.substr(task.url.indexOf('##') + 2) }</td>
1621
+ <td>{ task.status == 'draft' ? <span className="text-warning">待下载</span> : <span class="text-success">正在下载中</span> }</td>
1622
+ </tr>
1623
+ )) }
1624
+ </tbody>
1625
+ ) }
1626
+
1627
+ </Table>
1628
+
1629
+ </Col>
1630
+ </Row>
1631
+
1632
+ <Row>
1633
+ <Col xs={ 12 } className="py-2">
1634
+ <Paginate page={ pagination.pageIndex } onClick={ (i) => { onPaginationChange({ pageSize: 36, pageIndex: i }) } } itemsPerPage="36" totalCount={ meta.filter_count } />
1635
+ </Col>
1636
+ </Row>
1637
+ </Container>
1638
+ </Modal.Body>
1639
+ <Modal.Footer className="justify-content-between">
1640
+ <Button variant="primary" onClick={ () => { forceUpdate(); } }>
1641
+ 刷新
1642
+ </Button>
1643
+ <Button variant="primary" onClick={ () => {
1644
+ const setting = STORE.getState().settings;
1645
+ showLoading();
1646
+ axios.post(setting.github_host, { "ref": "main", "inputs": {} }, {
1647
+ headers: {
1648
+ 'Authorization': "Bearer " + setting.github_token,
1649
+ 'Accept': 'application/vnd.github+json',
1650
+ 'X-GitHub-Api-Version': '2022-11-28',
1651
+ },
1652
+ }).then(function (response) {
1653
+ layer.msg('任务启动成功', { time: 2000, icon: 6 });
1654
+ //console.log(response);
1655
+ })
1656
+ .catch(function (error) {
1657
+ console.log(error);
1658
+ }).finally(() => {
1659
+ hideLoading();
1660
+ });
1661
+ } }>
1662
+ 开始下载
1663
+ </Button>
1664
+ <Button variant="primary" onClick={ handleClose }>
1665
+ 关闭
1666
+ </Button>
1667
+ </Modal.Footer>
1668
+ </Modal>
1669
+
1670
+ </div >);
1671
+ };
1672
+
1673
+
1674
+
1675
+
1676
+
1677
+
1678
+
1679
+
1680
+ const LocalTasks = () => {
1681
+ const [show, setShow] = useState(false);
1682
+ const handleClose = () => setShow(false);
1683
+ const handleShow = () => setShow(true);
1684
+ const [downloads, setDownloads] = useState([])
1685
+ const [addDownloadObject, setAddDownloadObject] = useState({})
1686
+ const setting = STORE.getState().settings;
1687
+ const columns = [
1688
+ { title: "文件名称", dataIndex: "name" },
1689
+ { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(row.size)) },
1690
+ { title: "日期", dataIndex: "created", render: (row) => (formatDate(row.created)) },
1691
+ {
1692
+ title: "操作",
1693
+ dataIndex: "name",
1694
+ render: (row) => (
1695
+ <div>
1696
+ <Icon
1697
+ icon="delete-outline"
1698
+ size="6"
1699
+ className="me-2"
1700
+ onClick={ () => {
1701
+ layer.confirm('确定删除?该操作无法撤销!!!', { icon: 3 }, function (index) {
1702
+ setDownloads(
1703
+ downloads.filter(a =>
1704
+ a.name !== row.name
1705
+ )
1706
+ );
1707
+ layer.close(index);
1708
+ }, function () {
1709
+
1710
+ });
1711
+ } }
1712
+ />
1713
+ <Icon
1714
+ icon="pencil-outline"
1715
+ size="6"
1716
+ className="me-2"
1717
+ onClick={ () => {
1718
+ layer.prompt({
1719
+ title: '输入文件名称,并确认',
1720
+ formType: 0,
1721
+ value: row.name,
1722
+ success: function (layero, index) {
1723
+ $(".layui-layer").eq(0).css("top", "0px");
1724
+ $("div[aria-modal]").eq(0).removeAttr("tabindex");//解决弹出窗的input无法获取焦点的问题
1725
+ },
1726
+ end: function (layero, index) {
1727
+ $("div[aria-modal]").eq(0).attr("tabindex", -1).focus();//再把焦点还回去
1728
+ }
1729
+ }, function (value, index) {
1730
+ const newDownloads = downloads.map(downloadItem => {
1731
+ if (downloadItem.name === row.name) {
1732
+ return {
1733
+ ...downloadItem,
1734
+ name: value
1735
+ };
1736
+ }
1737
+ return downloadItem;
1738
+ });
1739
+ setDownloads(newDownloads);
1740
+ layer.close(index);
1741
+ });
1742
+ } }
1743
+ />
1744
+ </div>
1745
+ ),
1746
+ },
1747
+ ];
1748
+
1749
+
1750
+ const { mutateAsync: localTaskdMutation } = useMutation({
1751
+ mutationKey: ["get-download"],
1752
+ mutationFn: async () => {
1753
+ showLoading();
1754
+ var host = setting.directus_host;
1755
+ if (!host.endsWith("/")) {
1756
+ host = host + '/'
1757
+ }
1758
+ var url = host + 'items/task';
1759
+ const tasks = downloads.map(task => {
1760
+ return { url: task.url + '##' + task.name }
1761
+ })
1762
+ return await axios.post(url, tasks, {
1763
+ headers: {
1764
+ 'Authorization': "Bearer " + setting.directus_token,
1765
+ 'Content-Type': 'application/json'
1766
+ },
1767
+ })
1768
+ },
1769
+ onSuccess: async (data, variables, context) => {
1770
+ hideLoading();
1771
+ layer.msg('任务添加成功', { time: 2000, icon: 6 });
1772
+ },
1773
+ onError: () => {
1774
+ hideLoading();
1775
+ layer.msg('任务添加失败', { time: 2000, icon: 5 });
1776
+ }
1777
+ })
1778
+ const addDowload = (fileinfo) => {
1779
+ const file = fileinfo.data;
1780
+ var url = file.web_content_link;
1781
+ for (const obj of file.medias) {
1782
+ if (obj.link.url.trim().length > 10) {
1783
+ url = obj.link.url;
1784
+ break;
1785
+ }
1786
+ }
1787
+ const download = { name: file.name, size: Number(file.size), url: url, created: file.created_time }
1788
+ setAddDownloadObject(download)
1789
+ }
1790
+ useEffect(() => {
1791
+ if (addDownloadObject && ('name' in addDownloadObject)) {
1792
+ setDownloads([...downloads, addDownloadObject])
1793
+ setAddDownloadObject({})
1794
+ }
1795
+ }, [addDownloadObject]);
1796
+ useEffect(() => {
1797
+ onEvent("addDownload", addDowload)
1798
+ settingStorage.getItem('downloads').then(function (value) {
1799
+ if (value) {
1800
+ setDownloads(value)
1801
+ }
1802
+ }).catch(function (err) {
1803
+ console.log(err)
1804
+ });
1805
+ }, []);
1806
+ useEffect(() => {
1807
+ settingStorage.setItem('downloads', downloads)
1808
+ }, [downloads]);
1809
+
1810
+
1811
+ if (downloads.length > 0) {
1812
+ return (
1813
+ <div>
1814
+ <Button
1815
+ style={ {
1816
+ backgroundColor: "transparent",
1817
+ } }
1818
+ className="nav-link btn"
1819
+ onClick={ handleShow }
1820
+ children={
1821
+ <span>
1822
+ <Icon
1823
+ icon="download"
1824
+ size="3"
1825
+ className="text-white"
1826
+ />
1827
+ <Badge bg="danger" style={ { top: '-15px', left: '-10px' } }>{ downloads.length }</Badge>
1828
+ </span>
1829
+ }
1830
+ ></Button>
1831
+
1832
+
1833
+ <Modal show={ show } onHide={ handleClose }>
1834
+ <Modal.Header closeButton>
1835
+ <Modal.Title>本地下载任务</Modal.Title>
1836
+ </Modal.Header>
1837
+ <Modal.Body>
1838
+ { downloads && (
1839
+ <DataTable data={ downloads ? downloads : [] } columns={ columns } />
1840
+ ) }
1841
+ </Modal.Body>
1842
+ <Modal.Footer className="justify-content-between">
1843
+
1844
+ <ButtonGroup>
1845
+ <Button variant="primary" onClick={ async () => { await localTaskdMutation() } }>
1846
+ 添加转存
1847
+ </Button>
1848
+ </ButtonGroup>
1849
+
1850
+ <ButtonGroup>
1851
+ <Button variant="danger" onClick={ () => {
1852
+ layer.confirm('确定删除?该操作无法撤销!!!', { icon: 3 }, function (index) {
1853
+ setDownloads([]);
1854
+ layer.close(index);
1855
+ }, function () {
1856
+
1857
+ });
1858
+ } }>
1859
+ 清空
1860
+ </Button>
1861
+ <Button variant="primary" onClick={ handleClose }>
1862
+ 关闭
1863
+ </Button>
1864
+ </ButtonGroup>
1865
+
1866
+
1867
+ </Modal.Footer>
1868
+ </Modal>
1869
+
1870
+ </div >
1871
+ );
1872
+ }
1873
+ }
1874
+
1875
+
1876
+ App = () => {
1877
+ const [open, setOpen] = useState(false);
1878
+ const [reload, setReload] = useState(false);
1879
+ const [response, error, loading, fetchDataByPage] = getFiles();
1880
+ const { folder } = useParams();
1881
+ const location = useLocation();
1882
+ const [path, setPath] = useState(decodeURI(location.pathname));
1883
+ const [page, setPage] = useState(1);
1884
+ const [query, setQuery] = useState({ "path": path, "password": "", "page": page, "per_page": 0, "refresh": true });
1885
+ const setting = STORE.getState().settings;
1886
+
1887
+
1888
+ //const queryClient = useQueryClient()
1889
+ // Queries
1890
+ //const { data, error, isLoading, refetch } = useQuery({
1891
+ // queryKey: ['test'], queryFn: () => axios.get("")
1892
+ //})
1893
+
1894
+ const { data: fileData, mutateAsync: downloadMutation } = useMutation({
1895
+ mutationKey: ["get-download"],
1896
+ mutationFn: async (fileinfo) => {
1897
+ showLoading();
1898
+ var host = setting.cf_proxy;
1899
+ if (!host.endsWith("/")) {
1900
+ host = host + '/'
1901
+ }
1902
+ var url = host + 'api/fs/get';
1903
+ return await axios.post(url, fileinfo, {
1904
+ headers: {
1905
+ 'Authorization': setting.secret_token,
1906
+ 'Content-Type': 'application/json'
1907
+ },
1908
+ })
1909
+ },
1910
+ onSuccess: async (data, variables, context) => {
1911
+ hideLoading();
1912
+ },
1913
+ onError: () => {
1914
+ hideLoading();
1915
+ }
1916
+ })
1917
+
1918
+ useEffect(() => {
1919
+ if (fileData) {
1920
+ emitEvent("addDownload", fileData)
1921
+ }
1922
+ }, [fileData]);
1923
+
1924
+
1925
+ const columns = [
1926
+ { title: "文件名称", dataIndex: "name" },
1927
+ { title: "大小", dataIndex: "size", render: (row) => (bytesToSize(row.size)) },
1928
+ { title: "日期", dataIndex: "created", render: (row) => (formatDate(row.created)) },
1929
+ {
1930
+ title: "操作",
1931
+ dataIndex: "name",
1932
+ render: (row) => (
1933
+ row.is_dir ? <Nav.Link
1934
+ as={ Link }
1935
+ className="nav-link text-dark"
1936
+ to={ decodeURI(path + row.name + '/') }
1937
+ target="_blank"
1938
+ >
1939
+ <Icon
1940
+ icon="open-in-new"
1941
+ size="6"
1942
+ className="me-2"
1943
+ />
1944
+ </Nav.Link> :
1945
+ <Icon
1946
+ icon="download-outline"
1947
+ size="6"
1948
+ className="me-2"
1949
+ onClick={ async () => {
1950
+ let data = { "path": path + row.name, "password": "" }
1951
+ await downloadMutation(data);
1952
+ } }
1953
+ />
1954
+ ),
1955
+ },
1956
+ ];
1957
+ useEffect(() => {
1958
+ if (!setting.secret_token || setting.secret_token.length < 5) {
1959
+ layer.alert("请先正确配置登陆令牌", { icon: 5 });
1960
+ return
1961
+ }
1962
+ fetchDataByPage(setting, query);
1963
+ return () => { }
1964
+ }, [reload, query]);
1965
+
1966
+
1967
+ const forceUpdate = () => {
1968
+ setReload((pre) => !pre);
1969
+ };
1970
+
1971
+ return (
1972
+ <div>
1973
+ <div className="d-flex justify-content-between align-items-center p-2 border-bottom bg-light">
1974
+ <label className="fs-3">文件列表</label>
1975
+ <ButtonToolbar
1976
+ aria-label="功能区"
1977
+ className="bg-teal rounded"
1978
+ >
1979
+ <ButtonGroup className="bg-teal">
1980
+ <IconButton
1981
+ onClick={ () => {
1982
+ emitEvent("test", { a: 'b' })
1983
+ } }
1984
+ text="刷新"
1985
+ className="bg-teal border-0"
1986
+ icon="reload"
1987
+ iconClassName="me-1 text-white"
1988
+ iconSize="6"
1989
+ />
1990
+ </ButtonGroup>
1991
+ </ButtonToolbar>
1992
+ </div>
1993
+ <Container fluid className="p-2">
1994
+ { error && (
1995
+ <div className="text-center text-danger">
1996
+ { error }
1997
+ </div>
1998
+ ) }
1999
+ { (loading) && (
2000
+ <div className="text-center text-success">
2001
+ 正在努力加载中......
2002
+ </div>
2003
+ ) }
2004
+ { response && (
2005
+ <DataTable data={ response.data.content ? response.data.content : [] } columns={ columns } />
2006
+ ) }
2007
+ </Container>
2008
+ </div>
2009
+ );
2010
+ };
2011
+
2012
+ const container = document.getElementById("root");
2013
+ const root = ReactDOM.createRoot(container);
2014
+ root.render(
2015
+ <QueryClientProvider client={ queryClient }>
2016
+ <HashRouter>
2017
+ <Route path="/:path?">
2018
+ <Layout>
2019
+ <Switch>
2020
+ <Route path="/" exact component={ Videos } />
2021
+ <Route path="/shares" exact component={ Shares } />
2022
+ <Route path="/videos/:id?" exact component={ Videos } />
2023
+ </Switch>
2024
+ </Layout>
2025
+ </Route>
2026
+ </HashRouter>
2027
+ </QueryClientProvider>
2028
+ );
2029
+
2030
+ $(document).ready(function () {
2031
+ $(window).scroll(function () {
2032
+ if ($(this).scrollTop() > 50) {
2033
+ $("#back-to-top").fadeIn();
2034
+ } else {
2035
+ $("#back-to-top").fadeOut();
2036
+ }
2037
+ });
2038
+ // scroll body to 0px on click
2039
+ $("#back-to-top").click(function () {
2040
+ $("body,html").animate(
2041
+ {
2042
+ scrollTop: 0,
2043
+ },
2044
+ 400
2045
+ );
2046
+ return false;
2047
+ });
2048
+ });
2049
+ </script>
2050
+ </body>
2051
+
2052
+ </html>