|
|
-
-
- |
{(() => {
const status = getValidationStatus(source.key);
@@ -6137,7 +6593,11 @@ const VideoSourceConfig = ({
);
// 根据操作类型和结果显示不同的消息
- if (action === 'batch_delete' && result?.deleted !== undefined && result?.skipped !== undefined) {
+ if (
+ action === 'batch_delete' &&
+ result?.deleted !== undefined &&
+ result?.skipped !== undefined
+ ) {
const { deleted, skipped } = result;
if (skipped > 0) {
showAlert({
@@ -6268,11 +6728,19 @@ const VideoSourceConfig = ({
>
)}
-
+
+
@@ -6369,7 +6837,6 @@ const VideoSourceConfig = ({
- {/* 保存排序按钮 */}
- {orderChanged && (
-
-
-
- )}
+ {showWeightModal &&
+ createPortal(
+ <>
+ {
+ e.preventDefault();
+ }}
+ onWheel={(e) => {
+ e.preventDefault();
+ }}
+ style={{
+ touchAction: 'none',
+ }}
+ />
+ e.stopPropagation()}
+ >
+
+
+
+ 视频源权重设置
+
+
+
+
+
+ {
+ e.stopPropagation();
+ }}
+ onWheel={(e) => {
+ e.stopPropagation();
+ }}
+ style={{
+ touchAction: 'pan-y',
+ overscrollBehavior: 'contain',
+ }}
+ >
+
+
+ 排序越靠前,推荐权重越高;拖动后再次生成推荐值时,会把当前列表均匀映射到
+ 0~40。
+
+
+
+
+
+
+
+
+
+
+ 排序
+ 视频源
+ 状态
+ 推荐值
+ 生效权重
+
+
+
+ source.key)}
+ strategy={verticalListSortingStrategy}
+ >
+
+ {weightDraftSources.map((source, index) => {
+ const recommendedWeight =
+ recommendedWeightMap.get(source.key) ?? 0;
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >,
+ document.body
+ )}
{/* 有效性检测弹窗 */}
{showValidationModal &&
@@ -6486,7 +7064,7 @@ const VideoSourceConfig = ({
className={`px-4 py-2 ${
!searchKeyword.trim()
? buttonStyles.disabled
- : buttonStyles.primary
+ : buttonStyles.success
}`}
>
开始检测
@@ -6572,7 +7150,7 @@ const VideoSourceConfig = ({
isLoading('batchSource_batch_disable') ||
isLoading('batchSource_batch_delete')
? buttonStyles.disabled
- : buttonStyles.primary
+ : buttonStyles.success
}`}
>
{isLoading('batchSource_batch_enable') ||
@@ -6904,7 +7482,6 @@ const CategoryConfig = ({
- |
分类名称
|
@@ -7001,10 +7578,10 @@ const VideoSourceScriptLab = () => {
code: '',
enabled: true,
});
- const [testHook, setTestHook] = useState<'getSources' | 'search' | 'recommend' | 'detail' | 'resolvePlayUrl'>('getSources');
- const [testPayload, setTestPayload] = useState(
- JSON.stringify({}, null, 2)
- );
+ const [testHook, setTestHook] = useState<
+ 'getSources' | 'search' | 'recommend' | 'detail' | 'resolvePlayUrl'
+ >('getSources');
+ const [testPayload, setTestPayload] = useState(JSON.stringify({}, null, 2));
const [testOutput, setTestOutput] = useState('');
const importInputRef = useRef(null);
@@ -7068,7 +7645,10 @@ const VideoSourceScriptLab = () => {
setSelectedScriptId(null);
}
} catch (error) {
- showError(error instanceof Error ? error.message : '加载脚本失败', showAlert);
+ showError(
+ error instanceof Error ? error.message : '加载脚本失败',
+ showAlert
+ );
} finally {
setLoadingScripts(false);
}
@@ -7212,7 +7792,10 @@ const VideoSourceScriptLab = () => {
showSuccess('脚本已删除', showAlert);
await loadScripts(null);
}).catch((error) => {
- showError(error instanceof Error ? error.message : '删除失败', showAlert);
+ showError(
+ error instanceof Error ? error.message : '删除失败',
+ showAlert
+ );
});
},
});
@@ -7276,20 +7859,24 @@ const VideoSourceScriptLab = () => {
testHook === 'getSources'
? JSON.stringify({}, null, 2)
: testHook === 'search'
- ? JSON.stringify({ keyword: '凡人修仙传', page: 1, sourceId: 'main' }, null, 2)
+ ? JSON.stringify(
+ { keyword: '凡人修仙传', page: 1, sourceId: 'main' },
+ null,
+ 2
+ )
: testHook === 'recommend'
? JSON.stringify({ page: 1 }, null, 2)
: testHook === 'detail'
- ? JSON.stringify({ id: 'demo-id', sourceId: 'main' }, null, 2)
- : JSON.stringify(
- {
- sourceId: 'main',
- playUrl: 'https://example.com/video.m3u8',
- episodeIndex: 0,
- },
- null,
- 2
- )
+ ? JSON.stringify({ id: 'demo-id', sourceId: 'main' }, null, 2)
+ : JSON.stringify(
+ {
+ sourceId: 'main',
+ playUrl: 'https://example.com/video.m3u8',
+ episodeIndex: 0,
+ },
+ null,
+ 2
+ )
);
}, [testHook]);
@@ -7312,18 +7899,29 @@ const VideoSourceScriptLab = () => {
-
)}
@@ -8427,11 +9121,23 @@ const ThemeConfigComponent = ({
))}
setHomeBackgroundImages([...homeBackgroundImages, ''])}
+ onClick={() =>
+ setHomeBackgroundImages([...homeBackgroundImages, ''])
+ }
className='flex items-center gap-2 px-4 py-2 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'
>
-
@@ -8467,9 +9173,7 @@ const ThemeConfigComponent = ({
}
className='w-4 h-4 text-blue-600'
/>
-
- 默认圆点
-
+ 默认圆点
@@ -8513,9 +9213,24 @@ const ThemeConfigComponent = ({
{[
- { id: 'renako', name: '玲奈子', url: '/icons/q/renako.png', color: '#ec4899' },
- { id: 'irena', name: '伊蕾娜', url: '/icons/q/irena.png', color: '#f8fafc' },
- { id: 'emilia', name: '爱蜜莉雅', url: '/icons/q/emilia.png', color: '#f8fafc' },
+ {
+ id: 'renako',
+ name: '玲奈子',
+ url: '/icons/q/renako.png',
+ color: '#ec4899',
+ },
+ {
+ id: 'irena',
+ name: '伊蕾娜',
+ url: '/icons/q/irena.png',
+ color: '#f8fafc',
+ },
+ {
+ id: 'emilia',
+ name: '爱蜜莉雅',
+ url: '/icons/q/emilia.png',
+ color: '#f8fafc',
+ },
].map((thumb) => (
{
- (e.target as HTMLImageElement).src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="48" height="48"%3E%3Crect width="48" height="48" fill="%23ddd"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23999"%3E?%3C/text%3E%3C/svg%3E';
+ (e.target as HTMLImageElement).src =
+ 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="48" height="48"%3E%3Crect width="48" height="48" fill="%23ddd"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23999"%3E?%3C/text%3E%3C/svg%3E';
}}
/>
@@ -8580,11 +9296,14 @@ const ThemeConfigComponent = ({
}
/>
- 支持 PNG、JPG、GIF、WebP 格式,建议尺寸 32x32px,图片URL必须可公开访问
+ 支持 PNG、JPG、GIF、WebP 格式,建议尺寸
+ 32x32px,图片URL必须可公开访问
{themeSettings.progressThumbCustomUrl && (
- 预览:
+
+ 预览:
+

- ⚠️ 内置弹幕源为多人共享服务,稳定性可能受使用高峰影响,建议自行部署后使用自定义源。
+ ⚠️
+ 内置弹幕源为多人共享服务,稳定性可能受使用高峰影响,建议自行部署后使用自定义源。
)}
@@ -9462,7 +10192,9 @@ const SiteConfigComponent = ({
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
/>
- 配置后首页将显示 TMDB 即将上映电影。支持配置多个 API Key(用英文逗号分隔)以实现轮询,避免单个 Key 请求限制。获取 API Key 请访问{' '}
+ 配置后首页将显示 TMDB 即将上映电影。支持配置多个 API
+ Key(用英文逗号分隔)以实现轮询,避免单个 Key 请求限制。获取 API
+ Key 请访问{' '}
- 用于访问磁链搜索站点的系统代理。Cloudflare 部署环境下不会使用该代理。
+ 用于访问磁链搜索站点的系统代理。Cloudflare
+ 部署环境下不会使用该代理。
@@ -9732,7 +10465,9 @@ const SiteConfigComponent = ({
handleCommentsToggle(!siteSettings.EnableComments)}
+ onClick={() =>
+ handleCommentsToggle(!siteSettings.EnableComments)
+ }
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
siteSettings.EnableComments
? buttonStyles.toggleOn
@@ -9867,7 +10602,8 @@ const RegistrationConfigComponent = ({
}) => {
const { alertModal, showAlert, hideAlert } = useAlertModal();
const { isLoading, withLoading } = useLoadingState();
- const [showEnableRegistrationModal, setShowEnableRegistrationModal] = useState(false);
+ const [showEnableRegistrationModal, setShowEnableRegistrationModal] =
+ useState(false);
const [registrationSettings, setRegistrationSettings] = useState<{
EnableRegistration: boolean;
RequireRegistrationInviteCode: boolean;
@@ -9912,17 +10648,21 @@ const RegistrationConfigComponent = ({
if (config?.SiteConfig) {
setRegistrationSettings({
EnableRegistration: config.SiteConfig.EnableRegistration || false,
- RequireRegistrationInviteCode: config.SiteConfig.RequireRegistrationInviteCode || false,
+ RequireRegistrationInviteCode:
+ config.SiteConfig.RequireRegistrationInviteCode || false,
RegistrationInviteCode: config.SiteConfig.RegistrationInviteCode || '',
- RegistrationRequireTurnstile: config.SiteConfig.RegistrationRequireTurnstile || false,
+ RegistrationRequireTurnstile:
+ config.SiteConfig.RegistrationRequireTurnstile || false,
LoginRequireTurnstile: config.SiteConfig.LoginRequireTurnstile || false,
TurnstileSiteKey: config.SiteConfig.TurnstileSiteKey || '',
TurnstileSecretKey: config.SiteConfig.TurnstileSecretKey || '',
DefaultUserTags: config.SiteConfig.DefaultUserTags || [],
EnableOIDCLogin: config.SiteConfig.EnableOIDCLogin || false,
- EnableOIDCRegistration: config.SiteConfig.EnableOIDCRegistration || false,
+ EnableOIDCRegistration:
+ config.SiteConfig.EnableOIDCRegistration || false,
OIDCIssuer: config.SiteConfig.OIDCIssuer || '',
- OIDCAuthorizationEndpoint: config.SiteConfig.OIDCAuthorizationEndpoint || '',
+ OIDCAuthorizationEndpoint:
+ config.SiteConfig.OIDCAuthorizationEndpoint || '',
OIDCTokenEndpoint: config.SiteConfig.OIDCTokenEndpoint || '',
OIDCUserInfoEndpoint: config.SiteConfig.OIDCUserInfoEndpoint || '',
OIDCClientId: config.SiteConfig.OIDCClientId || '',
@@ -9973,7 +10713,8 @@ const RegistrationConfigComponent = ({
const updatedSiteConfig = {
...config.SiteConfig,
...registrationSettings,
- RegistrationInviteCode: registrationSettings.RegistrationInviteCode.trim(),
+ RegistrationInviteCode:
+ registrationSettings.RegistrationInviteCode.trim(),
};
const resp = await fetch('/api/admin/site', {
@@ -10012,7 +10753,10 @@ const RegistrationConfigComponent = ({
注册配置
-
+
基础注册设置
@@ -10024,7 +10768,11 @@ const RegistrationConfigComponent = ({
handleRegistrationToggle(!registrationSettings.EnableRegistration)}
+ onClick={() =>
+ handleRegistrationToggle(
+ !registrationSettings.EnableRegistration
+ )
+ }
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
registrationSettings.EnableRegistration
? buttonStyles.toggleOn
@@ -10052,7 +10800,12 @@ const RegistrationConfigComponent = ({
默认用户组
新注册的用户将自动分配到选中的用户组,选择"无用户组"为无限制
@@ -10094,7 +10848,8 @@ const RegistrationConfigComponent = ({
onClick={() =>
setRegistrationSettings((prev) => ({
...prev,
- RequireRegistrationInviteCode: !prev.RequireRegistrationInviteCode,
+ RequireRegistrationInviteCode:
+ !prev.RequireRegistrationInviteCode,
}))
}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
@@ -10147,19 +10902,24 @@ const RegistrationConfigComponent = ({
setRegistrationSettings((prev) => ({
...prev,
- RegistrationRequireTurnstile: !prev.RegistrationRequireTurnstile,
+ RegistrationRequireTurnstile:
+ !prev.RegistrationRequireTurnstile,
}))
}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
- !registrationSettings.TurnstileSiteKey || !registrationSettings.TurnstileSecretKey
+ !registrationSettings.TurnstileSiteKey ||
+ !registrationSettings.TurnstileSecretKey
? 'opacity-50 cursor-not-allowed bg-gray-300 dark:bg-gray-600'
: registrationSettings.RegistrationRequireTurnstile
- ? buttonStyles.toggleOn
- : buttonStyles.toggleOff
+ ? buttonStyles.toggleOn
+ : buttonStyles.toggleOff
}`}
>
开启后注册时需要通过Cloudflare Turnstile人机验证。
- {(!registrationSettings.TurnstileSiteKey || !registrationSettings.TurnstileSecretKey) && (
- 需要先配置Site Key和Secret Key才能启用。
+ {(!registrationSettings.TurnstileSiteKey ||
+ !registrationSettings.TurnstileSecretKey) && (
+
+ {' '}
+ 需要先配置Site Key和Secret Key才能启用。
+
)}
@@ -10188,7 +10952,10 @@ const RegistrationConfigComponent = ({
setRegistrationSettings((prev) => ({
...prev,
@@ -10196,11 +10963,12 @@ const RegistrationConfigComponent = ({
}))
}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
- !registrationSettings.TurnstileSiteKey || !registrationSettings.TurnstileSecretKey
+ !registrationSettings.TurnstileSiteKey ||
+ !registrationSettings.TurnstileSecretKey
? 'opacity-50 cursor-not-allowed bg-gray-300 dark:bg-gray-600'
: registrationSettings.LoginRequireTurnstile
- ? buttonStyles.toggleOn
- : buttonStyles.toggleOff
+ ? buttonStyles.toggleOn
+ : buttonStyles.toggleOff
}`}
>
开启后登录时需要通过Cloudflare Turnstile人机验证。
- {(!registrationSettings.TurnstileSiteKey || !registrationSettings.TurnstileSecretKey) && (
- 需要先配置Site Key和Secret Key才能启用。
+ {(!registrationSettings.TurnstileSiteKey ||
+ !registrationSettings.TurnstileSecretKey) && (
+
+ {' '}
+ 需要先配置Site Key和Secret Key才能启用。
+
)}
@@ -10275,325 +11047,346 @@ const RegistrationConfigComponent = ({
{/* 启用OIDC登录 */}
-
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- EnableOIDCLogin: !prev.EnableOIDCLogin,
- }))
- }
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
- registrationSettings.EnableOIDCLogin
- ? buttonStyles.toggleOn
- : buttonStyles.toggleOff
- }`}
- >
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ EnableOIDCLogin: !prev.EnableOIDCLogin,
+ }))
+ }
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
registrationSettings.EnableOIDCLogin
- ? buttonStyles.toggleThumbOn
- : buttonStyles.toggleThumbOff
+ ? buttonStyles.toggleOn
+ : buttonStyles.toggleOff
}`}
- />
-
-
-
- 开启后登录页面将显示OIDC登录按钮
-
+ >
+
+
+
+
+ 开启后登录页面将显示OIDC登录按钮
+
{/* 启用OIDC注册 */}
-
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- EnableOIDCRegistration: !prev.EnableOIDCRegistration,
- }))
- }
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
- registrationSettings.EnableOIDCRegistration
- ? buttonStyles.toggleOn
- : buttonStyles.toggleOff
- }`}
- >
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ EnableOIDCRegistration: !prev.EnableOIDCRegistration,
+ }))
+ }
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${
registrationSettings.EnableOIDCRegistration
- ? buttonStyles.toggleThumbOn
- : buttonStyles.toggleThumbOff
+ ? buttonStyles.toggleOn
+ : buttonStyles.toggleOff
}`}
- />
-
-
-
- 开启后允许通过OIDC方式注册新用户(需要先启用OIDC登录)
-
+ >
+
+
+
+
+ 开启后允许通过OIDC方式注册新用户(需要先启用OIDC登录)
+
{/* OIDC Issuer */}
-
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCIssuer: e.target.value,
- }))
- }
- className='flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
- {
- if (!registrationSettings.OIDCIssuer) {
- showError('请先输入Issuer URL', showAlert);
- return;
+
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCIssuer: e.target.value,
+ }))
}
-
- await withLoading('oidcDiscover', async () => {
- try {
- const res = await fetch('/api/admin/oidc-discover', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ issuerUrl: registrationSettings.OIDCIssuer }),
- });
-
- if (!res.ok) {
- const data = await res.json().catch(() => ({}));
- throw new Error(data.error || '获取配置失败');
- }
-
- const data = await res.json();
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCAuthorizationEndpoint: data.authorization_endpoint || '',
- OIDCTokenEndpoint: data.token_endpoint || '',
- OIDCUserInfoEndpoint: data.userinfo_endpoint || '',
- }));
- showSuccess('自动发现成功', showAlert);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : '自动发现失败,请手动配置端点';
- showError(errorMessage, showAlert);
- throw error;
+ className='flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+ {
+ if (!registrationSettings.OIDCIssuer) {
+ showError('请先输入Issuer URL', showAlert);
+ return;
}
- });
- }}
- disabled={isLoading('oidcDiscover')}
- className={`px-4 py-2 ${isLoading('oidcDiscover') ? buttonStyles.disabled : buttonStyles.primary} rounded-lg whitespace-nowrap sm:w-auto w-full`}
- >
- {isLoading('oidcDiscover') ? '发现中...' : '自动发现'}
-
-
-
- OIDC提供商的Issuer URL,填写后可点击"自动发现"按钮自动获取端点配置
-
+
+ await withLoading('oidcDiscover', async () => {
+ try {
+ const res = await fetch('/api/admin/oidc-discover', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ issuerUrl: registrationSettings.OIDCIssuer,
+ }),
+ });
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ throw new Error(data.error || '获取配置失败');
+ }
+
+ const data = await res.json();
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCAuthorizationEndpoint:
+ data.authorization_endpoint || '',
+ OIDCTokenEndpoint: data.token_endpoint || '',
+ OIDCUserInfoEndpoint: data.userinfo_endpoint || '',
+ }));
+ showSuccess('自动发现成功', showAlert);
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error
+ ? error.message
+ : '自动发现失败,请手动配置端点';
+ showError(errorMessage, showAlert);
+ throw error;
+ }
+ });
+ }}
+ disabled={isLoading('oidcDiscover')}
+ className={`px-4 py-2 ${
+ isLoading('oidcDiscover')
+ ? buttonStyles.disabled
+ : buttonStyles.primary
+ } rounded-lg whitespace-nowrap sm:w-auto w-full`}
+ >
+ {isLoading('oidcDiscover') ? '发现中...' : '自动发现'}
+
+
+
+ OIDC提供商的Issuer URL,填写后可点击"自动发现"按钮自动获取端点配置
+
{/* Authorization Endpoint */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCAuthorizationEndpoint: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 用户授权的端点URL
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCAuthorizationEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 用户授权的端点URL
+
{/* Token Endpoint */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCTokenEndpoint: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 交换授权码获取token的端点URL
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCTokenEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 交换授权码获取token的端点URL
+
{/* UserInfo Endpoint */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCUserInfoEndpoint: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 获取用户信息的端点URL
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCUserInfoEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 获取用户信息的端点URL
+
{/* OIDC Client ID */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCClientId: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 在OIDC提供商处注册应用后获得的Client ID
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCClientId: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在OIDC提供商处注册应用后获得的Client ID
+
{/* OIDC Client Secret */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCClientSecret: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 在OIDC提供商处注册应用后获得的Client Secret
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCClientSecret: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在OIDC提供商处注册应用后获得的Client Secret
+
{/* OIDC Redirect URI - 只读显示 */}
-
-
-
- {
- const uri = `${(window as any).RUNTIME_CONFIG?.SITE_BASE || window.location.origin}/api/auth/oidc/callback`;
- navigator.clipboard.writeText(uri);
- showSuccess('已复制到剪贴板', showAlert);
- }}
- className='absolute right-2 top-1/2 -translate-y-1/2 px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors'
- >
- 复制
-
-
-
- 这是系统自动生成的回调地址,基于环境变量SITE_BASE。请在OIDC提供商(如Keycloak、Auth0等)的应用配置中添加此地址作为允许的重定向URI
-
+
+
+
+ {
+ const uri = `${
+ (window as any).RUNTIME_CONFIG?.SITE_BASE ||
+ window.location.origin
+ }/api/auth/oidc/callback`;
+ navigator.clipboard.writeText(uri);
+ showSuccess('已复制到剪贴板', showAlert);
+ }}
+ className='absolute right-2 top-1/2 -translate-y-1/2 px-3 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors'
+ >
+ 复制
+
+
+
+ 这是系统自动生成的回调地址,基于环境变量SITE_BASE。请在OIDC提供商(如Keycloak、Auth0等)的应用配置中添加此地址作为允许的重定向URI
+
{/* OIDC登录按钮文字 */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCButtonText: e.target.value,
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
-
- 自定义OIDC登录按钮显示的文字,如"使用企业账号登录"、"使用SSO登录"等。留空则显示默认文字"使用OIDC登录"
-
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCButtonText: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 自定义OIDC登录按钮显示的文字,如"使用企业账号登录"、"使用SSO登录"等。留空则显示默认文字"使用OIDC登录"
+
{/* OIDC最低信任等级 */}
-
-
- setRegistrationSettings((prev) => ({
- ...prev,
- OIDCMinTrustLevel: e.target.value === '' ? 0 : parseInt(e.target.value),
- }))
- }
- className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
- />
+
+
+ setRegistrationSettings((prev) => ({
+ ...prev,
+ OIDCMinTrustLevel:
+ e.target.value === '' ? 0 : parseInt(e.target.value),
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
仅LinuxDo网站有效。设置为0时不判断,1-4表示最低信任等级要求
@@ -10989,7 +11782,9 @@ const SuwayomiConfigComponent = ({
const { isLoading, withLoading } = useLoadingState();
const [enabled, setEnabled] = useState(false);
const [serverURL, setServerURL] = useState('');
- const [authMode, setAuthMode] = useState<'none' | 'basic_auth' | 'simple_login'>('none');
+ const [authMode, setAuthMode] = useState<
+ 'none' | 'basic_auth' | 'simple_login'
+ >('none');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [defaultLang, setDefaultLang] = useState('zh');
@@ -11016,7 +11811,10 @@ const SuwayomiConfigComponent = ({
Username: authMode === 'none' ? '' : username,
Password: authMode === 'none' ? '' : password,
DefaultLang: defaultLang || 'zh',
- SourceIds: sourceIds.split(',').map((item) => item.trim()).filter(Boolean),
+ SourceIds: sourceIds
+ .split(',')
+ .map((item) => item.trim())
+ .filter(Boolean),
MaxSources: Math.max(1, maxSources || 10),
});
@@ -11042,7 +11840,10 @@ const SuwayomiConfigComponent = ({
showSuccess('漫画后端配置已保存', showAlert);
await refreshConfig();
} catch (error) {
- showError(error instanceof Error ? error.message : '保存失败', showAlert);
+ showError(
+ error instanceof Error ? error.message : '保存失败',
+ showAlert
+ );
throw error;
}
});
@@ -11070,7 +11871,10 @@ const SuwayomiConfigComponent = ({
showSuccess(data.message || '连接成功', showAlert);
} catch (error) {
- showError(error instanceof Error ? error.message : '测试连接失败', showAlert);
+ showError(
+ error instanceof Error ? error.message : '测试连接失败',
+ showAlert
+ );
throw error;
}
});
@@ -11083,8 +11887,14 @@ const SuwayomiConfigComponent = ({
关于漫画展馆 / Suwayomi
- • 漫画展馆通过 Suwayomi Server 的 GraphQL 接口搜索、拉取章节与阅读页。
- • 认证仅支持 basic_auth 与 simple_login;未开启认证时请选择“无认证”。
+
+ • 漫画展馆通过 Suwayomi Server 的 GraphQL
+ 接口搜索、拉取章节与阅读页。
+
+
+ • 认证仅支持 basic_auth 与
+ simple_login;未开启认证时请选择“无认证”。
+
• 可限制默认语言、可用源白名单,以及单次搜索最多查询的源数量。
• 保存后漫画模块会优先使用这里的配置,环境变量只作为兜底。
@@ -11093,21 +11903,31 @@ const SuwayomiConfigComponent = ({
- 启用漫画展馆
- 关闭后仍保留代码,但不建议在未配置时对用户开放入口。
+
+ 启用漫画展馆
+
+
+ 关闭后仍保留代码,但不建议在未配置时对用户开放入口。
+
setEnabled(!enabled)}
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${enabled ? 'bg-blue-600' : 'bg-gray-200 dark:bg-gray-700'}`}
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
+ enabled ? 'bg-blue-600' : 'bg-gray-200 dark:bg-gray-700'
+ }`}
>
-
+
- 只填服务根地址,程序会自动拼接 /api/graphql。
+
+ 只填服务根地址,程序会自动拼接 /api/graphql。
+
-
+
{[
{ value: 'none', label: '无认证' },
@@ -11129,7 +11953,11 @@ const SuwayomiConfigComponent = ({
setAuthMode(item.value as 'none' | 'basic_auth' | 'simple_login')}
+ onClick={() =>
+ setAuthMode(
+ item.value as 'none' | 'basic_auth' | 'simple_login'
+ )
+ }
className={`rounded-lg border px-3 py-2 text-sm transition-colors ${
authMode === item.value
? 'border-blue-500 bg-blue-50 text-blue-700 dark:border-blue-400 dark:bg-blue-900/30 dark:text-blue-200'
@@ -11141,14 +11969,17 @@ const SuwayomiConfigComponent = ({
))}
- basic_auth 使用 Basic Authorization 头;simple_login 会向 /login.html 提交表单并复用返回 Cookie。
+ basic_auth 使用 Basic Authorization 头;simple_login 会向
+ /login.html 提交表单并复用返回 Cookie。
{authMode !== 'none' && (
|