增加视频特殊源配置功能

This commit is contained in:
mtvpls
2026-06-09 15:25:22 +08:00
parent 3f9206e754
commit 65d89be93a
18 changed files with 454 additions and 26 deletions
+206 -2
View File
@@ -415,6 +415,7 @@ interface DataSource {
from: 'config' | 'custom';
proxyMode?: boolean;
weight?: number;
special?: boolean;
}
// 直播源数据类型
@@ -2069,7 +2070,7 @@ const UserConfig = ({
selectedDeviceUsername &&
createPortal(
<div
className='fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4'
className='fixed inset-0 bg-black bg-opacity-50 z-[10002] flex items-center justify-center p-4'
onClick={() => {
setShowUserDevicesModal(false);
setSelectedDeviceUsername(null);
@@ -6320,6 +6321,8 @@ const VideoSourceConfig = ({
// 有效性检测相关状态
const [showValidationModal, setShowValidationModal] = useState(false);
const [showWeightModal, setShowWeightModal] = useState(false);
const [showSpecialSourcesModal, setShowSpecialSourcesModal] = useState(false);
const [specialSourceDraftApis, setSpecialSourceDraftApis] = useState<string[]>([]);
const [weightDraftSources, setWeightDraftSources] = useState<DataSource[]>(
[]
);
@@ -6453,6 +6456,71 @@ const VideoSourceConfig = ({
});
};
const openSpecialSourcesModal = () => {
setSpecialSourceDraftApis(config?.SpecialSourceApis || []);
setShowSpecialSourcesModal(true);
};
const closeSpecialSourcesModal = () => {
setShowSpecialSourcesModal(false);
setSpecialSourceDraftApis([]);
};
const doSaveSpecialSources = async () => {
await withLoading('saveSpecialSources', async () => {
await callSourceApi({
action: 'set_special_sources',
keys: specialSourceDraftApis,
});
closeSpecialSourcesModal();
}).catch(() => {
console.error('操作失败', 'set_special_sources');
});
};
const handleSaveSpecialSources = async () => {
const enabledSourceKeys =
config?.SourceConfig?.filter((source) => !source.disabled).map(
(source) => source.key
) || [];
const selectedSet = new Set(specialSourceDraftApis);
const selectedAllEnabledSources =
enabledSourceKeys.length > 0 &&
enabledSourceKeys.every((key) => selectedSet.has(key));
if (selectedAllEnabledSources) {
setConfirmModal({
isOpen: true,
title: '确认设置特殊源',
message:
'你已将全部启用的视频源设置为特殊源,未开启特殊源开关的用户可能无法使用搜索。确定要继续保存吗?',
onConfirm: async () => {
await doSaveSpecialSources();
setConfirmModal({
isOpen: false,
title: '',
message: '',
onConfirm: () => {},
onCancel: () => {},
});
},
onCancel: () => {
setConfirmModal({
isOpen: false,
title: '',
message: '',
onConfirm: () => {},
onCancel: () => {},
});
},
});
return;
}
await doSaveSpecialSources();
};
const handleUpdateWeight = (key: string, weight: number) => {
// 先乐观更新本地状态
setSources((prev) =>
@@ -7273,6 +7341,19 @@ const VideoSourceConfig = ({
</>
)}
<div className='flex items-center gap-2 overflow-x-auto whitespace-nowrap order-1 sm:order-2'>
<button
onClick={openSpecialSourcesModal}
className={`${buttonStyles.secondary} flex shrink-0 items-center gap-1.5 whitespace-nowrap`}
title='批量选择哪些视频源属于特殊源'
>
<Settings size={14} />
<span></span>
{(config?.SpecialSourceApis?.length || 0) > 0 && (
<span className='rounded-full bg-rose-100 px-1.5 py-0.5 text-[10px] font-semibold text-rose-700 dark:bg-rose-900/30 dark:text-rose-300'>
{config?.SpecialSourceApis?.length}
</span>
)}
</button>
<button
onClick={openWeightModal}
className={`${buttonStyles.secondary} flex shrink-0 items-center gap-1.5 whitespace-nowrap`}
@@ -7423,6 +7504,129 @@ const VideoSourceConfig = ({
</table>
</div>
{showSpecialSourcesModal &&
createPortal(
<div
className='fixed inset-0 z-[10000] flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm'
onClick={closeSpecialSourcesModal}
>
<div
className='flex max-h-[84vh] w-full max-w-4xl flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-800'
onClick={(e) => e.stopPropagation()}
>
<div className='flex items-start justify-between gap-4 border-b border-gray-200 px-6 py-5 dark:border-gray-700'>
<div>
<h3 className='text-xl font-semibold text-gray-900 dark:text-gray-100'>
</h3>
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
访 /special Web
</p>
</div>
<button
onClick={closeSpecialSourcesModal}
className='text-2xl leading-none text-gray-400 transition-colors hover:text-gray-600 dark:hover:text-gray-300'
aria-label='关闭特殊源设置弹窗'
>
×
</button>
</div>
<div className='min-h-0 flex-1 overflow-y-auto px-6 py-5'>
<div className='mb-5 rounded-lg border border-rose-200 bg-rose-50 p-4 dark:border-rose-800 dark:bg-rose-900/20'>
<div className='text-sm font-medium text-rose-800 dark:text-rose-300'>
</div>
<p className='mt-1 text-sm text-rose-700 dark:text-rose-400'>
TVBoxOrionTVWebTV 使
</p>
</div>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3'>
{config?.SourceConfig?.map((source) => (
<label
key={source.key}
className='flex cursor-pointer items-center space-x-3 rounded-lg border border-gray-200 p-3 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-900/50'
>
<input
type='checkbox'
checked={specialSourceDraftApis.includes(source.key)}
onChange={(e) => {
if (e.target.checked) {
setSpecialSourceDraftApis((prev) =>
prev.includes(source.key) ? prev : [...prev, source.key]
);
} else {
setSpecialSourceDraftApis((prev) =>
prev.filter((api) => api !== source.key)
);
}
}}
className='rounded border-gray-300 text-rose-600 focus:ring-rose-500 dark:border-gray-600 dark:bg-gray-700'
/>
<div className='min-w-0 flex-1'>
<div className='truncate text-sm font-medium text-gray-900 dark:text-gray-100'>
{source.name}
</div>
<div className='truncate text-xs text-gray-500 dark:text-gray-400'>
{source.key}
</div>
</div>
</label>
))}
</div>
</div>
<div className='flex flex-wrap items-center justify-between gap-3 border-t border-gray-200 bg-gray-50 px-6 py-4 dark:border-gray-700 dark:bg-gray-900/30'>
<div className='flex flex-wrap gap-2'>
<button
onClick={() => setSpecialSourceDraftApis([])}
className={buttonStyles.quickAction}
>
</button>
<button
onClick={() => {
const allApis =
config?.SourceConfig?.filter((source) => !source.disabled).map(
(source) => source.key
) || [];
setSpecialSourceDraftApis(allApis);
}}
className={buttonStyles.quickAction}
>
</button>
</div>
<div className='flex items-center gap-3'>
<span className='text-sm text-gray-600 dark:text-gray-400'>
<span className='font-medium text-rose-600 dark:text-rose-400'>
{specialSourceDraftApis.length}
</span>
</span>
<button onClick={closeSpecialSourcesModal} className={buttonStyles.secondary}>
</button>
<button
onClick={handleSaveSpecialSources}
disabled={isLoading('saveSpecialSources')}
className={`px-4 py-2 ${
isLoading('saveSpecialSources')
? buttonStyles.disabled
: buttonStyles.success
}`}
>
{isLoading('saveSpecialSources') ? '保存中...' : '保存'}
</button>
</div>
</div>
</div>
</div>,
document.body
)}
{showWeightModal &&
createPortal(
<>
@@ -7636,7 +7840,7 @@ const VideoSourceConfig = ({
{confirmModal.isOpen &&
createPortal(
<div
className='fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4'
className='fixed inset-0 bg-black bg-opacity-50 z-[10020] flex items-center justify-center p-4'
onClick={confirmModal.onCancel}
>
<div