| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>HTML文件管理器</title> |
| <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> |
| <script src="https://unpkg.com/axios/dist/axios.min.js"></script> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; |
| background-color: #f5f5f5; |
| color: #333; |
| line-height: 1.6; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| .header { |
| background: white; |
| padding: 20px; |
| border-radius: 8px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| margin-bottom: 20px; |
| } |
| |
| .header h1 { |
| color: #2c3e50; |
| margin-bottom: 10px; |
| } |
| |
| .header p { |
| color: #7f8c8d; |
| } |
| |
| .toolbar { |
| background: white; |
| padding: 15px; |
| border-radius: 8px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| margin-bottom: 20px; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .btn { |
| background: #3498db; |
| color: white; |
| border: none; |
| padding: 8px 16px; |
| border-radius: 4px; |
| cursor: pointer; |
| font-size: 14px; |
| transition: background-color 0.3s; |
| } |
| |
| .btn:hover { |
| background: #2980b9; |
| } |
| |
| .btn-danger { |
| background: #e74c3c; |
| } |
| |
| .btn-danger:hover { |
| background: #c0392b; |
| } |
| |
| .btn-success { |
| background: #27ae60; |
| } |
| |
| .btn-success:hover { |
| background: #229954; |
| } |
| |
| .file-list { |
| background: white; |
| border-radius: 8px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| overflow: hidden; |
| } |
| |
| .file-item { |
| padding: 15px; |
| border-bottom: 1px solid #ecf0f1; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| transition: background-color 0.3s; |
| } |
| |
| .file-item:hover { |
| background-color: #f8f9fa; |
| } |
| |
| .file-item:last-child { |
| border-bottom: none; |
| } |
| |
| .file-info { |
| flex: 1; |
| } |
| |
| .file-title { |
| font-weight: bold; |
| color: #2c3e50; |
| margin-bottom: 5px; |
| } |
| |
| .file-meta { |
| font-size: 12px; |
| color: #7f8c8d; |
| } |
| |
| .file-actions { |
| display: flex; |
| gap: 10px; |
| } |
| |
| .loading { |
| text-align: center; |
| padding: 40px; |
| color: #7f8c8d; |
| } |
| |
| .empty { |
| text-align: center; |
| padding: 40px; |
| color: #7f8c8d; |
| } |
| |
| .modal { |
| display: none; |
| position: fixed; |
| z-index: 1000; |
| left: 0; |
| top: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0,0,0,0.5); |
| } |
| |
| .modal.show { |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .modal-content { |
| background-color: white; |
| padding: 20px; |
| border-radius: 8px; |
| width: 90%; |
| max-width: 800px; |
| max-height: 80vh; |
| overflow: auto; |
| } |
| |
| .modal-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 15px; |
| } |
| |
| .close { |
| font-size: 28px; |
| font-weight: bold; |
| cursor: pointer; |
| color: #aaa; |
| } |
| |
| .close:hover { |
| color: #000; |
| } |
| |
| .preview-frame { |
| width: 100%; |
| height: 400px; |
| border: 1px solid #ddd; |
| border-radius: 4px; |
| } |
| |
| .stats { |
| display: flex; |
| gap: 20px; |
| margin-bottom: 15px; |
| } |
| |
| .stat-item { |
| background: #ecf0f1; |
| padding: 10px 15px; |
| border-radius: 4px; |
| text-align: center; |
| } |
| |
| .stat-value { |
| font-size: 24px; |
| font-weight: bold; |
| color: #2c3e50; |
| } |
| |
| .stat-label { |
| font-size: 12px; |
| color: #7f8c8d; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| <div class="container"> |
| <div class="header"> |
| <h1>HTML文件管理器</h1> |
| <p>管理和预览已上传的HTML文件</p> |
| </div> |
| |
| <div class="toolbar"> |
| <div class="stats"> |
| <div class="stat-item"> |
| <div class="stat-value">{{ files.length }}</div> |
| <div class="stat-label">文件总数</div> |
| </div> |
| </div> |
| <button class="btn" @click="refreshFiles">刷新列表</button> |
| </div> |
| |
| <div class="file-list"> |
| <div v-if="loading" class="loading"> |
| 加载中... |
| </div> |
| |
| <div v-else-if="files.length === 0" class="empty"> |
| 暂无HTML文件 |
| </div> |
| |
| <div v-else> |
| <div v-for="file in files" :key="file.filename" class="file-item"> |
| <div class="file-info"> |
| <div class="file-title"> |
| {{ file.title || file.filename }} |
| </div> |
| <div class="file-meta"> |
| 文件名: {{ file.filename }} | 创建时间: {{ file.created_time }} |
| </div> |
| </div> |
| <div class="file-actions"> |
| <button class="btn btn-success" @click="previewFile(file)">预览</button> |
| <button class="btn" @click="openFile(file)">打开</button> |
| <button class="btn btn-danger" @click="deleteFile(file)">删除</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="modal" :class="{ show: showPreview }" @click="closePreview"> |
| <div class="modal-content" @click.stop> |
| <div class="modal-header"> |
| <h3>{{ currentFile?.title || currentFile?.filename || '预览' }}</h3> |
| <span class="close" @click="closePreview">×</span> |
| </div> |
| <iframe v-if="currentFile" :src="currentFile.url" class="preview-frame"></iframe> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const { createApp } = Vue; |
| |
| createApp({ |
| data() { |
| return { |
| files: [], |
| loading: false, |
| showPreview: false, |
| currentFile: null |
| }; |
| }, |
| methods: { |
| async loadFiles() { |
| this.loading = true; |
| try { |
| const response = await axios.get('/api/files'); |
| this.files = response.data.files; |
| } catch (error) { |
| console.error('加载文件列表失败:', error); |
| alert('加载文件列表失败'); |
| } finally { |
| this.loading = false; |
| } |
| }, |
| |
| refreshFiles() { |
| this.loadFiles(); |
| }, |
| |
| previewFile(file) { |
| this.currentFile = file; |
| this.showPreview = true; |
| }, |
| |
| closePreview() { |
| this.showPreview = false; |
| this.currentFile = null; |
| }, |
| |
| openFile(file) { |
| window.open(file.url, '_blank'); |
| }, |
| |
| async deleteFile(file) { |
| if (!confirm(`确定要删除文件 "${file.title || file.filename}" 吗?`)) { |
| return; |
| } |
| |
| try { |
| await axios.delete(`/api/files/${file.filename}`); |
| alert('文件删除成功'); |
| this.loadFiles(); |
| } catch (error) { |
| console.error('删除文件失败:', error); |
| alert('删除文件失败: ' + (error.response?.data?.detail || error.message)); |
| } |
| } |
| }, |
| |
| mounted() { |
| this.loadFiles(); |
| } |
| }).mount('#app'); |
| </script> |
| </body> |
| </html> |