Files
abot/admin/dashboard/templates/file_browser.html
2026-04-15 17:02:34 +08:00

155 lines
7.3 KiB
HTML

{% extends "base.html" %}
{% block title %}文件浏览 - 机器人管理后台{% endblock %}
{% block content %}
<div class="page-shell file-browser-page">
<div class="page-hero">
<div class="page-hero-copy">
<div class="page-eyebrow">Tools Workspace</div>
<h1>文件浏览</h1>
<p>将文件查看入口纳入同一套工具工作台,路径、导航和下载动作更清晰。</p>
</div>
<div class="page-hero-actions">
<div class="path-pill">{% raw %}{{ currentPath || '/' }}{% endraw %}</div>
<el-button type="primary" plain @click="loadFiles(currentPath === '/' ? '' : currentPath)"><i class="el-icon-refresh"></i> 刷新</el-button>
<el-button type="primary" @click="navigateUp"><i class="el-icon-arrow-up"></i> 上级目录</el-button>
</div>
</div>
<el-card class="workspace-card" shadow="hover">
<div slot="header" class="workspace-header">
<div>
<h3>文件列表</h3>
<p>支持目录切换、文件下载与修改时间查看。</p>
</div>
</div>
<el-table :data="fileList" style="width: 100%" v-loading="loading">
<el-table-column prop="name" label="名称" min-width="320">
<template slot-scope="scope">
<div class="entity-cell">
<div class="entity-avatar" :class="scope.row.is_dir ? 'entity-avatar--folder' : 'entity-avatar--file'">
<i :class="scope.row.is_dir ? 'el-icon-folder' : 'el-icon-document'"></i>
</div>
<div class="entity-copy">
<el-link :type="scope.row.is_dir ? 'primary' : 'info'" @click="scope.row.is_dir ? navigateTo(scope.row.name) : null">
{% raw %}{{ scope.row.name }}{% endraw %}
</el-link>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="110" align="center">
<template slot-scope="scope">{% raw %}{{ scope.row.is_dir ? '目录' : '文件' }}{% endraw %}</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="120" align="center">
<template slot-scope="scope">{% raw %}{{ scope.row.is_dir ? '-' : formatFileSize(scope.row.size) }}{% endraw %}</template>
</el-table-column>
<el-table-column prop="modified" label="修改时间" width="190">
<template slot-scope="scope">{% raw %}{{ formatDate(scope.row.modified) }}{% endraw %}</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template slot-scope="scope">
<el-button v-if="!scope.row.is_dir" type="primary" size="mini" @click="downloadFile(scope.row.name)">下载</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
currentPath: '',
fileList: [],
loading: false
}
},
created() {
this.currentView = '15';
this.loadFiles('');
},
methods: {
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
formatDate(timestamp) {
return new Date(timestamp * 1000).toLocaleString();
},
loadFiles(path) {
this.loading = true;
this.currentPath = path || '/';
axios.get('/api/list_files', { params: { path: path } })
.then(response => {
if (response.data.success) {
this.fileList = response.data.data.items;
} else {
this.$message.error('加载文件列表失败:' + response.data.message);
}
})
.catch(error => {
this.$message.error('加载文件列表失败');
console.error('Error:', error);
})
.finally(() => {
this.loading = false;
});
},
navigateTo(dirName) {
const newPath = this.currentPath === '/' ? dirName : this.currentPath + '/' + dirName;
this.loadFiles(newPath);
},
navigateUp() {
if (!this.currentPath || this.currentPath === '/') return;
const lastSlashIndex = this.currentPath.lastIndexOf('/');
if (lastSlashIndex <= 0) {
this.loadFiles('');
} else {
this.loadFiles(this.currentPath.substring(0, lastSlashIndex));
}
},
downloadFile(fileName) {
const filePath = this.currentPath === '/' ? fileName : this.currentPath + '/' + fileName;
window.location.href = '/api/download_file?path=' + encodeURIComponent(filePath);
}
}
});
</script>
<style>
.page-shell { display: flex; flex-direction: column; gap: 16px; }
.page-hero {
display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; padding: 24px 26px; border-radius: 24px;
background: linear-gradient(135deg, rgba(79,70,229,0.10), rgba(59,130,246,0.08), rgba(255,255,255,0.9));
border: 1px solid rgba(148, 163, 184, 0.16); box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
}
.page-hero-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.page-eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: #6366f1; font-weight: 700; margin-bottom: 8px; }
.page-hero-copy h1 { font-size: 30px; line-height: 1.1; margin-bottom: 10px; color: #0f172a; }
.page-hero-copy p { color: #64748b; font-size: 14px; }
.path-pill {
max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;
color: #475569; padding: 10px 14px; border-radius: 999px; background: rgba(255,255,255,0.82); border: 1px solid rgba(148,163,184,0.14);
}
.workspace-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.workspace-header h3 { font-size: 18px; margin-bottom: 4px; }
.workspace-header p { font-size: 13px; color: #64748b; }
.entity-cell { display: flex; align-items: center; gap: 12px; }
.entity-avatar {
width: 32px; height: 32px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
color: #4f46e5; background: rgba(79,70,229,0.10);
}
.entity-avatar--folder { color: #10b981; background: rgba(16,185,129,0.12); }
.entity-avatar--file { color: #3b82f6; background: rgba(59,130,246,0.10); }
</style>
{% endblock %}