批量操作emby源

This commit is contained in:
mtvpls
2026-01-10 11:17:10 +08:00
parent 00a5055817
commit a74911d655

View File

@@ -3445,6 +3445,7 @@ const EmbyConfigComponent = ({
const [sources, setSources] = useState<any[]>([]);
const [editingSource, setEditingSource] = useState<any | null>(null);
const [showAddForm, setShowAddForm] = useState(false);
const [selectedSources, setSelectedSources] = useState<Set<string>>(new Set());
// 表单状态
const [formData, setFormData] = useState({
@@ -3694,6 +3695,81 @@ const EmbyConfigComponent = ({
});
};
// 批量启用
const handleBatchEnable = async () => {
if (selectedSources.size === 0) return;
await withLoading('batchEnableEmby', async () => {
try {
const newSources = sources.map(s =>
selectedSources.has(s.key) ? { ...s, enabled: true } : s
);
const response = await fetch('/api/admin/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...config, EmbyConfig: { Sources: newSources } }),
});
if (!response.ok) throw new Error('批量启用失败');
await refreshConfig();
setSelectedSources(new Set());
showSuccess(`已启用 ${selectedSources.size} 个源`, showAlert);
} catch (error) {
showError(error instanceof Error ? error.message : '批量启用失败', showAlert);
}
});
};
// 批量禁用
const handleBatchDisable = async () => {
if (selectedSources.size === 0) return;
await withLoading('batchDisableEmby', async () => {
try {
const newSources = sources.map(s =>
selectedSources.has(s.key) ? { ...s, enabled: false } : s
);
const response = await fetch('/api/admin/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...config, EmbyConfig: { Sources: newSources } }),
});
if (!response.ok) throw new Error('批量禁用失败');
await refreshConfig();
setSelectedSources(new Set());
showSuccess(`已禁用 ${selectedSources.size} 个源`, showAlert);
} catch (error) {
showError(error instanceof Error ? error.message : '批量禁用失败', showAlert);
}
});
};
// 批量删除
const handleBatchDelete = async () => {
if (selectedSources.size === 0) return;
showAlert({
type: 'warning',
title: '确认批量删除',
message: `确定要删除选中的 ${selectedSources.size} 个源吗?此操作不可恢复。`,
showConfirm: true,
onConfirm: async () => {
await withLoading('batchDeleteEmby', async () => {
try {
const newSources = sources.filter(s => !selectedSources.has(s.key));
const response = await fetch('/api/admin/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...config, EmbyConfig: { Sources: newSources } }),
});
if (!response.ok) throw new Error('批量删除失败');
await refreshConfig();
setSelectedSources(new Set());
showSuccess(`已删除 ${selectedSources.size} 个源`, showAlert);
} catch (error) {
showError(error instanceof Error ? error.message : '批量删除失败', showAlert);
}
});
},
});
};
return (
<div className='space-y-6'>
<AlertModal
@@ -3713,14 +3789,51 @@ const EmbyConfigComponent = ({
<h3 className='text-lg font-medium text-gray-900 dark:text-gray-100'>
Emby ({sources.length})
</h3>
<button
onClick={handleAdd}
className={buttonStyles.success}
>
</button>
<div className='flex gap-2'>
<button
onClick={handleAdd}
className={buttonStyles.success}
>
</button>
</div>
</div>
{selectedSources.size > 0 && (
<div className='flex items-center gap-2 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg'>
<span className='text-sm text-gray-700 dark:text-gray-300'>
{selectedSources.size}
</span>
<button
onClick={handleBatchEnable}
disabled={isLoading('batchEnableEmby')}
className={buttonStyles.successSmall}
>
</button>
<button
onClick={handleBatchDisable}
disabled={isLoading('batchDisableEmby')}
className={buttonStyles.warningSmall}
>
</button>
<button
onClick={handleBatchDelete}
disabled={isLoading('batchDeleteEmby')}
className={buttonStyles.dangerSmall}
>
</button>
<button
onClick={() => setSelectedSources(new Set())}
className={buttonStyles.secondarySmall}
>
</button>
</div>
)}
{sources.length === 0 ? (
<div className='text-center py-8 text-gray-500 dark:text-gray-400'>
Emby源"添加新源"
@@ -3732,37 +3845,53 @@ const EmbyConfigComponent = ({
className='border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800'
>
<div className='flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3'>
<div className='flex-1'>
<div className='flex items-center gap-3 flex-wrap'>
<h4 className='text-base font-medium text-gray-900 dark:text-gray-100'>
{source.name}
</h4>
{source.isDefault && (
<span className='px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200 rounded'>
<div className='flex items-center gap-3 flex-1'>
<input
type='checkbox'
checked={selectedSources.has(source.key)}
onChange={(e) => {
const newSelected = new Set(selectedSources);
if (e.target.checked) {
newSelected.add(source.key);
} else {
newSelected.delete(source.key);
}
setSelectedSources(newSelected);
}}
className='w-4 h-4 text-blue-600 rounded border-gray-300 dark:border-gray-600'
/>
<div className='flex-1'>
<div className='flex items-center gap-3 flex-wrap'>
<h4 className='text-base font-medium text-gray-900 dark:text-gray-100'>
{source.name}
</h4>
{source.isDefault && (
<span className='px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200 rounded'>
</span>
)}
<span
className={`px-2 py-0.5 text-xs font-medium rounded ${
source.enabled
? 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200'
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
}`}
>
{source.enabled ? '已启用' : '已禁用'}
</span>
)}
<span
className={`px-2 py-0.5 text-xs font-medium rounded ${
source.enabled
? 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200'
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
}`}
>
{source.enabled ? '已启用' : '已禁用'}
</span>
</div>
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
: {source.key}
</p>
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
: {source.ServerURL}
</p>
{source.UserId && (
</div>
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
ID: {source.UserId}
: {source.key}
</p>
)}
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
: {source.ServerURL}
</p>
{source.UserId && (
<p className='mt-1 text-sm text-gray-600 dark:text-gray-400'>
ID: {source.UserId}
</p>
)}
</div>
</div>
<div className='flex gap-2 flex-wrap sm:flex-nowrap'>
<button