diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json
index 3142650..9d091c6 100644
--- a/public/locales/en-US/dashboard.json
+++ b/public/locales/en-US/dashboard.json
@@ -437,6 +437,11 @@
"capSiteKeyDes": "The site key from your Cap server dashboard.",
"capSecretKey": "Secret Key",
"capSecretKeyDes": "The secret key from your Cap server dashboard.",
+ "capAssetServer": "Asset Server Source",
+ "capAssetServerDes": "Choose the source for loading Cap captcha static assets. Using self-deployed server requires setting environment variables on the server side, please refer to <0>enable asset server0>.",
+ "capAssetServerJsdelivr": "jsDelivr CDN",
+ "capAssetServerUnpkg": "unpkg CDN",
+ "capAssetServerInstance": "Self-hosted server",
"captchaProvider": "Captcha provider",
"captchaWidth": "Width",
"captchaHeight": "Height",
diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json
index 617679c..83bba97 100644
--- a/public/locales/ja-JP/dashboard.json
+++ b/public/locales/ja-JP/dashboard.json
@@ -435,6 +435,11 @@
"capSiteKeyDes": "Cap サーバーダッシュボードから取得したサイトキー。",
"capSecretKey": "シークレットキー",
"capSecretKeyDes": "Cap サーバーダッシュボードから取得したシークレットキー。",
+ "capAssetServer": "静的リソースソース",
+ "capAssetServerDes": "Cap認証コードの静的リソースの読み込みソースを選択します。自己デプロイサーバーを使用するにはサーバー側で環境変数を設定する必要があります、<0>静的リソースサービスを有効にする0>を参照してください。",
+ "capAssetServerJsdelivr": "jsDelivr CDN",
+ "capAssetServerUnpkg": "unpkg CDN",
+ "capAssetServerInstance": "自己デプロイサーバー",
"captchaProvider": "認証コードタイプ",
"captchaWidth": "幅",
"captchaHeight": "高さ",
diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json
index 37f3255..16c603f 100644
--- a/public/locales/zh-CN/dashboard.json
+++ b/public/locales/zh-CN/dashboard.json
@@ -430,11 +430,16 @@
"turnstileSiteKSecret": "密钥",
"cap": "Cap",
"capInstanceURL": "实例 URL",
- "capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>独立模式文档0>。",
+ "capInstanceURLDes": "自部署 Cap 服务器的 URL 地址。详细信息请参考 <0>自部署模式文档0>。",
"capSiteKey": "站点密钥",
"capSiteKeyDes": "从 Cap 服务器控制面板获取的站点密钥。",
"capSecretKey": "私密密钥",
"capSecretKeyDes": "从 Cap 服务器控制面板获取的私密密钥。",
+ "capAssetServer": "静态资源服务源",
+ "capAssetServerDes": "选择 Cap 验证码静态资源的加载源。使用自部署服务器需要在服务器端设置环境变量,请参考 <0>开启静态资源服务0>。",
+ "capAssetServerJsdelivr": "jsDelivr CDN",
+ "capAssetServerUnpkg": "unpkg CDN",
+ "capAssetServerInstance": "自部署服务器",
"captchaProvider": "验证码类型",
"captchaWidth": "宽度",
"captchaHeight": "高度",
diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json
index 3805696..f9e2666 100644
--- a/public/locales/zh-TW/dashboard.json
+++ b/public/locales/zh-TW/dashboard.json
@@ -432,6 +432,11 @@
"capSiteKeyDes": "從 Cap 伺服器控制面板獲取的站點金鑰。",
"capSecretKey": "私密金鑰",
"capSecretKeyDes": "從 Cap 伺服器控制面板獲取的私密金鑰。",
+ "capAssetServer": "靜態資源服務源",
+ "capAssetServerDes": "選擇 Cap 驗證碼靜態資源的載入源。使用自部署伺服器需要在伺服器端設定環境變數,請參考 <0>開啟靜態資源服務0>。",
+ "capAssetServerJsdelivr": "jsDelivr CDN",
+ "capAssetServerUnpkg": "unpkg CDN",
+ "capAssetServerInstance": "自部署伺服器",
"captchaProvider": "驗證碼型別",
"captchaWidth": "寬度",
"captchaHeight": "高度",
diff --git a/src/api/site.ts b/src/api/site.ts
index dbd8ba4..52cf4db 100644
--- a/src/api/site.ts
+++ b/src/api/site.ts
@@ -26,6 +26,7 @@ export interface SiteConfig {
captcha_cap_instance_url?: string;
captcha_cap_site_key?: string;
captcha_cap_secret_key?: string;
+ captcha_cap_asset_server?: string;
register_enabled?: boolean;
logo?: string;
logo_light?: string;
diff --git a/src/component/Admin/Settings/Captcha/CapCaptcha.tsx b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx
index 907e15b..eb839f5 100644
--- a/src/component/Admin/Settings/Captcha/CapCaptcha.tsx
+++ b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx
@@ -1,7 +1,8 @@
import { Trans, useTranslation } from "react-i18next";
-import { FormControl, Link, Stack } from "@mui/material";
+import { FormControl, Link, Stack, ListItemText } from "@mui/material";
import SettingForm from "../../../Pages/Setting/SettingForm.tsx";
-import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx";
+import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx";
+import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx";
import * as React from "react";
import { NoMarginHelperText } from "../Settings.tsx";
@@ -78,6 +79,59 @@ const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => {
+
+
+
+ setSettings({
+ captcha_cap_asset_server: e.target.value,
+ })
+ }
+ >
+
+
+ {t("settings.capAssetServerJsdelivr")}
+
+
+
+
+ {t("settings.capAssetServerUnpkg")}
+
+
+
+
+ {t("settings.capAssetServerInstance")}
+
+
+
+
+ ,
+ ]}
+ />
+
+
+
);
};
diff --git a/src/component/Admin/Settings/Settings.tsx b/src/component/Admin/Settings/Settings.tsx
index f054862..4b2be9e 100644
--- a/src/component/Admin/Settings/Settings.tsx
+++ b/src/component/Admin/Settings/Settings.tsx
@@ -224,6 +224,7 @@ const Settings = () => {
"captcha_cap_instance_url",
"captcha_cap_site_key",
"captcha_cap_secret_key",
+ "captcha_cap_asset_server",
]}
>
diff --git a/src/component/Common/Captcha/CapCaptcha.tsx b/src/component/Common/Captcha/CapCaptcha.tsx
index 6787d8e..ec4ed50 100644
--- a/src/component/Common/Captcha/CapCaptcha.tsx
+++ b/src/component/Common/Captcha/CapCaptcha.tsx
@@ -4,6 +4,12 @@ import { useAppSelector } from "../../../redux/hooks.ts";
import { CaptchaParams } from "./Captcha.tsx";
import { Box, useTheme } from "@mui/material";
+// Cap Widget URLs
+const CAP_WASM_UNPKG_URL = "https://unpkg.com/@cap.js/wasm@0.0.4/browser/cap_wasm.js";
+const CAP_WASM_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.4/browser/cap_wasm.min.js";
+const CAP_WIDGET_UNPKG_URL = "https://unpkg.com/@cap.js/widget";
+const CAP_WIDGET_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/widget";
+
export interface CapProps {
onStateChange: (state: CaptchaParams) => void;
generation: number;
@@ -22,6 +28,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url);
const capSiteKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_site_key);
+ const capAssetServer = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_asset_server);
// Keep callback reference up to date
useEffect(() => {
@@ -132,6 +139,17 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
const initWidget = () => {
scriptLoadedRef.current = true;
+
+ // 根据配置设置WASM URL,保持资源加载的一致性
+ if (capAssetServer === "instance") {
+ (window as any).CAP_CUSTOM_WASM_URL = `${capInstanceURL.replace(/\/$/, "")}/assets/cap_wasm.min.js`;
+ } else if (capAssetServer === "unpkg") {
+ (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_UNPKG_URL;
+ } else {
+ // jsdelivr - 默认CDN
+ (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_JSDELIVR_URL;
+ }
+
// Add a small delay to ensure DOM is ready
setTimeout(() => {
createWidget();
@@ -141,11 +159,29 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
if (!script) {
script = document.createElement("script");
script.id = scriptId;
- script.src = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`;
+
+ // 根据配置选择静态资源源
+ let assetSource;
+ if (capAssetServer === "instance") {
+ assetSource = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`;
+ } else if (capAssetServer === "unpkg") {
+ assetSource = CAP_WIDGET_UNPKG_URL;
+ } else {
+ // jsdelivr - 默认CDN
+ assetSource = CAP_WIDGET_JSDELIVR_URL;
+ }
+
+ script.src = assetSource;
script.async = true;
script.onload = initWidget;
script.onerror = () => {
- console.error("Failed to load Cap widget script");
+ if (capAssetServer === "instance") {
+ console.error("Failed to load Cap widget script from instance server");
+ } else if (capAssetServer === "unpkg") {
+ console.error("Failed to load Cap widget script from unpkg CDN");
+ } else {
+ console.error("Failed to load Cap widget script from jsDelivr CDN");
+ }
};
document.head.appendChild(script);
} else if (scriptLoadedRef.current || (window as any).Cap) {
@@ -166,7 +202,7 @@ const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps
captchaRef.current.innerHTML = "";
}
};
- }, [capInstanceURL, capSiteKey, t]);
+ }, [capInstanceURL, capSiteKey, capAssetServer, t]);
if (!capInstanceURL || !capSiteKey) {
return null;