Files
CloudFlare-ImgBed/admin-imgtc.html
MarSeventh 4e0c55d1f9 init
2024-07-19 23:26:06 +08:00

450 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ImgTC | Admin</title>
<!-- Import CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css">
<script src="https://js.sentry-cdn.com/219f636ac7bde5edab2c3e16885cb535.min.js" crossorigin="anonymous"></script>
<style>
body {
background: linear-gradient(90deg, #ffd7e4 0%, #c8f1ff 100%);
font-family: 'Arial', sans-serif;
color: #333;
margin: 0;
padding: 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: background-color 0.5s ease, box-shadow 0.5s ease;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
.header-content:hover {
background-color: rgba(255, 255, 255, 0.85);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.2);
}
.title {
font-size: 1.8em;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
color: #333;
}
.title:hover {
color: #B39DDB; /* 使用柔和的淡紫色 */
}
.search-card {
margin-left: auto;
margin-right: 20px;
}
.stats {
font-size: 1.2em;
margin-right: 20px;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
color: #333;
}
.stats .fa-database {
margin-right: 10px;
font-size: 1.5em;
transition: color 0.3s ease;
color: inherit;
}
.stats:hover {
background-color: #f0eaf8; /* 使用柔和的淡紫色 */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15);
color: #B39DDB; /* 使用柔和的淡紫色 */
}
.stats:hover .fa-database {
color: #B39DDB; /* 使用柔和的淡紫色 */
}
.header-content .actions {
display: flex;
align-items: center;
gap: 15px;
}
.header-content .actions i {
font-size: 1.5em;
cursor: pointer;
transition: color 0.3s, transform 0.3s;
color: #333;
}
.header-content .actions i:hover {
color: #B39DDB; /* 使用柔和的淡紫色 */
transform: scale(1.2);
}
.header-content .actions .el-dropdown-link i {
color: #333;
}
.header-content .actions .el-dropdown-link i:hover {
color: #B39DDB; /* 使用柔和的淡紫色 */
}
.header-content .actions .disabled {
color: #bbb;
pointer-events: none;
}
.header-content .actions .enabled {
color: #B39DDB; /* 使用柔和的淡紫色 */
}
.search-card .el-input__inner {
border-radius: 20px;
width: 300px;
height: 40px;
font-size: 1.2em;
border: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: width 0.3s;
}
.search-card .el-input__inner:focus {
width: 400px;
}
.main-container {
display: flex;
flex-direction: column;
padding: 20px;
min-height: calc(100vh - 80px);
}
.content {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 20px;
padding: 10px;
flex-grow: 1;
}
.el-card {
width: 100%;
background: rgba(255, 255, 255, 0.6);
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
transition: transform 0.3s ease;
}
.el-card:hover {
transform: scale(1.05);
}
.el-image {
width: 100%;
height: 200px;
object-fit: cover;
transition: opacity 0.3s ease;
}
.el-image:hover {
opacity: 0.8;
}
.file-info {
padding: 10px;
background: rgba(0, 0, 0, 0.6);
color: white;
text-align: center;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
.image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.el-card:hover .image-overlay {
opacity: 1;
}
.overlay-buttons {
display: flex;
gap: 10px;
pointer-events: auto;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
padding-bottom: 20px;
}
.el-checkbox {
position: absolute;
top: 10px;
right: 10px;
transform: scale(1.5);
z-index: 10;
}
</style>
</head>
<body>
<div id="app">
<el-container>
<el-header>
<div class="header-content">
<span class="title" @click="refreshDashboard">Dashboard</span>
<div class="search-card">
<el-input v-model="search" size="mini" placeholder="输入关键字搜索"></el-input>
</div>
<span class="stats">
<i class="fas fa-database"></i> 记录总数量: {{ Number }}
</span>
<div class="actions">
<el-tooltip content="排序" placement="bottom">
<el-dropdown @command="sort" :hide-on-click="false">
<span class="el-dropdown-link">
<i :class="sortIcon"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="dateDesc" :class="{ 'el-dropdown-menu__item--selected': sortOption === 'dateDesc' }">按时间倒序</el-dropdown-item>
<el-dropdown-item command="nameAsc" :class="{ 'el-dropdown-menu__item--selected': sortOption === 'nameAsc' }">按名称升序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-tooltip>
<el-tooltip content="批量复制" placement="bottom">
<i class="fas fa-link" :class="{ disabled: selectedFiles.length === 0 }" @click="handleBatchCopy"></i>
</el-tooltip>
<el-tooltip content="批量删除" placement="bottom">
<i class="fas fa-trash-alt" :class="{ disabled: selectedFiles.length === 0 }" @click="handleBatchDelete"></i>
</el-tooltip>
<el-tooltip content="退出登录" placement="bottom">
<i class="fas fa-home" @click="handleLogout"></i>
</el-tooltip>
</div>
</div>
</el-header>
<el-main class="main-container">
<div class="content">
<template v-for="(item, index) in paginatedTableData" :key="index">
<el-card>
<el-checkbox v-model="item.selected"></el-checkbox>
<el-image
:src="'/file/' + item.name"
:preview-src-list="['/file/' + item.name]"
fit="cover"
lazy></el-image>
<div class="image-overlay">
<div class="overlay-buttons">
<el-button size="mini" type="primary" @click.stop="handleCopy(index, item.name)">复制地址</el-button>
<el-button size="mini" type="danger" @click.stop="handleDelete(index, item.name)">删除</el-button>
</div>
</div>
<div class="file-info">{{ item.name }}</div>
</el-card>
</template>
</div>
<div class="pagination-container">
<el-pagination
background
layout="prev, pager, next"
:total="filteredTableData.length"
:page-size="pageSize"
@current-change="handlePageChange"
:current-page.sync="currentPage">
</el-pagination>
</div>
</el-main>
</el-container>
</div>
<!-- Import Vue before Element -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<!-- Import JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data: {
Number: 0,
showLogoutButton: false,
tableData: [],
search: '',
currentPage: 1,
pageSize: 15,
selectedFiles: [],
sortOption: 'dateDesc',
isUploading: false
},
computed: {
filteredTableData() {
return this.tableData.filter(data => !this.search || data.name.toLowerCase().includes(this.search.toLowerCase()));
},
paginatedTableData() {
const sortedData = this.sortData(this.filteredTableData);
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return sortedData.slice(start, end);
},
sortIcon() {
return this.sortOption === 'dateDesc' ? 'fas fa-sort-amount-down' : 'fas fa-sort-alpha-up';
}
},
watch: {
tableData: {
handler(newData) {
this.selectedFiles = newData.filter(file => file.selected);
},
deep: true
},
sortOption(newOption) {
localStorage.setItem('sortOption', newOption);
}
},
methods: {
refreshDashboard() {
location.reload();
},
handleDelete(index, key) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
fetch(`./api/manage/delete/${key}`, { method: 'GET', credentials: 'include' })
.then(response => response.ok ? this.tableData.splice(index, 1) : Promise.reject())
.then(() => {
this.updateStats();
this.$message.success('删除成功!');
})
.catch(() => this.$message.error('删除失败,请检查网络连接'));
}).catch(() => this.$message.info('已取消删除'));
},
handleBatchDelete() {
this.$confirm('此操作将永久删除选中的文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const promises = this.selectedFiles.map(file => fetch(`./api/manage/delete/${file.name}`, { method: 'GET', credentials: 'include' }));
Promise.all(promises)
.then(results => {
results.forEach((response, index) => {
if (response.ok) {
const fileIndex = this.tableData.findIndex(file => file.name === this.selectedFiles[index].name);
if (fileIndex !== -1) {
this.tableData.splice(fileIndex, 1);
}
}
});
this.selectedFiles = [];
this.updateStats();
this.$message.success('批量删除成功!');
})
.catch(() => this.$message.error('批量删除失败,请检查网络连接'));
}).catch(() => this.$message.info('已取消批量删除'));
},
handleBatchCopy() {
const links = this.selectedFiles.map(file => `${document.location.origin}/file/${file.name}`).join('\n');
navigator.clipboard ? navigator.clipboard.writeText(links).then(() => this.$message.success('批量复制链接成功~')) :
this.copyToClipboardFallback(links);
},
copyToClipboardFallback(text) {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.style.position = 'fixed';
textarea.style.clip = 'rect(0 0 0 0)';
textarea.style.top = '10px';
textarea.value = text;
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
this.$message.success('批量复制链接成功~');
},
handleLogout() {
window.location.href = '/';
},
handleCopy(index, key) {
const text = `${document.location.origin}/file/${key}`;
navigator.clipboard ? navigator.clipboard.writeText(text).then(() => this.$message.success('复制文件链接成功~')) :
this.copyToClipboardFallback(text);
},
handlePageChange(page) {
this.currentPage = page;
},
updateStats() {
this.Number = this.tableData.length;
},
sort(command) {
this.sortOption = command;
},
sortData(data) {
return this.sortOption === 'nameAsc' ? data.sort((a, b) => a.name.localeCompare(b.name)) :
data.sort((a, b) => b.metadata.TimeStamp - a.metadata.TimeStamp);
}
},
mounted() {
fetch("./api/manage/check", { method: 'GET', credentials: 'include' })
.then(response => response.text())
.then(result => result === "true" ? this.showLogoutButton = true : window.location.href = "./api/manage/login")
.catch(() => this.$message.error('同步数据时出错,请检查网络连接'));
fetch("./api/manage/list", { method: 'GET', credentials: 'include' })
.then(response => response.json())
.then(result => {
this.tableData = result.map(file => ({ ...file, selected: false }));
this.updateStats();
const savedSortOption = localStorage.getItem('sortOption');
if (savedSortOption) {
this.sortOption = savedSortOption;
}
this.sortData(this.tableData);
})
.catch(() => this.$message.error('同步数据时出错,请检查网络连接'));
}
});
</script>
</body>
</html>