Skip to content

Commit ac8eb56

Browse files
committed
feat(subscribecal): 优化时间线事件处理逻辑
重构了时间线事件的获取与格式化逻辑,使用 Date 对象替代字符串处理时间, 提升时间解析准确性与代码可维护性。同时修复时间显示和排序逻辑, 确保事件按本地时间正确展示和排序。 统一提取时间解析函数至 Dashboard 组件,避免重复定义, 并增强时间格式校验与错误处理能力。
1 parent add96f0 commit ac8eb56

File tree

4 files changed

+107
-82
lines changed

4 files changed

+107
-82
lines changed

frontend/subscribecal/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "moviepilot-plugin-subscribecal",
33
"private": true,
4-
"version": "1.0.1",
4+
"version": "1.1.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

frontend/subscribecal/src/components/Dashboard.vue

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,70 +24,123 @@ const timeLineGroups = reactive<TimeLineGroup[]>([])
2424
2525
// 组件状态
2626
const loading = ref<boolean>(true)
27-
const userScrolled = ref(false); // 是否发生过用户滑动
28-
let fixedBaseIndex = -1; // 固定的 baseIndex,仅在用户滑动后生效
27+
const userScrolled = ref(false) // 是否发生过用户滑动
28+
let fixedBaseIndex = -1 // 固定的 baseIndex,仅在用户滑动后生效
2929
3030
// 当前已加载的天数范围
3131
const loadedRange = reactive({
3232
before: 0, // 已加载的过去天数
3333
after: 0, // 已加载的未来天数
34-
});
34+
})
3535
36-
async function fetchTimeLineGroups(beforeDays, afterDays) {
36+
async function fetchTimeLineGroups(beforeDays: number, afterDays: number) {
3737
try {
38-
const res: Object = await props.api.get(`plugin/SubscribeCal/grouped_events`, {
38+
const res: any[] = await props.api.get(`plugin/SubscribeCal/grouped_events`, {
3939
params: { before_days: beforeDays, after_days: afterDays }
40-
});
41-
42-
// 提取并排序
43-
const groups = Object.entries(res)
44-
.map(([date, items]) => ({
45-
date,
46-
items
47-
}))
48-
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
49-
50-
// 使用Map进行去重,保证每个日期只保留一条数据
51-
const uniqueGroups = new Map();
40+
})
41+
42+
// 按日期分组
43+
const grouped = new Map<string, TimeLineGroup>()
44+
45+
res.forEach(item => {
46+
// 解析UTC时间
47+
const startDate = parseUTCDateTime(item.dtstart)
48+
49+
if (startDate) {
50+
const localDateStr = getLocalISODateString(startDate)
51+
const endDate = parseUTCDateTime(item.dtend)
52+
53+
if (endDate) item.dtend = endDate
54+
// 格式化项目,使用解析后的Date对象
55+
const formattedItem = {
56+
...item,
57+
// 使用本地时间
58+
dtstart: startDate,
59+
}
60+
61+
if (!grouped.has(localDateStr)) {
62+
grouped.set(localDateStr, {
63+
date: localDateStr,
64+
items: []
65+
})
66+
}
67+
68+
grouped.get(localDateStr)!.items.push(formattedItem)
69+
}
70+
})
71+
72+
// 转换为数组并排序
73+
const groups = Array.from(grouped.values())
74+
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
75+
76+
// 去重
77+
const uniqueGroups = new Map<string, TimeLineGroup>()
5278
groups.forEach(group => {
5379
if (!uniqueGroups.has(group.date)) {
54-
uniqueGroups.set(group.date, group);
80+
uniqueGroups.set(group.date, group)
5581
}
56-
});
82+
})
5783
58-
const filteredGroups = Array.from(uniqueGroups.values());
84+
const filteredGroups = Array.from(uniqueGroups.values())
5985
6086
if (beforeDays > loadedRange.before) {
6187
// 添加过往数据
62-
timeLineGroups.unshift(...filteredGroups);
63-
loadedRange.before = beforeDays;
88+
timeLineGroups.unshift(...filteredGroups)
89+
loadedRange.before = beforeDays
6490
}
6591
6692
if (afterDays > loadedRange.after) {
6793
// 添加未来数据
68-
timeLineGroups.push(...filteredGroups);
69-
loadedRange.after = afterDays;
94+
timeLineGroups.push(...filteredGroups)
95+
loadedRange.after = afterDays
7096
}
7197
} catch (error) {
72-
console.error(error);
98+
console.error(error)
99+
}
100+
}
101+
102+
/**
103+
* 将 %Y%m%dT%H%M%SZ 格式的时间字符串转为 Date 对象(UTC 时间)
104+
* @param dateStr 输入格式如 '20250405T160000Z'
105+
* @returns {Date|null} 解析后的 本地时间 Date 对象,失败返回 null
106+
*/
107+
const parseUTCDateTime = (dateStr: string): Date | null => {
108+
// 简单校验格式是否符合 YYYYMMDDTHHMMSSZ
109+
const regex = /^\d{8}T\d{6}Z$/
110+
if (!regex.test(dateStr)) {
111+
console.warn(`Invalid date format: ${dateStr}`)
112+
return null
73113
}
114+
115+
const year = parseInt(dateStr.slice(0, 4), 10)
116+
const month = parseInt(dateStr.slice(4, 6), 10) - 1 // 月份从 0 开始
117+
const day = parseInt(dateStr.slice(6, 8), 10)
118+
const hour = parseInt(dateStr.slice(9, 11), 10)
119+
const minute = parseInt(dateStr.slice(11, 13), 10)
120+
const second = parseInt(dateStr.slice(13, 15), 10)
121+
122+
return new Date(Date.UTC(year, month, day, hour, minute, second))
74123
}
75124
125+
function getLocalISODateString(date: Date = new Date()): string {
126+
const offset = date.getTimezoneOffset() * 60000
127+
return new Date(date.getTime() - offset).toISOString().slice(0, 10)
128+
}
76129
77130
function getBaseIndex(): number {
78131
if (userScrolled.value && fixedBaseIndex >= 0) {
79-
return fixedBaseIndex;
132+
return fixedBaseIndex
80133
}
81-
const todayStr = new Date().toISOString().split('T')[0];
82-
const todayIndex = timeLineGroups.findIndex(g => g.date === todayStr);
134+
const todayStr = getLocalISODateString()
135+
const todayIndex = timeLineGroups.findIndex(g => g.date === todayStr)
83136
84137
if (todayIndex !== -1) {
85-
return todayIndex;
138+
return todayIndex
86139
}
87140
88141
const futureIndex = timeLineGroups.findIndex(g => {
89-
const groupDate = new Date(g.date);
90-
const today = new Date();
142+
const groupDate = new Date(g.date)
143+
const today = new Date()
91144
return groupDate >= new Date(today.getFullYear(), today.getMonth(), today.getDate())
92145
})
93146

frontend/subscribecal/src/components/cards/CalendarEvent.vue

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -33,48 +33,23 @@ const handleDialogClick = (event: Event) => {
3333
}
3434
}
3535
36-
/**
37-
* 将 %Y%m%dT%H%M%SZ 格式的时间字符串转为 Date 对象(UTC 时间)
38-
* @param dateStr 输入格式如 '20250405T160000Z'
39-
* @returns {Date|null} 解析后的 UTC 时间 Date 对象,失败返回 null
40-
*/
41-
const parseUTCDateTime = (dateStr: string): Date | null => {
42-
// 简单校验格式是否符合 YYYYMMDDTHHMMSSZ
43-
const regex = /^\d{8}T\d{6}Z$/
44-
if (!regex.test(dateStr)) {
45-
console.warn(`Invalid date format: ${dateStr}`)
46-
return null
47-
}
48-
49-
const year = parseInt(dateStr.slice(0, 4), 10)
50-
const month = parseInt(dateStr.slice(4, 6), 10) - 1 // 月份从 0 开始
51-
const day = parseInt(dateStr.slice(6, 8), 10)
52-
const hour = parseInt(dateStr.slice(9, 11), 10)
53-
const minute = parseInt(dateStr.slice(11, 13), 10)
54-
const second = parseInt(dateStr.slice(13, 15), 10)
55-
56-
// 使用 UTC 时间构造
57-
return new Date(Date.UTC(year, month, day, hour, minute, second))
58-
}
59-
6036
function getStatusColor(event: TimeLineItem): string {
61-
// 如果开始和结束时间都是 16:00,则认为时间不准确
62-
if (event.dtstart?.slice(9, 13) === '1600' && event.dtend?.slice(9, 13) === '1600') {
37+
const startHour = event.dtstart?.getHours() ?? -1
38+
const startMinute = event.dtstart?.getMinutes() ?? -1
39+
const endHour = event.dtend?.getHours() ?? -1
40+
const endMinute = event.dtend?.getMinutes() ?? -1
41+
// 事件时间为00:00 - 23:59 则认为时间不准确
42+
if (startHour === 0 && startMinute === 0 && endHour === 23 && endMinute === 59) {
6343
return '#9C27B0' // 深紫红 - 时间不准确
6444
}
6545
66-
const date = parseUTCDateTime(event.dtstart)
67-
if (!date) return '#CCCCCC' // 浅灰 - 默认未知状态
68-
69-
// 转换为本地时间
70-
const localDate = new Date(date.getTime() + new Date().getTimezoneOffset() * 60000)
71-
const localHour = localDate.getHours()
46+
if (!event.dtstart) return '#CCCCCC' // 浅灰 - 默认未知状态
7247
73-
if (localHour >= 6 && localHour < 12) return '#FFA000' // 橘黄色 - 上午
74-
else if (localHour >= 12 && localHour < 14) return '#FFF176' // 浅金黄 - 中午
75-
else if (localHour >= 14 && localHour < 18) return '#4DD0E1' // 天蓝 - 下午
76-
else if (localHour >= 18 && localHour < 20) return '#FB8C00' // 橘红 - 傍晚
77-
else if (localHour >= 20 || localHour < 6) return '#303F9F' // 深蓝紫 - 夜间/凌晨
48+
if (startHour >= 6 && startHour < 12) return '#FFA000' // 橘黄色 - 上午
49+
else if (startHour >= 12 && startHour < 14) return '#FFF176' // 浅金黄 - 中午
50+
else if (startHour >= 14 && startHour < 18) return '#4DD0E1' // 天蓝 - 下午
51+
else if (startHour >= 18 && startHour < 20) return '#FB8C00' // 橘红 - 傍晚
52+
else if (startHour >= 20 || startHour < 6) return '#303F9F' // 深蓝紫 - 夜间/凌晨
7853
7954
return '#CCCCCC' // 默认浅灰色
8055
}
@@ -87,14 +62,11 @@ const sortedEvents = computed(() => {
8762
}
8863
8964
return events
90-
.filter(event => event?.dtstart)
65+
.filter(event => event.dtstart)
9166
.sort((a, b) => {
92-
const formatDateString = (dateStr: string): string => {
93-
return `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}T${dateStr.slice(9, 11)}:${dateStr.slice(11, 13)}:${dateStr.slice(13, 15)}Z`
94-
}
9567
96-
const dateA = Date.parse(formatDateString(a.dtstart))
97-
const dateB = Date.parse(formatDateString(b.dtstart))
68+
const dateA = a.dtstart.getTime()
69+
const dateB = b.dtstart.getTime()
9870
9971
// 非法日期排在后面
10072
if (isNaN(dateA)) return 1
@@ -188,9 +160,9 @@ function getIconForEventType(type: string): string {
188160
:icon="getIconForEventType(event.type)"
189161
fill-dot
190162
>
191-
<VCard>
192-
<div class="d-flex justify-space-between flex-nowrap flex-row">
193-
<div class="ma-auto">
163+
<VCard class="pl-4">
164+
<div class="d-flex flex-row align-center">
165+
<div class="mr-4">
194166
<VImg
195167
height="75"
196168
width="50"
@@ -215,8 +187,8 @@ function getIconForEventType(type: string): string {
215187
<span class="mr-4">{{ event.vote ?? '暂无' }}</span>
216188
<v-icon small color="primary" class="mr-1">mdi-clock-time-four-outline</v-icon>
217189
<span>
218-
{{ parseUTCDateTime(event.dtstart)?.toTimeString().slice(0, 5) }} -
219-
{{ parseUTCDateTime(event.dtend)?.toTimeString().slice(0, 5) }}
190+
{{ event.dtstart.toTimeString().slice(0, 5) }} -
191+
{{ event.dtend.toTimeString().slice(0, 5) }}
220192
</span>
221193
</VCardText>
222194
</div>

frontend/subscribecal/src/types/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ export interface TimeLineItem {
22
// 订阅表ID
33
id: number
44
// 起始时间
5-
dtstart: string
5+
dtstart: Date
66
// 结束时间
7-
dtend: string
7+
dtend: Date
88
// 标题
99
summary: string
1010
// 描述

0 commit comments

Comments
 (0)