mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-13 04:03:42 +08:00
切换上传渠道为telegram
This commit is contained in:
@@ -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`,至此,正常情况下项目已经成功部署。
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- import CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/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/[email protected]/dist/vue.min.js" integrity="sha256-kXTEJcRFN330VirZFl6gj9+UM6gIKW195fYZeR3xDhc=" crossorigin="anonymous"></script>
|
||||
<!-- import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>
|
||||
@@ -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/[email protected]/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/[email protected]/dist/vue.min.js"></script>
|
||||
<!-- Import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>
|
||||
+431
-316
@@ -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/[email protected]/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/[email protected]/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/[email protected]/dist/vue.min.js" integrity="sha256-kXTEJcRFN330VirZFl6gj9+UM6gIKW195fYZeR3xDhc=" crossorigin="anonymous"></script>
|
||||
<!-- import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.js" integrity="sha256-OFVFYfqhQ9nDnKh+NfIsefpy/fnjTwkK909ZYgo45nw=" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Import Vue before Element -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
|
||||
<!-- Import JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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>
|
||||
@@ -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}
|
||||
+21
-9
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
+112
-14
@@ -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
@@ -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>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user