From b4be18e71f777f3c653e5f6529245d1a9ea9a8af Mon Sep 17 00:00:00 2001 From: zangse Date: Thu, 23 Jul 2026 06:04:55 +0800 Subject: [PATCH] feat(calendar): add movie/TV series filter to subscription calendar (#575) --- src/locales/en-US.ts | 4 + src/locales/zh-CN.ts | 4 + src/locales/zh-TW.ts | 4 + src/views/subscribe/FullCalendarView.vue | 104 +++++++++++++++++- .../__tests__/FullCalendarView.spec.ts | 83 ++++++++++++++ 5 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 5a14e4e9..94258181 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -1314,6 +1314,10 @@ export default { movie: 'Movie', imageLoadFailed: 'Failed', noMatchingEvents: 'No calendar items match the current filters', + mediaTypeFilterTitle: 'Media type filter', + filterAll: 'All', + filterMovie: 'Movie', + filterTv: 'TV Series', }, storage: { name: 'Name', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 02e205c0..d98f8f1d 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1307,6 +1307,10 @@ export default { movie: '电影', imageLoadFailed: '加载失败', noMatchingEvents: '暂无符合筛选条件的日历内容', + mediaTypeFilterTitle: '媒体类型筛选', + filterAll: '全部', + filterMovie: '电影', + filterTv: '电视剧', }, storage: { name: '名称', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 90c1417b..c549f28b 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1305,6 +1305,10 @@ export default { movie: '電影', imageLoadFailed: '載入失敗', noMatchingEvents: '暫無符合篩選條件的日曆內容', + mediaTypeFilterTitle: '媒體類型篩選', + filterAll: '全部', + filterMovie: '電影', + filterTv: '電視劇', }, storage: { name: '名稱', diff --git a/src/views/subscribe/FullCalendarView.vue b/src/views/subscribe/FullCalendarView.vue index a8785e43..39f57fb2 100644 --- a/src/views/subscribe/FullCalendarView.vue +++ b/src/views/subscribe/FullCalendarView.vue @@ -145,6 +145,28 @@ const mobileSelectedFilterValue = ref(ALL_MOBILE_FILTER_VALUE) // 移动端默认隐藏已经过期的日历项。 const mobileHideExpired = ref(true) +// 媒体类型筛选取值。 +type MediaTypeFilterValue = 'all' | 'movie' | 'tv' + +// 当前媒体类型筛选值,桌面端和移动端共用。 +const mediaTypeFilter = ref('all') + +// 判断日历事件是否命中当前媒体类型筛选。 +function matchesMediaTypeFilter(event: CalendarEventInfo) { + if (mediaTypeFilter.value === 'all') return true + + const isMovie = event.mediaType === '电影' + + return mediaTypeFilter.value === 'movie' ? isMovie : !isMovie +} + +// 媒体类型筛选选项,桌面端和移动端共用。 +const mediaTypeFilterOptions = computed<{ label: string; value: MediaTypeFilterValue }[]>(() => [ + { label: t('calendar.filterAll'), value: 'all' }, + { label: t('calendar.filterMovie'), value: 'movie' }, + { label: t('calendar.filterTv'), value: 'tv' }, +]) + // 移动端剧集筛选项。 const mobileSeriesFilterOptions = computed(() => { const optionMap = new Map() @@ -178,6 +200,10 @@ const mobileSeriesFilterOptions = computed(() => { // 移动端筛选后的日历事件。 const mobileFilteredCalendarEvents = computed(() => { return rawCalendarEvents.value.filter(event => { + if (!matchesMediaTypeFilter(event)) { + return false + } + if (mobileSelectedFilterValue.value !== ALL_MOBILE_FILTER_VALUE && event.title !== mobileSelectedFilterValue.value) { return false } @@ -219,6 +245,13 @@ const mobileCalendarDayGroups = computed(() => { }) }) +watch(mediaTypeFilter, () => { + // 切换筛选前先收起所有已展开日期,避免 expandCalendarDay 直接写入 FullCalendar + // 内部事件的 visibleEvents 残留旧筛选条件下的展开内容。 + expandedDateKeys.value = new Set() + renderVisibleCalendarEvents() +}) + watch(mobileSeriesFilterOptions, options => { if ( mobileSelectedFilterValue.value !== ALL_MOBILE_FILTER_VALUE && @@ -282,7 +315,7 @@ function normalizeCalendarEventOrder(events: CalendarEventInfo[]) { function renderVisibleCalendarEvents() { const groupedEvents = new Map() - rawCalendarEvents.value.forEach(event => { + rawCalendarEvents.value.filter(matchesMediaTypeFilter).forEach(event => { const dateKey = getDateKey(event.start) if (!dateKey) return @@ -297,7 +330,9 @@ function renderVisibleCalendarEvents() { // 展开指定日期在桌面日历中的折叠事件。 function expandCalendarDay(dateKey: string) { const currentScrollY = window.scrollY - const events = rawCalendarEvents.value.filter(event => getDateKey(event.start) === dateKey) + const events = rawCalendarEvents.value + .filter(matchesMediaTypeFilter) + .filter(event => getDateKey(event.start) === dateKey) const calendarApi = calendarRef.value?.getApi() expandedDateKeys.value = new Set(expandedDateKeys.value).add(dateKey) @@ -667,6 +702,20 @@ onActivated(() => {