This commit is contained in:
GH Action - Upstream Sync
2025-08-29 01:57:37 +00:00
108 changed files with 348 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
<div align="center">
<a href="https://github.com/MarSeventh/CloudFlare-ImgBed"><img width="80%" alt="logo" src="static/readme/banner.png"/></a>
<p><em>🗂️开源文件托管解决方案,支持 Docker 和无服务器部署,支持 Telegram Bot 、 Cloudflare R2 、S3 等多种存储渠道</em></p>
<p><em>🗂️开源文件托管解决方案,支持 Docker 和无服务器部署,支持 Telegram Bot 、 Cloudflare R2 、S3 等多种存储渠道,支持 WebDAV 协议和多种 RESTful API</em></p>
<p>
<a href="https://github.com/MarSeventh/CloudFlare-ImgBed/blob/main/README.md">简体中文</a> | <a href="https://github.com/MarSeventh/CloudFlare-ImgBed/blob/main/README_en.md">English</a> | <a href="https://cfbed.sanyue.de">官方网站</a>
</p>
@@ -111,11 +111,8 @@
![image-20250313204325002](static/readme/202503132043265.png)
</details>
# 4. Tips
- **前端开源**:参见[MarSeventh/Sanyue-ImgHub](https://github.com/MarSeventh/Sanyue-ImgHub)项目。

View File

@@ -1,6 +1,6 @@
<div align="center">
<a href="https://github.com/MarSeventh/CloudFlare-ImgBed"><img width="80%" alt="logo" src="static/readme/banner.png"/></a>
<p><em>🗂Open-source file hosting solution, supporting Docker and serverless deployment, supporting multiple storage channels such as Telegram Bot, Cloudflare R2, S3, etc.</em></p>
<p><em>🗂Open-source file hosting solution, supporting Docker and serverless deployment, supporting multiple storage channels such as Telegram Bot, Cloudflare R2, S3, etc., supporting WebDAV protocol and various RESTful APIs.</em></p>
<p>
<a href="https://github.com/MarSeventh/CloudFlare-ImgBed/blob/main/README.md">简体中文</a> | <a href="https://github.com/MarSeventh/CloudFlare-ImgBed/blob/main/README_en.md">English</a> | <a
href="https://cfbed.sanyue.de/en">Official Website</a>

Binary file not shown.

Binary file not shown.

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

BIN
css/936.973d7489.css.gz Normal file

Binary file not shown.

View File

@@ -71,6 +71,15 @@ export async function getOthersConfig(db, env) {
fixed: false,
}
// WebDAV
const kvWebDAV = settingsKV.webDAV || {}
settings.webDAV = {
enabled: kvWebDAV.enabled ?? false,
username: kvWebDAV.username || '',
password: kvWebDAV.password || '',
fixed: false,
}
return settings;
}

290
functions/dav/[[path]].js Normal file
View File

@@ -0,0 +1,290 @@
// WebDAV 服务支持
import { fetchSecurityConfig, fetchOthersConfig } from "../utils/sysConfig";
export async function onRequest(context) {
const { request, env } = context;
const authResponse = await checkAuth(request, env);
if (authResponse) return authResponse;
// 从请求路径中替换第一个 /dav 部分
const url = new URL(request.url);
url.pathname = url.pathname.replace(/^\/dav/, '') || '/';
const modifiedRequest = new Request(url.toString(), request);
switch (modifiedRequest.method) {
case 'OPTIONS': return handleOptions(modifiedRequest);
case 'PROPFIND': return handlePropfind(modifiedRequest, env);
case 'PUT': return handlePut(modifiedRequest, env);
case 'DELETE': return handleDelete(modifiedRequest, env);
case 'GET': return handleGet(modifiedRequest, env);
case 'MKCOL': return new Response(null, { status: 201 });
default: return new Response('Method Not Allowed', { status: 405 });
}
}
// --- UTILITY FUNCTIONS ---
async function getApiHeaders(env) {
const securityConfig = await fetchSecurityConfig(env);
const adminUsername = securityConfig.auth.admin.adminUsername;
const adminPassword = securityConfig.auth.admin.adminPassword;
const authCode = securityConfig.auth.user.authCode;
let credentials = btoa('unset:unset');
if (adminUsername && adminPassword) {
credentials = btoa(`${adminUsername}:${adminPassword}`);
}
return {
'Authorization': `Basic ${credentials}`,
'authCode': authCode || ''
};
}
async function checkAuth(request, env) {
const othersConfig = await fetchOthersConfig(env);
const enabled = othersConfig.webDAV.enabled;
if (!enabled) return new Response('WebDAV is disabled', { status: 403 }); // WebDAV disabled
const davUser = othersConfig.webDAV.username;
const davPass = othersConfig.webDAV.password;
if (!davUser || !davPass) return null; // No auth required
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
return new Response('Authorization required', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="WebDAV"' },
});
}
const [scheme, encoded] = authHeader.split(' ');
if (scheme !== 'Basic' || !encoded) {
return new Response('Malformed Authorization header', { status: 400 });
}
const [user, pass] = atob(encoded).split(':');
if (user !== davUser || pass !== davPass) {
return new Response('Invalid credentials', { status: 403 });
}
return null;
}
// --- WEBDAV METHOD HANDLERS ---
function handleOptions(request) {
return new Response(null, {
status: 204,
headers: {
'Allow': 'OPTIONS, GET, PUT, DELETE, PROPFIND, MKCOL',
'DAV': '1, 2',
'MS-Author-Via': 'DAV',
},
});
}
async function handleGet(request, env) {
const path = decodeURIComponent(new URL(request.url).pathname);
if (path.endsWith('/')) { // Directory listing
try {
const dir = path === '/' ? '' : path.substring(1, path.length - 1);
const contents = await fetchDirectoryContents(dir, env, request);
const html = generateDirectoryListingHtml(path, contents);
return new Response(html, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
} catch (error) {
console.error('GET (directory) failed:', error.stack);
return new Response(`Error listing directory: ${error.message}`, { status: 500 });
}
} else { // File download
try {
const fileUrl = new URL(`/file${path}`, request.url);
const fileResponse = await fetch(fileUrl.toString());
if (!fileResponse.ok) {
return new Response('File not found', { status: fileResponse.status, statusText: fileResponse.statusText });
}
const response = new Response(fileResponse.body, fileResponse);
response.headers.set('Access-Control-Allow-Origin', '*');
return response;
} catch (error) {
console.error('GET (file) failed:', error.stack);
return new Response(`Error getting file: ${error.message}`, { status: 500 });
}
}
}
async function handlePut(request, env) {
const fullPath = decodeURIComponent(new URL(request.url).pathname.substring(1));
if (!fullPath || fullPath.endsWith('/')) {
return new Response('Invalid file name', { status: 400 });
}
const lastSlashIndex = fullPath.lastIndexOf('/');
const uploadFolder = lastSlashIndex > -1 ? fullPath.substring(0, lastSlashIndex) : '';
const fileName = lastSlashIndex > -1 ? fullPath.substring(lastSlashIndex + 1) : fullPath;
const fileContent = await request.blob();
const formData = new FormData();
formData.append('file', fileContent, fileName);
const uploadUrl = new URL(`/upload`, request.url);
if (uploadFolder) {
uploadUrl.searchParams.set('uploadFolder', uploadFolder);
}
try {
const response = await fetch(uploadUrl.toString(), {
method: 'POST',
body: formData,
headers: await getApiHeaders(env)
});
const result = await response.json();
if (response.ok && Array.isArray(result) && result.length > 0 && result[0].src) {
return new Response(null, { status: 201 }); // Created
} else {
const errorMsg = result.error || JSON.stringify(result);
console.error('Upload API error:', errorMsg);
return new Response(`Upload failed: ${errorMsg}`, { status: 500 });
}
} catch (error) {
console.error('Fetch to upload API failed:', error.stack);
return new Response('Failed to contact upload service', { status: 502 });
}
}
async function handleDelete(request, env) {
const path = decodeURIComponent(new URL(request.url).pathname.substring(1));
if (!path) return new Response('Invalid path for DELETE', { status: 400 });
const isFolder = path.endsWith('/');
const cleanPath = isFolder ? path.slice(0, -1) : path;
const deleteUrl = new URL(`/api/manage/delete/${cleanPath}`, request.url);
if (isFolder) deleteUrl.searchParams.set('folder', 'true');
try {
const response = await fetch(deleteUrl.toString(), {
method: 'DELETE',
headers: await getApiHeaders(env)
});
const result = await response.json();
if (result.success) {
return new Response(null, { status: 204 }); // No Content
} else {
console.error('Delete API error:', JSON.stringify(result));
return new Response(`Deletion failed: ${result.error || 'API error'}`, { status: 500 });
}
} catch (error) {
console.error('Delete operation failed:', error.stack);
return new Response(`Internal server error: ${error.message}`, { status: 500 });
}
}
async function handlePropfind(request, env) {
const path = decodeURIComponent(new URL(request.url).pathname);
try {
const dir = path === '/' ? '' : path.substring(1, path.endsWith('/') ? path.length - 1 : path.length);
const contents = await fetchDirectoryContents(dir, env, request);
const xml = generateWebDAVXml(path, contents);
return new Response(xml, { status: 207, headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
} catch (error) {
console.error('Propfind failed:', error.stack);
return new Response(`Failed to list files: ${error.message}`, { status: 500 });
}
}
// --- API DATA FETCHING ---
async function fetchDirectoryContents(dir, env, request) {
let allFiles = [];
let allDirectories = [];
const count = -1; // Fetch all items
const listUrl = new URL(`/api/manage/list`, request.url);
listUrl.searchParams.set('dir', dir);
listUrl.searchParams.set('count', count);
const response = await fetch(listUrl.toString(), { headers: await getApiHeaders(env) });
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API fetch error: Status ${response.status} - ${errorText}`);
}
const result = await response.json();
if (result.error) {
throw new Error(`API error: ${result.error} - ${result.message}`);
}
if (result.files && result.files.length > 0) allFiles = allFiles.concat(result.files);
if (result.directories && result.directories.length > 0) allDirectories = allDirectories.concat(result.directories);
return { files: allFiles, directories: [...new Set(allDirectories)] };
}
// --- HTML and XML GENERATION ---
function generateDirectoryListingHtml(basePath, contents) {
let fileLinks = '';
let dirLinks = '';
for (const dir of contents.directories) {
const fullDirPath = `/dav/${dir}/`;
const dirName = dir.split('/').pop();
dirLinks += `<li><a href="${fullDirPath}"><strong>${dirName}/</strong></a></li>`;
}
for (const file of contents.files) {
const fullFilePath = `/dav/${file.name}`;
const fileName = file.name.split('/').pop();
const fileSize = file.metadata && file.metadata['FileSize']
? `${file.metadata['FileSize']} MB`
: 'N/A';
fileLinks += `<li><a href="${fullFilePath}">${fileName}</a> - ${fileSize}</li>`;
}
let parentDirLink = '';
if (basePath !== '/') {
const parentPath = new URL('..', `http://dummy.com${basePath}`).pathname;
parentDirLink = `<li><a href="/dav${parentPath}"><strong>../ (Parent Directory)</strong></a></li>`;
}
return `<!DOCTYPE html><html><head><title>Index of ${basePath}</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body{font-family:sans-serif;padding:20px}li{margin:5px 0}</style></head><body><h1>Index of ${basePath}</h1><ul>${parentDirLink}${dirLinks}${fileLinks}</ul></body></html>`;
}
function generateWebDAVXml(basePath, contents) {
let responses = '';
const currentPath = basePath.endsWith('/') ? basePath : `${basePath}/`;
responses += createCollectionXml(currentPath);
for (const dir of contents.directories) {
responses += createCollectionXml(`/${dir}/`);
}
for (const file of contents.files) {
responses += createFileXml(file);
}
return `<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:">${responses}</D:multistatus>`;
}
function createCollectionXml(path) {
const now = new Date().toUTCString();
const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path;
const name = cleanPath.split('/').pop() || '';
return `<D:response><D:href>${encodeURI(path)}</D:href><D:propstat><D:prop><D:displayname>${name}</D:displayname><D:resourcetype><D:collection/></D:resourcetype><D:creationdate>${now}</D:creationdate><D:getlastmodified>${now}</D:getlastmodified></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>`;
}
function createFileXml(file) {
const now = new Date().toUTCString();
const fileSize = file.metadata && file.metadata['File-Size'] ? file.metadata['File-Size'] : "0";
return `<D:response><D:href>${encodeURI(`/${file.name}`)}</D:href><D:propstat><D:prop><D:displayname>${file.name.split('/').pop()}</D:displayname><D:resourcetype/><D:creationdate>${now}</D:creationdate><D:getlastmodified>${now}</D:getlastmodified><D:getcontentlength>${fileSize}</D:getcontentlength></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>`;
}

View File

@@ -0,0 +1,3 @@
import { checkDatabaseConfig } from '../utils/middleware';
export const onRequest = [checkDatabaseConfig];

View File

@@ -11,15 +11,15 @@ import { D1Database } from './d1Database.js';
* @returns {Object} 数据库适配器实例
*/
export function createDatabaseAdapter(env) {
// 检查是否配置了D1数据库
if (env.img_d1 && typeof env.img_d1.prepare === 'function') {
// 检查是否配置了数据库
if (env.img_url && typeof env.img_url.get === 'function') {
// 使用KV存储
return new KVAdapter(env.img_url);
} else if (env.img_d1 && typeof env.img_d1.prepare === 'function') {
// 使用D1数据库
return new D1Database(env.img_d1);
} else if (env.img_url && typeof env.img_url.get === 'function') {
// 回退到KV存储
return new KVAdapter(env.img_url);
} else {
console.error('No database configured. Please configure either D1 (env.img_d1) or KV (env.img_url)');
console.error('No database configured. Please configure either KV (env.img_url) or D1 (env.img_d1).');
return null;
}
}

View File

@@ -123,7 +123,7 @@ export async function checkDatabaseConfig(context) {
JSON.stringify({
success: false,
error: "数据库未配置 / Database not configured",
message: "请配置 D1 数据库 (env.img_d1) 或 KV 存储 (env.img_url)。 / Please configure D1 database (env.img_d1) or KV storage (env.img_url)."
message: "请配置 KV 存储 (env.img_url) 或 D1 数据库 (env.img_d1)。 / Please configure KV storage (env.img_url) or D1 database (env.img_d1)."
}),
{
status: 500,

View File

@@ -21,9 +21,10 @@ export async function userAuthCheck(env, url, request, requiredPermission = null
const securityConfig = await fetchSecurityConfig(env);
const rightAuthCode = securityConfig.auth.user.authCode;
// 优先从请求 URL 获取 authCode
// 优先从请求 URL 参数获取 authCode
let authCode = url.searchParams.get('authCode');
// 如果 URL 中没有 authCode从 Referer 中获取
// 如果 URL 参数中没有 authCode从 Referer 中获取
if (!authCode) {
const referer = request.headers.get('Referer');
if (referer) {
@@ -35,10 +36,12 @@ export async function userAuthCheck(env, url, request, requiredPermission = null
}
}
}
// 如果 Referer 中没有 authCode从请求头中获取
if (!authCode) {
authCode = request.headers.get('authCode');
}
// 如果请求头中没有 authCode从 Cookie 中获取
if (!authCode) {
const cookies = request.headers.get('Cookie');

View File

@@ -1,4 +1,4 @@
<!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"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.fe3722ff.js"></script><link href="/css/app.ed56be35.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><style>/* 下拉菜单样式 */
<!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"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.42f0e844.js"></script><link href="/css/app.6e9711cc.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><style>/* 下拉菜单样式 */
.el-dropdown__popper.el-popper {
border-radius: 12px;
border: none;

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

1
js/162.8a5db2a3.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/162.8a5db2a3.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/226.4c3e9291.js.gz Normal file

Binary file not shown.

1
js/226.4c3e9291.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/226.4c3e9291.js.map.gz Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

1
js/259.eab0b4ae.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/259.eab0b4ae.js.map.gz Normal file

Binary file not shown.

2
js/442.a40d3211.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/442.a40d3211.js.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/442.a40d3211.js.map.gz Normal file

Binary file not shown.

2
js/459.3f268506.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/459.3f268506.js.gz Normal file

Binary file not shown.

1
js/459.3f268506.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/459.3f268506.js.map.gz Normal file

Binary file not shown.

2
js/499.d357974a.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/499.d357974a.js.gz Normal file

Binary file not shown.

1
js/499.d357974a.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/499.d357974a.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,2 +1,2 @@
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[548],{4452:function(e,t,s){s.d(t,{A:function(){return v}});var n=s(6975),a=s(47),i=(s(5331),s(9648),s(9623)),l=(s(9092),s(4632)),o=s(3525),r=s(6768),d=s(4232),u=s(5130);const c={class:"login"},h={class:"login-container"},p={class:"login-title",tabindex:"0"},m={class:"input-wrapper"};function f(e,t,s,f,b,g){const y=o.A,k=l.A,w=i.WK,v=a.S2,L=n.A;return(0,r.uX)(),(0,r.CE)("div",c,[(0,r.bF)(y,{class:"toggle-dark"}),(0,r.bF)(k),(0,r.Lk)("div",h,[(0,r.Lk)("h1",p,(0,d.v_)(s.title),1),((0,r.uX)(!0),(0,r.CE)(r.FK,null,(0,r.pI)(s.fields,(e,s)=>((0,r.uX)(),(0,r.CE)("div",{key:e.key,class:"input-container"},[(0,r.Lk)("label",{class:"input-name",ref_for:!0,ref:`inputLabel${s}`,style:(0,d.Tr)({"--underline-width":b.labelUnderlineWidths[s]+"px"})},(0,d.v_)(e.label),5),(0,r.Lk)("div",m,[(0,r.bF)(w,{modelValue:b.formData[e.key],"onUpdate:modelValue":t=>b.formData[e.key]=t,placeholder:e.placeholder,type:e.type||"text","show-password":e.showPassword,class:"password-input",onKeyup:(0,u.jR)(g.handleSubmit,["enter","native"]),onFocus:g.handleInputFocus,onBlur:g.handleInputBlur},null,8,["modelValue","onUpdate:modelValue","placeholder","type","show-password","onKeyup","onFocus","onBlur"]),t[0]||(t[0]=(0,r.Lk)("div",{class:"input-underline"},null,-1))])]))),128)),(0,r.bF)(v,{class:"submit",type:"primary",onClick:g.handleSubmit},{default:(0,r.k6)(()=>[(0,r.eW)((0,d.v_)(s.submitText),1)]),_:1},8,["onClick"])]),(0,r.bF)(L,{class:"footer"})])}s(8111),s(7588);var b=s(782),g=s(8903),y={name:"BaseLogin",mixins:[g.A],props:{title:{type:String,required:!0},fields:{type:Array,required:!0},submitText:{type:String,default:"登录"},backgroundKey:{type:String,required:!0},isAdmin:{type:Boolean,default:!1}},data(){return{formData:{},labelUnderlineWidths:[]}},computed:{...(0,b.L8)(["userConfig"])},watch:{fields:{handler(){this.$nextTick(()=>{this.calculateLabelWidths()})},deep:!0}},components:{Footer:n.A,ToggleDark:o.A,Logo:l.A},mounted(){this.initFormData(),this.initializeBackground(this.backgroundKey,".login",!this.isAdmin,!0),this.$nextTick(()=>{this.calculateLabelWidths()})},methods:{initFormData(){const e={};this.fields.forEach(t=>{e[t.key]=""}),this.formData=e,this.labelUnderlineWidths=new Array(this.fields.length).fill(0)},calculateLabelWidths(){this.$nextTick(()=>{this.fields.forEach((e,t)=>{const s=this.$refs[`inputLabel${t}`];if(s&&s[0]){const n=document.createElement("canvas"),a=n.getContext("2d"),i=s[0],l=window.getComputedStyle(i);a.font=`${l.fontWeight} ${l.fontSize} ${l.fontFamily}`;const o=a.measureText(e.label).width;this.labelUnderlineWidths[t]=Math.ceil(o)+3}})})},handleSubmit(){this.$emit("submit",{...this.formData})},handleInputFocus(e){const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.add("focused")}},handleInputBlur(e){const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.remove("focused")}}}},k=s(1241);const w=(0,k.A)(y,[["render",f],["__scopeId","data-v-61197d6f"]]);var v=w},8351:function(e,t,s){s.r(t),s.d(t,{default:function(){return u}});var n=s(4452),a=s(6768);function i(e,t,s,i,l,o){const r=n.A;return(0,a.uX)(),(0,a.Wv)(r,{title:"管理端登录",fields:l.loginFields,"submit-text":"登录","background-key":"adminLoginBkImg","is-admin":!0,onSubmit:o.handleLogin},null,8,["fields","onSubmit"])}s(4114),s(4979);var l=s(9189),o={data(){return{loginFields:[{key:"username",label:"用户名",placeholder:"请输入用户名",type:"text"},{key:"password",label:"密码",placeholder:"请输入密码",type:"password",showPassword:!0}]}},components:{BaseLogin:n.A},methods:{async handleLogin(e){const{username:t,password:s}=e,n=btoa(`${t}:${s}`);try{const e=await l.A.get("/api/manage/check",{headers:{Authorization:`Basic ${n}`},withCredentials:!0});200===e.status&&(this.$store.commit("setCredentials",n),this.$router.push("/dashboard"))}catch(a){a.response&&401===a.response.status?this.$message.error("用户名或密码错误"):this.$message.error("服务器错误")}}}},r=s(1241);const d=(0,r.A)(o,[["render",i]]);var u=d}}]);
//# sourceMappingURL=548.a8fc5a24.js.map
//# sourceMappingURL=548.6255b5bc.js.map

BIN
js/548.6255b5bc.js.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/548.6255b5bc.js.map.gz Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +1,2 @@
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[585],{4452:function(e,t,n){n.d(t,{A:function(){return v}});var s=n(6975),i=n(47),a=(n(5331),n(9648),n(9623)),o=(n(9092),n(4632)),l=n(3525),r=n(6768),u=n(4232),d=n(5130);const c={class:"login"},h={class:"login-container"},p={class:"login-title",tabindex:"0"},m={class:"input-wrapper"};function f(e,t,n,f,g,b){const y=l.A,k=o.A,w=a.WK,v=i.S2,L=s.A;return(0,r.uX)(),(0,r.CE)("div",c,[(0,r.bF)(y,{class:"toggle-dark"}),(0,r.bF)(k),(0,r.Lk)("div",h,[(0,r.Lk)("h1",p,(0,u.v_)(n.title),1),((0,r.uX)(!0),(0,r.CE)(r.FK,null,(0,r.pI)(n.fields,(e,n)=>((0,r.uX)(),(0,r.CE)("div",{key:e.key,class:"input-container"},[(0,r.Lk)("label",{class:"input-name",ref_for:!0,ref:`inputLabel${n}`,style:(0,u.Tr)({"--underline-width":g.labelUnderlineWidths[n]+"px"})},(0,u.v_)(e.label),5),(0,r.Lk)("div",m,[(0,r.bF)(w,{modelValue:g.formData[e.key],"onUpdate:modelValue":t=>g.formData[e.key]=t,placeholder:e.placeholder,type:e.type||"text","show-password":e.showPassword,class:"password-input",onKeyup:(0,d.jR)(b.handleSubmit,["enter","native"]),onFocus:b.handleInputFocus,onBlur:b.handleInputBlur},null,8,["modelValue","onUpdate:modelValue","placeholder","type","show-password","onKeyup","onFocus","onBlur"]),t[0]||(t[0]=(0,r.Lk)("div",{class:"input-underline"},null,-1))])]))),128)),(0,r.bF)(v,{class:"submit",type:"primary",onClick:b.handleSubmit},{default:(0,r.k6)(()=>[(0,r.eW)((0,u.v_)(n.submitText),1)]),_:1},8,["onClick"])]),(0,r.bF)(L,{class:"footer"})])}n(8111),n(7588);var g=n(782),b=n(8903),y={name:"BaseLogin",mixins:[b.A],props:{title:{type:String,required:!0},fields:{type:Array,required:!0},submitText:{type:String,default:"登录"},backgroundKey:{type:String,required:!0},isAdmin:{type:Boolean,default:!1}},data(){return{formData:{},labelUnderlineWidths:[]}},computed:{...(0,g.L8)(["userConfig"])},watch:{fields:{handler(){this.$nextTick(()=>{this.calculateLabelWidths()})},deep:!0}},components:{Footer:s.A,ToggleDark:l.A,Logo:o.A},mounted(){this.initFormData(),this.initializeBackground(this.backgroundKey,".login",!this.isAdmin,!0),this.$nextTick(()=>{this.calculateLabelWidths()})},methods:{initFormData(){const e={};this.fields.forEach(t=>{e[t.key]=""}),this.formData=e,this.labelUnderlineWidths=new Array(this.fields.length).fill(0)},calculateLabelWidths(){this.$nextTick(()=>{this.fields.forEach((e,t)=>{const n=this.$refs[`inputLabel${t}`];if(n&&n[0]){const s=document.createElement("canvas"),i=s.getContext("2d"),a=n[0],o=window.getComputedStyle(a);i.font=`${o.fontWeight} ${o.fontSize} ${o.fontFamily}`;const l=i.measureText(e.label).width;this.labelUnderlineWidths[t]=Math.ceil(l)+3}})})},handleSubmit(){this.$emit("submit",{...this.formData})},handleInputFocus(e){const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.add("focused")}},handleInputBlur(e){const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.remove("focused")}}}},k=n(1241);const w=(0,k.A)(y,[["render",f],["__scopeId","data-v-61197d6f"]]);var v=w},9206:function(e,t,n){n.r(t),n.d(t,{default:function(){return p}});var s=n(4452),i=n(6768);function a(e,t,n,a,o,l){const r=s.A;return(0,i.uX)(),(0,i.Wv)(r,{title:l.loginTitle,fields:o.loginFields,"submit-text":"登录","background-key":"loginBkImg","is-admin":!1,onSubmit:l.handleLogin},null,8,["title","fields","onSubmit"])}n(4114);var o=n(4570),l=n.n(o),r=n(9189),u=n(782),d={data(){return{loginFields:[{key:"password",label:"密码",placeholder:"请输入认证码",type:"password",showPassword:!0}]}},computed:{...(0,u.L8)(["userConfig"]),ownerName(){return this.userConfig?.ownerName||"Sanyue"},loginTitle(){return`登录到 ${this.ownerName} 图床`}},components:{BaseLogin:s.A},methods:{handleLogin(e){const{password:t}=e,n=""===t?"unset":t;r.A.post("/api/login",{authCode:t}).then(e=>{200===e.status?(l().set("authCode",n,"14d"),this.$router.push("/"),this.$message.success("登录成功")):this.$message.error("登录失败,请检查密码是否正确")}).catch(e=>{this.$message.error("登录失败,请检查密码是否正确")})}}},c=n(1241);const h=(0,c.A)(d,[["render",a]]);var p=h}}]);
//# sourceMappingURL=585.41725b71.js.map
//# sourceMappingURL=585.c628b4e4.js.map

File diff suppressed because one or more lines are too long

BIN
js/585.c628b4e4.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/845.1093f728.js.gz Normal file

Binary file not shown.

1
js/845.1093f728.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/845.1093f728.js.map.gz Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/917.d81a041b.js.gz Normal file

Binary file not shown.

1
js/917.d81a041b.js.map Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More