diff --git a/CLAUDE.md b/CLAUDE.md index f9a7a32..8df8457 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - 视频评论查询和搜索 - 节目管理和视频关联 - 弹幕数据管理(支持B站、西瓜视频、抖音) +- 直播日历(/calendar,按稿件标题中的直播日期展示月历) - WebAuthn 身份验证 - 文件上传和管理 diff --git a/app/Http/Controllers/CalendarController.php b/app/Http/Controllers/CalendarController.php new file mode 100644 index 0000000..24571c0 --- /dev/null +++ b/app/Http/Controllers/CalendarController.php @@ -0,0 +1,70 @@ +get("month", ""); + if (preg_match("/^(\d{4})-(0[1-9]|1[0-2])$/", $month_param, $matches)) { + $month = Carbon::createFromDate((int)$matches[1], (int)$matches[2], 1)->startOfMonth(); + } else { + $month = Carbon::now()->startOfMonth(); + } + + $videos = Videos::query()->orderBy("created_at")->get(["id", "bvid", "title", "total_duration"]); + $by_date = VideoCalendarUtil::group_by_live_date($videos); + $parsed_count = array_sum(array_map("sizeof", $by_date)); + + $weeks = []; + $current_week = array_fill(0, 7, null); + $cursor = $month->copy(); + $month_end = $month->copy()->endOfMonth(); + while ($cursor->lte($month_end)) { + $day_index = $cursor->dayOfWeekIso - 1; // 周一为一周之始 + $date_key = $cursor->format("Y-m-d"); + $current_week[$day_index] = [ + "day" => $cursor->day, + "videos" => $by_date[$date_key] ?? [], + "is_today" => $cursor->isToday(), + ]; + if ($day_index === 6) { + $weeks[] = $current_week; + $current_week = array_fill(0, 7, null); + } + $cursor->addDay(); + } + if (sizeof(array_filter($current_week)) > 0) { + $weeks[] = $current_week; + } + + $month_prefix = $month->format("Y-m"); + $live_days = sizeof(array_filter(array_keys($by_date), function (string $date) use ($month_prefix) { + return strpos($date, $month_prefix) === 0; + })); + + $min_month = sizeof($by_date) > 0 ? Carbon::createFromFormat("Y-m-d", array_key_first($by_date))->startOfMonth() : $month->copy(); + $max_month = sizeof($by_date) > 0 ? Carbon::createFromFormat("Y-m-d", array_key_last($by_date))->startOfMonth() : $month->copy(); + $prev_month = $month->copy()->subMonth(); + $next_month = $month->copy()->addMonth(); + + return view("calendar.index", [ + "weeks" => $weeks, + "month" => $month, + "is_current_month" => $month->eq(Carbon::now()->startOfMonth()), + "live_days" => $live_days, + "unparsed_count" => sizeof($videos) - $parsed_count, + "prev_month" => $prev_month, + "next_month" => $next_month, + "has_prev" => $prev_month->gte($min_month), + "has_next" => $next_month->lte($max_month), + ]); + } +} diff --git a/app/Util/VideoCalendarUtil.php b/app/Util/VideoCalendarUtil.php new file mode 100644 index 0000000..ba9745d --- /dev/null +++ b/app/Util/VideoCalendarUtil.php @@ -0,0 +1,48 @@ +startOfDay(); + } + + /** + * 按直播日期分组稿件,返回 ['Y-m-d' => [video, ...]],按日期升序。 + * 标题无法解析的稿件不会进入结果。 + */ + public static function group_by_live_date(iterable $videos): array + { + $grouped = []; + foreach ($videos as $video) { + $date = static::parse_live_date($video->title ?? null); + if ($date === null) { + continue; + } + $grouped[$date->format("Y-m-d")][] = $video; + } + ksort($grouped); + return $grouped; + } +} diff --git a/resources/views/calendar/index.blade.php b/resources/views/calendar/index.blade.php new file mode 100644 index 0000000..d154360 --- /dev/null +++ b/resources/views/calendar/index.blade.php @@ -0,0 +1,86 @@ + +
+ +