mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-09 01:36:45 +08:00
feat(subscribe): 支持设置单条订阅搜索周期
This commit is contained in:
@@ -100,6 +100,8 @@ export interface ManualScrapeOptions {
|
||||
|
||||
// 订阅
|
||||
export interface Subscribe {
|
||||
/** 独立定时搜索间隔(小时);null 或缺省跟随系统。 */
|
||||
search_interval?: number | null
|
||||
// 订阅ID
|
||||
id: number
|
||||
// 订阅名称
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -4067,6 +4067,12 @@ export default {
|
||||
bestVersionFull: '全集洗版',
|
||||
},
|
||||
subscribeEdit: {
|
||||
searchInterval: '搜索周期',
|
||||
searchIntervalSystem: '跟随系统',
|
||||
searchIntervalCustom: '自定义',
|
||||
searchIntervalHours: '搜索间隔(小时)',
|
||||
searchIntervalHint: '仅在开启订阅定时搜索时生效;手动搜索和 RSS 刷新不受影响。',
|
||||
searchIntervalInvalid: '请输入 1–8760 的整数小时数',
|
||||
titleDefault: '默认订阅规则',
|
||||
titleEdit: '编辑订阅',
|
||||
seasonFormat: '第 {number} 季',
|
||||
|
||||
@@ -4036,6 +4036,12 @@ export default {
|
||||
bestVersionFull: '全集洗版',
|
||||
},
|
||||
subscribeEdit: {
|
||||
searchInterval: '搜尋週期',
|
||||
searchIntervalSystem: '跟隨系統',
|
||||
searchIntervalCustom: '自訂',
|
||||
searchIntervalHours: '搜尋間隔(小時)',
|
||||
searchIntervalHint: '僅在開啟訂閱定時搜尋時生效;手動搜尋與 RSS 更新不受影響。',
|
||||
searchIntervalInvalid: '請輸入 1–8760 的整數小時數',
|
||||
titleDefault: '默認訂閱規則',
|
||||
titleEdit: '編輯訂閱',
|
||||
seasonFormat: '第 {number} 季',
|
||||
|
||||
Reference in New Issue
Block a user