mirror of
https://github.com/MarSeventh/CloudFlare-ImgBed.git
synced 2026-01-31 09:03:19 +08:00
切换上传渠道为telegram
This commit is contained in:
40
README.md
40
README.md
@@ -1,6 +1,6 @@
|
||||
# CloudFlare-ImgBed
|
||||
|
||||
免费图片托管解决方案,基于 Cloudflare Pages 和 telegra.ph(图片通过压缩,不受5MB大小限制) 。
|
||||
免费图片托管解决方案,基于 Cloudflare Pages 和 Telegram 。
|
||||
|
||||
**体验地址**:[Sanyue ImgHub (demo-cloudflare-imgbed.pages.dev)](https://demo-cloudflare-imgbed.pages.dev/)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## 1.Introduction
|
||||
|
||||
免费图片托管解决方案,保留原版全部功能的基础上,实现了**登录鉴权**、**页面自定义**、**上传图片预览**、**一键切换上传方式**(**拖拽上传**、**粘贴上传**)、**多文件上传**、**整体复制**、**多格式复制**、**上传前自动压缩**(可上传>5MB图片)等功能。
|
||||
免费图片托管解决方案,保留原版全部功能的基础上,实现了**登录鉴权**、**页面自定义**、**上传图片预览**、**一键切换上传方式**(**拖拽上传**、**粘贴上传**)、**多文件上传**、**整体复制**、**多格式复制**、**上传前自动压缩**(提升加载性能)等功能。
|
||||
|
||||

|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 2.Features
|
||||
|
||||
- **开源**
|
||||
@@ -41,7 +43,7 @@
|
||||
- 支持批量上传(不限同时选择文件数量,但为了保证稳定性,同时处于上传状态的文件最多为10个)
|
||||
- 上传显示实时上传进度
|
||||
- **上传后图片无需手动点击,可直接展示在管理页面中**
|
||||
- **突破5MB大小限制,超过5MB的图片上传前会自动压缩**(超过5MB的视频暂不支持)
|
||||
- **大于5MB在前端进行压缩,提升上传稳定性和加载性能**
|
||||
|
||||
- **多样化复制**
|
||||
|
||||
@@ -78,13 +80,31 @@
|
||||
|
||||
### 3.1直接使用
|
||||
|
||||
**部署方式**与**环境变量**和原仓库保持一致。(**修改完环境变量,重新部署才能生效**,见[3.1章最后一节](#3.1.10注意!!!))
|
||||
**注意修改完环境变量,重新部署才能生效**,见[3.1章最后一节](#3.1.10注意!!!)
|
||||
|
||||
#### 3.1.1提前准备
|
||||
|
||||
- **Telegram的`BOT_TOKEN`和`CHAT_ID`**
|
||||
|
||||
首先需要拥有一个Telegram账户,然后按照以下步骤获取`BOT_TOKEN`和`CHAT_ID`。
|
||||
|
||||
1. 向[@BotFather](https://t.me/BotFather)发送`/newbot`,按照提示输入bot的备注、用户名等信息。成功创建后获得`BOT_TOKEN`。
|
||||
|
||||

|
||||
|
||||
2. 创建一个新的频道(Channel),进入新建的频道,选择频道管理,将刚才创建的机器人设为频道管理员。
|
||||
|
||||
<img src="C:\Users\King.xx\AppData\Roaming\Typora\typora-user-images\image-20240907174731039.png" style="zoom:50%;" />
|
||||
|
||||
<img src="C:\Users\King.xx\AppData\Roaming\Typora\typora-user-images\image-20240907174830310.png" alt="image-20240907174830310" style="zoom:50%;" />
|
||||
|
||||
3. 向[@VersaToolsBot](https://t.me/VersaToolsBot)发消息,按步骤操作获取`CHAT_ID`(频道ID)
|
||||
|
||||

|
||||
|
||||
- **部署于Cloudflare**
|
||||
|
||||
只需准备一个**Cloudflare账户**,然后按照[3.1.2.1节](#3.1.2.1部署于Cloudflare)的步骤即可完成部署。
|
||||
需准备一个**Cloudflare账户**,然后按照[3.1.2.1节](#3.1.2.1部署于Cloudflare)的步骤即可完成部署。
|
||||
|
||||
- **部署于服务器**
|
||||
|
||||
@@ -96,7 +116,7 @@
|
||||
|
||||
##### 3.1.2.1部署于Cloudflare
|
||||
|
||||
依托于CF的强大能力,只需简单 3 步,即可部署本项目,拥有自己的图床。
|
||||
依托于CF的强大能力,只需简单几步,即可部署本项目,拥有自己的图床。
|
||||
|
||||
1. Fork 本仓库 (注意:必须使用 Git 或者 Wrangler 命令行工具部署后才能正常使用,[文档](https://developers.cloudflare.com/pages/functions/get-started/#deploy-your-function))
|
||||
|
||||
@@ -104,7 +124,9 @@
|
||||
|
||||

|
||||
|
||||
3. 按照页面提示输入项目名称,选择需要连接的 git 仓库,点击`部署站点`即可完成部署
|
||||
3. 按照页面提示输入项目名称,选择需要连接的 git 仓库,点击`部署站点`
|
||||
3. 将3.1.1中获取的`BOT_TOKEN`和`CHAT_ID`分别添加到环境变量中,对应**环境变量名为`TG_BOT_TOKEN`和`TG_CHAT_ID`**。
|
||||
3. `重试部署`,此时项目即可正常使用
|
||||
|
||||
##### 3.1.2.2部署于服务器
|
||||
|
||||
@@ -112,7 +134,7 @@
|
||||
|
||||
2. 切换到项目根目录,运行`npm install`,安装所需依赖。
|
||||
|
||||
3. 在项目根目录下新建`wrangler.toml`配置文件,其内容为项目名称,环境变量等,可根据后文环境变量配置进行个性化修改。(详情参见官方文档[Configuration - Wrangler (cloudflare.com)](https://developers.cloudflare.com/workers/wrangler/configuration/))
|
||||
3. 在项目根目录下新建`wrangler.toml`配置文件,其内容为项目名称,环境变量(**包括`TG_BOT_TOKEN`和`TG_CHAT_ID`等必填参数**)等,可根据后文环境变量配置进行个性化修改。(详情参见官方文档[Configuration - Wrangler (cloudflare.com)](https://developers.cloudflare.com/workers/wrangler/configuration/))
|
||||
|
||||
> 配置文件样例:
|
||||
>
|
||||
@@ -125,6 +147,8 @@
|
||||
> AllowRandom = "true"
|
||||
> BASIC_USER = "user"
|
||||
> BASIC_PASS = "pass"
|
||||
> TG_BOT_TOKEN = "your_bot_token"
|
||||
> TG_CHAT_ID = "your_bot_id"
|
||||
> ```
|
||||
|
||||
4. 在项目根目录下运行`npm run start`,至此,正常情况下项目已经成功部署。
|
||||
|
||||
341
admin-detail.html
Normal file
341
admin-detail.html
Normal file
@@ -0,0 +1,341 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/theme-chalk/index.css" integrity="sha256-ghr1zmXTODLKl1HULQd6fq1MIe7m3FJiNTOCT8sddLM=" crossorigin="anonymous">
|
||||
<style>
|
||||
.el-image__inner.el-image__inner {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
}
|
||||
.el-image {
|
||||
text-align: center;
|
||||
}
|
||||
.el-button+.el-button {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script src="https://js.sentry-cdn.com/219f636ac7bde5edab2c3e16885cb535.min.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div style="
|
||||
margin: auto;
|
||||
line-height: 60px;
|
||||
font-size: xx-large;
|
||||
position: relative;
|
||||
">Dashboard
|
||||
|
||||
|
||||
<span style="
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
" v-if="showLogoutButton">
|
||||
<a href="./admin.html">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="warning"
|
||||
>主管理页</el-button>
|
||||
</a>
|
||||
<a href="./admin-waterfall.html">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
>瀑布流</el-button>
|
||||
</a>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="info"
|
||||
@click="handleLogout()">退出登录</el-button></span></div>
|
||||
</el-header>
|
||||
<el-main><el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="always">
|
||||
记录总数量:
|
||||
{{ Number }}
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!--<el-col :span="8">
|
||||
<el-card shadow="hover">
|
||||
<el-tooltip class="item" effect="dark" content="白名单数量" placement="top-start">
|
||||
|
||||
</el-tooltip>
|
||||
白名单数量:{{ WhiteList }}
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover">
|
||||
<el-tooltip class="item" effect="dark" content="黑名单数量" placement="top-start">
|
||||
|
||||
</el-tooltip>
|
||||
黑名单数量:{{ BlackList }}
|
||||
</el-card>
|
||||
</el-col>-->
|
||||
</el-row>
|
||||
<template>
|
||||
<el-table
|
||||
:data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
label="name"
|
||||
prop="name">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="preview"
|
||||
prop="preview"
|
||||
align="center">
|
||||
<template slot-scope="scope">
|
||||
<video v-if="scope.row.name.indexOf('.mp4')>0" style="width: 100%; height: 180px;" controls>
|
||||
<source :src="'/file/'+scope.row.name" type="video/mp4">
|
||||
</video>
|
||||
<el-image
|
||||
v-else
|
||||
style="width: 100%; height: 100%;"
|
||||
:src="'/file/'+scope.row.name"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="['/file/'+scope.row.name]"
|
||||
fit="cover"
|
||||
lazy
|
||||
/>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="data"
|
||||
prop="data">
|
||||
<template slot-scope="scope">
|
||||
<el-popover trigger="hover" placement="top">
|
||||
<p>{{ scope.row.metadata }}</p>
|
||||
<div slot="reference" class="name-wrapper">
|
||||
<el-tag size="medium">{{ scope.row.metadata }}</el-tag>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="right">
|
||||
<template slot="header" slot-scope="scope">
|
||||
<el-input
|
||||
v-model="search"
|
||||
size="mini"
|
||||
placeholder="输入关键字搜索"/>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleCopy(scope.$index,scope.row.name)">复制地址</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleWhite(scope.$index,scope.row.name)">白名单</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="info"
|
||||
@click="handleBlock(scope.$index,scope.row.name)">黑名单</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.$index,scope.row.name)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</body>
|
||||
<!-- import Vue before Element -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js" integrity="sha256-kXTEJcRFN330VirZFl6gj9+UM6gIKW195fYZeR3xDhc=" crossorigin="anonymous"></script>
|
||||
<!-- import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/index.js" integrity="sha256-OFVFYfqhQ9nDnKh+NfIsefpy/fnjTwkK909ZYgo45nw=" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
var app=new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
Number:0,
|
||||
WhiteList:0,
|
||||
BlackList:0,
|
||||
showLogoutButton:false,
|
||||
tableData: [],
|
||||
dialogFormVisible: false,
|
||||
formLabelWidth: '120px',
|
||||
form: {
|
||||
name: '',
|
||||
id: ''
|
||||
},
|
||||
search: '',
|
||||
password:'123456'
|
||||
},
|
||||
methods: {
|
||||
handleBlock(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认加入黑名单吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
|
||||
fetch("./api/manage/block/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);
|
||||
this.tableData[index].metadata=result;})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
},
|
||||
handleDelete(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认删除该条记录吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
|
||||
fetch("./api/manage/delete/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);this.tableData.remove(index);})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
},
|
||||
handleWhite(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认加入白名单吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
fetch("./api/manage/white/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);this.tableData[index].metadata=result;})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
|
||||
},
|
||||
handleLogout(){
|
||||
window.location.href="./api/manage/logout";
|
||||
},
|
||||
handleCopy(index, key) {
|
||||
const text = `${document.location.origin}/file/${key}`;
|
||||
if (navigator.clipboard) {
|
||||
// clipboard api 复制
|
||||
navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
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', true);
|
||||
// 移除输入框
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
this.$message({
|
||||
message: '复制文件链接成功~',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
//check if the user is logged in
|
||||
//read the basic auth credientials from the browser
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
fetch("./api/manage/check", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);
|
||||
if(result=="true"){
|
||||
this.showLogoutButton=true;
|
||||
}else if(result=="Not using basic auth."){
|
||||
|
||||
}
|
||||
else{
|
||||
window.location.href="./api/manage/login";
|
||||
}
|
||||
})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
|
||||
|
||||
Array.prototype.remove = function(from, to) {
|
||||
var rest = this.slice((to || from) + 1 || this.length);
|
||||
this.length = from < 0 ? this.length + from : from;
|
||||
return this.push.apply(this, rest);
|
||||
};
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
|
||||
};
|
||||
|
||||
|
||||
fetch("./api/manage/list", requestOptions)
|
||||
//判断是否需要登录
|
||||
.then(response => {
|
||||
if(response.status==401){
|
||||
alert("请先登录");
|
||||
window.location.href="./api/manage/login";
|
||||
}
|
||||
else{
|
||||
return response;
|
||||
}
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {this.tableData=JSON.parse(result);console.log(result);this.Number=this.tableData.length})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
</script><!-- Hotjar Tracking Code -->
|
||||
<script>
|
||||
(function(h,o,t,j,a,r){
|
||||
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||
h._hjSettings={hjid:2531461,hjsv:6};
|
||||
a=o.getElementsByTagName('head')[0];
|
||||
r=o.createElement('script');r.async=1;
|
||||
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
(function(c,l,a,r,i,t,y){
|
||||
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
||||
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
||||
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
||||
})(window, document, "clarity", "script", "7t5ai7agat");
|
||||
</script>
|
||||
</html>
|
||||
450
admin-imgtc.html
450
admin-imgtc.html
@@ -1,450 +0,0 @@
|
||||
<!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>
|
||||
747
admin.html
747
admin.html
@@ -1,341 +1,456 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/theme-chalk/index.css" integrity="sha256-ghr1zmXTODLKl1HULQd6fq1MIe7m3FJiNTOCT8sddLM=" crossorigin="anonymous">
|
||||
<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>
|
||||
.el-image__inner.el-image__inner {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
}
|
||||
.el-image {
|
||||
text-align: center;
|
||||
}
|
||||
.el-button+.el-button {
|
||||
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>
|
||||
<script src="https://js.sentry-cdn.com/219f636ac7bde5edab2c3e16885cb535.min.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<div style="
|
||||
margin: auto;
|
||||
line-height: 60px;
|
||||
font-size: xx-large;
|
||||
position: relative;
|
||||
">Dashboard
|
||||
|
||||
|
||||
<span style="
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
" v-if="showLogoutButton">
|
||||
<a href="./admin-imgtc.html">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="warning"
|
||||
>网格视图</el-button>
|
||||
</a>
|
||||
<a href="./admin-waterfall.html">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
>瀑布流</el-button>
|
||||
</a>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="info"
|
||||
@click="handleLogout()">退出登录</el-button></span></div>
|
||||
</el-header>
|
||||
<el-main><el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="always">
|
||||
记录总数量:
|
||||
{{ Number }}
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!--<el-col :span="8">
|
||||
<el-card shadow="hover">
|
||||
<el-tooltip class="item" effect="dark" content="白名单数量" placement="top-start">
|
||||
|
||||
</el-tooltip>
|
||||
白名单数量:{{ WhiteList }}
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover">
|
||||
<el-tooltip class="item" effect="dark" content="黑名单数量" placement="top-start">
|
||||
|
||||
</el-tooltip>
|
||||
黑名单数量:{{ BlackList }}
|
||||
</el-card>
|
||||
</el-col>-->
|
||||
</el-row>
|
||||
<template>
|
||||
<el-table
|
||||
:data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
label="name"
|
||||
prop="name">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="preview"
|
||||
prop="preview"
|
||||
align="center">
|
||||
<template slot-scope="scope">
|
||||
<video v-if="scope.row.name.indexOf('.mp4')>0" style="width: 100%; height: 180px;" controls>
|
||||
<source :src="'/file/'+scope.row.name" type="video/mp4">
|
||||
</video>
|
||||
<el-image
|
||||
v-else
|
||||
style="width: 100%; height: 100%;"
|
||||
:src="'/file/'+scope.row.name"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="['/file/'+scope.row.name]"
|
||||
fit="cover"
|
||||
lazy
|
||||
/>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="data"
|
||||
prop="data">
|
||||
<template slot-scope="scope">
|
||||
<el-popover trigger="hover" placement="top">
|
||||
<p>{{ scope.row.metadata }}</p>
|
||||
<div slot="reference" class="name-wrapper">
|
||||
<el-tag size="medium">{{ scope.row.metadata }}</el-tag>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="right">
|
||||
<template slot="header" slot-scope="scope">
|
||||
<el-input
|
||||
v-model="search"
|
||||
size="mini"
|
||||
placeholder="输入关键字搜索"/>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleCopy(scope.$index,scope.row.name)">复制地址</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleWhite(scope.$index,scope.row.name)">白名单</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="info"
|
||||
@click="handleBlock(scope.$index,scope.row.name)">黑名单</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.$index,scope.row.name)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<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-user-cog" @click="handleGoToAdmin"></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.metadata?.FileName || item.name }}</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-main>
|
||||
</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>
|
||||
</body>
|
||||
<!-- import Vue before Element -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js" integrity="sha256-kXTEJcRFN330VirZFl6gj9+UM6gIKW195fYZeR3xDhc=" crossorigin="anonymous"></script>
|
||||
<!-- import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.3/lib/index.js" integrity="sha256-OFVFYfqhQ9nDnKh+NfIsefpy/fnjTwkK909ZYgo45nw=" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- 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>
|
||||
var app=new Vue({
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
Number:0,
|
||||
WhiteList:0,
|
||||
BlackList:0,
|
||||
showLogoutButton:false,
|
||||
Number: 0,
|
||||
showLogoutButton: false,
|
||||
tableData: [],
|
||||
dialogFormVisible: false,
|
||||
formLabelWidth: '120px',
|
||||
form: {
|
||||
name: '',
|
||||
id: ''
|
||||
},
|
||||
search: '',
|
||||
password:'123456'
|
||||
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: {
|
||||
handleBlock(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认加入黑名单吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
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' }));
|
||||
|
||||
fetch("./api/manage/block/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);
|
||||
this.tableData[index].metadata=result;})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
},
|
||||
handleDelete(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认删除该条记录吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
|
||||
fetch("./api/manage/delete/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);this.tableData.remove(index);})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
},
|
||||
handleWhite(index,key) {
|
||||
console.log(key);
|
||||
if (confirm("确认加入白名单吗?")) {
|
||||
console.log("Yes")
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
fetch("./api/manage/white/"+key, requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);this.tableData[index].metadata=result;})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
} else {
|
||||
console.log("No")
|
||||
}
|
||||
|
||||
},
|
||||
handleLogout(){
|
||||
window.location.href="./api/manage/logout";
|
||||
},
|
||||
handleCopy(index, key) {
|
||||
const text = `${document.location.origin}/file/${key}`;
|
||||
if (navigator.clipboard) {
|
||||
// clipboard api 复制
|
||||
navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
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', true);
|
||||
// 移除输入框
|
||||
document.body.removeChild(textarea);
|
||||
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 = '/';
|
||||
},
|
||||
handleGoToAdmin() {
|
||||
window.location.href = '/admin-detail';
|
||||
},
|
||||
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);
|
||||
}
|
||||
this.$message({
|
||||
message: '复制文件链接成功~',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
//check if the user is logged in
|
||||
//read the basic auth credientials from the browser
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
};
|
||||
fetch("./api/manage/check", requestOptions)
|
||||
.then(response => response.text())
|
||||
.then(result => {console.log(result);
|
||||
if(result=="true"){
|
||||
this.showLogoutButton=true;
|
||||
}else if(result=="Not using basic auth."){
|
||||
|
||||
}
|
||||
else{
|
||||
window.location.href="./api/manage/login";
|
||||
}
|
||||
})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
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('同步数据时出错,请检查网络连接'));
|
||||
|
||||
|
||||
|
||||
Array.prototype.remove = function(from, to) {
|
||||
var rest = this.slice((to || from) + 1 || this.length);
|
||||
this.length = from < 0 ? this.length + from : from;
|
||||
return this.push.apply(this, rest);
|
||||
};
|
||||
var requestOptions = {
|
||||
method: 'GET',
|
||||
redirect: 'follow',
|
||||
//include authorization credientials
|
||||
credentials: 'include'
|
||||
|
||||
};
|
||||
|
||||
|
||||
fetch("./api/manage/list", requestOptions)
|
||||
//判断是否需要登录
|
||||
.then(response => {
|
||||
if(response.status==401){
|
||||
alert("请先登录");
|
||||
window.location.href="./api/manage/login";
|
||||
}
|
||||
else{
|
||||
return response;
|
||||
}
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {this.tableData=JSON.parse(result);console.log(result);this.Number=this.tableData.length})
|
||||
.catch(error => {alert("An error occurred while synchronizing data with the server, please check the network connection");console.log('error', error)});
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
</script><!-- Hotjar Tracking Code -->
|
||||
<script>
|
||||
(function(h,o,t,j,a,r){
|
||||
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||
h._hjSettings={hjid:2531461,hjsv:6};
|
||||
a=o.getElementsByTagName('head')[0];
|
||||
r=o.createElement('script');r.async=1;
|
||||
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
(function(c,l,a,r,i,t,y){
|
||||
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
||||
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
||||
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
||||
})(window, document, "clarity", "script", "7t5ai7agat");
|
||||
</script>
|
||||
</html>
|
||||
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>
|
||||
1
css/531.62a86c8f.css
Normal file
1
css/531.62a86c8f.css
Normal file
@@ -0,0 +1 @@
|
||||
@keyframes breathe-2e593758{50%{box-shadow:0 0 10px 5px #409eff;opacity:.8}}.upload-form[data-v-2e593758],.upload-list-card[data-v-2e593758]{display:flex;flex-direction:column;justify-content:center;align-items:center}.upload-list-card[data-v-2e593758]{width:55vw;height:7vh;margin-top:10px;border-radius:15px;opacity:.8;background-color:hsla(0,0%,100%,.7);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.upload-list-container[data-v-2e593758]{width:55vw;height:7vh}.upload-list-card.upload-list-busy[data-v-2e593758],.upload-list-container.upload-list-busy[data-v-2e593758]{height:35vh}.upload-list-item[data-v-2e593758]{display:flex;align-items:center;justify-content:space-between;margin:5px;border:1px solid #a5bef7;padding:5px;border-radius:15px}.upload-list-item-name[data-v-2e593758]{font-size:small;font-weight:700;width:28vw;margin-bottom:5px}.upload-list-item-content[data-v-2e593758]{display:flex;flex-direction:column;margin-left:10px}.upload-list-item-url-text[data-v-2e593758]{width:28vw}.upload-list-item-url-row[data-v-2e593758]{display:flex;flex-direction:row;align-items:center}.upload-list-item-progress[data-v-2e593758]{margin-top:3px;width:28vw}.upload-list-item-action[data-v-2e593758]{display:flex;flex-direction:column;align-items:center}.upload-list-item-action-button[data-v-2e593758]{margin:2px}.upload-card[data-v-2e593758]{width:55vw;padding:20px;background:none}.upload-card-busy[data-v-2e593758] .el-upload-dragger{height:25vh}.paste-mode[data-v-2e593758] .el-upload{pointer-events:none}[data-v-2e593758] .el-upload-dragger{display:flex;flex-direction:column;justify-content:center;align-items:center;height:45vh;border-radius:15px;border:3px dashed #409eff;opacity:.7;background-color:hsla(0,0%,100%,.6);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all .3s ease}[data-v-2e593758] .el-upload-dragger.is-dragover,[data-v-2e593758] .el-upload-dragger:hover{opacity:.8;box-shadow:0 0 10px 5px #409eff}.is-uploading[data-v-2e593758] .el-upload-dragger{animation:breathe-2e593758 3s infinite}.el-upload__text[data-v-2e593758]{font-weight:700;font-size:medium;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-upload__tip[data-v-2e593758]{font-size:small;color:#faebd7;-webkit-user-select:none;-moz-user-select:none;user-select:none}.upload-list-dashboard[data-v-2e593758]{display:flex;justify-content:space-between;align-items:center;padding:10px}.upload-list-dashboard-title[data-v-2e593758]{font-size:medium;font-weight:700}.page-footer[data-v-7c801e22]{position:fixed;bottom:0;display:flex;justify-content:center;align-items:center;width:100vw;color:#f0f8ff;font-size:large;-webkit-user-select:none;-moz-user-select:none;user-select:none}.footer-name[data-v-7c801e22]{color:#faebd7;font-weight:700;text-decoration:none}.toolbar[data-v-124b8fea]{position:fixed;bottom:8vh;right:1.5vw;display:flex;flex-direction:column;align-items:center;z-index:100}.toolbar-button[data-v-124b8fea]{border:none;transition:all .3s ease;margin-bottom:10px;margin-left:0}.toolbar-button[data-v-124b8fea]:hover{box-shadow:0 0 10px 0 rgba(0,0,0,.1);transform:translateY(-3px)}[data-v-124b8fea] .el-dialog{border-radius:12px;background-color:hsla(0,0%,100%,.7);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);box-shadow:0 0 10px 2px rgba(0,0,0,.1)}.dialog-action[data-v-124b8fea]{display:flex;justify-content:center;margin-top:20px}.header[data-v-124b8fea]{display:flex;justify-content:center;align-items:center;padding:15px;position:fixed;top:5vh;color:#ffebcd;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-decoration:none}.main-title[data-v-124b8fea]{background:linear-gradient(90deg,#effac3,#f3a060);-webkit-background-clip:text;background-clip:text;color:transparent;text-decoration:none}.logo[data-v-124b8fea]{height:80px;width:80px;margin-right:5px}.upload-home[data-v-124b8fea]{display:flex;flex-direction:column;justify-content:center;align-items:center;transition:background-image 1s ease-in-out;background-size:cover;background-attachment:fixed;height:100vh}.upload[data-v-124b8fea]{position:fixed;top:20vh}.background-image1[data-v-124b8fea],.background-image2[data-v-124b8fea]{position:fixed;top:0;left:0;width:100%;height:100%;-o-object-fit:cover;object-fit:cover;z-index:-1;opacity:0;transition:all 1s ease-in-out}
|
||||
@@ -27,14 +27,23 @@ export async function onRequest(context) { // Contents of context object
|
||||
return Response.redirect(new URL("/block-img.html", request.url).href, 302); // Ensure URL is correctly formed
|
||||
}
|
||||
}
|
||||
const imgRecord = await env.img_url.getWithMetadata(params.id);
|
||||
|
||||
const response = fetch('https://telegra.ph/' + url.pathname + url.search, {
|
||||
let targetUrl = '';
|
||||
if (imgRecord.metadata?.Channel === 'Telegram') {
|
||||
targetUrl = `https://api.telegram.org/file/bot${env.TG_BOT_TOKEN}/${imgRecord.metadata.TgFilePath}`;
|
||||
} else {
|
||||
targetUrl = 'https://telegra.ph/' + url.pathname + url.search;
|
||||
}
|
||||
const fileName = imgRecord.metadata?.FileName || 'file';
|
||||
const encodedFileName = encodeURIComponent(fileName);
|
||||
const fileType = imgRecord.metadata?.FileType || 'image/jpeg';
|
||||
|
||||
const response = await fetch(targetUrl, {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
}).then(async (response) => {
|
||||
console.log(response.ok); // true if the response status is 2xx
|
||||
console.log(response.status); // 200
|
||||
if (response.ok) {
|
||||
// Referer header equal to the admin page
|
||||
console.log(url.origin + "/admin")
|
||||
@@ -46,8 +55,6 @@ export async function onRequest(context) { // Contents of context object
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") { } else {
|
||||
//check the record from kv
|
||||
const record = await env.img_url.getWithMetadata(params.id);
|
||||
console.log("record")
|
||||
console.log(record)
|
||||
if (record.metadata === null) {
|
||||
|
||||
} else {
|
||||
@@ -93,7 +100,6 @@ export async function onRequest(context) { // Contents of context object
|
||||
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
|
||||
console.log("Not enbaled KV")
|
||||
|
||||
} else {
|
||||
//add image to kv
|
||||
await env.img_url.put(params.id, "", {
|
||||
@@ -102,7 +108,7 @@ export async function onRequest(context) { // Contents of context object
|
||||
|
||||
}
|
||||
} else {
|
||||
await fetch(`https://api.moderatecontent.com/moderate/?key=` + apikey + `&url=https://telegra.ph/` + url.pathname + url.search).
|
||||
await fetch(`https://api.moderatecontent.com/moderate/?key=` + apikey + `&url=${targetUrl}`).
|
||||
then(async (response) => {
|
||||
let moderate_data = await response.json();
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") { } else {
|
||||
@@ -121,6 +127,12 @@ export async function onRequest(context) { // Contents of context object
|
||||
return response;
|
||||
});
|
||||
|
||||
return response;
|
||||
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set('Content-Disposition', `inline; filename="${encodedFileName}"`);
|
||||
headers.set('Content-Type', fileType);
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,30 @@ function getCookieValue(cookies, name) {
|
||||
|
||||
export async function onRequestPost(context) { // Contents of context object
|
||||
const { request, env, params, waitUntil, next, data } = context;
|
||||
|
||||
// await errorHandling(context);
|
||||
// telemetryData(context);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const clonedRequest = await request.clone();
|
||||
|
||||
const formdata = await request.formData();
|
||||
const fileType = formdata.get('file').type;
|
||||
const fileName = formdata.get('file').name;
|
||||
|
||||
const fileTypeMap = {
|
||||
'image/': {'url': 'sendPhoto', 'type': 'photo'},
|
||||
'video/': {'url': 'sendVideo', 'type': 'video'},
|
||||
'audio/': {'url': 'sendAudio', 'type': 'audio'},
|
||||
'application/pdf': {'url': 'sendDocument', 'type': 'document'},
|
||||
};
|
||||
|
||||
const defaultType = {'url': 'sendDocument', 'type': 'document'};
|
||||
|
||||
const sendFunction = Object.keys(fileTypeMap).find(key => fileType.startsWith(key))
|
||||
? fileTypeMap[Object.keys(fileTypeMap).find(key => fileType.startsWith(key))]
|
||||
: defaultType;
|
||||
|
||||
// 优先从请求 URL 获取 authCode
|
||||
let authCode = url.searchParams.get('authCode');
|
||||
// 如果 URL 中没有 authCode,从 Referer 中获取
|
||||
@@ -59,11 +82,14 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
if (isAuthCodeDefined(env.AUTH_CODE) && !isValidAuthCode(env.AUTH_CODE, authCode)) {
|
||||
return new UnauthorizedException("error");
|
||||
}
|
||||
const clonedRequest = request.clone();
|
||||
await errorHandling(context);
|
||||
telemetryData(context);
|
||||
|
||||
// 构建目标 URL 时剔除 authCode 参数
|
||||
const targetUrl = new URL(url.pathname, 'https://telegra.ph');
|
||||
// const targetUrl = new URL(url.pathname, 'https://telegra.ph'); // telegraph接口,已失效,缅怀
|
||||
const targetUrl = new URL(`https://api.telegram.org/bot${env.TG_BOT_TOKEN}/${sendFunction.url}`); // telegram接口
|
||||
let newFormdata = new FormData();
|
||||
newFormdata.append('chat_id', env.TG_CHAT_ID);
|
||||
newFormdata.append(sendFunction.type, formdata.get('file'));
|
||||
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key !== 'authCode') {
|
||||
targetUrl.searchParams.append(key, value);
|
||||
@@ -72,16 +98,33 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
// 复制请求头并剔除 authCode
|
||||
const headers = new Headers(clonedRequest.headers);
|
||||
headers.delete('authCode');
|
||||
const response = await fetch(targetUrl.href, {
|
||||
method: clonedRequest.method,
|
||||
headers: headers,
|
||||
body: clonedRequest.body,
|
||||
});
|
||||
|
||||
let res = new Response('upload error, check your environment params!', { status: 400 });
|
||||
try {
|
||||
const response = await fetch(targetUrl.href, {
|
||||
method: clonedRequest.method,
|
||||
headers: {
|
||||
"User-Agent": " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
|
||||
},
|
||||
body: newFormdata,
|
||||
});
|
||||
const clonedRes = await response.clone().json(); // 等待响应克隆和解析完成
|
||||
const fileInfo = getFile(clonedRes);
|
||||
const filePath = await getFilePath(env, fileInfo.file_id);
|
||||
// 若上传成功,将响应返回给客户端
|
||||
if (response.ok) {
|
||||
res = new Response(
|
||||
JSON.stringify([{ 'src': `/file/${fileInfo.file_id}` }]),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const src = clonedRes[0].src;
|
||||
const id = src.split('/').pop();
|
||||
// const src = clonedRes[0].src;
|
||||
// const id = src.split('/').pop();
|
||||
const id = fileInfo.file_id;
|
||||
const img_url = env.img_url;
|
||||
const apikey = env.ModerateContentApiKey;
|
||||
|
||||
@@ -94,13 +137,13 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const fetchResponse = await fetch(`https://api.moderatecontent.com/moderate/?key=${apikey}&url=https://telegra.ph/${src}`);
|
||||
const fetchResponse = await fetch(`https://api.moderatecontent.com/moderate/?key=${apikey}&url=https://api.telegram.org/file/bot${env.TG_BOT_TOKEN}/${filePath}`);
|
||||
if (!fetchResponse.ok) {
|
||||
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
|
||||
}
|
||||
const moderate_data = await fetchResponse.json();
|
||||
await env.img_url.put(id, "", {
|
||||
metadata: { ListType: "None", Label: moderate_data.rating_label, TimeStamp: time },
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: moderate_data.rating_label, TimeStamp: time, Channel: "Telegram", TgFilePath: filePath },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Moderate Error:', error);
|
||||
@@ -112,6 +155,61 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
return response;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
function getFile(response) {
|
||||
try {
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getFileDetails = (file) => ({
|
||||
file_id: file.file_id,
|
||||
file_name: file.file_name || file.file_unique_id
|
||||
});
|
||||
|
||||
if (response.result.photo) {
|
||||
const largestPhoto = response.result.photo.reduce((prev, current) =>
|
||||
(prev.file_size > current.file_size) ? prev : current
|
||||
);
|
||||
return getFileDetails(largestPhoto);
|
||||
}
|
||||
|
||||
if (response.result.video) {
|
||||
return getFileDetails(response.result.video);
|
||||
}
|
||||
|
||||
if (response.result.document) {
|
||||
return getFileDetails(response.result.document);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error getting file id:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getFilePath(env, file_id) {
|
||||
try {
|
||||
const url = `https://api.telegram.org/bot${env.TG_BOT_TOKEN}/getFile?file_id=${file_id}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"User-Agent": " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome"
|
||||
},
|
||||
})
|
||||
|
||||
let responseData = await res.json();
|
||||
if (responseData.ok) {
|
||||
const file_path = responseData.result.file_path
|
||||
return file_path
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ export async function errorHandling(context) {
|
||||
}
|
||||
} catch (e) { console.log(e) }
|
||||
const sampleRate = env.sampleRate || remoteSampleRate;
|
||||
console.log("sampleRate", sampleRate);
|
||||
return sentryPlugin({
|
||||
dsn: "https://44b7b443108ec6d298044b125ff89d28@o4507644548022272.ingest.us.sentry.io/4507644555100160",
|
||||
tracesSampleRate: sampleRate,
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.204e01e6.js"></script><script defer="defer" src="/js/app.437dccab.js"></script><link href="/css/chunk-vendors.b85f6a1a.css" rel="stylesheet"><link href="/css/app.93429def.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.204e01e6.js"></script><script defer="defer" src="/js/app.99e6b921.js"></script><link href="/css/chunk-vendors.b85f6a1a.css" rel="stylesheet"><link href="/css/app.93429def.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
2
js/531.d7b43da6.js
Normal file
2
js/531.d7b43da6.js
Normal file
File diff suppressed because one or more lines are too long
1
js/531.d7b43da6.js.map
Normal file
1
js/531.d7b43da6.js.map
Normal file
File diff suppressed because one or more lines are too long
2
js/app.99e6b921.js
Normal file
2
js/app.99e6b921.js
Normal file
File diff suppressed because one or more lines are too long
1
js/app.99e6b921.js.map
Normal file
1
js/app.99e6b921.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user