index.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Air8000 文件管理系统</title>
  7. <style>
  8. body {
  9. font-family: 'Microsoft YaHei', Arial, sans-serif;
  10. background-color: #f5f5f5;
  11. margin: 0;
  12. padding: 20px;
  13. color: #333;
  14. }
  15. .container {
  16. max-width: 1200px;
  17. margin: auto;
  18. background: white;
  19. padding: 20px;
  20. border-radius: 8px;
  21. box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  22. }
  23. .header {
  24. background: #2c3e50;
  25. color: white;
  26. padding: 15px;
  27. border-radius: 5px;
  28. margin-bottom: 20px;
  29. display: flex;
  30. justify-content: space-between;
  31. align-items: center;
  32. }
  33. .login-form {
  34. max-width: 400px;
  35. margin: 100px auto;
  36. padding: 40px;
  37. background: white;
  38. border-radius: 8px;
  39. box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
  40. }
  41. .form-group {
  42. margin-bottom: 15px;
  43. }
  44. label {
  45. display: block;
  46. margin-bottom: 5px;
  47. font-weight: bold;
  48. }
  49. input[type="text"], input[type="password"] {
  50. width: 100%;
  51. padding: 10px;
  52. border: 1px solid #ddd;
  53. border-radius: 4px;
  54. box-sizing: border-box;
  55. }
  56. button {
  57. background-color: #3498db;
  58. color: white;
  59. border: none;
  60. padding: 10px 20px;
  61. border-radius: 4px;
  62. cursor: pointer;
  63. font-size: 16px;
  64. }
  65. button:hover {
  66. background-color: #2980b9;
  67. }
  68. .file-list {
  69. width: 100%;
  70. border-collapse: collapse;
  71. margin-top: 20px;
  72. }
  73. .file-list th, .file-list td {
  74. padding: 12px;
  75. text-align: left;
  76. border-bottom: 1px solid #ddd;
  77. }
  78. .file-list th {
  79. background-color: #f8f9fa;
  80. font-weight: bold;
  81. }
  82. .file-list tr:hover {
  83. background-color: #f5f5f5;
  84. }
  85. .download-btn {
  86. background-color: #27ae60;
  87. color: white;
  88. padding: 5px 10px;
  89. text-decoration: none;
  90. border-radius: 3px;
  91. font-size: 12px;
  92. }
  93. .download-btn:hover {
  94. background-color: #219a52;
  95. }
  96. .delete-btn {
  97. background-color: #e74c3c;
  98. color: white;
  99. border: none;
  100. padding: 5px 10px;
  101. border-radius: 3px;
  102. font-size: 12px;
  103. cursor: pointer;
  104. }
  105. .delete-btn:hover {
  106. background-color: #c0392b;
  107. }
  108. .breadcrumb {
  109. padding: 10px 0;
  110. margin-bottom: 20px;
  111. }
  112. .breadcrumb a {
  113. color: #3498db;
  114. text-decoration: none;
  115. cursor: pointer;
  116. }
  117. .breadcrumb a:hover {
  118. text-decoration: underline;
  119. }
  120. .hidden {
  121. display: none;
  122. }
  123. .error {
  124. color: #e74c3c;
  125. margin-top: 10px;
  126. }
  127. </style>
  128. </head>
  129. <body>
  130. <div class="container">
  131. <!-- 登录页面 -->
  132. <div id="loginPage" class="login-form">
  133. <h2>Air8000 文件管理系统登录</h2>
  134. <div class="form-group">
  135. <label for="username">用户名:</label>
  136. <input type="text" id="username">
  137. </div>
  138. <div class="form-group">
  139. <label for="password">密码:</label>
  140. <input type="password" id="password">
  141. </div>
  142. <button onclick="login()">登录</button>
  143. <div id="loginError" class="error hidden"></div>
  144. </div>
  145. <!-- 文件管理页面 -->
  146. <div id="filePage" class="hidden">
  147. <div class="header">
  148. <h1>Air8000 文件管理系统</h1>
  149. <div>
  150. <button onclick="scanFiles()" style="margin-right: 10px;">扫描文件</button>
  151. <button onclick="logout()">退出登录</button>
  152. </div>
  153. </div>
  154. <div class="breadcrumb" id="breadcrumb">
  155. <a onclick="navigateTo('/')">根目录</a>
  156. <span> | </span>
  157. <a onclick="navigateTo('/sd')">TF/SD目录</a>
  158. </div>
  159. <table class="file-list">
  160. <thead>
  161. <tr>
  162. <th>名称</th>
  163. <th>大小</th>
  164. <th>操作</th>
  165. </tr>
  166. </thead>
  167. <tbody id="fileListBody">
  168. </tbody>
  169. </table>
  170. </div>
  171. </div>
  172. <script>
  173. let currentPath = '/';
  174. let isLoggedIn = false;
  175. function login() {
  176. const username = document.getElementById('username').value;
  177. const password = document.getElementById('password').value;
  178. fetch('/login', {
  179. method: 'POST',
  180. headers: {
  181. 'Content-Type': 'application/json',
  182. },
  183. credentials: 'include', // 确保发送和接收cookies
  184. body: JSON.stringify({username: username, password: password})
  185. })
  186. .then(response => {
  187. // 打印所有响应头,查看Cookie设置
  188. console.log('登录响应头:', response.headers);
  189. return response.json();
  190. })
  191. .then(data => {
  192. console.log('登录响应数据:', data);
  193. if (data.success) {
  194. isLoggedIn = true;
  195. // 存储session_id到localStorage作为备用认证方式
  196. if (data.session_id) {
  197. localStorage.setItem('session_id', data.session_id);
  198. console.log('已存储SessionID到localStorage:', data.session_id);
  199. }
  200. document.getElementById('loginPage').classList.add('hidden');
  201. document.getElementById('filePage').classList.remove('hidden');
  202. loadFiles('/luadb');
  203. } else {
  204. document.getElementById('loginError').textContent = data.message || '登录失败';
  205. document.getElementById('loginError').classList.remove('hidden');
  206. }
  207. })
  208. .catch(error => {
  209. console.error('登录请求错误:', error);
  210. document.getElementById('loginError').textContent = '登录请求失败';
  211. document.getElementById('loginError').classList.remove('hidden');
  212. });
  213. }
  214. function logout() {
  215. fetch('/logout', {
  216. method: 'POST',
  217. credentials: 'include' // 确保发送cookies
  218. })
  219. .then(() => {
  220. isLoggedIn = false;
  221. // 清除localStorage中的session_id
  222. localStorage.removeItem('session_id');
  223. document.getElementById('filePage').classList.add('hidden');
  224. document.getElementById('loginPage').classList.remove('hidden');
  225. document.getElementById('username').value = '';
  226. document.getElementById('password').value = '';
  227. document.getElementById('loginError').classList.add('hidden');
  228. });
  229. }
  230. // 扫描文件函数
  231. function scanFiles() {
  232. if (!isLoggedIn) return;
  233. // 获取用户名和密码用于URL参数认证
  234. const username = document.getElementById('username')?.value || 'admin';
  235. const password = document.getElementById('password')?.value || '123456';
  236. // 构建带认证参数的扫描请求URL
  237. const url = '/scan-files?username=' + encodeURIComponent(username) +
  238. '&password=' + encodeURIComponent(password);
  239. console.log('发送文件扫描请求,URL:', url);
  240. // 显示扫描提示
  241. alert('开始扫描文件,请查看系统日志了解扫描进度...');
  242. fetch(url, {
  243. method: 'GET',
  244. credentials: 'include'
  245. })
  246. .then(response => {
  247. if (!response.ok) {
  248. throw new Error('扫描请求错误: ' + response.status);
  249. }
  250. return response.json();
  251. })
  252. .then(data => {
  253. console.log('扫描响应:', data);
  254. if (data && data.success) {
  255. alert('文件扫描完成!已扫描到 ' + data.foundFiles + ' 个文件,显示扫描到的用户文件。');
  256. // 重新加载文件列表
  257. loadFiles(currentPath);
  258. } else {
  259. alert('文件扫描失败: ' + (data.message || '未知错误'));
  260. }
  261. })
  262. .catch(error => {
  263. console.error('扫描文件请求错误:', error);
  264. alert('扫描文件请求失败');
  265. });
  266. }
  267. function loadFiles(path) {
  268. if (!isLoggedIn) return;
  269. // 准备请求头
  270. const headers = {
  271. 'Content-Type': 'application/json'
  272. };
  273. // 由于传统认证方式不可靠,我们使用URL参数认证
  274. // 获取用户名和密码用于URL参数认证
  275. const username = document.getElementById('username')?.value || 'admin';
  276. const password = document.getElementById('password')?.value || '123456';
  277. // 构建带认证参数的URL
  278. const url = '/list?path=' + encodeURIComponent(path) +
  279. '&username=' + encodeURIComponent(username) +
  280. '&password=' + encodeURIComponent(password);
  281. console.log('使用URL参数认证,请求URL:', url);
  282. fetch(url, {
  283. credentials: 'include', // 确保发送cookies
  284. headers: headers
  285. })
  286. .then(response => {
  287. if (!response.ok) {
  288. throw new Error('网络响应错误: ' + response.status);
  289. }
  290. return response.json();
  291. })
  292. .then(data => {
  293. console.log('文件列表数据:', data);
  294. // 只使用服务器返回的数据
  295. if (data && data.success && Array.isArray(data.files)) {
  296. displayFiles(data.files, path);
  297. } else {
  298. // 如果数据无效,显示空列表
  299. displayFiles([], path);
  300. }
  301. updateBreadcrumb(path);
  302. })
  303. .catch(error => {
  304. console.error('加载文件列表错误:', error);
  305. // 发生错误时显示空列表
  306. displayFiles([], path);
  307. updateBreadcrumb(path);
  308. });
  309. }
  310. function displayFiles(files, path) {
  311. const tbody = document.getElementById('fileListBody');
  312. tbody.innerHTML = '';
  313. // 确保files是数组
  314. if (!Array.isArray(files)) {
  315. files = [];
  316. }
  317. console.log('显示文件数量:', files.length);
  318. files.forEach(file => {
  319. // 确保文件对象有必要的属性
  320. const safeFile = {
  321. name: file.name || "未知文件名",
  322. size: file.size || 0,
  323. isDirectory: file.isDirectory || false,
  324. path: file.path || (path + '/' + (file.name || "未知文件名"))
  325. };
  326. const row = document.createElement('tr');
  327. let nameCell, actionCell;
  328. if (safeFile.isDirectory) {
  329. nameCell = `<td><a href="#" onclick="navigateTo('${encodeURIComponent(path + '/' + safeFile.name)}')">${safeFile.name}/</a></td>`;
  330. actionCell = '<td></td>';
  331. } else {
  332. nameCell = `<td>${safeFile.name}</td>`;
  333. // 为下载链接添加URL参数认证
  334. const username = document.getElementById('username')?.value || 'admin';
  335. const password = document.getElementById('password')?.value || '123456';
  336. const downloadUrl = '/download?path=' + encodeURIComponent(safeFile.path) +
  337. '&username=' + encodeURIComponent(username) +
  338. '&password=' + encodeURIComponent(password);
  339. // 添加下载和删除按钮
  340. actionCell = `<td>
  341. <a href="${downloadUrl}" class="download-btn" style="margin-right: 5px;">下载</a>
  342. <button class="delete-btn" onclick="deleteFile('${encodeURIComponent(safeFile.path)}')">删除</button>
  343. </td>`;
  344. }
  345. row.innerHTML = `
  346. ${nameCell}
  347. <td>${formatSize(safeFile.size)}</td>
  348. ${actionCell}
  349. `;
  350. tbody.appendChild(row);
  351. });
  352. }
  353. // 删除文件函数
  354. function deleteFile(filePath) {
  355. if (confirm('确定要删除这个文件吗?')) {
  356. // 获取用户名和密码用于URL参数认证
  357. const username = document.getElementById('username')?.value || 'admin';
  358. const password = document.getElementById('password')?.value || '123456';
  359. // 构建带认证参数的删除请求URL
  360. const url = '/delete?path=' + filePath +
  361. '&username=' + encodeURIComponent(username) +
  362. '&password=' + encodeURIComponent(password);
  363. console.log('使用URL参数认证进行删除操作,请求URL:', url);
  364. fetch(url, {
  365. method: 'POST',
  366. credentials: 'include'
  367. })
  368. .then(response => response.json())
  369. .then(data => {
  370. if (data.success) {
  371. // 删除成功后重新加载文件列表
  372. loadFiles(currentPath);
  373. } else {
  374. alert('删除失败: ' + (data.message || '未知错误'));
  375. }
  376. })
  377. .catch(error => {
  378. alert('删除请求失败');
  379. });
  380. }
  381. }
  382. function updateBreadcrumb(path) {
  383. const breadcrumb = document.getElementById('breadcrumb');
  384. // 先设置根目录和TF/SD目录链接
  385. breadcrumb.innerHTML = '<a onclick="navigateTo(\'\')">根目录</a><span> | </span><a onclick="navigateTo(\'/sd\')">TF/SD目录</a>';
  386. // 然后添加当前路径的层次结构(如果不是根目录)
  387. if (path !== '/' && path !== '/sd') {
  388. const parts = path.split('/').filter(p => p);
  389. let current = '';
  390. // 仅在非根目录和非SD目录时添加分隔符
  391. breadcrumb.innerHTML += ' > ';
  392. parts.forEach((part, index) => {
  393. current += '/' + part;
  394. if (index > 0) {
  395. breadcrumb.innerHTML += ' > ';
  396. }
  397. breadcrumb.innerHTML += '<a onclick="navigateTo(\'' + current + '\')">' + part + '</a>';
  398. });
  399. }
  400. }
  401. function navigateTo(path) {
  402. currentPath = path;
  403. loadFiles(path);
  404. }
  405. function formatSize(bytes) {
  406. if (bytes === 0) return '0 B';
  407. const k = 1024;
  408. const sizes = ['B', 'KB', 'MB', 'GB'];
  409. const i = Math.floor(Math.log(bytes) / Math.log(k));
  410. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  411. }
  412. // 格式化日期函数已移除
  413. // 启动后检查认证状态
  414. window.onload = function() {
  415. fetch('/check-auth', {
  416. credentials: 'include' // 确保发送cookies
  417. })
  418. .then(response => response.json())
  419. .then(data => {
  420. if (data.authenticated) {
  421. isLoggedIn = true;
  422. document.getElementById('loginPage').classList.add('hidden');
  423. document.getElementById('filePage').classList.remove('hidden');
  424. loadFiles('/luadb');
  425. }
  426. });
  427. };
  428. </script>
  429. </body>
  430. </html>