mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-13 04:03:42 +08:00
@@ -2,3 +2,4 @@
|
||||
data/*
|
||||
node_modules
|
||||
.DS_Store
|
||||
docs/Sanyue-ImgHub
|
||||
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
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.
@@ -125,6 +125,12 @@ async function deleteFile(env, fileId, cdnUrl, url) {
|
||||
const db = getDatabase(env);
|
||||
const img = await db.getWithMetadata(fileId);
|
||||
|
||||
// 如果文件记录不存在,直接返回成功(幂等删除)
|
||||
if (!img) {
|
||||
console.warn(`File ${fileId} not found in database, skipping delete`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果是R2渠道的图片,需要删除R2中对应的图片
|
||||
if (img.metadata?.Channel === 'CloudflareR2') {
|
||||
const R2DataBase = env.img_r2;
|
||||
@@ -137,6 +143,7 @@ async function deleteFile(env, fileId, cdnUrl, url) {
|
||||
}
|
||||
|
||||
// 删除数据库中的记录
|
||||
// 注意:容量统计现在由索引自动维护,删除文件后索引更新时会自动重新计算
|
||||
await db.delete(fileId);
|
||||
|
||||
// 清除CDN缓存
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 容量配额管理 API
|
||||
* GET: 获取各渠道容量统计(从索引元数据读取)
|
||||
* POST: 重新统计容量(触发索引重建)
|
||||
*/
|
||||
|
||||
import { getIndexMeta, rebuildIndex } from '../../utils/indexManager.js';
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
|
||||
export async function onRequest(context) {
|
||||
const { request, env } = context;
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
// GET: 获取容量统计(从索引元数据读取,只需 1 次读取)
|
||||
if (request.method === 'GET') {
|
||||
return await getQuotaStats(context);
|
||||
}
|
||||
|
||||
// POST: 重新统计容量(触发索引重建)
|
||||
if (request.method === 'POST') {
|
||||
return await recalculateQuota(context);
|
||||
}
|
||||
|
||||
return new Response('Method not allowed', { status: 405, headers: corsHeaders });
|
||||
}
|
||||
|
||||
// 获取各渠道容量统计(从索引元数据读取)
|
||||
async function getQuotaStats(context) {
|
||||
try {
|
||||
const indexMeta = await getIndexMeta(context);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
quotaStats: indexMeta.channelStats || {},
|
||||
totalSizeMB: indexMeta.totalSizeMB || 0,
|
||||
totalCount: indexMeta.totalCount || 0,
|
||||
lastUpdated: indexMeta.lastUpdated
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 重新统计容量(触发索引重建,会重新计算所有容量统计)
|
||||
async function recalculateQuota(context) {
|
||||
try {
|
||||
// 重建索引会自动重新计算所有容量统计
|
||||
const result = await rebuildIndex(context);
|
||||
|
||||
if (!result.success) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: result.error || 'Failed to rebuild index'
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
});
|
||||
}
|
||||
|
||||
// 重建完成后,获取最新的统计数据
|
||||
const indexMeta = await getIndexMeta(context);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
message: 'Quota recalculated successfully',
|
||||
channelStats: indexMeta.channelStats || {},
|
||||
totalSizeMB: indexMeta.totalSizeMB || 0,
|
||||
totalCount: indexMeta.totalCount || 0,
|
||||
totalUniqueFiles: result.indexedCount
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ export async function getUploadConfig(db, env) {
|
||||
if (cfr2Channels[0]) {
|
||||
cfr2Channels[0].publicUrl = r2.publicUrl
|
||||
cfr2Channels[0].enabled = r2.enabled
|
||||
cfr2Channels[0].quota = r2.quota // 保留容量限制配置
|
||||
}
|
||||
|
||||
continue
|
||||
@@ -153,6 +154,7 @@ export async function getUploadConfig(db, env) {
|
||||
// 如果环境变量未删除,进行覆盖操作
|
||||
if (s3Channels[0]) {
|
||||
s3Channels[0].enabled = s.enabled
|
||||
s3Channels[0].quota = s.quota // 保留容量限制配置
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
@@ -257,7 +257,10 @@ async function mergeR2ChunksInfo(context, uploadId, completedChunks, metadata) {
|
||||
// 使用multipart info中的finalFileId更新metadata
|
||||
const finalFileId = multipartInfo.key;
|
||||
metadata.Channel = "CloudflareR2";
|
||||
metadata.ChannelName = "R2_env";
|
||||
// 从 R2 设置中获取渠道名称
|
||||
const r2Settings = context.uploadConfig.cfr2;
|
||||
const r2ChannelName = r2Settings.channels?.[0]?.name || "R2_env";
|
||||
metadata.ChannelName = r2ChannelName;
|
||||
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);
|
||||
|
||||
// 清理multipart info
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function onRequest(context) { // Contents of context object
|
||||
|
||||
// 读取各项配置,存入 context
|
||||
const securityConfig = await fetchSecurityConfig(env);
|
||||
const uploadConfig = await fetchUploadConfig(env);
|
||||
const uploadConfig = await fetchUploadConfig(env, context);
|
||||
|
||||
context.securityConfig = securityConfig;
|
||||
context.uploadConfig = uploadConfig;
|
||||
@@ -227,12 +227,16 @@ async function uploadFileToCloudflareR2(context, fullId, metadata, returnLink) {
|
||||
|
||||
const R2DataBase = env.img_r2;
|
||||
|
||||
// 写入R2数据库
|
||||
await R2DataBase.put(fullId, formdata.get('file'));
|
||||
// 写入R2数据库,获取实际存储大小
|
||||
const r2Object = await R2DataBase.put(fullId, formdata.get('file'));
|
||||
|
||||
// 更新metadata
|
||||
metadata.Channel = "CloudflareR2";
|
||||
metadata.ChannelName = "R2_env";
|
||||
metadata.ChannelName = r2Channel.name || "R2_env";
|
||||
// 使用 R2 返回的实际文件大小
|
||||
if (r2Object && r2Object.size) {
|
||||
metadata.FileSize = (r2Object.size / 1024 / 1024).toFixed(2);
|
||||
}
|
||||
|
||||
// 图像审查,采用R2的publicUrl
|
||||
const R2PublicUrl = r2Channel.publicUrl;
|
||||
@@ -394,7 +398,7 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName
|
||||
|
||||
if (fileSize > CHUNK_SIZE) {
|
||||
// 大文件分片上传
|
||||
return await uploadLargeFileToTelegram(env, file, fullId, metadata, fileName, fileType, url, returnLink, tgBotToken, tgChatId, tgChannel);
|
||||
return await uploadLargeFileToTelegram(context, file, fullId, metadata, fileName, fileType, returnLink, tgBotToken, tgChatId, tgChannel);
|
||||
}
|
||||
|
||||
// 由于TG会把gif后缀的文件转为视频,所以需要修改后缀名绕过限制
|
||||
|
||||
@@ -178,6 +178,7 @@ export async function purgeCDNCache(env, cdnUrl, url, normalizedFolder) {
|
||||
}
|
||||
|
||||
// 结束上传:清除缓存,维护索引
|
||||
// 注意:容量统计现在由索引自动维护,不需要单独更新 quota
|
||||
export async function endUpload(context, fileId, metadata) {
|
||||
const { env, url } = context;
|
||||
|
||||
@@ -186,7 +187,7 @@ export async function endUpload(context, fileId, metadata) {
|
||||
const normalizedFolder = (url.searchParams.get('uploadFolder') || '').replace(/^\/+/, '').replace(/\/{2,}/g, '/').replace(/\/$/, '');
|
||||
await purgeCDNCache(env, cdnUrl, url, normalizedFolder);
|
||||
|
||||
// 更新文件索引
|
||||
// 更新文件索引(索引更新时会自动计算容量统计)
|
||||
await addFileToIndex(context, fileId, metadata);
|
||||
}
|
||||
|
||||
|
||||
@@ -785,6 +785,46 @@ export async function getIndexInfo(context) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取索引元数据(轻量级,只读取 meta,不读取整个索引)
|
||||
* 用于容量检查等场景,避免读取整个索引
|
||||
* @param {Object} context - 上下文对象
|
||||
* @returns {Object} 索引元数据,包含 totalCount, totalSizeMB, channelStats 等
|
||||
*/
|
||||
export async function getIndexMeta(context) {
|
||||
const { env } = context;
|
||||
const db = getDatabase(env);
|
||||
|
||||
try {
|
||||
const metadataStr = await db.get(INDEX_META_KEY);
|
||||
if (!metadataStr) {
|
||||
return {
|
||||
success: false,
|
||||
totalCount: 0,
|
||||
totalSizeMB: 0,
|
||||
channelStats: {}
|
||||
};
|
||||
}
|
||||
|
||||
const metadata = JSON.parse(metadataStr);
|
||||
return {
|
||||
success: true,
|
||||
totalCount: metadata.totalCount || 0,
|
||||
totalSizeMB: metadata.totalSizeMB || 0,
|
||||
channelStats: metadata.channelStats || {},
|
||||
lastUpdated: metadata.lastUpdated
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting index meta:', error);
|
||||
return {
|
||||
success: false,
|
||||
totalCount: 0,
|
||||
totalSizeMB: 0,
|
||||
channelStats: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* ============= 原子操作相关函数 ============= */
|
||||
|
||||
/**
|
||||
@@ -1300,10 +1340,31 @@ async function saveChunkedIndex(context, index) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// 保存索引元数据
|
||||
// 计算各渠道容量统计
|
||||
const channelStats = {};
|
||||
let totalSizeMB = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const channelName = file.metadata?.ChannelName;
|
||||
const fileSize = parseFloat(file.metadata?.FileSize) || 0;
|
||||
|
||||
totalSizeMB += fileSize;
|
||||
|
||||
if (channelName) {
|
||||
if (!channelStats[channelName]) {
|
||||
channelStats[channelName] = { usedMB: 0, fileCount: 0 };
|
||||
}
|
||||
channelStats[channelName].usedMB += fileSize;
|
||||
channelStats[channelName].fileCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存索引元数据(包含容量统计)
|
||||
const metadata = {
|
||||
lastUpdated: index.lastUpdated,
|
||||
totalCount: index.totalCount,
|
||||
totalSizeMB: Math.round(totalSizeMB * 100) / 100,
|
||||
channelStats,
|
||||
lastOperationId: index.lastOperationId,
|
||||
chunkCount: chunks.length,
|
||||
chunkSize: INDEX_CHUNK_SIZE
|
||||
@@ -1319,7 +1380,7 @@ async function saveChunkedIndex(context, index) {
|
||||
|
||||
await Promise.all(savePromises);
|
||||
|
||||
console.log(`Saved chunked index: ${chunks.length} chunks, ${files.length} total files`);
|
||||
console.log(`Saved chunked index: ${chunks.length} chunks, ${files.length} total files, ${totalSizeMB.toFixed(2)} MB`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,8 +3,57 @@ import { getSecurityConfig } from '../api/manage/sysConfig/security';
|
||||
import { getPageConfig } from '../api/manage/sysConfig/page';
|
||||
import { getOthersConfig } from '../api/manage/sysConfig/others';
|
||||
import { getDatabase } from './databaseAdapter.js';
|
||||
import { getIndexMeta } from './indexManager.js';
|
||||
|
||||
export async function fetchUploadConfig(env) {
|
||||
/**
|
||||
* 根据容量限制过滤渠道
|
||||
* @param {Object} context - 上下文对象(包含 env)
|
||||
* @param {Array} channels - 渠道列表
|
||||
* @returns {Array} 过滤后的渠道列表
|
||||
*/
|
||||
async function filterChannelsByQuota(context, channels) {
|
||||
// 先检查是否有任何渠道启用了容量限制,如果都没启用则跳过 KV 读取
|
||||
const hasQuotaEnabled = channels.some(ch => ch.quota?.enabled && ch.quota?.limitGB);
|
||||
if (!hasQuotaEnabled) {
|
||||
return channels; // 无需读取 KV,直接返回所有渠道
|
||||
}
|
||||
|
||||
// 获取索引元数据(只需 1 次读取)
|
||||
const indexMeta = await getIndexMeta(context);
|
||||
const channelStats = indexMeta.channelStats || {};
|
||||
|
||||
const result = [];
|
||||
for (const channel of channels) {
|
||||
// 未启用容量限制,直接通过
|
||||
if (!channel.quota?.enabled || !channel.quota?.limitGB) {
|
||||
result.push(channel);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 从索引元数据中获取该渠道的容量统计
|
||||
const stats = channelStats[channel.name] || { usedMB: 0, fileCount: 0 };
|
||||
|
||||
const usedGB = stats.usedMB / 1024;
|
||||
const limitGB = channel.quota.limitGB;
|
||||
const threshold = channel.quota.threshold || 95;
|
||||
|
||||
// 未超过阈值,渠道可用
|
||||
if ((usedGB / limitGB) * 100 < threshold) {
|
||||
result.push(channel);
|
||||
} else {
|
||||
console.log(`Channel ${channel.name} quota exceeded: ${usedGB.toFixed(2)}GB / ${limitGB}GB (${threshold}% threshold)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to check quota for channel ${channel.name}:`, error);
|
||||
// 检查失败时保守处理,允许使用该渠道
|
||||
result.push(channel);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function fetchUploadConfig(env, context = null) {
|
||||
try {
|
||||
const db = getDatabase(env);
|
||||
const settings = await getUploadConfig(db, env);
|
||||
@@ -13,6 +62,13 @@ export async function fetchUploadConfig(env) {
|
||||
settings.cfr2.channels = settings.cfr2.channels.filter((channel) => channel.enabled);
|
||||
settings.s3.channels = settings.s3.channels.filter((channel) => channel.enabled);
|
||||
|
||||
// 根据容量限制过滤渠道(仅 R2 和 S3)
|
||||
// 需要 context 来调用 getIndexMeta
|
||||
if (context) {
|
||||
settings.cfr2.channels = await filterChannelsByQuota(context, settings.cfr2.channels);
|
||||
settings.s3.channels = await filterChannelsByQuota(context, settings.s3.channels);
|
||||
}
|
||||
|
||||
return settings;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch upload config:', error);
|
||||
|
||||
+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"><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/chunk-vendors.780b6559.js"></script><script defer="defer" src="/js/app.da914c09.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.14879ca1.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"><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/chunk-vendors.780b6559.js"></script><script defer="defer" src="/js/app.47599341.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.14879ca1.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>
|
||||
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
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.
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
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.
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
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
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[672],{2560:function(e,t,i){i.r(t),i.d(t,{default:function(){return p}});var s=i(2619),n=i(6768);function a(e,t,i,a,o,l){const r=s.A;return(0,n.uX)(),(0,n.Wv)(r,{title:l.loginTitle,fields:o.loginFields,"submit-text":"登录","background-key":"loginBkImg","is-admin":!1,loading:o.isLoading,onSubmit:l.handleLogin},null,8,["title","fields","loading","onSubmit"])}i(4114);var o=i(4570),l=i.n(o),r=i(457),d=i(8401),u={data(){return{isLoading:!1,loginFields:[{key:"password",label:"密码",placeholder:"请输入认证码",type:"password",showPassword:!0,icon:"Lock"}]}},computed:{...(0,d.L8)(["userConfig"]),ownerName(){return this.userConfig?.ownerName||"Sanyue"},loginTitle(){return`登录到 ${this.ownerName} 图床`}},components:{BaseLogin:s.A},methods:{async handleLogin(e){const{password:t}=e,i=""===t?"unset":t;this.isLoading=!0;const s=new Promise(e=>setTimeout(e,500)),n=r.A.post("/api/login",{authCode:t}).then(e=>({res:e})).catch(e=>({err:e}));try{const[e]=await Promise.all([n,s]);e.res&&200===e.res.status?(l().set("authCode",i,"14d"),this.$router.push("/"),this.$message.success("登录成功")):(this.isLoading=!1,this.$message.error("登录失败,请检查密码是否正确"))}catch(a){this.isLoading=!1,this.$message.error("系统错误")}}}},c=i(1241);const h=(0,c.A)(u,[["render",a]]);var p=h},2619:function(e,t,i){i.d(t,{A:function(){return F}});var s=i(6975),n=i(47),a=(i(5331),i(9648),i(9623)),o=(i(6711),i(813)),l=(i(4896),i(2583)),r=i(4453),d=i(6768),u=i(4232),c=i(5130);const h={class:"login-container"},p={class:"login-title",tabindex:"0"},g={class:"input-wrapper"},m={key:0,class:"loading-ring"},f={key:1};function b(e,t,i,b,y,k){const w=r.A,L=l.A,v=o.tk,F=a.WK,C=n.S2,A=s.A;return(0,d.uX)(),(0,d.CE)("div",{class:(0,u.C4)(["login",{"is-focused":y.isFocused}])},[(0,d.bF)(w,{class:"toggle-dark"}),(0,d.bF)(L),(0,d.Lk)("div",h,[(0,d.Lk)("h1",p,(0,u.v_)(i.title),1),((0,d.uX)(!0),(0,d.CE)(d.FK,null,(0,d.pI)(i.fields,(e,t)=>((0,d.uX)(),(0,d.CE)("div",{key:e.key,class:"input-container"},[(0,d.Lk)("label",{class:"input-name",ref_for:!0,ref:`inputLabel${t}`,style:(0,u.Tr)({"--underline-width":y.labelUnderlineWidths[t]+"px"})},(0,u.v_)(e.label),5),(0,d.Lk)("div",g,[(0,d.bF)(F,{modelValue:y.formData[e.key],"onUpdate:modelValue":t=>y.formData[e.key]=t,placeholder:e.placeholder,type:e.type||"text","show-password":e.showPassword,class:"password-input",onKeyup:(0,c.jR)(k.handleSubmit,["enter","native"]),onFocus:k.handleInputFocus,onBlur:k.handleInputBlur},(0,d.eX)({_:2},[e.icon?{name:"prefix",fn:(0,d.k6)(()=>[(0,d.bF)(v,{class:"el-input__icon"},{default:(0,d.k6)(()=>[((0,d.uX)(),(0,d.Wv)((0,d.$y)(e.icon)))]),_:2},1024)]),key:"0"}:void 0]),1032,["modelValue","onUpdate:modelValue","placeholder","type","show-password","onKeyup","onFocus","onBlur"])])]))),128)),(0,d.bF)(C,{class:(0,u.C4)(["submit",{"is-loading":i.loading}]),type:"primary",onClick:k.handleSubmit,disabled:i.loading},{default:(0,d.k6)(()=>[i.loading?((0,d.uX)(),(0,d.CE)("div",m)):((0,d.uX)(),(0,d.CE)("span",f,(0,u.v_)(i.submitText),1))]),_:1},8,["class","onClick","disabled"])]),(0,d.bF)(A,{class:"footer"})],2)}i(8111),i(7588);var y=i(8401),k=i(8903),w={name:"BaseLogin",mixins:[k.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},loading:{type:Boolean,default:!1}},data(){return{formData:{},labelUnderlineWidths:[],isFocused:!1}},computed:{...(0,y.L8)(["userConfig"])},watch:{fields:{handler(){this.$nextTick(()=>{this.calculateLabelWidths()})},deep:!0}},components:{Footer:s.A,ToggleDark:r.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 i=this.$refs[`inputLabel${t}`];if(i&&i[0]){const s=document.createElement("canvas"),n=s.getContext("2d"),a=i[0],o=window.getComputedStyle(a);n.font=`${o.fontWeight} ${o.fontSize} ${o.fontFamily}`;const l=n.measureText(e.label).width;this.labelUnderlineWidths[t]=Math.ceil(l)+3}})})},handleSubmit(){this.loading||this.$emit("submit",{...this.formData})},handleInputFocus(e){this.isFocused=!0;const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.add("focused")}},handleInputBlur(e){this.isFocused=!1;const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.remove("focused")}}}},L=i(1241);const v=(0,L.A)(w,[["render",b],["__scopeId","data-v-ddf8586a"]]);var F=v}}]);
|
||||
//# sourceMappingURL=672.bf344cbb.js.map
|
||||
//# sourceMappingURL=672.11bec4e1.js.map
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[698],{2619:function(e,t,s){s.d(t,{A:function(){return F}});var i=s(6975),n=s(47),a=(s(5331),s(9648),s(9623)),o=(s(6711),s(813)),l=(s(4896),s(2583)),r=s(4453),d=s(6768),u=s(4232),c=s(5130);const h={class:"login-container"},p={class:"login-title",tabindex:"0"},m={class:"input-wrapper"},g={key:0,class:"loading-ring"},f={key:1};function b(e,t,s,b,y,k){const w=r.A,L=l.A,v=o.tk,F=a.WK,$=n.S2,A=i.A;return(0,d.uX)(),(0,d.CE)("div",{class:(0,u.C4)(["login",{"is-focused":y.isFocused}])},[(0,d.bF)(w,{class:"toggle-dark"}),(0,d.bF)(L),(0,d.Lk)("div",h,[(0,d.Lk)("h1",p,(0,u.v_)(s.title),1),((0,d.uX)(!0),(0,d.CE)(d.FK,null,(0,d.pI)(s.fields,(e,t)=>((0,d.uX)(),(0,d.CE)("div",{key:e.key,class:"input-container"},[(0,d.Lk)("label",{class:"input-name",ref_for:!0,ref:`inputLabel${t}`,style:(0,u.Tr)({"--underline-width":y.labelUnderlineWidths[t]+"px"})},(0,u.v_)(e.label),5),(0,d.Lk)("div",m,[(0,d.bF)(F,{modelValue:y.formData[e.key],"onUpdate:modelValue":t=>y.formData[e.key]=t,placeholder:e.placeholder,type:e.type||"text","show-password":e.showPassword,class:"password-input",onKeyup:(0,c.jR)(k.handleSubmit,["enter","native"]),onFocus:k.handleInputFocus,onBlur:k.handleInputBlur},(0,d.eX)({_:2},[e.icon?{name:"prefix",fn:(0,d.k6)(()=>[(0,d.bF)(v,{class:"el-input__icon"},{default:(0,d.k6)(()=>[((0,d.uX)(),(0,d.Wv)((0,d.$y)(e.icon)))]),_:2},1024)]),key:"0"}:void 0]),1032,["modelValue","onUpdate:modelValue","placeholder","type","show-password","onKeyup","onFocus","onBlur"])])]))),128)),(0,d.bF)($,{class:(0,u.C4)(["submit",{"is-loading":s.loading}]),type:"primary",onClick:k.handleSubmit,disabled:s.loading},{default:(0,d.k6)(()=>[s.loading?((0,d.uX)(),(0,d.CE)("div",g)):((0,d.uX)(),(0,d.CE)("span",f,(0,u.v_)(s.submitText),1))]),_:1},8,["class","onClick","disabled"])]),(0,d.bF)(A,{class:"footer"})],2)}s(8111),s(7588);var y=s(8401),k=s(8903),w={name:"BaseLogin",mixins:[k.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},loading:{type:Boolean,default:!1}},data(){return{formData:{},labelUnderlineWidths:[],isFocused:!1}},computed:{...(0,y.L8)(["userConfig"])},watch:{fields:{handler(){this.$nextTick(()=>{this.calculateLabelWidths()})},deep:!0}},components:{Footer:i.A,ToggleDark:r.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 i=document.createElement("canvas"),n=i.getContext("2d"),a=s[0],o=window.getComputedStyle(a);n.font=`${o.fontWeight} ${o.fontSize} ${o.fontFamily}`;const l=n.measureText(e.label).width;this.labelUnderlineWidths[t]=Math.ceil(l)+3}})})},handleSubmit(){this.loading||this.$emit("submit",{...this.formData})},handleInputFocus(e){this.isFocused=!0;const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.add("focused")}},handleInputBlur(e){this.isFocused=!1;const t=e.target.closest(".input-container");if(t){const e=t.querySelector(".input-wrapper");e&&e.classList.remove("focused")}}}},L=s(1241);const v=(0,L.A)(w,[["render",b],["__scopeId","data-v-ddf8586a"]]);var F=v},3950:function(e,t,s){s.r(t),s.d(t,{default:function(){return u}});var i=s(2619),n=s(6768);function a(e,t,s,a,o,l){const r=i.A;return(0,n.uX)(),(0,n.Wv)(r,{title:"管理端登录",fields:o.loginFields,"submit-text":"登录","background-key":"adminLoginBkImg","is-admin":!0,loading:o.isLoading,onSubmit:l.handleLogin},null,8,["fields","loading","onSubmit"])}s(4114),s(4979);var o=s(457),l={data(){return{isLoading:!1,loginFields:[{key:"username",label:"用户名",placeholder:"请输入用户名",type:"text",icon:"User"},{key:"password",label:"密码",placeholder:"请输入密码",type:"password",showPassword:!0,icon:"Lock"}]}},components:{BaseLogin:i.A},methods:{async handleLogin(e){const{username:t,password:s}=e,i=btoa(`${t}:${s}`);this.isLoading=!0;const n=new Promise(e=>setTimeout(e,1e3)),a=o.A.get("/api/manage/check",{headers:{Authorization:`Basic ${i}`},withCredentials:!0}).then(e=>({response:e})).catch(e=>({error:e}));try{const[e]=await Promise.all([a,n]);if(e.response&&200===e.response.status)this.$store.commit("setCredentials",i),this.$router.push("/dashboard");else{const t=e.error||new Error("Unknown error");this.isLoading=!1,t.response&&401===t.response.status?this.$message.error("用户名或密码错误"):this.$message.error("服务器错误")}}catch(l){this.isLoading=!1,this.$message.error("系统错误")}}}},r=s(1241);const d=(0,r.A)(l,[["render",a]]);var u=d}}]);
|
||||
//# sourceMappingURL=698.ca48e466.js.map
|
||||
//# sourceMappingURL=698.77f859a8.js.map
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
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
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
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Reference in New Issue
Block a user