主域名 nginx配置
server
{
listen 80 default_server;
server_name s*****c;
index index.html;
root /smb;
#CERT-APPLY-CHECK--START
# 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
include /www/server/panel/vhost/nginx/well-known/smb.abin.cc.conf;
#CERT-APPLY-CHECK--END
add_header Cache-Control no-cache;
add_before_body /DownJS/header.html;
add_after_body /DownJS/footer.html;
auth_basic "User Authentication";# 添加这一行
auth_basic_user_file /******tpasswd; //配置密码路径
#文件访问
charset utf-8;
autoindex on;
autoindex_exact_size off;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-80.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/s*****c.conf;
#REWRITE-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/s*****c.log;
error_log /www/wwwlogs/s*****c.error.log;
}
其他双域名配置
server
{
listen 80;
server_name h*****c h*****c;
index index.html;
root /smb;
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
add_header Access-Control-Allow-Headers 'Range';
add_header Access-Control-Expose-Headers 'Content-Length, Content-Range';
# 如果你需要支持 OPTIONS 预检请求,可以加上:
if ($request_method = 'OPTIONS') {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
error_page 404 /404.html;
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-80.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/s*****c.conf;
#REWRITE-END
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
access_log /www/wwwlogs/h*****c.log;
error_log /www/wwwlogs/h*****c.error.log;
}
根目录文件
DownJS
footer.html
<script>
document.querySelectorAll('a').forEach(a=>{
a.addEventListener('click', e=>{
e.preventDefault();
parallelDownload(a.href);
});
});
</script>
header.html
<script src="/DownJS/parallel.js" defer></script>
<link rel="stylesheet" href="/DownJS/styles.css">
parallel.js
(() => {
// 创建按钮容器
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = "position:absolute;top:10px;right:10px;z-index:9999;display:flex;gap:10px;";
// 退出按钮
const exitButton = document.createElement("button");
exitButton.innerText = "退出";
exitButton.style.cssText = "padding:5px 10px;background:#f44336;color:white;border:none;border-radius:3px;cursor:pointer;";
exitButton.onclick = () => {
window.location.href="http://1@smb.abin.cc:880";
};
// 清除缓存按钮
const clearCacheButton = document.createElement("button");
clearCacheButton.innerText = "清除缓存";
clearCacheButton.style.cssText = "padding:5px 10px;background:#2196F3;color:white;border:none;border-radius:3px;cursor:pointer;";
clearCacheButton.onclick = async () => {
try {
clearCacheButton.disabled = true;
clearCacheButton.innerText = "正在清除...";
// 打开数据库
const db = await openDB();
// 清除所有缓存数据
await clearAllCache(db);
clearCacheButton.innerText = "缓存已清除";
setTimeout(() => {
clearCacheButton.innerText = "清除缓存";
clearCacheButton.disabled = false;
}, 2000);
} catch (error) {
console.error("清除缓存失败:", error);
clearCacheButton.innerText = "清除失败";
setTimeout(() => {
clearCacheButton.innerText = "清除缓存";
clearCacheButton.disabled = false;
}, 2000);
}
};
// 添加按钮到容器
buttonContainer.appendChild(clearCacheButton);
buttonContainer.appendChild(exitButton);
// 添加容器到页面
document.body.appendChild(buttonContainer);
})();
async function parallelDownload(url) {
try {
// 重置进度条和速度显示
resetProgress();
stopSpeedMonitor();
const urlObj = new URL(url);
const path = urlObj.pathname;
// 判断路径是否以"/"结尾,如果是则直接跳转
if (path.endsWith('/')) {
console.log("检测到目录路径,直接跳转:", url);
window.location.href = url;
return;
}
const mirrors = [
"http://home.abin.cc:880",
"http://home2.abin.cc:880"
];
const fullUrls = mirrors.map(base => new URL(path, base).toString());
// 获取文件大小
const db = await openDB();
// 断点续传:尝试获取已存储的文件大小
let size = await getStoredFileSize(db, path);
let isResume = false;
if (!size) {
// 新下载:获取文件大小
size = await getFileSize(fullUrls[0]);
console.log("获取文件大小", size);
if (isNaN(size) || size <= 0) {
throw new Error("文件大小获取失败");
}
// 存储文件大小以便断点续传
await storeFileSize(db, path, size);
} else {
console.log("检测到未完成的下载,继续下载文件,大小:", size);
isResume = true;
}
// 对于小于1MB的文件,直接下载
if (size < 1024 * 1024) {
console.log("文件小于1MB,直接下载");
showProgress();
updateProgress(0);
const response = await fetch(fullUrls[0]);
if (!response.ok) {
throw new Error(`下载失败: ${response.status}`);
}
const blob = await response.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = decodeURIComponent(path.split("/").pop());
a.click();
updateProgress(100);
setTimeout(() => {
resetProgress();
}, 3000);
return;
}
// 重新创建进度条UI
removeProgressBar();
showProgress(); // 显示进度条
updateProgress(0); // 确保进度从0开始
// 为大文件优化分片数量
let numChunks = 100;
// 对于大文件(>500MB),增加分片数量,以便更频繁地更新进度
if (size > 500 * 1024 * 1024) { // 500MB
numChunks = 200;
}
if (size > 1024 * 1024 * 1024) { // 1GB
numChunks = 500;
}
const chunkSize = Math.ceil(size / numChunks);
console.log(`文件大小: ${formatSize(size)}, 分片数: ${numChunks}, 每个分片大小: ${formatSize(chunkSize)}`);
// 使用双精度浮点数进行进度计算
const progressState = {
downloaded: 0,
total: size,
lastProgressUpdate: Date.now(),
lastDownloadedBytes: 0,
bytesPerSecond: 0
};
let downloadedChunks = new Array(numChunks).fill(false);
let chunkData = new Array(numChunks).fill(null);
// 用于跟踪正在下载的分片
const downloadingChunks = new Set();
// 获取已下载的进度(断点续传)
let totalDownloaded = 0;
for (let i = 0; i < numChunks; i++) {
const storedData = await getChunk(db, path, i);
if (storedData) {
chunkData[i] = storedData;
downloadedChunks[i] = true;
// 确保使用byteLength而不是length
totalDownloaded += storedData.byteLength;
}
}
// 正确设置已下载大小
progressState.downloaded = totalDownloaded;
progressState.lastDownloadedBytes = totalDownloaded;
if (progressState.total <= 0) {
throw new Error("文件大小无效");
}
// 计算初始进度百分比
const initialPercentComplete = (progressState.downloaded / progressState.total * 100).toFixed(2);
updateProgress(initialPercentComplete);
console.log("当前下载进度:", initialPercentComplete + "%", `(${formatSize(progressState.downloaded)}/${formatSize(progressState.total)})`);
// 启动高精度进度条更新
startProgressUpdater(progressState);
// 启动速度监控
startSpeedMonitor(progressState);
// 计算待下载的分片索引
const chunksToDownload = [];
for (let i = 0; i < numChunks; i++) {
if (!downloadedChunks[i]) {
chunksToDownload.push(i);
}
}
console.log(`需要下载 ${chunksToDownload.length} 个分片,共 ${numChunks} 个`);
// 如果没有分片需要下载,直接完成
if (chunksToDownload.length === 0) {
console.log("所有分片已下载完成,直接合并");
} else {
// 创建下载队列和互斥锁
// 使用共享队列让所有镜像抢占式地下载分片
const downloadQueue = [...chunksToDownload];
// 启动多个线路同时下载
const downloadPromises = mirrors.map((mirror, index) => {
return downloadFromMirror(
mirror,
path,
downloadQueue,
downloadingChunks,
chunkSize,
size,
db,
chunkData,
downloadedChunks,
progressState,
index
);
});
// 等待所有下载线路完成
await Promise.all(downloadPromises);
}
// 停止高精度进度条更新
stopProgressUpdater();
// 检查是否所有分片都已下载
const allChunksDownloaded = downloadedChunks.every(status => status === true);
if (!allChunksDownloaded) {
throw new Error("部分分片下载失败,请重试");
}
// ✅ 合并下载的所有分片
const blobParts = chunkData.map(chunk => new Blob([chunk]));
const finalBlob = new Blob(blobParts);
const a = document.createElement("a");
a.href = URL.createObjectURL(finalBlob);
a.download = decodeURIComponent(path.split("/").pop());
a.click();
updateProgress(100);
stopSpeedMonitor();
// 清除缓存
console.log("下载完成,正在清除缓存...");
await clearChunks(db, path);
console.log("缓存清除完成");
// 延迟重置进度条,让用户能看到100%完成的状态
setTimeout(() => {
resetProgress();
}, 3000);
} catch (e) {
console.error("下载出错:", e);
alert("下载失败:" + e.message);
stopSpeedMonitor();
stopProgressUpdater();
} finally {
// 确保即使出错也会尝试清理定时器
stopProgressUpdater();
// 确保即使出错也会尝试清理缓存
try {
const db = await openDB();
await clearChunks(db, path);
console.log("缓存清理完成");
} catch (e) {
console.error("清理缓存出错:", e);
}
}
}
// 从指定镜像抢占式下载分片
async function downloadFromMirror(mirrorBase, path, downloadQueue, downloadingChunks, chunkSize, totalSize, db, chunkData, downloadedChunks, progressState, mirrorIndex) {
const mirrorUrl = new URL(path, mirrorBase).toString();
console.log(`镜像 ${mirrorIndex+1}(${mirrorBase}) 开始下载`);
// 并发数量 - 为大文件增加并发数
const concurrentLimit = totalSize > 1024 * 1024 * 1024 ? 10 : 5; // 大于1GB时使用10个并发
let activeDownloads = 0;
// 下载单个分片
async function downloadChunk(chunkIndex) {
// 如果该分片已经完成或正在下载,跳过
if (downloadedChunks[chunkIndex] || downloadingChunks.has(chunkIndex)) {
return true;
}
// 标记为正在下载
downloadingChunks.add(chunkIndex);
try {
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize - 1, totalSize - 1);
const res = await fetch(mirrorUrl, {
headers: { Range: `bytes=${start}-${end}` }
});
if (!res.ok && res.status !== 206) {
throw new Error(`分段请求失败: 状态码 ${res.status}`);
}
// 使用流式读取响应,实时更新进度
const reader = res.body.getReader();
let receivedLength = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
receivedLength += value.length;
// 实时更新下载进度
progressState.downloaded += value.length;
// 计算下载速度
const now = Date.now();
const elapsed = now - progressState.lastProgressUpdate;
if (elapsed > 1000) { // 每秒更新一次速度
const bytesDownloaded = progressState.downloaded - progressState.lastDownloadedBytes;
progressState.bytesPerSecond = Math.round(bytesDownloaded * 1000 / elapsed);
progressState.lastDownloadedBytes = progressState.downloaded;
progressState.lastProgressUpdate = now;
}
}
// 合并所有块
const chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (const chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}
// 转换为ArrayBuffer
const buffer = chunksAll.buffer;
// 存储和标记为完成
chunkData[chunkIndex] = buffer;
await storeChunk(db, path, chunkIndex, buffer);
downloadedChunks[chunkIndex] = true;
console.log(`镜像 ${mirrorIndex+1} 下载分片 ${chunkIndex} 完成,总进度:${(progressState.downloaded / progressState.total * 100).toFixed(2)}%`);
return true;
} catch (error) {
console.error(`镜像 ${mirrorIndex+1} 下载分片 ${chunkIndex} 失败:`, error);
return false;
} finally {
// 无论成功失败,都移除正在下载标记
downloadingChunks.delete(chunkIndex);
activeDownloads--;
}
}
// 主下载循环
while (downloadQueue.length > 0 || activeDownloads > 0) {
// 如果当前下载数量未达到上限且队列中有任务
while (activeDownloads < concurrentLimit && downloadQueue.length > 0) {
// 从队列中取出一个分片索引
const chunkIndex = downloadQueue.shift();
// 如果该分片已完成,跳过
if (downloadedChunks[chunkIndex]) {
continue;
}
// 如果该分片正在被其他镜像下载,放回队列末尾
if (downloadingChunks.has(chunkIndex)) {
downloadQueue.push(chunkIndex);
continue;
}
// 开始下载
activeDownloads++;
downloadChunk(chunkIndex).catch(err => {
console.error(`处理分片下载异常:`, err);
// 下载失败,将分片放回队列
if (!downloadedChunks[chunkIndex]) {
downloadQueue.push(chunkIndex);
}
});
}
// 等待一小段时间再继续检查
await new Promise(resolve => setTimeout(resolve, 100));
// 如果队列为空但还有活动下载,等待它们完成
if (downloadQueue.length === 0 && activeDownloads > 0) {
await new Promise(resolve => setTimeout(resolve, 500));
}
// 如果所有分片都已下载完成,退出循环
if (downloadedChunks.every(status => status === true)) {
break;
}
}
console.log(`镜像 ${mirrorIndex+1}(${mirrorBase}) 下载任务完成`);
}
// 获取文件大小
async function getFileSize(url) {
try {
const res = await fetch(url, {
method: "GET",
headers: { Range: "bytes=0-0" } // 只请求第一个字节
});
if (!res.ok) throw new Error("无法获取文件大小");
const contentRange = res.headers.get("content-range");
if (contentRange) {
// 从Content-Range头中提取总大小
const totalSize = parseInt(contentRange.split("/")[1], 10);
if (!isNaN(totalSize)) {
return totalSize;
}
}
// 如果没有Content-Range,尝试从Content-Length获取
const contentLength = res.headers.get("content-length");
if (contentLength) {
return parseInt(contentLength, 10);
}
throw new Error("无法获取文件大小");
} catch (e) {
console.error("获取文件大小失败:", e);
throw new Error("无法获取文件大小");
}
}
// 显示进度条
function showProgress() {
let container = document.getElementById("progress-container");
if (!container) {
container = document.createElement("div");
container.id = "progress-container";
container.style.cssText = "width:100%;background:#eee;border-radius:5px;overflow:hidden;height:24px;font-family:sans-serif;";
const bar = document.createElement("div");
bar.id = "progress-bar";
bar.style.cssText = "height:100%;width:0%;background:#4caf50;text-align:center;color:white;line-height:24px;float:left;";
const speed = document.createElement("div");
speed.id = "speed-display";
speed.style.cssText = "position: absolute;right:0;float:right;padding-right:10px;color:#333;font-size:14px;line-height:24px;";
speed.innerText = "速度:0 KB/s";
container.appendChild(bar);
container.appendChild(speed);
// ✅ 插入到 <h1> 和 <hr> 之间
const h1 = document.querySelector("h1");
const hr = document.querySelector("hr");
if (h1 && hr) {
hr.parentNode.insertBefore(container, hr);
} else {
document.body.appendChild(container); // fallback
}
}
}
// 更新下载进度
function updateProgress(percent) {
const bar = document.getElementById("progress-bar");
if (bar) {
// 确保百分比是数字并且精确到小数点后2位
const displayPercent = typeof percent === 'string' ? percent : percent.toFixed(2);
bar.style.width = displayPercent + "%";
// 进度条文字内容
if (parseFloat(displayPercent) >= 100) {
bar.innerText = '下载完成。正在弹窗中...';
} else if (parseFloat(displayPercent) <= 0) {
bar.innerText = "准备下载...";
} else {
bar.innerText = displayPercent + "%";
}
}
}
// 打开 IndexedDB
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("downloadDB", 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("chunks")) {
db.createObjectStore("chunks");
}
if (!db.objectStoreNames.contains("metadata")) {
db.createObjectStore("metadata");
}
};
request.onsuccess = (event) => resolve(event.target.result);
request.onerror = (event) => reject(event.target.error);
});
}
// 存储文件大小
function storeFileSize(db, path, size) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["metadata"], "readwrite");
const store = transaction.objectStore("metadata");
store.put(size, `${path}_size`);
transaction.oncomplete = () => resolve();
transaction.onerror = (event) => reject(event.target.error);
});
}
// 获取存储的文件大小
function getStoredFileSize(db, path) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["metadata"]);
const store = transaction.objectStore("metadata");
const request = store.get(`${path}_size`);
request.onsuccess = (event) => resolve(event.target.result);
request.onerror = (event) => reject(event.target.error);
});
}
// 存储数据到 IndexedDB
function storeChunk(db, path, chunkIndex, chunkData) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["chunks"], "readwrite");
const store = transaction.objectStore("chunks");
store.put(chunkData, `${path}_chunk_${chunkIndex}`);
transaction.oncomplete = () => resolve();
transaction.onerror = (event) => reject(event.target.error);
});
}
// 从 IndexedDB 获取数据
function getChunk(db, path, chunkIndex) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["chunks"]);
const store = transaction.objectStore("chunks");
const request = store.get(`${path}_chunk_${chunkIndex}`);
request.onsuccess = (event) => {
const result = event.target.result;
resolve(result); // 可能是null,这是正常的
};
request.onerror = (event) => {
console.error(`获取分片${chunkIndex}失败:`, event.target.error);
reject(event.target.error);
};
});
}
// 清除下载的分片和元数据
function clearChunks(db, path) {
return new Promise((resolve, reject) => {
try {
// 清除分片数据
const chunksTransaction = db.transaction(["chunks"], "readwrite");
const chunksStore = chunksTransaction.objectStore("chunks");
// 获取所有键
const getAllKeysRequest = chunksStore.getAllKeys();
getAllKeysRequest.onsuccess = () => {
const keys = getAllKeysRequest.result;
const pathPrefix = `${path}_chunk_`;
let deletedCount = 0;
// 删除与当前路径相关的所有分片
keys.forEach(key => {
if (typeof key === 'string' && key.startsWith(pathPrefix)) {
chunksStore.delete(key);
deletedCount++;
}
});
console.log(`已清除${deletedCount}个分片缓存`);
};
chunksTransaction.oncomplete = () => {
// 清除元数据
const metadataTransaction = db.transaction(["metadata"], "readwrite");
const metadataStore = metadataTransaction.objectStore("metadata");
metadataStore.delete(`${path}_size`);
metadataTransaction.oncomplete = () => {
console.log(`已清除文件大小元数据缓存`);
resolve();
};
metadataTransaction.onerror = (event) => {
console.error("清除元数据失败:", event.target.error);
reject(event.target.error);
};
};
chunksTransaction.onerror = (event) => {
console.error("清除分片数据失败:", event.target.error);
reject(event.target.error);
};
} catch (error) {
console.error("清除缓存时出错:", error);
reject(error);
}
});
}
// 速度监控
let speedTimer = null;
let lastDownloaded = 0;
function startSpeedMonitor(progressState) {
lastDownloaded = progressState.downloaded;
speedTimer = setInterval(() => {
const delta = progressState.downloaded - lastDownloaded;
lastDownloaded = progressState.downloaded;
const speedInBytes = delta;
progressState.bytesPerSecond = speedInBytes;
updateSpeedDisplay(speedInBytes);
}, 1000); // 每秒更新一次
}
function stopSpeedMonitor() {
clearInterval(speedTimer);
speedTimer = null;
}
// 重置进度条
function resetProgress() {
const bar = document.getElementById("progress-bar");
const speedDisplay = document.getElementById("speed-display");
if (bar) {
bar.style.width = "0%";
bar.innerText = "准备下载...";
}
if (speedDisplay) {
speedDisplay.innerText = "速度:0 KB/s";
}
lastDownloaded = 0;
}
// 完全移除进度条,下次重新创建
function removeProgressBar() {
const container = document.getElementById("progress-container");
if (container) {
container.remove();
}
}
// 高精度进度条更新器
let progressUpdateTimer = null;
function startProgressUpdater(progressState) {
// 停止现有定时器
stopProgressUpdater();
// 创建新的高频率定时器
progressUpdateTimer = setInterval(() => {
if (progressState && progressState.total > 0) {
const percent = (progressState.downloaded / progressState.total * 100).toFixed(2);
updateProgress(percent);
// 更新速度显示
updateSpeedDisplay(progressState.bytesPerSecond);
}
}, 10); // 每10毫秒更新一次
}
function stopProgressUpdater() {
if (progressUpdateTimer) {
clearInterval(progressUpdateTimer);
progressUpdateTimer = null;
}
}
function updateSpeedDisplay(bytesPerSecond) {
const speedDisplay = document.getElementById("speed-display");
if (!speedDisplay) return;
const speedText = formatSpeed(bytesPerSecond);
speedDisplay.innerText = speedText;
}
// 格式化文件大小
function formatSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 格式化速度
function formatSpeed(bytesPerSecond) {
if (bytesPerSecond === 0) return '速度:0 KB/s';
if (bytesPerSecond >= 1024 * 1024) {
return `速度:${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
} else {
return `速度:${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
}
}
// 清除所有缓存数据
async function clearAllCache(db) {
return new Promise((resolve, reject) => {
try {
console.log("开始清除所有缓存...");
// 清除所有分片数据
const chunksTransaction = db.transaction(["chunks"], "readwrite");
const chunksStore = chunksTransaction.objectStore("chunks");
const chunksRequest = chunksStore.clear();
chunksTransaction.oncomplete = () => {
// 清除所有元数据
const metadataTransaction = db.transaction(["metadata"], "readwrite");
const metadataStore = metadataTransaction.objectStore("metadata");
const metadataRequest = metadataStore.clear();
metadataTransaction.oncomplete = () => {
console.log("所有缓存已清除");
// alert("所有下载缓存已清除!");
resolve();
};
metadataTransaction.onerror = (event) => {
console.error("清除元数据失败:", event.target.error);
reject(event.target.error);
};
};
chunksTransaction.onerror = (event) => {
console.error("清除分片数据失败:", event.target.error);
reject(event.target.error);
};
} catch (error) {
console.error("清除缓存时出错:", error);
reject(error);
}
});
}
style.css
body {
font-size: x-large;
}
button {
position:relative;
top:10px;
right:10px;
z-index:9999;
padding:8px 14px;
font-size:14px;
background:red;
color:white;
border:none;
border-radius:6px;
cursor:pointer;
box-shadow:0 2px 4px rgba(0,0,0,0.2);
}
评论区