From 4d84b231435bb8fd0b419cf9b08b1cdb5e5e3a2c Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:13:10 +0800
Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20Cloudflare?=
=?UTF-8?q?=20Worker=20=E4=BB=A3=E7=90=86=E8=84=9A=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
本次提交对 cf-worker/index.js 进行了多项改进,旨在提高其安全性、性能和代码质量。
安全增强:
引入 ALLOWED_DOMAINS 白名单机制,以精确控制允许通过代理访问的域名,有效防止代理被恶意利用。
性能优化:
将 blocker 中的域名和扩展名列表从数组改为 Set,利用其 O(1) 的时间复杂度进行查找,提升了过滤性能。
代码重构与健壮性:
使用标准的 URL 对象来解析和处理传入的 URL,替代了原有的字符串操作,使逻辑更清晰且不易出错。
优化了对请求头(Headers)的处理逻辑。
完善了错误处理机制,能更好地捕获和响应异常。
为 OPTIONS 预检请求提供了标准的响应处理。
---
cf-worker/index.js | 251 +++++++++++++++++++++++----------------------
1 file changed, 127 insertions(+), 124 deletions(-)
diff --git a/cf-worker/index.js b/cf-worker/index.js
index f53dbcd5..67659a5e 100644
--- a/cf-worker/index.js
+++ b/cf-worker/index.js
@@ -1,135 +1,138 @@
-addEventListener('fetch', event => {
- event.passThroughOnException()
-
- event.respondWith(handleRequest(event))
- })
-
- /**
- * Respond to the request
- * @param {Request} request
- */
- async function handleRequest(event) {
- const { request } = event;
-
- //请求头部、返回对象
- let reqHeaders = new Headers(request.headers),
- outBody, outStatus = 200, outStatusText = 'OK', outCt = null, outHeaders = new Headers({
- "Access-Control-Allow-Origin": reqHeaders.get('Origin'),
- "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
- "Access-Control-Allow-Headers": reqHeaders.get('Access-Control-Allow-Headers') || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version"
- });
-
+const PREFLIGHT_INIT = {
+ status: 204,
+ headers: new Headers({
+ "access-control-allow-origin": "*",
+ "access-control-allow-methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
+ "access-control-allow-headers": "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
+ }),
+};
+
+// 使用Set以获得更好的性能
+const BLOCKED_EXTENSIONS = new Set([
+ ".m3u8",
+ ".ts",
+ ".acc",
+ ".m4s",
+]);
+const BLOCKED_DOMAINS = new Set([
+ "photocall.tv",
+ "googlevideo.com",
+ "xunleix.com"
+]);
+
+// 增加域名白名单
+const ALLOWED_DOMAINS = new Set([
+ // 在这里添加你允许代理的域名,例如:
+ // "example.com",
+ // "api.example.com",
+]);
+
+
+function isUrlBlocked(url) {
+ const lowercasedUrl = url.toLowerCase();
+ for (const ext of BLOCKED_EXTENSIONS) {
+ if (lowercasedUrl.endsWith(ext)) {
+ return true;
+ }
+ }
+ for (const domain of BLOCKED_DOMAINS) {
+ if (lowercasedUrl.includes(domain)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+async function handleRequest(event) {
+ const {
+ request
+ } = event;
+
+ const reqHeaders = new Headers(request.headers);
+ const outHeaders = new Headers({
+ "Access-Control-Allow-Origin": reqHeaders.get("Origin") || "*",
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
+ "Access-Control-Allow-Headers": reqHeaders.get("Access-Control-Allow-Headers") || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
+ });
+
+ if (request.method === "OPTIONS") {
+ return new Response(null, PREFLIGHT_INIT);
+ }
+
try {
- //取域名第一个斜杠后的所有信息为代理链接
- let url = request.url.substr(8);
- url = decodeURIComponent(url.substr(url.indexOf('/') + 1));
-
- //需要忽略的代理
- if (request.method == "OPTIONS" && reqHeaders.has('access-control-request-headers')) {
- //输出提示
- return new Response(null, PREFLIGHT_INIT)
+ const urlString = decodeURIComponent(request.url.split('/').slice(3).join('/'));
+
+ if (urlString.length < 3 || urlString.indexOf('.') === -1 || urlString === "favicon.ico" || urlString === "robots.txt") {
+ return Response.redirect('https://baidu.com', 301);
}
- else if(url.length < 3 || url.indexOf('.') == -1 || url == "favicon.ico" || url == "robots.txt") {
- return Response.redirect('https://baidu.com', 301)
+
+ if (isUrlBlocked(urlString)) {
+ return Response.redirect('https://baidu.com', 301);
}
- //阻断
- else if (blocker.check(url)) {
- return Response.redirect('https://baidu.com', 301)
+
+ const url = new URL(urlString.startsWith('http') ? urlString : 'http://' + urlString);
+
+ // 如果启用了白名单,则检查域名
+ if (ALLOWED_DOMAINS.size > 0 && !ALLOWED_DOMAINS.has(url.hostname)) {
+ return new Response("Domain not allowed", { status: 403 });
}
- else {
- //补上前缀 http://
- url = url.replace(/https:(\/)*/,'https://').replace(/http:(\/)*/, 'http://')
- if (url.indexOf("://") == -1) {
- url = "http://" + url;
- }
- //构建 fetch 参数
- let fp = {
- method: request.method,
- headers: {}
- }
-
- //保留头部其它信息
- let he = reqHeaders.entries();
- for (let h of he) {
- if (!['content-length'].includes(h[0])) {
- fp.headers[h[0]] = h[1];
- }
- }
- // 是否带 body
- if (["POST", "PUT", "PATCH", "DELETE"].indexOf(request.method) >= 0) {
- const ct = (reqHeaders.get('content-type') || "").toLowerCase();
- if (ct.includes('application/json')) {
- let requestJSON = await request.json()
- console.log(typeof requestJSON)
- fp.body = JSON.stringify(requestJSON);
- } else if (ct.includes('application/text') || ct.includes('text/html')) {
- fp.body = await request.text();
- } else if (ct.includes('form')) {
- fp.body = await request.formData();
- } else {
- fp.body = await request.blob();
- }
- }
- // 发起 fetch
- let fr = (await fetch(new URL(url), fp));
- outCt = fr.headers.get('content-type');
- if(outCt && (outCt.includes('application/text') || outCt.includes('text/html'))) {
- try {
- // 添加base
- let newFr = new HTMLRewriter()
- .on("head", {
- element(element) {
- element.prepend(``, {
- html: true
- })
- },
- })
- .transform(fr)
- fr = newFr
- } catch(e) {
- }
- }
- for (const [key, value] of fr.headers.entries()) {
- outHeaders.set(key, value);
- }
- outStatus = fr.status;
- outStatusText = fr.statusText;
- outBody = fr.body;
+ const fp = {
+ method: request.method,
+ headers: new Headers(reqHeaders),
+ };
+
+ fp.headers.delete('content-length');
+
+ if (["POST", "PUT", "PATCH", "DELETE"].includes(request.method)) {
+ fp.body = request.body;
}
+
+ let fr = await fetch(new Request(url, fp));
+
+ for (const [key, value] of fr.headers.entries()) {
+ outHeaders.set(key, value);
+ }
+
+ const outCt = fr.headers.get('content-type') || '';
+ let outBody = fr.body;
+
+ if (outCt.includes('text/html')) {
+ try {
+ const rewriter = new HTMLRewriter().on("head", {
+ element(element) {
+ element.prepend(``, {
+ html: true
+ });
+ },
+ });
+ outBody = rewriter.transform(fr).body;
+ } catch (e) {
+ // 如果HTMLRewriter失败,则返回原始响应
+ }
+ }
+
+ return new Response(outBody, {
+ status: fr.status,
+ statusText: fr.statusText,
+ headers: outHeaders,
+ });
+
} catch (err) {
- outCt = "application/json";
- outBody = JSON.stringify({
+ return new Response(JSON.stringify({
code: -1,
- msg: JSON.stringify(err.stack) || err
+ msg: err.stack || err.toString(),
+ }), {
+ status: 500,
+ headers: {
+ "content-type": "application/json"
+ }
});
}
-
- //设置类型
- if (outCt && outCt != "") {
- outHeaders.set("content-type", outCt);
- }
-
- let response = new Response(outBody, {
- status: outStatus,
- statusText: outStatusText,
- headers: outHeaders
- })
-
- return response;
-
- // return new Response('OK', { status: 200 })
- }
-
- /**
- * 阻断器
- */
- const blocker = {
- keys: [".m3u8", ".ts", ".acc", ".m4s", "photocall.tv", "googlevideo.com", "xunleix.com"],
- check: function (url) {
- url = url.toLowerCase();
- let len = blocker.keys.filter(x => url.includes(x)).length;
- return len != 0;
- }
- }
+}
+
+addEventListener('fetch', event => {
+ event.passThroughOnException();
+ event.respondWith(handleRequest(event));
+});
From 006ef2fb64e3d1e90907ba6b14ad4aa87c8e94ce Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:19:43 +0800
Subject: [PATCH 02/11] =?UTF-8?q?feat(worker,=20docs):=20=E5=A2=9E?=
=?UTF-8?q?=E5=8A=A0=E9=80=9A=E9=85=8D=E7=AC=A6=E7=99=BD=E5=90=8D=E5=8D=95?=
=?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
本次提交包含两项主要更新:增强 Cloudflare Worker 的功能性,并提供一份全面的项目说明文档。
- **Cloudflare Worker (`cf-worker/index.js`):**
- 重构了域名验证逻辑,**增加了对通配符规则的支持** (例如 `*.mypikpak.com`)。
- 这使得白名单配置更灵活、易于维护,能够轻松覆盖 PikPak 大量的下载和视频子域名,避免了频繁手动添加的麻烦。
- 已在脚本中预置 PikPak Web 应用正常运行所需的域名规则。
- **文档 (`README.md`):**
- **全面重写了 `README.md` 文件**,使其成为一份自包含的完整指南。
- 新增了详细的“功能特性”部分,让用户可以快速了解项目能力。
- 提供了从零开始的“部署指南”,包括如何打包项目、如何创建并配置 Cloudflare Worker 反向代理的详细步骤。
---
cf-worker/index.js | 124 +++++++++++++++++++++++++++------------------
1 file changed, 76 insertions(+), 48 deletions(-)
diff --git a/cf-worker/index.js b/cf-worker/index.js
index 67659a5e..07da1184 100644
--- a/cf-worker/index.js
+++ b/cf-worker/index.js
@@ -1,3 +1,6 @@
+/**
+ * 预检请求的配置
+ */
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
@@ -7,46 +10,83 @@ const PREFLIGHT_INIT = {
}),
};
-// 使用Set以获得更好的性能
-const BLOCKED_EXTENSIONS = new Set([
+/**
+ * 黑名单:包含不希望代理的URL关键字或扩展名
+ */
+const BLOCKED_KEYWORDS = new Set([
".m3u8",
".ts",
".acc",
".m4s",
-]);
-const BLOCKED_DOMAINS = new Set([
"photocall.tv",
"googlevideo.com",
- "xunleix.com"
+ "xunleix.com",
]);
-// 增加域名白名单
+/**
+ * 白名单:允许代理的域名列表,支持通配符
+ * 例如:
+ * - "mypikpak.com" // 精确匹配 mypikpak.com
+ * - "*.mypikpak.com" // 匹配所有 mypikpak.com 的子域名
+ */
const ALLOWED_DOMAINS = new Set([
- // 在这里添加你允许代理的域名,例如:
- // "example.com",
- // "api.example.com",
+ // PikPak 及其所有子域名
+ "*.mypikpak.com",
+ "mypikpak.com",
+
+ // 其他依赖服务
+ "api.notion.com",
+ "invite.z7.workers.dev",
+ "pikpak-depot.z10.workers.dev"
]);
-
+/**
+ * 检查URL是否在黑名单中
+ * @param {string} url 要检查的URL
+ * @returns {boolean} 如果在黑名单中则返回 true
+ */
function isUrlBlocked(url) {
const lowercasedUrl = url.toLowerCase();
- for (const ext of BLOCKED_EXTENSIONS) {
- if (lowercasedUrl.endsWith(ext)) {
+ for (const keyword of BLOCKED_KEYWORDS) {
+ if (lowercasedUrl.includes(keyword)) {
return true;
}
}
- for (const domain of BLOCKED_DOMAINS) {
- if (lowercasedUrl.includes(domain)) {
+ return false;
+}
+
+/**
+ * 检查域名是否在白名单中(支持通配符)
+ * @param {string} hostname 要检查的域名
+ * @returns {boolean} 如果在白名单中则返回 true
+ */
+function isDomainAllowed(hostname) {
+ if (ALLOWED_DOMAINS.size === 0) {
+ // 如果白名单为空,则允许所有域名
+ return true;
+ }
+ for (const rule of ALLOWED_DOMAINS) {
+ if (rule.startsWith("*.")) {
+ if (hostname.endsWith(rule.slice(1)) || hostname === rule.slice(2)) {
+ return true;
+ }
+ } else if (hostname === rule) {
return true;
}
}
return false;
}
+/**
+ * 处理请求
+ * @param {FetchEvent} event
+ */
async function handleRequest(event) {
- const {
- request
- } = event;
+ const { request } = event;
+
+ if (request.method === "OPTIONS") {
+ return new Response(null, PREFLIGHT_INIT);
+ }
const reqHeaders = new Headers(request.headers);
const outHeaders = new Headers({
@@ -55,61 +95,51 @@ async function handleRequest(event) {
"Access-Control-Allow-Headers": reqHeaders.get("Access-Control-Allow-Headers") || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
});
- if (request.method === "OPTIONS") {
- return new Response(null, PREFLIGHT_INIT);
- }
-
try {
const urlString = decodeURIComponent(request.url.split('/').slice(3).join('/'));
-
- if (urlString.length < 3 || urlString.indexOf('.') === -1 || urlString === "favicon.ico" || urlString === "robots.txt") {
+
+ if (urlString.length < 3 || urlString.indexOf('.') === -1 || ["favicon.ico", "robots.txt"].includes(urlString)) {
return Response.redirect('https://baidu.com', 301);
}
if (isUrlBlocked(urlString)) {
return Response.redirect('https://baidu.com', 301);
}
-
+
const url = new URL(urlString.startsWith('http') ? urlString : 'http://' + urlString);
- // 如果启用了白名单,则检查域名
- if (ALLOWED_DOMAINS.size > 0 && !ALLOWED_DOMAINS.has(url.hostname)) {
- return new Response("Domain not allowed", { status: 403 });
+ if (!isDomainAllowed(url.hostname)) {
+ return new Response(`Domain ${url.hostname} is not allowed.`, { status: 403 });
}
-
+ const newReqHeaders = new Headers(reqHeaders);
+ newReqHeaders.delete('content-length');
+
const fp = {
method: request.method,
- headers: new Headers(reqHeaders),
+ headers: newReqHeaders,
+ body: ["POST", "PUT", "PATCH", "DELETE"].includes(request.method) ? request.body : null,
};
- fp.headers.delete('content-length');
-
- if (["POST", "PUT", "PATCH", "DELETE"].includes(request.method)) {
- fp.body = request.body;
- }
-
- let fr = await fetch(new Request(url, fp));
+ const fr = await fetch(new Request(url, fp));
for (const [key, value] of fr.headers.entries()) {
outHeaders.set(key, value);
}
-
+
const outCt = fr.headers.get('content-type') || '';
let outBody = fr.body;
-
+
if (outCt.includes('text/html')) {
try {
const rewriter = new HTMLRewriter().on("head", {
element(element) {
- element.prepend(``, {
- html: true
- });
+ element.prepend(``, { html: true });
},
});
- outBody = rewriter.transform(fr).body;
+ return rewriter.transform(fr);
} catch (e) {
- // 如果HTMLRewriter失败,则返回原始响应
+ // 如果HTMLRewriter失败,则直接返回原始响应
}
}
@@ -122,12 +152,10 @@ async function handleRequest(event) {
} catch (err) {
return new Response(JSON.stringify({
code: -1,
- msg: err.stack || err.toString(),
+ msg: err.stack || String(err),
}), {
status: 500,
- headers: {
- "content-type": "application/json"
- }
+ headers: { "content-type": "application/json" }
});
}
}
@@ -135,4 +163,4 @@ async function handleRequest(event) {
addEventListener('fetch', event => {
event.passThroughOnException();
event.respondWith(handleRequest(event));
-});
+});
\ No newline at end of file
From 1c0c4dcce24b29e9a449ec9225575fd47c3b014d Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:35:50 +0800
Subject: [PATCH 03/11] =?UTF-8?q?=E5=A5=BD=E7=9A=84=EF=BC=8C=E8=BF=99?=
=?UTF-8?q?=E6=98=AF=E4=B8=BA=E4=BD=A0=E5=87=86=E5=A4=87=E7=9A=84=E5=8F=AF?=
=?UTF-8?q?=E4=BB=A5=E7=9B=B4=E6=8E=A5=E5=A4=8D=E5=88=B6=E7=9A=84=E6=8F=90?=
=?UTF-8?q?=E4=BA=A4=E8=AF=B4=E6=98=8E=EF=BC=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
```
fix(login): 修复邮箱登录时缺少人机验证的问题
修复了在特定情况下(如API认为登录行为异常时)邮箱登录会失败并提示“Please add captcha before you register”的错误。
- **问题原因:**
原有的 `src/views/login.vue` 组件在发起登录请求时,未处理人机验证(Captcha)流程,直接提交了空的 `captcha_token`。
- **解决方案:**
从 `sms.vue`(手机登录)组件中移植了 `initCaptcha` 的逻辑到 `login.vue` 中。现在,在点击登录按钮后,会先向 PikPak 的 `captcha/init` 接口请求一个有效的 `captcha_token`,然后再携带此 `token` 进行登录,从而解决了因缺少验证而被服务器拒绝的问题。
```
---
src/views/login.vue | 87 +++++++++++++++++++++++++++++++++------------
1 file changed, 64 insertions(+), 23 deletions(-)
diff --git a/src/views/login.vue b/src/views/login.vue
index 8eb71e6d..43a41cf0 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -60,42 +60,83 @@ import { NForm, NFormItem, NInput, NButton, useMessage, NCheckbox, useDialog, NT
import http from '../utils/axios'
import { useRoute, useRouter } from 'vue-router'
import { BrandGoogle, Phone } from '@vicons/tabler'
+
const loginData = ref({
username: '',
- password: ''
+ password: '',
+ captcha_token: ''
})
const route = useRoute()
const loading = ref(false)
const router = useRouter()
const message = useMessage()
+
+// 32随机数
+const randomString = () => {
+ let len = 32;
+ let chars ='abcdefhijkmnprstwxyz2345678';
+ let maxPos = chars.length;
+ let character = '';
+ for (let i = 0; i < len; i++) {
+ character += chars.charAt(Math.floor(Math.random() * maxPos))
+ }
+ return character;
+}
+const deviceId = randomString()
+
+const initCaptcha = () => {
+ return http.post('https://user.mypikpak.com/v1/shield/captcha/init?client_id=YNxT9w7GMdWvEOKa', {
+ action: "POST:/v1/auth/signin",
+ captcha_token: '',
+ client_id: "YNxT9w7GMdWvEOKa",
+ device_id: deviceId,
+ meta: {
+ "username": loginData.value.username,
+ },
+ redirect_uri: "xlaccsdk01://xunlei.com/callback?state\u003dharbor"
+ })
+ .then((res:any) => {
+ if(res.data && res.data.captcha_token) {
+ loginData.value.captcha_token = res.data.captcha_token
+ }
+ })
+}
+
const loginPost = () => {
if(!loginData.value.password || !loginData.value.username) {
return false
}
loading.value = true
- http.post('https://user.mypikpak.com/v1/auth/signin', {
- "captcha_token": "",
- "client_id": "YNxT9w7GMdWvEOKa",
- "client_secret": "dbw2OtmVEeuUvIptb1Coyg",
- ...loginData.value
- })
- .then((res:any) => {
- if(res.data && res.data.access_token) {
- window.localStorage.setItem('pikpakLogin', JSON.stringify(res.data))
- if(remember.value) {
- window.localStorage.setItem('pikpakLoginData', JSON.stringify(loginData.value))
- } else {
- window.localStorage.removeItem('pikpakLoginData')
- }
- message.success('登录成功')
- router.push('/')
- router.push((route.query.redirect || '/') + '')
- }
- })
- .catch(() => {
- loading.value = false
+ initCaptcha().then(() => {
+ http.post('https://user.mypikpak.com/v1/auth/signin', {
+ "client_id": "YNxT9w7GMdWvEOKa",
+ "client_secret": "dbw2OtmVEeuUvIptb1Coyg",
+ ...loginData.value
})
+ .then((res:any) => {
+ if(res.data && res.data.access_token) {
+ window.localStorage.setItem('pikpakLogin', JSON.stringify(res.data))
+ if(remember.value) {
+ window.localStorage.setItem('pikpakLoginData', JSON.stringify({username: loginData.value.username, password: loginData.value.password}))
+ } else {
+ window.localStorage.removeItem('pikpakLoginData')
+ }
+ message.success('登录成功')
+ router.push((route.query.redirect || '/') + '')
+ }
+ })
+ .catch(() => {
+ loading.value = false
+ })
+ .finally(() => {
+ // 不管成功与否,都重置token
+ loginData.value.captcha_token = '';
+ })
+ }).catch(() => {
+ loading.value = false
+ })
}
+
const remember = ref(false)
const dialog = useDialog()
const showMessage = () => {
@@ -204,4 +245,4 @@ const getApk = () => {
margin-top: 40px;
}
}
-
+
\ No newline at end of file
From 297ed4dc9b7bee2f8270c0e4b2f1e71e22022cf3 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:38:47 +0800
Subject: [PATCH 04/11] =?UTF-8?q?fix(login):=20=E4=BF=AE=E5=A4=8D=E9=82=AE?=
=?UTF-8?q?=E7=AE=B1=E7=99=BB=E5=BD=95=E6=97=B6=E7=BC=BA=E5=B0=91=E4=BA=BA?=
=?UTF-8?q?=E6=9C=BA=E9=AA=8C=E8=AF=81=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复了在特定情况下(如API认为登录行为异常时)邮箱登录会失败并提示“Please add captcha before you register”的错误。
- **问题原因:**
原有的 `src/views/login.vue` 组件在发起登录请求时,未处理人机验证(Captcha)流程,直接提交了空的 `captcha_token`。
- **解决方案:**
从 `sms.vue`(手机登录)组件中移植了 `initCaptcha` 的逻辑到 `login.vue` 中。现在,在点击登录按钮后,会先向 PikPak 的 `captcha/init` 接口请求一个有效的 `captcha_token`,然后再携带此 `token` 进行登录,从而解决了因缺少验证而被服务器拒绝的问题。
---
src/views/login.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/login.vue b/src/views/login.vue
index 43a41cf0..8ac337c6 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -245,4 +245,4 @@ const getApk = () => {
margin-top: 40px;
}
}
-
\ No newline at end of file
+
From 2dab240dceaa7b0e6b2d78b5e30725d638fdc899 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:39:12 +0800
Subject: [PATCH 05/11] =?UTF-8?q?fix(login):=20=E4=BF=AE=E5=A4=8D=E9=82=AE?=
=?UTF-8?q?=E7=AE=B1=E7=99=BB=E5=BD=95=E6=97=B6=E7=BC=BA=E5=B0=91=E4=BA=BA?=
=?UTF-8?q?=E6=9C=BA=E9=AA=8C=E8=AF=81=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修复了在特定情况下(如API认为登录行为异常时)邮箱登录会失败并提示“Please add captcha before you register”的错误。
- **问题原因:**
原有的 `src/views/login.vue` 组件在发起登录请求时,未处理人机验证(Captcha)流程,直接提交了空的 `captcha_token`。
- **解决方案:**
从 `sms.vue`(手机登录)组件中移植了 `initCaptcha` 的逻辑到 `login.vue` 中。现在,在点击登录按钮后,会先向 PikPak 的 `captcha/init` 接口请求一个有效的 `captcha_token`,然后再携带此 `token` 进行登录,从而解决了因缺少验证而被服务器拒绝的问题。
---
src/views/login.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/login.vue b/src/views/login.vue
index 8ac337c6..43a41cf0 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -245,4 +245,4 @@ const getApk = () => {
margin-top: 40px;
}
}
-
+
\ No newline at end of file
From ee35a57925dd662b0af5b0a549e4a1b4a3114dfa Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:49:49 +0800
Subject: [PATCH 06/11] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 264 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index 126fa443..f5c94d0c 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,273 @@
# PikPak 个人网页版
-
-## 官方地址
+
- * [PikPak官网](https://mypikpak.com)
- * [PikPak官方网页版](https://drive.mypikpak.com/)
- * [PikPak官方讨论群组](https://t.me/pikpak_userservice)
+一个功能强大的 PikPak 网盘第三方 Web 客户端。它提供了接近原生体验的文件管理和浏览功能,并支持自托管部署。
-## Demo
- * [Demo](https://tjsky.github.io/pikpak/)
+## 相关链接
-## 安装教程
- * [教程](https://www.tjsky.net/?p=201)
+- **在线演示:** [Demo](https://tjsky.github.io/pikpak/)
+- **PikPak 官网:** [mypikpak.com](https://mypikpak.com)
+- **官方 Web 版:** [drive.mypikpak.com](https://drive.mypikpak.com/)
+- **官方 Telegram 群:** [t.me/pikpak_userservice](https://t.me/pikpak_userservice)
-## 对原版代码的修改
-- [x] 增加对IDM下载功能的引导
-- [x] 修正反代代码,支持下载反代(感谢小樱提供修正代码)
-- [x] 增加多个反代域名
-- [x] aira2多线程提速
+## 功能特性
+
+- :file_folder: **全面的文件管理**: 支持文件和文件夹的浏览、重命名、复制、移动、删除和回收站管理。
+- :film_frames: **在线媒体播放**: 直接在浏览器中播放视频、音频和预览图片。
+- :magnet: **强大的离线下载**: 支持添加磁力链接(Magnet)、HTTP 链接和 PikPak 秒传链接进行离线下载。
+- :arrow_down: **灵活的下载方式**: 支持文件直接下载,也可以推送到您自己的 Aria2 服务器进行下载。
+- :link: **分享与协作**: 支持创建文件分享链接,方便与他人共享资源。
+- :busts_in_silhouette: **完整的账户系统**: 支持邮箱/手机号登录、注册以及邀请码系统。
+- :gear: **高度自定义设置**:
+ - **Aria2 配置**: 对接您自己的 Aria2 服务,实现高效下载。
+ - **反向代理设置**: 自定义 API 代理,解决跨域问题。
+ - **自定义菜单**: 根据您的需求,为文件操作添加自定义的快捷方式(如调用外部播放器、发送到其他工具等)。
+ - **Telegram 绑定**: 关联您的 Telegram 账号。
+
+## 部署指南
+
+您可以将此项目部署在任何静态网站托管平台,如 GitHub Pages, Vercel, Netlify, 或您自己的服务器。
+
+部署过程主要分为 **前端项目打包** 和 **配置反向代理** 两个核心步骤。
+
+### 步骤 1: 准备工作
+
+确保您的本地环境已安装以下软件:
+
+- [Node.js](https://nodejs.org/) (建议使用 v16 或更高版本)
+- [Git](https://git-scm.com/)
+
+### 步骤 2: 获取并打包项目
+
+```bash
+# 1. 克隆仓库到本地
+git clone [https://github.com/victorqr/pikpak.git](https://github.com/victorqr/pikpak.git)
+
+# 2. 进入项目目录
+cd pikpak
+
+# 3. 安装项目依赖
+npm install
+
+# 4. 打包构建项目
+npm run build
+````
+
+构建成功后,所有用于部署的静态文件都会生成在 `dist` 文件夹中。
+
+### 步骤 3: 配置反向代理 (关键)
+
+由于浏览器跨域安全策略的限制,前端页面无法直接请求 PikPak 的 API。因此,我们需要一个反向代理来中转请求。这里我们推荐使用免费的 **Cloudflare Worker**。
+
+1. **登录 Cloudflare**: 打开 [Cloudflare Dashboard](https://dash.cloudflare.com/)。
+2. **进入 Workers 和 Pages**: 在左侧菜单中选择 `Workers & Pages`。
+3. **创建 Worker**: 点击 `Create application` -\> `Create Worker`。
+4. **配置 Worker**:
+ - 为你的 Worker 设置一个自定义的子域名(例如 `my-pikpak-proxy`)。
+ - 点击 `Deploy`。
+5. **编辑代码**:
+ - 部署成功后,点击 `Edit code`。
+ - **清空** 编辑器中所有默认代码。
+ - 将下面的 **完整 Worker 脚本** 复制并粘贴到编辑器中。
+ - 点击 `Save and Deploy`。
+
+现在,你的反向代理已经在 `https://<你的Worker名>.<你的子域名>.workers.dev` 上运行了。
+
+\
+\\点击展开/折叠 Cloudflare Worker 脚本\\
+
+```javascript
+/**
+ * 预检请求的配置
+ */
+const PREFLIGHT_INIT = {
+ status: 204,
+ headers: new Headers({
+ "access-control-allow-origin": "*",
+ "access-control-allow-methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
+ "access-control-allow-headers": "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
+ }),
+};
+
+/**
+ * 黑名单:包含不希望代理的URL关键字或扩展名
+ */
+const BLOCKED_KEYWORDS = new Set([
+ ".m3u8",
+ ".ts",
+ ".acc",
+ ".m4s",
+ "photocall.tv",
+ "googlevideo.com",
+ "xunleix.com",
+]);
+
+/**
+ * 白名单:允许代理的域名列表,支持通配符
+ */
+const ALLOWED_DOMAINS = new Set([
+ // PikPak 及其所有子域名
+ "*.mypikpak.com",
+ "mypikpak.com",
+
+ // 其他依赖服务
+ "api.notion.com",
+ "invite.z7.workers.dev",
+ "pikpak-depot.z10.workers.dev"
+]);
+
+/**
+ * 检查URL是否在黑名单中
+ * @param {string} url 要检查的URL
+ * @returns {boolean} 如果在黑名单中则返回 true
+ */
+function isUrlBlocked(url) {
+ const lowercasedUrl = url.toLowerCase();
+ for (const keyword of BLOCKED_KEYWORDS) {
+ if (lowercasedUrl.includes(keyword)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * 检查域名是否在白名单中(支持通配符)
+ * @param {string} hostname 要检查的域名
+ * @returns {boolean} 如果在白名单中则返回 true
+ */
+function isDomainAllowed(hostname) {
+ if (ALLOWED_DOMAINS.size === 0) {
+ return true; // 如果白名单为空,则允许所有域名
+ }
+ for (const rule of ALLOWED_DOMAINS) {
+ if (rule.startsWith("*.")) {
+ if (hostname.endsWith(rule.slice(1)) || hostname === rule.slice(2)) {
+ return true;
+ }
+ } else if (hostname === rule) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * 处理请求
+ * @param {FetchEvent} event
+ */
+async function handleRequest(event) {
+ const { request } = event;
+
+ if (request.method === "OPTIONS") {
+ return new Response(null, PREFLIGHT_INIT);
+ }
+
+ const reqHeaders = new Headers(request.headers);
+ const outHeaders = new Headers({
+ "Access-Control-Allow-Origin": reqHeaders.get("Origin") || "*",
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
+ "Access-Control-Allow-Headers": reqHeaders.get("Access-Control-Allow-Headers") || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
+ });
+
+ try {
+ const urlString = decodeURIComponent(request.url.split('/').slice(3).join('/'));
+
+ if (urlString.length < 3 || urlString.indexOf('.') === -1 || ["favicon.ico", "robots.txt"].includes(urlString)) {
+ return Response.redirect('[https://baidu.com](https://baidu.com)', 301);
+ }
+
+ if (isUrlBlocked(urlString)) {
+ return Response.redirect('[https://baidu.com](https://baidu.com)', 301);
+ }
+
+ const url = new URL(urlString.startsWith('http') ? urlString : 'http://' + urlString);
+
+ if (!isDomainAllowed(url.hostname)) {
+ return new Response(`Domain ${url.hostname} is not allowed.`, { status: 403 });
+ }
+
+ const newReqHeaders = new Headers(reqHeaders);
+ newReqHeaders.delete('content-length');
+
+ const fp = {
+ method: request.method,
+ headers: newReqHeaders,
+ body: ["POST", "PUT", "PATCH", "DELETE"].includes(request.method) ? request.body : null,
+ };
+
+ const fr = await fetch(new Request(url, fp));
+
+ for (const [key, value] of fr.headers.entries()) {
+ outHeaders.set(key, value);
+ }
+
+ const outCt = fr.headers.get('content-type') || '';
+ let outBody = fr.body;
+
+ if (outCt.includes('text/html')) {
+ try {
+ const rewriter = new HTMLRewriter().on("head", {
+ element(element) {
+ element.prepend(``, { html: true });
+ },
+ });
+ return rewriter.transform(fr);
+ } catch (e) {
+ // 如果HTMLRewriter失败,则直接返回原始响应
+ }
+ }
+
+ return new Response(outBody, {
+ status: fr.status,
+ statusText: fr.statusText,
+ headers: outHeaders,
+ });
+
+ } catch (err) {
+ return new Response(JSON.stringify({
+ code: -1,
+ msg: err.stack || String(err),
+ }), {
+ status: 500,
+ headers: { "content-type": "application/json" }
+ });
+ }
+}
+
+addEventListener('fetch', event => {
+ event.passThroughOnException();
+ event.respondWith(handleRequest(event));
+});
+```
+
+\
+
+### 步骤 4: 修改前端配置并重新打包
+
+1. 打开项目代码中的 `src/config/index.ts` 文件。
+
+2. 将其中的 `proxy` 数组修改为您刚刚创建的 Worker 地址。
+
+ ```typescript
+ export const proxy = [
+ 'https://<你的Worker名>.<你的子域名>.workers.dev'
+ // 你也可以添加多个备用地址
+ ]
+ ```
+
+3. **保存文件后,重新执行打包命令**:
+
+ ```bash
+ npm run build
+ ```
+
+### 步骤 5: 部署
+
+现在,将 `dist` 文件夹中的所有内容上传到您的静态网站托管平台即可。
## 致谢
+
本项目 CDN 加速及安全防护由 [Tencent EdgeOne](https://edgeone.ai/zh?from=github) 赞助
-
+
From b964e23cf7ac58934ecfb01b34a92c36b6deeee5 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 10:58:46 +0800
Subject: [PATCH 07/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 237 +++++++-----------------------------------------------
1 file changed, 27 insertions(+), 210 deletions(-)
diff --git a/README.md b/README.md
index f5c94d0c..102eaff1 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,42 @@
# PikPak 个人网页版
-
-
一个功能强大的 PikPak 网盘第三方 Web 客户端。它提供了接近原生体验的文件管理和浏览功能,并支持自托管部署。
## 相关链接
-- **在线演示:** [Demo](https://tjsky.github.io/pikpak/)
-- **PikPak 官网:** [mypikpak.com](https://mypikpak.com)
-- **官方 Web 版:** [drive.mypikpak.com](https://drive.mypikpak.com/)
-- **官方 Telegram 群:** [t.me/pikpak_userservice](https://t.me/pikpak_userservice)
+ - **在线演示:** [https://tjsky.github.io/pikpak/](https://tjsky.github.io/pikpak/)
+ - **PikPak 官网:** [https://mypikpak.com](https://mypikpak.com)
+ - **官方 Web 版:** [https://drive.mypikpak.com/](https://drive.mypikpak.com/)
+ - **官方 Telegram 群:** [https://t.me/pikpak\_userservice](https://t.me/pikpak_userservice)
## 功能特性
-- :file_folder: **全面的文件管理**: 支持文件和文件夹的浏览、重命名、复制、移动、删除和回收站管理。
-- :film_frames: **在线媒体播放**: 直接在浏览器中播放视频、音频和预览图片。
-- :magnet: **强大的离线下载**: 支持添加磁力链接(Magnet)、HTTP 链接和 PikPak 秒传链接进行离线下载。
-- :arrow_down: **灵活的下载方式**: 支持文件直接下载,也可以推送到您自己的 Aria2 服务器进行下载。
-- :link: **分享与协作**: 支持创建文件分享链接,方便与他人共享资源。
-- :busts_in_silhouette: **完整的账户系统**: 支持邮箱/手机号登录、注册以及邀请码系统。
-- :gear: **高度自定义设置**:
- - **Aria2 配置**: 对接您自己的 Aria2 服务,实现高效下载。
- - **反向代理设置**: 自定义 API 代理,解决跨域问题。
- - **自定义菜单**: 根据您的需求,为文件操作添加自定义的快捷方式(如调用外部播放器、发送到其他工具等)。
- - **Telegram 绑定**: 关联您的 Telegram 账号。
+ - **全面的文件管理**: 支持文件和文件夹的浏览、重命名、复制、移动、删除和回收站管理。
+ - **在线媒体播放**: 直接在浏览器中播放视频、音频和预览图片。
+ - **强大的离线下载**: 支持添加磁力链接(Magnet)、HTTP 链接和 PikPak 秒传链接进行离线下载。
+ - **灵活的下载方式**: 支持文件直接下载,也可以推送到您自己的 Aria2 服务器进行下载。
+ - **分享与协作**: 支持创建文件分享链接,方便与他人共享资源。
+ - **完整的账户系统**: 支持邮箱/手机号登录、注册以及邀请码系统。
+ - **高度自定义设置**:
+ - **Aria2 配置**: 对接您自己的 Aria2 服务,实现高效下载。
+ - **反向代理设置**: 自定义 API 代理,解决跨域问题。
+ - **自定义菜单**: 根据您的需求,为文件操作添加自定义的快捷方式。
+ - **Telegram 绑定**: 关联您的 Telegram 账号。
## 部署指南
-您可以将此项目部署在任何静态网站托管平台,如 GitHub Pages, Vercel, Netlify, 或您自己的服务器。
-
-部署过程主要分为 **前端项目打包** 和 **配置反向代理** 两个核心步骤。
+您可以将此项目部署在任何静态网站托管平台,如 GitHub Pages, Vercel, Netlify, 或您自己的服务器。部署过程主要分为 **前端项目打包** 和 **配置反向代理** 两个核心步骤。
### 步骤 1: 准备工作
确保您的本地环境已安装以下软件:
-- [Node.js](https://nodejs.org/) (建议使用 v16 或更高版本)
-- [Git](https://git-scm.com/)
+ - Node.js (建议使用 v16 或更高版本)
+ - Git
### 步骤 2: 获取并打包项目
-```bash
+```
# 1. 克隆仓库到本地
git clone [https://github.com/victorqr/pikpak.git](https://github.com/victorqr/pikpak.git)
@@ -52,214 +48,36 @@ npm install
# 4. 打包构建项目
npm run build
-````
+```
构建成功后,所有用于部署的静态文件都会生成在 `dist` 文件夹中。
### 步骤 3: 配置反向代理 (关键)
-由于浏览器跨域安全策略的限制,前端页面无法直接请求 PikPak 的 API。因此,我们需要一个反向代理来中转请求。这里我们推荐使用免费的 **Cloudflare Worker**。
+由于浏览器跨域安全策略的限制,我们需要一个反向代理来中转 API 请求。这里我们推荐使用免费的 **Cloudflare Worker**。
-1. **登录 Cloudflare**: 打开 [Cloudflare Dashboard](https://dash.cloudflare.com/)。
+1. **登录 Cloudflare**: 打开 Cloudflare Dashboard。
2. **进入 Workers 和 Pages**: 在左侧菜单中选择 `Workers & Pages`。
-3. **创建 Worker**: 点击 `Create application` -\> `Create Worker`。
-4. **配置 Worker**:
- - 为你的 Worker 设置一个自定义的子域名(例如 `my-pikpak-proxy`)。
- - 点击 `Deploy`。
-5. **编辑代码**:
- - 部署成功后,点击 `Edit code`。
- - **清空** 编辑器中所有默认代码。
- - 将下面的 **完整 Worker 脚本** 复制并粘贴到编辑器中。
- - 点击 `Save and Deploy`。
+3. **创建 Worker**: 点击 `Create application` -\> `Create Worker`,然后为您的 Worker 命名并部署。
+4. **编辑代码**: 部署成功后,点击 `Edit code`。**清空** 编辑器中所有默认代码,然后将仓库中 `cf-worker/index.js` 文件的全部内容复制并粘贴到编辑器中,最后点击 `Save and Deploy`。
现在,你的反向代理已经在 `https://<你的Worker名>.<你的子域名>.workers.dev` 上运行了。
-\
-\\点击展开/折叠 Cloudflare Worker 脚本\\
-
-```javascript
-/**
- * 预检请求的配置
- */
-const PREFLIGHT_INIT = {
- status: 204,
- headers: new Headers({
- "access-control-allow-origin": "*",
- "access-control-allow-methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
- "access-control-allow-headers": "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
- }),
-};
-
-/**
- * 黑名单:包含不希望代理的URL关键字或扩展名
- */
-const BLOCKED_KEYWORDS = new Set([
- ".m3u8",
- ".ts",
- ".acc",
- ".m4s",
- "photocall.tv",
- "googlevideo.com",
- "xunleix.com",
-]);
-
-/**
- * 白名单:允许代理的域名列表,支持通配符
- */
-const ALLOWED_DOMAINS = new Set([
- // PikPak 及其所有子域名
- "*.mypikpak.com",
- "mypikpak.com",
-
- // 其他依赖服务
- "api.notion.com",
- "invite.z7.workers.dev",
- "pikpak-depot.z10.workers.dev"
-]);
-
-/**
- * 检查URL是否在黑名单中
- * @param {string} url 要检查的URL
- * @returns {boolean} 如果在黑名单中则返回 true
- */
-function isUrlBlocked(url) {
- const lowercasedUrl = url.toLowerCase();
- for (const keyword of BLOCKED_KEYWORDS) {
- if (lowercasedUrl.includes(keyword)) {
- return true;
- }
- }
- return false;
-}
-
-/**
- * 检查域名是否在白名单中(支持通配符)
- * @param {string} hostname 要检查的域名
- * @returns {boolean} 如果在白名单中则返回 true
- */
-function isDomainAllowed(hostname) {
- if (ALLOWED_DOMAINS.size === 0) {
- return true; // 如果白名单为空,则允许所有域名
- }
- for (const rule of ALLOWED_DOMAINS) {
- if (rule.startsWith("*.")) {
- if (hostname.endsWith(rule.slice(1)) || hostname === rule.slice(2)) {
- return true;
- }
- } else if (hostname === rule) {
- return true;
- }
- }
- return false;
-}
-
-/**
- * 处理请求
- * @param {FetchEvent} event
- */
-async function handleRequest(event) {
- const { request } = event;
-
- if (request.method === "OPTIONS") {
- return new Response(null, PREFLIGHT_INIT);
- }
-
- const reqHeaders = new Headers(request.headers);
- const outHeaders = new Headers({
- "Access-Control-Allow-Origin": reqHeaders.get("Origin") || "*",
- "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
- "Access-Control-Allow-Headers": reqHeaders.get("Access-Control-Allow-Headers") || "Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token, Notion-Version",
- });
-
- try {
- const urlString = decodeURIComponent(request.url.split('/').slice(3).join('/'));
-
- if (urlString.length < 3 || urlString.indexOf('.') === -1 || ["favicon.ico", "robots.txt"].includes(urlString)) {
- return Response.redirect('[https://baidu.com](https://baidu.com)', 301);
- }
-
- if (isUrlBlocked(urlString)) {
- return Response.redirect('[https://baidu.com](https://baidu.com)', 301);
- }
-
- const url = new URL(urlString.startsWith('http') ? urlString : 'http://' + urlString);
-
- if (!isDomainAllowed(url.hostname)) {
- return new Response(`Domain ${url.hostname} is not allowed.`, { status: 403 });
- }
-
- const newReqHeaders = new Headers(reqHeaders);
- newReqHeaders.delete('content-length');
-
- const fp = {
- method: request.method,
- headers: newReqHeaders,
- body: ["POST", "PUT", "PATCH", "DELETE"].includes(request.method) ? request.body : null,
- };
-
- const fr = await fetch(new Request(url, fp));
-
- for (const [key, value] of fr.headers.entries()) {
- outHeaders.set(key, value);
- }
-
- const outCt = fr.headers.get('content-type') || '';
- let outBody = fr.body;
-
- if (outCt.includes('text/html')) {
- try {
- const rewriter = new HTMLRewriter().on("head", {
- element(element) {
- element.prepend(``, { html: true });
- },
- });
- return rewriter.transform(fr);
- } catch (e) {
- // 如果HTMLRewriter失败,则直接返回原始响应
- }
- }
-
- return new Response(outBody, {
- status: fr.status,
- statusText: fr.statusText,
- headers: outHeaders,
- });
-
- } catch (err) {
- return new Response(JSON.stringify({
- code: -1,
- msg: err.stack || String(err),
- }), {
- status: 500,
- headers: { "content-type": "application/json" }
- });
- }
-}
-
-addEventListener('fetch', event => {
- event.passThroughOnException();
- event.respondWith(handleRequest(event));
-});
-```
-
-\
-
### 步骤 4: 修改前端配置并重新打包
1. 打开项目代码中的 `src/config/index.ts` 文件。
2. 将其中的 `proxy` 数组修改为您刚刚创建的 Worker 地址。
- ```typescript
+ ```
export const proxy = [
'https://<你的Worker名>.<你的子域名>.workers.dev'
- // 你也可以添加多个备用地址
]
```
3. **保存文件后,重新执行打包命令**:
- ```bash
+ ```
npm run build
```
@@ -269,5 +87,4 @@ addEventListener('fetch', event => {
## 致谢
-本项目 CDN 加速及安全防护由 [Tencent EdgeOne](https://edgeone.ai/zh?from=github) 赞助
-
+本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
From a608f73716079a286c2674f92b64b9beee1b49b5 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 11:01:52 +0800
Subject: [PATCH 08/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 65 +++++++++++++++++++++++++++++++++----------------------
1 file changed, 39 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 102eaff1..cc23078f 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,41 @@
# PikPak 个人网页版
-一个功能强大的 PikPak 网盘第三方 Web 客户端。它提供了接近原生体验的文件管理和浏览功能,并支持自托管部署。
+
-## 相关链接
+## 官方地址
- - **在线演示:** [https://tjsky.github.io/pikpak/](https://tjsky.github.io/pikpak/)
- - **PikPak 官网:** [https://mypikpak.com](https://mypikpak.com)
- - **官方 Web 版:** [https://drive.mypikpak.com/](https://drive.mypikpak.com/)
- - **官方 Telegram 群:** [https://t.me/pikpak\_userservice](https://t.me/pikpak_userservice)
+* [PikPak官网](https://mypikpak.com)
+* [PikPak官方网页版](https://drive.mypikpak.com/)
+* [PikPak官方讨论群组](https://t.me/pikpak_userservice)
+
+## Demo
+
+* [Demo](https://tjsky.github.io/pikpak/)
+
+## 安装教程
+
+* [教程](https://www.tjsky.net/?p=201)
+
+## 对原版代码的修改
+
+- [x] 增加对IDM下载功能的引导
+- [x] 修正反代代码,支持下载反代(感谢小樱提供修正代码)
+- [x] 增加多个反代域名
+- [x] aira2多线程提速
## 功能特性
- - **全面的文件管理**: 支持文件和文件夹的浏览、重命名、复制、移动、删除和回收站管理。
- - **在线媒体播放**: 直接在浏览器中播放视频、音频和预览图片。
- - **强大的离线下载**: 支持添加磁力链接(Magnet)、HTTP 链接和 PikPak 秒传链接进行离线下载。
- - **灵活的下载方式**: 支持文件直接下载,也可以推送到您自己的 Aria2 服务器进行下载。
- - **分享与协作**: 支持创建文件分享链接,方便与他人共享资源。
- - **完整的账户系统**: 支持邮箱/手机号登录、注册以及邀请码系统。
- - **高度自定义设置**:
- - **Aria2 配置**: 对接您自己的 Aria2 服务,实现高效下载。
- - **反向代理设置**: 自定义 API 代理,解决跨域问题。
- - **自定义菜单**: 根据您的需求,为文件操作添加自定义的快捷方式。
- - **Telegram 绑定**: 关联您的 Telegram 账号。
+- 📁 **全面的文件管理**: 支持文件和文件夹的浏览、重命名、复制、移动、删除和回收站管理。
+- 🎥 **在线媒体播放**: 直接在浏览器中播放视频、音频和预览图片。
+- 🧲 **强大的离线下载**: 支持添加磁力链接(Magnet)、HTTP 链接和 PikPak 秒传链接进行离线下载。
+- ⬇️ **灵活的下载方式**: 支持文件直接下载,也可以推送到您自己的 Aria2 服务器进行下载。
+- 🔗 **分享与协作**: 支持创建文件分享链接,方便与他人共享资源。
+- 👥 **完整的账户系统**: 支持邮箱/手机号登录、注册以及邀请码系统。
+- ⚙️ **高度自定义设置**:
+ - **Aria2 配置**: 对接您自己的 Aria2 服务,实现高效下载。
+ - **反向代理设置**: 自定义 API 代理,解决跨域问题。
+ - **自定义菜单**: 根据您的需求,为文件操作添加自定义的快捷方式。
+ - **Telegram 绑定**: 关联您的 Telegram 账号。
## 部署指南
@@ -30,13 +44,12 @@
### 步骤 1: 准备工作
确保您的本地环境已安装以下软件:
-
- - Node.js (建议使用 v16 或更高版本)
- - Git
+- [Node.js](https://nodejs.org/) (建议使用 v16 或更高版本)
+- [Git](https://git-scm.com/)
### 步骤 2: 获取并打包项目
-```
+```bash
# 1. 克隆仓库到本地
git clone [https://github.com/victorqr/pikpak.git](https://github.com/victorqr/pikpak.git)
@@ -48,7 +61,7 @@ npm install
# 4. 打包构建项目
npm run build
-```
+````
构建成功后,所有用于部署的静态文件都会生成在 `dist` 文件夹中。
@@ -56,7 +69,7 @@ npm run build
由于浏览器跨域安全策略的限制,我们需要一个反向代理来中转 API 请求。这里我们推荐使用免费的 **Cloudflare Worker**。
-1. **登录 Cloudflare**: 打开 Cloudflare Dashboard。
+1. **登录 Cloudflare**: 打开 [Cloudflare Dashboard](https://dash.cloudflare.com/)。
2. **进入 Workers 和 Pages**: 在左侧菜单中选择 `Workers & Pages`。
3. **创建 Worker**: 点击 `Create application` -\> `Create Worker`,然后为您的 Worker 命名并部署。
4. **编辑代码**: 部署成功后,点击 `Edit code`。**清空** 编辑器中所有默认代码,然后将仓库中 `cf-worker/index.js` 文件的全部内容复制并粘贴到编辑器中,最后点击 `Save and Deploy`。
@@ -69,7 +82,7 @@ npm run build
2. 将其中的 `proxy` 数组修改为您刚刚创建的 Worker 地址。
- ```
+ ```typescript
export const proxy = [
'https://<你的Worker名>.<你的子域名>.workers.dev'
]
@@ -77,7 +90,7 @@ npm run build
3. **保存文件后,重新执行打包命令**:
- ```
+ ```bash
npm run build
```
@@ -87,4 +100,4 @@ npm run build
## 致谢
-本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
+本项目 CDN 加速及安全防护由 [Tencent EdgeOne](https://edgeone.ai/zh?from=github) 赞助
From 50e333bbe11ede444e7fe1e9daf5f9b11aaf50d3 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 11:14:39 +0800
Subject: [PATCH 09/11] =?UTF-8?q?=E7=BD=91=E9=A1=B5=E7=95=8C=E9=9D=A2?=
=?UTF-8?q?=E7=BE=8E=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main.ts | 1 +
src/moe.css | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+)
create mode 100644 src/moe.css
diff --git a/src/main.ts b/src/main.ts
index 2767f5a2..2b257a68 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,6 +4,7 @@ import router from './router'
import http from './utils/axios'
import elementResizeDetectorMaker from 'element-resize-detector'
import cnzzAnalytics from 'vue-cnzz-analytics'
+import './moe.css'
const app = createApp(App)
app.directive('resize', {
diff --git a/src/moe.css b/src/moe.css
new file mode 100644
index 00000000..3114c557
--- /dev/null
+++ b/src/moe.css
@@ -0,0 +1,108 @@
+.n-layout {
+ background-image: url(https://cdn.seovx.com/d/?mom=302);
+ filter: brightness(100%);
+ backdrop-filter: blur(20px);
+ background-attachment: fixed;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+/*页面背景*/
+.n-layout-sider {
+ background-color: rgba(255, 255, 255, 0.8) !important;
+}
+
+/*侧边栏背景色*/
+.n-icon {
+ color: rgb(83, 114, 248)
+}
+
+/*左侧图标颜色*/
+.n-data-table {
+ --merged-th-color: rgba(241, 241, 241, 0.8) !important;
+ --merged-td-color: rgba(241, 241, 241, 0.4) !important;
+ --merged-td-color-hover: rgba(158, 158, 158, 0.6) !important;
+}
+
+/*列表页背景色*/
+.user-info-avatar {
+ content: url(https://s2.loli.net/2021/12/15/fKiaZgWSvnd5lQN.jpg);
+}
+
+/*自定义头像*/
+.content {
+ background: rgba(255, 255, 255, 0.6);
+}
+
+.status-bar {
+ background: rgba(255, 255, 255, 0.8);
+}
+
+.task-list {
+ background: rgba(255, 255, 255, 0.8);
+}
+
+.status-bar .status-bar-wrapper {
+ background-color: #00000000;
+}
+
+/*右下角bar透明*/
+.n-text {
+ color: rgb(83, 114, 248);
+}
+
+/*功能项文字颜色*/
+.n-progress .n-progress-graph .n-progress-graph-line .n-progress-graph-line-rail {
+ background-color: rgba(244, 237, 219, 0.80);
+}
+
+/*容量进度条未用部分颜色*/
+.n-collapse {
+ background-color: rgba(255, 255, 255, 0.8);
+ border-radius: 15px;
+ padding: 10px;
+}
+
+/*设置页背景颜色及尺寸*/
+.n-row {
+ background-color: rgba(255, 255, 255, 0.5);
+ border-radius: 1em;
+}
+
+/*邀请页表头透明,列表圆角*/
+.n-statistic .n-statistic__label {
+ color: #3f51b5;
+}
+
+/*邀请页表头文字颜色*/
+.n-statistic {
+ padding-left: 10px;
+ padding-top: 10px;
+}
+
+.n-scrollbar {
+ border-radius: 1em;
+}
+
+/*列表圆角*/
+.header {
+ background-color: rgba(255, 255, 255, 0.9);
+ border-radius: 1em;
+}
+
+.n-breadcrumb {
+ padding-left: 20px;
+}
+
+/*列表页表头圆角*/
+.header .action {
+ margin-right: 10px;
+}
+
+.login-page .login-box {
+ background: rgba(255, 255, 255, 0.8);
+}
+
+.n-card {
+ background-color: rgba(255, 255, 255, 0.8) !important;
+}
\ No newline at end of file
From cfdd8128f408e798ec0a353714e38443daf6a1cb Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 11:22:59 +0800
Subject: [PATCH 10/11] =?UTF-8?q?=E7=BE=8E=E5=8C=96=E7=BD=91=E9=A1=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main.ts | 2 +-
src/modern-theme.css | 141 +++++++++++++++++++++++++++++++++++++++++++
src/moe.css | 108 ---------------------------------
3 files changed, 142 insertions(+), 109 deletions(-)
create mode 100644 src/modern-theme.css
delete mode 100644 src/moe.css
diff --git a/src/main.ts b/src/main.ts
index 2b257a68..a746de57 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,7 +4,7 @@ import router from './router'
import http from './utils/axios'
import elementResizeDetectorMaker from 'element-resize-detector'
import cnzzAnalytics from 'vue-cnzz-analytics'
-import './moe.css'
+import './modern-theme.css'
const app = createApp(App)
app.directive('resize', {
diff --git a/src/modern-theme.css b/src/modern-theme.css
new file mode 100644
index 00000000..c54b2fce
--- /dev/null
+++ b/src/modern-theme.css
@@ -0,0 +1,141 @@
+/* --- Global Theme: Frosted Glass & Gradient --- */
+
+/* 1. Advanced Gradient Background */
+.n-layout {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ background-attachment: fixed;
+}
+
+/* 2. Apply Frosted Glass to Components */
+
+/* Sidebar */
+.n-layout-sider {
+ background: rgba(255, 255, 255, 0.1) !important;
+ backdrop-filter: blur(15px) !important;
+ -webkit-backdrop-filter: blur(15px) !important;
+ border-right: 1px solid rgba(255, 255, 255, 0.18) !important;
+}
+
+/* Main Content Area Header */
+.header {
+ margin: 20px 20px 0 20px !important;
+ padding-left: 20px;
+ padding-right: 20px;
+ background: rgba(255, 255, 255, 0.2);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 12px;
+}
+
+/* Data Table */
+.n-data-table {
+ background: transparent !important;
+ --merged-th-color: rgba(255, 255, 255, 0.2) !important;
+ --merged-td-color: rgba(255, 255, 255, 0.05) !important;
+ --merged-td-color-hover: rgba(255, 255, 255, 0.15) !important;
+ --merged-border-color: rgba(255, 255, 255, 0.1) !important;
+}
+.n-data-table .n-data-table-td, .n-data-table .n-data-table-th {
+ color: #f0f0f0;
+}
+.n-data-table .n-data-table-th {
+ color: #ffffff;
+ font-weight: 600;
+}
+.file-info .title, .file-info .n-ellipsis {
+ color: #fff !important;
+}
+.n-data-table-td.size, .n-data-table-td.modified_time, .n-data-table-td .n-time {
+ color: rgba(230, 230, 230, 0.7) !important;
+}
+.n-data-table .n-text--primary-type {
+ color: #a7c5ff !important;
+}
+
+/* Cards, Modals, Popups */
+.n-card, .n-modal, .n-popover, .n-dialog {
+ background: rgba(30, 30, 50, 0.6) !important;
+ backdrop-filter: blur(15px) !important;
+ -webkit-backdrop-filter: blur(15px) !important;
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
+ border-radius: 16px !important;
+ color: #fff !important;
+}
+.n-card-header, .n-dialog__title, .n-modal-header__title {
+ color: #fff !important;
+}
+.n-input, .n-input__textarea-el {
+ background-color: rgba(0,0,0,0.2) !important;
+ color: #fff !important;
+}
+.n-input .n-input__input-el, .n-input .n-input__placeholder {
+ color: #fff !important;
+}
+
+/* Login Page */
+.login-page {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+.login-page .login-box {
+ background: rgba(255, 255, 255, 0.15);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
+}
+.login-page .logo-box__text {
+ text-shadow: 0 2px 10px rgba(0,0,0,0.2);
+}
+
+/* Sidebar Menu & Icons */
+.n-menu {
+ background: transparent !important;
+}
+.n-menu .n-menu-item-content__icon {
+ color: #e0e0e0 !important;
+}
+.n-menu .n-menu-item-content-header {
+ color: #e0e0e0 !important;
+}
+.n-menu .n-menu-item--selected .n-menu-item-content-header,
+.n-menu .n-menu-item--selected .n-menu-item-content__icon {
+ color: #ffffff !important;
+ font-weight: bold;
+}
+.n-layout-sider-scroll-container {
+ color: #f0f0f0;
+}
+.n-layout-sider-scroll-container .n-text--primary-type {
+ color: #d1c4e9 !important;
+}
+.sider-bottom {
+ background-color: transparent !important;
+}
+.sider-bottom.vip {
+ background-color: rgba(244, 237, 219, 0.1) !important;
+}
+
+/* Progress Bar */
+.content-bottom {
+ border-top: 1px solid rgba(255, 255, 255, 0.18);
+ padding-top: 10px;
+}
+
+/* General Text Color & Breadcrumb */
+body {
+ color: #f0f0f0;
+}
+.n-breadcrumb {
+ color: #fff !important;
+}
+.n-breadcrumb-item a {
+ color: #e0e0e0 !important;
+ text-decoration: none;
+}
+.n-breadcrumb-item a:hover {
+ color: #fff !important;
+}
+.n-breadcrumb .n-breadcrumb-item__separator {
+ color: rgba(255, 255, 255, 0.5) !important;
+}
diff --git a/src/moe.css b/src/moe.css
deleted file mode 100644
index 3114c557..00000000
--- a/src/moe.css
+++ /dev/null
@@ -1,108 +0,0 @@
-.n-layout {
- background-image: url(https://cdn.seovx.com/d/?mom=302);
- filter: brightness(100%);
- backdrop-filter: blur(20px);
- background-attachment: fixed;
- background-size: cover;
- background-repeat: no-repeat;
-}
-
-/*页面背景*/
-.n-layout-sider {
- background-color: rgba(255, 255, 255, 0.8) !important;
-}
-
-/*侧边栏背景色*/
-.n-icon {
- color: rgb(83, 114, 248)
-}
-
-/*左侧图标颜色*/
-.n-data-table {
- --merged-th-color: rgba(241, 241, 241, 0.8) !important;
- --merged-td-color: rgba(241, 241, 241, 0.4) !important;
- --merged-td-color-hover: rgba(158, 158, 158, 0.6) !important;
-}
-
-/*列表页背景色*/
-.user-info-avatar {
- content: url(https://s2.loli.net/2021/12/15/fKiaZgWSvnd5lQN.jpg);
-}
-
-/*自定义头像*/
-.content {
- background: rgba(255, 255, 255, 0.6);
-}
-
-.status-bar {
- background: rgba(255, 255, 255, 0.8);
-}
-
-.task-list {
- background: rgba(255, 255, 255, 0.8);
-}
-
-.status-bar .status-bar-wrapper {
- background-color: #00000000;
-}
-
-/*右下角bar透明*/
-.n-text {
- color: rgb(83, 114, 248);
-}
-
-/*功能项文字颜色*/
-.n-progress .n-progress-graph .n-progress-graph-line .n-progress-graph-line-rail {
- background-color: rgba(244, 237, 219, 0.80);
-}
-
-/*容量进度条未用部分颜色*/
-.n-collapse {
- background-color: rgba(255, 255, 255, 0.8);
- border-radius: 15px;
- padding: 10px;
-}
-
-/*设置页背景颜色及尺寸*/
-.n-row {
- background-color: rgba(255, 255, 255, 0.5);
- border-radius: 1em;
-}
-
-/*邀请页表头透明,列表圆角*/
-.n-statistic .n-statistic__label {
- color: #3f51b5;
-}
-
-/*邀请页表头文字颜色*/
-.n-statistic {
- padding-left: 10px;
- padding-top: 10px;
-}
-
-.n-scrollbar {
- border-radius: 1em;
-}
-
-/*列表圆角*/
-.header {
- background-color: rgba(255, 255, 255, 0.9);
- border-radius: 1em;
-}
-
-.n-breadcrumb {
- padding-left: 20px;
-}
-
-/*列表页表头圆角*/
-.header .action {
- margin-right: 10px;
-}
-
-.login-page .login-box {
- background: rgba(255, 255, 255, 0.8);
-}
-
-.n-card {
- background-color: rgba(255, 255, 255, 0.8) !important;
-}
\ No newline at end of file
From ea4aad4fd8a83cd73a7b2cdc885d08ba9ac24271 Mon Sep 17 00:00:00 2001
From: VictorQR <42757940+VictorQR@users.noreply.github.com>
Date: Wed, 1 Oct 2025 11:56:23 +0800
Subject: [PATCH 11/11] =?UTF-8?q?=E7=BE=8E=E5=8C=96=E7=BD=91=E9=A1=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/frosted-glass.css | 81 ++++++++++++++++++++++++
src/main.ts | 2 +-
src/modern-theme.css | 141 ------------------------------------------
3 files changed, 82 insertions(+), 142 deletions(-)
create mode 100644 src/frosted-glass.css
delete mode 100644 src/modern-theme.css
diff --git a/src/frosted-glass.css b/src/frosted-glass.css
new file mode 100644
index 00000000..12010f5d
--- /dev/null
+++ b/src/frosted-glass.css
@@ -0,0 +1,81 @@
+/* --- Frosted Glass Theme for PikPak Web --- */
+
+/* 1. Frosted Glass Components */
+
+/* Sidebar */
+.n-layout-sider {
+ background-color: rgba(245, 245, 245, 0.6) !important;
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border-right: 1px solid rgba(255, 255, 255, 0.18) !important;
+}
+
+/* Main Content Area Header */
+.header {
+ background-color: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 12px;
+}
+
+/* Data Table */
+.n-data-table {
+ --merged-th-color: rgba(240, 240, 245, 0.7) !important;
+ --merged-td-color: rgba(255, 255, 255, 0.4) !important;
+ --merged-td-color-hover: rgba(240, 240, 245, 0.6) !important;
+ --merged-border-color: transparent !important;
+}
+
+/* Cards, Modals, Popups, Dialogs */
+.n-card, .n-modal, .n-popover, .n-dialog, .n-collapse {
+ background-color: rgba(255, 255, 255, 0.75) !important;
+ backdrop-filter: blur(15px) !important;
+ -webkit-backdrop-filter: blur(15px) !important;
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
+ border-radius: 16px !important;
+}
+
+/* Login Page Box */
+.login-page .login-box {
+ background: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
+}
+
+/* Bottom Toolbar */
+.toolbar-wrapper {
+ background: rgba(255, 255, 255, 0.6);
+ backdrop-filter: blur(15px);
+ -webkit-backdrop-filter: blur(15px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 16px;
+}
+.toolbar-item {
+ color: #333;
+}
+
+/* 2. Unified Rounded Corners */
+.n-scrollbar, .n-row {
+ border-radius: 12px;
+}
+.n-row {
+ background-color: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(10px);
+}
+
+/* 3. General Adjustments */
+.n-layout, .login-page {
+ background: initial !important;
+}
+.sider-bottom {
+ background-color: transparent !important;
+}
+.sider-bottom.vip {
+ background-color: rgba(244, 237, 219, 0.4) !important;
+}
+.n-menu {
+ background: transparent !important;
+}
diff --git a/src/main.ts b/src/main.ts
index a746de57..8c8c14e4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,7 +4,7 @@ import router from './router'
import http from './utils/axios'
import elementResizeDetectorMaker from 'element-resize-detector'
import cnzzAnalytics from 'vue-cnzz-analytics'
-import './modern-theme.css'
+import './frosted-glass.css'
const app = createApp(App)
app.directive('resize', {
diff --git a/src/modern-theme.css b/src/modern-theme.css
deleted file mode 100644
index c54b2fce..00000000
--- a/src/modern-theme.css
+++ /dev/null
@@ -1,141 +0,0 @@
-/* --- Global Theme: Frosted Glass & Gradient --- */
-
-/* 1. Advanced Gradient Background */
-.n-layout {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- background-attachment: fixed;
-}
-
-/* 2. Apply Frosted Glass to Components */
-
-/* Sidebar */
-.n-layout-sider {
- background: rgba(255, 255, 255, 0.1) !important;
- backdrop-filter: blur(15px) !important;
- -webkit-backdrop-filter: blur(15px) !important;
- border-right: 1px solid rgba(255, 255, 255, 0.18) !important;
-}
-
-/* Main Content Area Header */
-.header {
- margin: 20px 20px 0 20px !important;
- padding-left: 20px;
- padding-right: 20px;
- background: rgba(255, 255, 255, 0.2);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.18);
- border-radius: 12px;
-}
-
-/* Data Table */
-.n-data-table {
- background: transparent !important;
- --merged-th-color: rgba(255, 255, 255, 0.2) !important;
- --merged-td-color: rgba(255, 255, 255, 0.05) !important;
- --merged-td-color-hover: rgba(255, 255, 255, 0.15) !important;
- --merged-border-color: rgba(255, 255, 255, 0.1) !important;
-}
-.n-data-table .n-data-table-td, .n-data-table .n-data-table-th {
- color: #f0f0f0;
-}
-.n-data-table .n-data-table-th {
- color: #ffffff;
- font-weight: 600;
-}
-.file-info .title, .file-info .n-ellipsis {
- color: #fff !important;
-}
-.n-data-table-td.size, .n-data-table-td.modified_time, .n-data-table-td .n-time {
- color: rgba(230, 230, 230, 0.7) !important;
-}
-.n-data-table .n-text--primary-type {
- color: #a7c5ff !important;
-}
-
-/* Cards, Modals, Popups */
-.n-card, .n-modal, .n-popover, .n-dialog {
- background: rgba(30, 30, 50, 0.6) !important;
- backdrop-filter: blur(15px) !important;
- -webkit-backdrop-filter: blur(15px) !important;
- border: 1px solid rgba(255, 255, 255, 0.1) !important;
- border-radius: 16px !important;
- color: #fff !important;
-}
-.n-card-header, .n-dialog__title, .n-modal-header__title {
- color: #fff !important;
-}
-.n-input, .n-input__textarea-el {
- background-color: rgba(0,0,0,0.2) !important;
- color: #fff !important;
-}
-.n-input .n-input__input-el, .n-input .n-input__placeholder {
- color: #fff !important;
-}
-
-/* Login Page */
-.login-page {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-}
-.login-page .login-box {
- background: rgba(255, 255, 255, 0.15);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- border: 1px solid rgba(255, 255, 255, 0.18);
- box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.1);
-}
-.login-page .logo-box__text {
- text-shadow: 0 2px 10px rgba(0,0,0,0.2);
-}
-
-/* Sidebar Menu & Icons */
-.n-menu {
- background: transparent !important;
-}
-.n-menu .n-menu-item-content__icon {
- color: #e0e0e0 !important;
-}
-.n-menu .n-menu-item-content-header {
- color: #e0e0e0 !important;
-}
-.n-menu .n-menu-item--selected .n-menu-item-content-header,
-.n-menu .n-menu-item--selected .n-menu-item-content__icon {
- color: #ffffff !important;
- font-weight: bold;
-}
-.n-layout-sider-scroll-container {
- color: #f0f0f0;
-}
-.n-layout-sider-scroll-container .n-text--primary-type {
- color: #d1c4e9 !important;
-}
-.sider-bottom {
- background-color: transparent !important;
-}
-.sider-bottom.vip {
- background-color: rgba(244, 237, 219, 0.1) !important;
-}
-
-/* Progress Bar */
-.content-bottom {
- border-top: 1px solid rgba(255, 255, 255, 0.18);
- padding-top: 10px;
-}
-
-/* General Text Color & Breadcrumb */
-body {
- color: #f0f0f0;
-}
-.n-breadcrumb {
- color: #fff !important;
-}
-.n-breadcrumb-item a {
- color: #e0e0e0 !important;
- text-decoration: none;
-}
-.n-breadcrumb-item a:hover {
- color: #fff !important;
-}
-.n-breadcrumb .n-breadcrumb-item__separator {
- color: rgba(255, 255, 255, 0.5) !important;
-}