增加视频特殊源配置功能
This commit is contained in:
+206
-2
@@ -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'>
|
||||
这里维护的是特殊源列表,不是用户权限;TVBox、OrionTV、WebTV 始终不会使用这些特殊源。
|
||||
</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
|
||||
|
||||
Reference in New Issue
Block a user