9393 </div >
9494 </div >
9595
96+ <!-- 热门视频清理设置(仅清理接口显示) -->
97+ <div v-if =" isPopularCleanupEndpoint" class =" bg-white dark:bg-gray-800 rounded-lg p-1.5 border border-gray-200 dark:border-gray-700" >
98+ <h4 class =" text-xs font-semibold text-gray-600 uppercase tracking-wider mb-1 flex items-center" >
99+ <svg class =" w-3 h-3 mr-1 text-[#fb7299]" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" >
100+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 10c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z" />
101+ </svg >
102+ 热门视频清理
103+ </h4 >
104+ <div class =" grid grid-cols-1 md:grid-cols-2 gap-2" >
105+ <div class =" md:col-span-2" >
106+ <label for =" popularCleanupYear" class =" block text-xs font-medium text-gray-700 dark:text-gray-300 mb-0.5" >清理年份(可选)</label >
107+ <select
108+ id =" popularCleanupYear"
109+ v-model =" popularCleanupYear"
110+ class =" block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-[#fb7299] focus:ring-[#fb7299] text-xs py-1 disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed dark:bg-gray-700 dark:text-gray-100 disabled:dark:bg-gray-800 disabled:dark:text-gray-500"
111+ :disabled =" popularCleanupYearsLoading"
112+ >
113+ <option :value =" null" >全部年份(不传 year)</option >
114+ <option
115+ v-for =" year in popularCleanupYearOptions"
116+ :key =" year"
117+ :value =" year"
118+ >
119+ {{ year }}
120+ </option >
121+ </select >
122+ <div v-if =" popularCleanupYearsLoading" class =" text-xs text-gray-500 dark:text-gray-400 mt-0.5" >
123+ 正在获取可用年份...
124+ </div >
125+ <div v-else-if =" popularCleanupYearsMessage" class =" text-xs text-gray-500 dark:text-gray-400 mt-0.5" >
126+ {{ popularCleanupYearsMessage }}
127+ </div >
128+ <div v-else-if =" popularCleanupDefaultYear" class =" text-xs text-gray-500 dark:text-gray-400 mt-0.5" >
129+ 建议默认年份:{{ popularCleanupDefaultYear }}
130+ </div >
131+ </div >
132+ </div >
133+ </div >
134+
96135 <!-- 调度设置 -->
97136 <div class =" bg-white dark:bg-gray-800 rounded-lg p-1.5 border border-gray-200 dark:border-gray-700" >
98137 <h4 class =" text-xs font-semibold text-gray-600 uppercase tracking-wider mb-1 flex items-center" >
@@ -268,7 +307,8 @@ import {
268307 updateSchedulerTask ,
269308 getSchedulerTaskDetail ,
270309 getAvailableEndpoints ,
271- addSubTask
310+ addSubTask ,
311+ getPopularCleanupYears
272312} from ' ../../../api/api'
273313
274314const props = defineProps ({
@@ -320,6 +360,34 @@ const form = reactive({
320360const paramsJson = ref (' {}' )
321361const paramsError = ref (' ' )
322362
363+ const POPULAR_CLEANUP_ENDPOINT_PATH = ' /bilibili/popular/cleanup'
364+
365+ const parseEndpointUrlParts = (endpoint ) => {
366+ const raw = (endpoint || ' ' ).trim ()
367+ if (! raw) return { path: ' ' , searchParams: new URLSearchParams () }
368+
369+ try {
370+ const url = new URL (raw, ' http://placeholder' )
371+ return { path: url .pathname || ' ' , searchParams: url .searchParams || new URLSearchParams () }
372+ } catch {
373+ const [pathPart , queryPart ] = raw .split (' ?' )
374+ return { path: (pathPart || raw).trim (), searchParams: new URLSearchParams (queryPart || ' ' ) }
375+ }
376+ }
377+
378+ const normalizeEndpointPath = (path ) => {
379+ let normalized = (path || ' ' ).trim ()
380+ if (! normalized) return ' '
381+ if (! normalized .startsWith (' /' )) normalized = ` /${ normalized} `
382+ normalized = normalized .replace (/ \/ + $ / , ' ' )
383+ return normalized || ' /'
384+ }
385+
386+ const isPopularCleanupEndpointPath = (path ) => {
387+ const normalized = normalizeEndpointPath (path)
388+ return normalized === POPULAR_CLEANUP_ENDPOINT_PATH || normalized .endsWith (POPULAR_CLEANUP_ENDPOINT_PATH )
389+ }
390+
323391// 可用的API端点
324392const availableEndpoints = ref ([])
325393
@@ -331,6 +399,122 @@ const expandedTags = reactive({})
331399const methodFilter = ref (' ALL' )
332400const selectedEndpoint = ref (' ' )
333401
402+ const popularCleanupYears = ref ([])
403+ const popularCleanupDefaultYear = ref (null )
404+ const popularCleanupYearsMessage = ref (' ' )
405+ const popularCleanupYearsLoading = ref (false )
406+ const popularCleanupYearTouched = ref (false )
407+ const cachedPopularCleanupYear = ref (undefined )
408+
409+ const popularCleanupEndpointParts = computed (() => parseEndpointUrlParts (form .endpoint ))
410+
411+ const isPopularCleanupEndpoint = computed (() => {
412+ return isPopularCleanupEndpointPath (popularCleanupEndpointParts .value .path )
413+ })
414+
415+ const popularCleanupYearFromEndpointQuery = computed (() => {
416+ const year = popularCleanupEndpointParts .value .searchParams .get (' year' )
417+ if (! year) return null
418+ const normalized = Number .parseInt (year, 10 )
419+ return Number .isFinite (normalized) ? normalized : null
420+ })
421+
422+ const popularCleanupYear = computed ({
423+ get : () => (Object .prototype .hasOwnProperty .call (form .params , ' year' ) ? form .params .year : null ),
424+ set : (year ) => {
425+ popularCleanupYearTouched .value = true
426+ if (year === null || year === undefined || year === ' ' ) {
427+ if (Object .prototype .hasOwnProperty .call (form .params , ' year' )) {
428+ delete form .params .year
429+ }
430+ return
431+ }
432+ form .params .year = year
433+ }
434+ })
435+
436+ const popularCleanupYearOptions = computed (() => {
437+ const years = Array .isArray (popularCleanupYears .value ) ? [... popularCleanupYears .value ] : []
438+ const currentYear = form .params ? .year
439+ const normalizedCurrentYear = typeof currentYear === ' string' ? Number .parseInt (currentYear, 10 ) : currentYear
440+
441+ if (Number .isFinite (normalizedCurrentYear) && ! years .includes (normalizedCurrentYear)) {
442+ years .push (normalizedCurrentYear)
443+ }
444+
445+ return years
446+ .filter ((y ) => Number .isFinite (y))
447+ .sort ((a , b ) => b - a)
448+ })
449+
450+ const applyPopularCleanupYearFromEndpointQuery = () => {
451+ if (! isPopularCleanupEndpoint .value ) return
452+ const yearFromQuery = popularCleanupYearFromEndpointQuery .value
453+ if (yearFromQuery === null || yearFromQuery === undefined ) return
454+ if (! Object .prototype .hasOwnProperty .call (form .params , ' year' )) {
455+ form .params .year = yearFromQuery
456+ }
457+ }
458+
459+ const normalizePopularCleanupYearParam = () => {
460+ if (! form .params || typeof form .params !== ' object' ) return
461+ if (! Object .prototype .hasOwnProperty .call (form .params , ' year' )) return
462+
463+ const year = form .params .year
464+ if (year === null || year === undefined || year === ' ' ) {
465+ delete form .params .year
466+ return
467+ }
468+
469+ if (typeof year === ' string' ) {
470+ const normalized = Number .parseInt (year, 10 )
471+ if (Number .isFinite (normalized)) {
472+ form .params .year = normalized
473+ } else {
474+ delete form .params .year
475+ }
476+ }
477+ }
478+
479+ const fetchPopularCleanupYears = async () => {
480+ if (popularCleanupYearsLoading .value ) return
481+
482+ popularCleanupYearsLoading .value = true
483+ popularCleanupYearsMessage .value = ' '
484+ popularCleanupDefaultYear .value = null
485+
486+ try {
487+ const response = await getPopularCleanupYears ()
488+ if (response .data && response .data .status === ' success' ) {
489+ popularCleanupYears .value = Array .isArray (response .data .data ) ? response .data .data : []
490+ popularCleanupDefaultYear .value = response .data .default_year ?? null
491+ popularCleanupYearsMessage .value = response .data .message || ' '
492+
493+ // 新建任务:当用户尚未手动选择且未设置 year 时,自动使用后端建议默认年份
494+ if (
495+ isPopularCleanupEndpoint .value &&
496+ ! props .isEditing &&
497+ ! popularCleanupYearTouched .value &&
498+ ! Object .prototype .hasOwnProperty .call (form .params , ' year' ) &&
499+ popularCleanupDefaultYear .value !== null &&
500+ popularCleanupDefaultYear .value !== undefined
501+ ) {
502+ form .params .year = popularCleanupDefaultYear .value
503+ }
504+ } else {
505+ popularCleanupYears .value = []
506+ popularCleanupYearsMessage .value = response .data ? .message || ' 获取年份列表失败'
507+ popularCleanupDefaultYear .value = null
508+ }
509+ } catch (error) {
510+ popularCleanupYears .value = []
511+ popularCleanupYearsMessage .value = ' 获取年份列表出错: ' + (error .message || ' 未知错误' )
512+ popularCleanupDefaultYear .value = null
513+ } finally {
514+ popularCleanupYearsLoading .value = false
515+ }
516+ }
517+
334518// 按tag对API进行分组
335519const groupedEndpoints = computed (() => {
336520 const groups = {}
@@ -478,6 +662,8 @@ const resetForm = () => {
478662 selectedEndpoint .value = ' '
479663 paramsJson .value = ' {}'
480664 paramsError .value = ' '
665+ popularCleanupYearTouched .value = false
666+ cachedPopularCleanupYear .value = undefined
481667 showApiSelector .value = false
482668 showDependencySelector .value = false
483669 apiSearchQuery .value = ' '
@@ -592,6 +778,11 @@ const loadTaskDetail = async (taskId) => {
592778 form .endpoint = taskInfo .config .endpoint
593779 form .method = taskInfo .config .method
594780 form .params = taskInfo .config .params || {}
781+ if (isPopularCleanupEndpointPath (taskInfo .config .endpoint )) {
782+ popularCleanupYearTouched .value = true
783+ applyPopularCleanupYearFromEndpointQuery ()
784+ normalizePopularCleanupYearParam ()
785+ }
595786 form .schedule_type = taskInfo .config .schedule_type
596787 form .schedule_time = taskInfo .config .schedule_time
597788 form .enabled = taskInfo .config .enabled
@@ -630,6 +821,37 @@ const loadTaskDetail = async (taskId) => {
630821 }
631822}
632823
824+ watch (
825+ () => [props .show , isPopularCleanupEndpoint .value ],
826+ async ([show , isCleanup ]) => {
827+ if (! show || ! isCleanup) return
828+ applyPopularCleanupYearFromEndpointQuery ()
829+ normalizePopularCleanupYearParam ()
830+ await fetchPopularCleanupYears ()
831+ }
832+ )
833+
834+ watch (isPopularCleanupEndpoint, (isCleanup , wasCleanup ) => {
835+ if (wasCleanup && ! isCleanup) {
836+ cachedPopularCleanupYear .value = popularCleanupYear .value
837+ if (Object .prototype .hasOwnProperty .call (form .params , ' year' )) {
838+ delete form .params .year
839+ }
840+ return
841+ }
842+
843+ if (! wasCleanup && isCleanup) {
844+ normalizePopularCleanupYearParam ()
845+ if (
846+ cachedPopularCleanupYear .value !== undefined &&
847+ ! Object .prototype .hasOwnProperty .call (form .params , ' year' ) &&
848+ cachedPopularCleanupYear .value !== null
849+ ) {
850+ form .params .year = cachedPopularCleanupYear .value
851+ }
852+ }
853+ })
854+
633855// 获取可用的API端点
634856const fetchAvailableEndpoints = async () => {
635857 try {
0 commit comments