feat(subscribe): 支持设置单条订阅搜索周期

This commit is contained in:
jxxghp
2026-09-08 07:07:15 +08:00
parent e11ebcadf3
commit b2b0810908
6 changed files with 114 additions and 1 deletions
+2
View File
@@ -100,6 +100,8 @@ export interface ManualScrapeOptions {
// 订阅
export interface Subscribe {
/** 独立定时搜索间隔(小时);null 或缺省跟随系统。 */
search_interval?: number | null
// 订阅ID
id: number
// 订阅名称
+44 -1
View File
@@ -78,6 +78,7 @@ const subscribeForm = ref<Subscribe>({
media_source: undefined,
media_id: undefined,
state: '',
search_interval: null,
last_update: '',
username: '',
sites: [],
@@ -90,6 +91,20 @@ const subscribeForm = ref<Subscribe>({
episode_group: '',
})
// 切换为系统周期时显式提交 null,确保覆盖已保存的独立周期。
const customSearchInterval = computed({
get: () => subscribeForm.value.search_interval != null,
set: value => {
subscribeForm.value.search_interval = value ? 24 : null
},
})
// 空输入和非整数不能保存;接口采用同样的小时范围。
const searchIntervalValid = computed(() => {
const interval = subscribeForm.value.search_interval
return interval == null || (Number.isInteger(Number(interval)) && Number(interval) >= 1 && Number(interval) <= 8760)
})
// 提示框
const $toast = useToast()
@@ -190,6 +205,7 @@ const filterRuleGroupOptions = computed(() => {
// 调用API修改订阅
async function updateSubscribeInfo() {
if (!searchIntervalValid.value) return
const displayName = getSubscribeDisplayName()
try {
await api.put<null>('subscribe/', subscribeForm.value, { feedback: 'silent' })
@@ -209,7 +225,7 @@ async function updateSubscribeInfo() {
// 设置用户设置的默认订阅规则
async function saveDefaultSubscribeConfig() {
if (!canAdmin.value) return
if (!canAdmin.value || !searchIntervalValid.value) return
const typeName = getDefaultSubscribeTypeName()
try {
@@ -523,6 +539,32 @@ onMounted(() => {
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" :md="customSearchInterval ? 6 : 12">
<VSelect
v-model="customSearchInterval"
:items="[
{ title: t('dialog.subscribeEdit.searchIntervalSystem'), value: false },
{ title: t('dialog.subscribeEdit.searchIntervalCustom'), value: true },
]"
:label="t('dialog.subscribeEdit.searchInterval')"
:hint="t('dialog.subscribeEdit.searchIntervalHint')"
persistent-hint
prepend-inner-icon="mdi-timer-outline"
/>
</VCol>
<VCol v-if="customSearchInterval" cols="12" md="6">
<VTextField
v-model.number="subscribeForm.search_interval"
type="number"
min="1"
max="8760"
step="1"
:label="t('dialog.subscribeEdit.searchIntervalHours')"
:error-messages="searchIntervalValid ? [] : [t('dialog.subscribeEdit.searchIntervalInvalid')]"
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VAutocomplete
@@ -677,6 +719,7 @@ onMounted(() => {
<VBtn
color="primary"
variant="flat"
:disabled="!searchIntervalValid"
@click=";`${props.default ? saveDefaultSubscribeConfig() : updateSubscribeInfo()}`"
prepend-icon="mdi-content-save"
class="px-5"
@@ -117,6 +117,55 @@ describe('SubscribeEditDialog', () => {
expect(screen.getByText('2 季 • 24 集')).toBeInTheDocument()
})
it('saves a custom search interval and explicitly restores the system setting', async () => {
const updated = vi.fn()
server.use(
subscribeDetailsHandler(
820,
createSubscribe({ id: 820, name: '搜索周期测试', search_interval: null, type: '电影' }),
),
updateSubscribeHandler({ success: true }, 200, updated),
)
useDialogOptions()
const user = userEvent.setup()
await renderDialog({ subid: 820 })
await screen.findByText('搜索周期测试')
await user.click(screen.getByLabelText('搜索周期'))
await user.click(await screen.findByRole('option', { name: '自定义' }))
await fireEvent.update(screen.getByLabelText('搜索间隔(小时)'), '6')
await user.click(screen.getByRole('button', { name: '保存' }))
await waitFor(() => expect(updated).toHaveBeenCalledOnce())
expect(updated.mock.calls[0][0]).toMatchObject({ search_interval: 6 })
await user.click(screen.getByLabelText('搜索周期'))
await user.click(await screen.findByRole('option', { name: '跟随系统' }))
expect(screen.queryByLabelText('搜索间隔(小时)')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: '保存' }))
await waitFor(() => expect(updated).toHaveBeenCalledTimes(2))
expect(updated.mock.calls[1][0]).toMatchObject({ search_interval: null })
})
it('loads a saved custom interval and prevents invalid values from being submitted', async () => {
const updated = vi.fn()
server.use(
subscribeDetailsHandler(
821,
createSubscribe({ id: 821, name: '周期校验测试', search_interval: 48, type: '电影' }),
),
updateSubscribeHandler({ success: true }, 200, updated),
)
useDialogOptions()
await renderDialog({ subid: 821 })
expect(await screen.findByLabelText('搜索间隔(小时)')).toHaveValue(48)
for (const value of ['', '0', '-1', '1.5', '8761']) {
await fireEvent.update(screen.getByLabelText('搜索间隔(小时)'), value)
expect(screen.getByRole('button', { name: '保存' })).toBeDisabled()
}
expect(updated).not.toHaveBeenCalled()
await fireEvent.update(screen.getByLabelText('搜索间隔(小时)'), '24')
expect(screen.getByRole('button', { name: '保存' })).toBeEnabled()
})
it('keeps movie titles free of season suffixes and skips episode groups', async () => {
const record = createSubscribe({ id: 802, media_id: '8020', name: '电影测试项', season: undefined, type: '电影' })
const episodeGroupsRequested = vi.fn()
+7
View File
@@ -4158,6 +4158,13 @@ export default {
bestVersionFull: 'Full',
},
subscribeEdit: {
searchInterval: 'Search schedule',
searchIntervalSystem: 'Use system setting',
searchIntervalCustom: 'Custom',
searchIntervalHours: 'Search interval (hours)',
searchIntervalHint:
'Requires scheduled subscription search to be enabled. Manual searches and RSS refreshes are unaffected.',
searchIntervalInvalid: 'Enter a whole number of hours from 1 to 8760',
titleDefault: 'Default Subscription Rules',
titleEdit: 'Edit Subscription',
seasonFormat: 'Season {number}',
+6
View File
@@ -4067,6 +4067,12 @@ export default {
bestVersionFull: '全集洗版',
},
subscribeEdit: {
searchInterval: '搜索周期',
searchIntervalSystem: '跟随系统',
searchIntervalCustom: '自定义',
searchIntervalHours: '搜索间隔(小时)',
searchIntervalHint: '仅在开启订阅定时搜索时生效;手动搜索和 RSS 刷新不受影响。',
searchIntervalInvalid: '请输入 18760 的整数小时数',
titleDefault: '默认订阅规则',
titleEdit: '编辑订阅',
seasonFormat: '第 {number} 季',
+6
View File
@@ -4036,6 +4036,12 @@ export default {
bestVersionFull: '全集洗版',
},
subscribeEdit: {
searchInterval: '搜尋週期',
searchIntervalSystem: '跟隨系統',
searchIntervalCustom: '自訂',
searchIntervalHours: '搜尋間隔(小時)',
searchIntervalHint: '僅在開啟訂閱定時搜尋時生效;手動搜尋與 RSS 更新不受影響。',
searchIntervalInvalid: '請輸入 18760 的整數小時數',
titleDefault: '默認訂閱規則',
titleEdit: '編輯訂閱',
seasonFormat: '第 {number} 季',