首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 日曆類別實作指南:兩種風格完整代碼與前端整合技巧

     編輯:PHP 綜合知識

前言

日曆元件是後台管理系統、預約平台、行事曆應用的基礎組件。純 PHP 實作日曆邏輯(無需 JavaScript)具有首屏渲染快、SEO 友善、伺服器端可控制權限等優勢。本文提供兩種風格的完整 PHP 日曆類別:

  1. 經典表格式:傳統月曆表格,支援上下月導航、今日高亮
  2. 增強互動式:整合下拉選單快速跳轉、表單提交、日期點擊回傳參數

兩者均採用物件導向設計,易於擴展(如加入農曆、節假日、事件標記)。

方案一:經典表格式日曆類別

<?php
/**
 * 經典月曆表格類別
 * 功能:產生 HTML 表格、上下月導航、今日高亮、可自訂樣式類別
 */
class CalendarClassic
{
    private int $year;
    private int $month;
    private array $weekLabels = ['日', '一', '二', '三', '四', '五', '六'];
    private string $tableClass = 'calendar-table';
    private string $todayClass = 'today';
    private string $otherMonthClass = 'other-month';
    private string $navUrl; // 導航連結基礎 URL

    public function __construct(?int $year = null, ?int $month = null, string $navUrl = '')
    {
        $this->year   = $year   ?? (int)date('Y');
        $this->month  = $month  ?? (int)date('m');
        $this->navUrl = $navUrl ?: $_SERVER['PHP_SELF'];
    }

    public function setWeekLabels(array $labels): self
    {
        $this->weekLabels = $labels;
        return $this;
    }

    public function setTableClass(string $class): self { $this->tableClass = $class; return $this; }
    public function setTodayClass(string $class): self { $this->todayClass = $class; return $this; }
    public function setOtherMonthClass(string $class): self { $this->otherMonthClass = $class; return $this; }

    /** 取得該月天數 */
    private function getDaysInMonth(): int
    {
        return (int)date('t', mktime(0, 0, 0, $this->month, 1, $this->year));
    }

    /** 取得該月 1 號是星期幾 (0=日 ... 6=六) */
    private function getFirstWeekday(): int
    {
        return (int)date('w', mktime(0, 0, 0, $this->month, 1, $this->year));
    }

    /** 產生上一月/下一月導航連結 */
    private function getNavLinks(): string
    {
        $prevMonth = $this->month - 1;
        $prevYear  = $this->year;
        if ($prevMonth < 1) { $prevMonth = 12; $prevYear--; }

        $nextMonth = $this->month + 1;
        $nextYear  = $this->year;
        if ($nextMonth > 12) { $nextMonth = 1; $nextYear++; }

        $prevUrl = $this->navUrl . "?year=$prevYear&month=$prevMonth";
        $nextUrl = $this->navUrl . "?year=$nextYear&month=$nextMonth";

        return "<td colspan='2'><a href='$prevUrl'><<</a></td>"
             . "<td colspan='3' class='title'>{$this->year} 年 {$this->month} 月</td>"
             . "<td colspan='2'><a href='$nextUrl'>>></a></td>";
    }

    /** 渲染完整 HTML 表格 */
    public function render(): string
    {
        $daysInMonth = $this->getDaysInMonth();
        $firstWeekday = $this->getFirstWeekday();
        $today = (int)date('j');
        $isCurrentMonth = (date('Y') == $this->year && date('m') == $this->month);

        $html = "<table class='{$this->tableClass}'>\n";
        $html .= "  <tr class='nav-row'>" . $this->getNavLinks() . "</tr>\n";
        $html .= "  <tr class='week-header'>";
        foreach ($this->weekLabels as $label) {
            $html .= "<th>$label</th>";
        }
        $html .= "</tr>\n";

        $day = 1;
        for ($week = 0; $week < 6; $week++) {
            $html .= "  <tr>";
            for ($wd = 0; $wd < 7; $wd++) {
                $cellClass = [];
                $content = '';

                if ($week === 0 && $wd < $firstWeekday) {
                    // 上月尾巴
                    $prevMonthDays = (int)date('t', mktime(0,0,0,$this->month,0,$this->year));
                    $content = $prevMonthDays - $firstWeekday + $wd + 1;
                    $cellClass[] = $this->otherMonthClass;
                } elseif ($day > $daysInMonth) {
                    // 下月開頭
                    $content = $day - $daysInMonth;
                    $cellClass[] = $this->otherMonthClass;
                    $day++;
                } else {
                    // 本月日期
                    $content = $day;
                    if ($isCurrentMonth && $day === $today) {
                        $cellClass[] = $this->todayClass;
                    }
                    $day++;
                }

                $classAttr = $cellClass ? " class='" . implode(' ', $cellClass) . "'" : '';
                $html .= "<td$classAttr><div class='day-num'>$content</div></td>";
            }
            $html .= "</tr>\n";
            if ($day > $daysInMonth && $week > 3) break; // 已經顯示完本月且至少 4 週
        }
        $html .= "</table>";
        return $html;
    }
}

// ---------- 使用範例 ----------
require_once 'CalendarClassic.php';

$cal = new CalendarClassic(
    year: isset($_GET['year']) ? (int)$_GET['year'] : null,
    month: isset($_GET['month']) ? (int)$_GET['month'] : null,
    navUrl: $_SERVER['PHP_SELF']
);
$cal->setWeekLabels(['Sun','Mon','Tue','Wed','Thu','Fri','Sat'])
    ->setTableClass('cal')
    ->setTodayClass('today');

echo $cal->render();

配套 CSS(可直接內嵌)

<style>
.cal {border-collapse: collapse; width: 100%; max-width: 400px; font-family: sans-serif;}
.cal th, .cal td {border: 1px solid #ddd; padding: 8px; text-align: center; height: 40px;}
.cal .nav-row td {background: #f0f4f8; font-weight: bold;}
.cal .nav-row a {color: #3366cc; text-decoration: none;}
.cal .week-header th {background: #3366cc; color: white;}
.cal .today {background: #ffe0b2; font-weight: bold;}
.cal .other-month {color: #ccc; background: #fafafa;}
.cal .day-num {font-size: 1.1em;}
</style>

方案二:增強互動式日曆(含表單跳轉、日期參數回傳)

適用於需要使用者選擇日期後提交表單、AJAX 載入該日行程、快速跳轉年月的場景。

<?php
/**
 * 增強互動式日曆
 * 功能:日期連結帶參數、年月下拉選單快速跳轉、今日高亮、可擴展事件標記
 */
class CalendarEnhanced
{
    private int $year;
    private int $month;
    private int $selectedDay; // 被點選/高亮的日期
    private array $events = []; // ['YYYY-MM-DD' => ['title'=>..., 'color'=>...]]
    private string $baseUrl;

    public function __construct(?int $year = null, ?int $month = null, ?int $day = null, string $baseUrl = '')
    {
        $this->year        = $year ?? (int)date('Y');
        $this->month       = $month ?? (int)date('m');
        $this->selectedDay = $day ?? (int)date('d');
        $this->baseUrl     = $baseUrl ?: $_SERVER['PHP_SELF'];
    }

    public function addEvent(string $date, string $title, string $color = '#e74c3c'): self
    {
        $this->events[$date] = ['title' => $title, 'color' => $color];
        return $this;
    }

    public function setEvents(array $events): self { $this->events = $events; return $this; }

    private function getDaysInMonth(): int { return (int)date('t', mktime(0,0,0,$this->month,1,$this->year)); }
    private function getFirstWeekday(): int { return (int)date('w', mktime(0,0,0,$this->month,1,$this->year)); }

    private function buildUrl(array $params): string
    {
        return $this->baseUrl . '?' . http_build_query(array_merge(
            ['year' => $this->year, 'month' => $this->month], $params
        ));
    }

    public function render(): string
    {
        $daysInMonth = $this->getDaysInMonth();
        $firstWd = $this->getFirstWeekday();
        $weekLabels = ['日','一','二','三','四','五','六'];

        $html = "<div class='cal-enhanced'>\n";
        $html .= "  <table>\n";

        // 標題列 + 年月下拉選單
        $html .= "  <tr><td colspan='7' class='header'>";
        $html .= "<a href='{$this->buildUrl(['year'=>$this->year-1])}' title='上一年'><<</a> ";
        $html .= "<a href='{$this->buildUrl(['month'=>$this->month-1])}' title='上一月'><</a> ";

        // 年份下拉
        $html .= "<select name='year' onchange='this.form.submit()'>";
        for ($y = 1970; $y <= 2039; $y++) {
            $sel = $y == $this->year ? ' selected' : '';
            $html .= "<option value='$y'$sel>$y</option>";
        }
        $html .= "</select> 年 ";

        // 月份下拉
        $html .= "<select name='month' onchange='this.form.submit()'>";
        for ($m = 1; $m <= 12; $m++) {
            $sel = $m == $this->month ? ' selected' : '';
            $html .= "<option value='$m'$sel>$m</option>";
        }
        $html .= "</select> 月 ";

        $html .= "<a href='{$this->buildUrl(['month'=>$this->month+1])}' title='下一月'>>></a> ";
        $html .= "<a href='{$this->buildUrl(['year'=>$this->year+1])}' title='下一年'>>></a>";
        $html .= "</td></tr>\n";

        // 星期標題
        $html .= "  <tr class='week'>";
        foreach ($weekLabels as $w) $html .= "<th>$w</th>";
        $html .= "</tr>\n";

        // 日期格
        $day = 1;
        for ($week = 0; $week < 6; $week++) {
            $html .= "  <tr>";
            for ($wd = 0; $wd < 7; $wd++) {
                $classes = [];
                $content = '';
                $attrs = '';

                if ($week === 0 && $wd < $firstWd) {
                    $prevLastDay = (int)date('t', mktime(0,0,0,$this->month,0,$this->year));
                    $content = $prevLastDay - $firstWd + $wd + 1;
                    $classes[] = 'other';
                } elseif ($day > $daysInMonth) {
                    $content = $day - $daysInMonth;
                    $classes[] = 'other';
                    $day++;
                } else {
                    $content = $day;
                    $dateKey = sprintf('%04d-%02d-%02d', $this->year, $this->month, $day);

                    // 日期連結:點擊帶入 day 參數
                    $url = $this->buildUrl(['day' => $day]);
                    $attrs = "href='$url'";

                    if ($day === $this->selectedDay) $classes[] = 'selected';
                    if (date('Y-m-d') === $dateKey) $classes[] = 'today';
                    if (isset($this->events[$dateKey])) {
                        $classes[] = 'has-event';
                        $attrs .= " title='" . htmlspecialchars($this->events[$dateKey]['title']) . "'";
                    }
                    $day++;
                }

                $classAttr = $classes ? " class='" . implode(' ', $classes) . "'" : '';
                $html .= "<td$classAttr>";
                if ($attrs) $html .= "<a $attrs>$content</a>";
                else $html .= "<span>$content</span>";

                // 事件指示點
                if (isset($dateKey) && isset($this->events[$dateKey])) {
                    $color = $this->events[$dateKey]['color'];
                    $html .= "<span class='event-dot' style='background:$color'></span>";
                }
                $html .= "</td>";
            }
            $html .= "</tr>\n";
            if ($day > $daysInMonth && $week > 3) break;
        }

        $html .= "  </table>\n";
        $html .= "</div>";
        return $html;
    }
}

// ---------- 使用範例 ----------
require_once 'CalendarEnhanced.php';

$cal = new CalendarEnhanced(
    year: $_GET['year'] ?? null,
    month: $_GET['month'] ?? null,
    day: $_GET['day'] ?? null
);

// 模擬事件資料(實際可從資料庫讀取)
$cal->addEvent('2024-01-15', '團隊會議', '#3498db')
    ->addEvent('2024-01-20', '專案截止', '#e74c3c')
    ->addEvent('2024-01-28', '客戶拜訪', '#2ecc71');

echo $cal->render();

增強版 CSS

<style>
.cal-enhanced {font-family: sans-serif; max-width: 500px;}
.cal-enhanced table {border-collapse: collapse; width: 100%;}
.cal-enhanced th, .cal-enhanced td {border: 1px solid #eee; padding: 6px; text-align: center; position: relative; height: 38px;}
.cal-enhanced .header {background: #f8f9fa; padding: 10px; text-align: center;}
.cal-enhanced .header select {padding: 4px; margin: 0 4px;}
.cal-enhanced .week th {background: #34495e; color: white; font-weight: normal;}
.cal-enhanced td.other {color: #bbb; background: #fdfdfd;}
.cal-enhanced td.today {background: #fff3cd;}
.cal-enhanced td.selected {background: #3498db; color: white;}
.cal-enhanced td.has-event a {font-weight: bold;}
.cal-enhanced .event-dot {display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-top: 2px;}
.cal-enhanced a {text-decoration: none; color: inherit; display: block;}
.cal-enhanced a:hover {background: #e8f4fd;}
</style>

進階擴展方向

功能實作提示
農曆/節氣顯示引入 overtrue/laravel-chinese-calendar 或自行實作農曆算法
法定假日標記建立假日資料表,addEvent() 批次載入
多月聯動顯示實例化多個 Calendar 並並排渲染(CSS Flex/Grid)
AJAX 載入行程日期 <a> 改為 data-date 屬性,JS 監聽 click 發送 fetch
日期範圍選擇雙擊/拖曳選取,前端維護 startDate/endDate 狀態
國際化 (i18n)週標題、月份名稱、年份格式抽離為語言包

結語

純 PHP 實作日曆類別的優勢在於邏輯集中於伺服器端、首屏無 JS 閃動、易於整合權限控管(如特定角色不可選特定日期)。本文提供的兩種風格分別適合「純顯示導航」與「互動選擇回傳」場景,開發者可直接複製使用,或依專案需求擴展農曆、假日、事件、AJAX 等進階功能。關鍵在於將日期計算邏輯封裝於類別內部,對外僅暴露 render(),保持模板乾淨、易維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved