Skip to content

新增后台管理图片分页功能以及data数据的显示优化、新增url短链功能 #226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 113 additions & 39 deletions admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@
.el-button+.el-button {
margin: 0;
}

.json-viewer {
background-color: #f5f7fa;
padding: 10px;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
}

.json-key {
color: #409EFF;
}

.json-string {
color: #67C23A;
}

.json-number {
color: #E6A23C;
}

.json-boolean {
color: #F56C6C;
}
</style>
<script src="https://js.sentry-cdn.com/219f636ac7bde5edab2c3e16885cb535.min.js" crossorigin="anonymous"></script>
</head>
Expand Down Expand Up @@ -73,42 +97,61 @@
</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>
<div>
<el-table :data="paginatedData" 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" width="400">
<div class="json-viewer">
<span class="json-key">"ListType"</span>: <span class="json-string">"{{scope.row.metadata.ListType}}"</span>,
<span class="json-key">"Label"</span>: <span class="json-string">"{{scope.row.metadata.Label}}"</span>,
<span class="json-key">"TimeStamp"</span>: <span class="json-number">{{scope.row.metadata.TimeStamp}}</span>,
<span class="json-key">"liked"</span>: <span class="json-boolean">{{scope.row.metadata.liked}}</span>,
<span class="json-key">"shortId"</span>: <span class="json-string">"{{scope.row.metadata.shortId}}"</span>
</div>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ formatMetadata(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>

<!-- 添加分页组件 -->
<div class="pagination-container" style="margin-top: 20px; text-align: center;">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="filteredTableData.length">
</el-pagination>
</div>
</div>
</template>
</el-main>
</el-container>
Expand All @@ -129,6 +172,8 @@
BlackList: 0,
showLogoutButton: false,
tableData: [],
currentPage: 1,
pageSize: 10,
dialogFormVisible: false,
formLabelWidth: '120px',
form: {
Expand All @@ -138,7 +183,29 @@
search: '',
password: '123456'
},
computed: {
// 根据搜索条件过滤数据
filteredTableData() {
return this.tableData.filter(data =>
!this.search || data.name.toLowerCase().includes(this.search.toLowerCase())
);
},
// 根据当前页码和每页条数计算分页数据
paginatedData() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.filteredTableData.slice(start, end);
}
},
methods: {
// 添加分页方法
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1; // 重置到第一页
},
handleCurrentChange(val) {
this.currentPage = val;
},
handleBlock(index, key) {
console.log(key);
if (confirm("确认加入黑名单吗?")) {
Expand Down Expand Up @@ -205,8 +272,15 @@
handleLogout() {
window.location.href = "./api/manage/logout";
},
formatMetadata(metadata) {
if (!metadata) return '';
const date = new Date(metadata.TimeStamp);
return `${metadata.ListType} | ${metadata.Label} | ${date.toLocaleString()}`;
},
handleCopy(index, key) {
const text = `${document.location.origin}/file/${key}`;
// 使用metadata中的shortId而不是原始key
const shortId = this.tableData[index].metadata.shortId;
const text = `${document.location.origin}/file/${shortId}`;
if (navigator.clipboard) {
// clipboard api 复制
navigator.clipboard.writeText(text);
Expand All @@ -227,7 +301,7 @@
document.body.removeChild(textarea);
}
this.$message({
message: '复制文件链接成功~',
message: '复制短链接成功~',
type: 'success'
});
},
Expand Down Expand Up @@ -310,4 +384,4 @@
})(window, document, "clarity", "script", "7t5ai7agat");
</script>

</html>
</html>
113 changes: 83 additions & 30 deletions functions/file/[id].js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,42 @@ export async function onRequest(context) {
} = context;

const url = new URL(request.url);
let fileUrl = 'https://telegra.ph/' + url.pathname + url.search
if (url.pathname.length > 39) {
let fileUrl = 'https://telegra.ph/' + url.pathname + url.search;
let originalId = params.id;

// 检查是否是短链接格式(6位字符)
if (originalId.length === 6) {
// 从KV中查找原始ID
if (env.img_url) {
// 列出所有KV记录
const kvList = await env.img_url.list();
// 遍历查找匹配的shortId
for (const key of kvList.keys) {
const record = await env.img_url.getWithMetadata(key.name);
if (record && record.metadata && record.metadata.shortId === originalId) {
originalId = key.name;
break;
}
}
}
}

if (originalId.length > 39) {
const formdata = new FormData();
formdata.append("file_id", url.pathname);
formdata.append("file_id", originalId);

const requestOptions = {
method: "POST",
body: formdata,
redirect: "follow"
};
// /file/AgACAgEAAxkDAAMDZt1Gzs4W8dQPWiQJxO5YSH5X-gsAAt-sMRuWNelGOSaEM_9lHHgBAAMCAANtAAM2BA.png
//get the AgACAgEAAxkDAAMDZt1Gzs4W8dQPWiQJxO5YSH5X-gsAAt-sMRuWNelGOSaEM_9lHHgBAAMCAANtAAM2BA
console.log(url.pathname.split(".")[0].split("/")[2])
const filePath = await getFilePath(env, url.pathname.split(".")[0].split("/")[2]);
console.log(filePath)
fileUrl = `https://api.telegram.org/file/bot${env.TG_Bot_Token}/${filePath}`;


console.log(originalId.split(".")[0]);
const filePath = await getFilePath(env, originalId.split(".")[0]);
console.log(filePath);
if (filePath) {
fileUrl = `https://api.telegram.org/file/bot${env.TG_Bot_Token}/${filePath}`;
}
}

const response = await fetch(fileUrl, {
Expand All @@ -34,16 +53,25 @@ export async function onRequest(context) {
// Log response details
console.log(response.ok, response.status);

// 获取文件扩展名
const fileExtension = originalId.split('.').pop().toLowerCase();

// 创建新的Response对象,添加正确的Content-Type
const newResponse = new Response(response.body, response);
const contentType = getContentType(fileExtension);
newResponse.headers.set('Content-Type', contentType);
newResponse.headers.set('Content-Disposition', 'inline');

// If the response is OK, proceed with further checks
if (response.ok) {
// Allow the admin page to directly view the image
if (request.headers.get('Referer') === `${url.origin}/admin`) {
return response;
return newResponse;
}

// Fetch KV metadata if available
if (env.img_url) {
const record = await env.img_url.getWithMetadata(params.id);
const record = await env.img_url.getWithMetadata(originalId);
console.log("Record:", record);

// Ensure metadata exists and add default values for missing properties
Expand All @@ -52,12 +80,13 @@ export async function onRequest(context) {
ListType: record.metadata.ListType || "None",
Label: record.metadata.Label || "None",
TimeStamp: record.metadata.TimeStamp || Date.now(),
liked: record.metadata.liked !== undefined ? record.metadata.liked : false
liked: record.metadata.liked !== undefined ? record.metadata.liked : false,
shortId: record.metadata.shortId
};

// Handle based on ListType and Label
if (metadata.ListType === "White") {
return response;
return newResponse;
} else if (metadata.ListType === "Block" || metadata.Label === "adult") {
const referer = request.headers.get('Referer');
const redirectUrl = referer ? "https://static-res.pages.dev/teleimage/img-block-compressed.png" : `${url.origin}/block-img.html`;
Expand All @@ -68,11 +97,6 @@ export async function onRequest(context) {
if (env.WhiteList_Mode === "true") {
return Response.redirect(`${url.origin}/whitelist-on.html`, 302);
}
} else {
// If metadata does not exist, initialize it in KV with default values
await env.img_url.put(params.id, "", {
metadata: { ListType: "None", Label: "None", TimeStamp: Date.now(), liked: false },
});
}
}

Expand All @@ -84,25 +108,29 @@ export async function onRequest(context) {
console.log("Moderate Data:", moderateData);

if (env.img_url) {
await env.img_url.put(params.id, "", {
metadata: { ListType: "None", Label: moderateData.rating_label, TimeStamp: time, liked: false },
// 获取现有记录
const record = await env.img_url.getWithMetadata(originalId);
const metadata = record && record.metadata ? record.metadata : {
ListType: "None",
TimeStamp: time,
liked: false
};

// 更新Label但保留其他元数据
metadata.Label = moderateData.rating_label;

await env.img_url.put(originalId, "", {
metadata: metadata
});
}

if (moderateData.rating_label === "adult") {
return Response.redirect(`${url.origin}/block-img.html`, 302);
}
} else if (env.img_url) {
// Add image to KV with default metadata if ModerateContentApiKey is not available
console.log("KV not enabled for moderation, adding default metadata.");
await env.img_url.put(params.id, "", {
metadata: { ListType: "None", Label: "None", TimeStamp: time, liked: false },
});
}
}

return response;

return newResponse;
}

async function getFilePath(env, file_id) {
Expand Down Expand Up @@ -130,4 +158,29 @@ async function getFilePath(env, file_id) {
console.error('Error fetching file path:', error.message);
return null;
}
}
}

// 添加getContentType辅助函数
function getContentType(extension) {
const contentTypes = {
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'webp': 'image/webp',
'svg': 'image/svg+xml',
'ico': 'image/x-icon',
'bmp': 'image/bmp'
};
return contentTypes[extension] || 'application/octet-stream';
}

// 生成6位短链接ID
function generateShortId() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
Loading