
本文介绍如何使用 angularjs 和 php 构建一个动态禁用日期的日期选择器:每月首次加载时默认禁用所有日期,通过 ajax 从后端获取当月不可用日期列表后,实时启用可选日期,全程无需 jquery。
本文介绍如何使用 angularjs 和 php 构建一个动态禁用日期的日期选择器:每月首次加载时默认禁用所有日期,通过 ajax 从后端获取当月不可用日期列表后,实时启用可选日期,全程无需 jquery。
在构建预约、预订或日程类应用时,常需根据数据库中已占用/关闭的日期动态控制日期选择器的可用性。AngularJS 生态中推荐使用 Angular UI Bootstrap 的 datepickerPopup(官方维护、零 jQuery 依赖),它原生支持通过 date-disabled 表达式自定义禁用逻辑,完美契合“按月异步加载禁用日期”的需求。
✅ 实现步骤概览
- 前端初始化日期选择器,初始状态禁用全部日期(显示加载态);
- 监听月份切换事件(datepicker-mode="month" + ng-change 或 $watch datepickerOptions 中的 minDate/maxDate);
- 触发 PHP 后端接口(如 /api/unavailable-dates.php),传入年月参数(如 ?year=2024&month=10);
- PHP 查询数据库返回 JSON 格式的不可用日期数组(如 ["2024-10-05", "2024-10-12"]);
- 前端将返回日期解析为 Date 对象并缓存,更新 dateDisabled 函数,使匹配日期自动禁用。
? 核心代码示例
HTML 模板(含加载指示器):
<div ng-controller="DatePickerController as ctrl">
<div ng-if="ctrl.loading" class="text-center">
<span class="spinner-border spinner-border-sm"></span> 加载可用日期...
</div>
<div datepicker-popup="yyyy-MM-dd" ng-model="ctrl.selectedDate" is-open="ctrl.opened" date-disabled="ctrl.disabled(date, mode)" ng-click="ctrl.openCalendar()" show-button-bar="false">
</div>
<button type="button" class="btn btn-default" ng-click="ctrl.openCalendar()">
<i class="glyphicon glyphicon-calendar"></i> 选择日期
</button>
</div>
AngularJS Controller(关键逻辑):
app.controller('DatePickerController', function($scope, $http, $timeout) {
var ctrl = this;
ctrl.selectedDate = null;
ctrl.unavailableDates = []; // 存储禁用日期(Date 实例)
ctrl.loading = true;
// 判断日期是否禁用(供 date-disabled 调用)
ctrl.disabled = function(date, mode) {
if (mode !== 'day') return false;
if (ctrl.loading) return true; // 加载中:全部禁用
return ctrl.unavailableDates.some(d =>
d.getFullYear() === date.getFullYear() &&
d.getMonth() === date.getMonth() &&
d.getDate() === date.getDate()
);
};
// 监听月份变化(需配合 datepicker-options 使用)
ctrl.datepickerOptions = {
minDate: new Date(),
maxDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
initDate: new Date(),
startingDay: 1
};
// 每次打开日历或切换月份时刷新禁用日期
ctrl.loadUnavailableDates = function(year, month) {
ctrl.loading = true;
$http.get('/api/unavailable-dates.php', {
params: { year: year, month: month + 1 } // JS 月份从0开始,PHP需+1
}).then(function(response) {
ctrl.unavailableDates = response.data.map(str => new Date(str));
}).catch(function() {
ctrl.unavailableDates = [];
}).finally(function() {
ctrl.loading = false;
$scope.$apply(); // 确保视图更新(若在非Angular上下文触发)
});
};
// 初始化加载当前月
var now = new Date();
ctrl.loadUnavailableDates(now.getFullYear(), now.getMonth());
// 手动打开日历时触发加载(可选增强)
ctrl.openCalendar = function() {
ctrl.opened = true;
var viewDate = ctrl.datepickerOptions.initDate || new Date();
ctrl.loadUnavailableDates(viewDate.getFullYear(), viewDate.getMonth());
};
});
PHP 后端示例(/api/unavailable-dates.php):
<?php header('Content-Type: application/json; charset=utf-8');
$year = (int)$_GET['year'] ?: date('Y');
$month = (int)$_GET['month'] ?: date('n');
// 安全验证:限制查询范围(如仅允许未来12个月)
if ($year < date('Y') || $year > date('Y') + 1 || $month 12) {
echo json_encode([]);
exit;
}
// 假设 PDO 连接已建立:$pdo
$stmt = $pdo->prepare(
"SELECT DATE(date) as unavailable_date
FROM bookings
WHERE YEAR(date) = ? AND MONTH(date) = ?
AND status = 'booked'"
);
$stmt->execute([$year, $month]);
$result = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo json_encode($result); // 输出 ["2024-10-05","2024-10-12"]
?>
⚠️ 注意事项与最佳实践
- 性能优化:避免每次点击都请求——缓存已加载月份的禁用日期,仅当切换到新月份时发起请求;
- 时区一致性:确保 PHP 服务器与前端浏览器使用相同时区(推荐统一用 UTC 存储和计算);
- 错误处理:网络失败时应保留上月缓存或提供友好的提示,而非永久禁用;
- 无障碍支持:禁用日期仍需正确渲染 aria-disabled="true",Angular UI Bootstrap 默认支持;
- 替代方案提醒:若项目已引入 Kendo UI,其 disabledDates 选项同样适用,但需注意其商业授权条款。
通过以上方案,你将获得一个轻量、可维护、无 jQuery 依赖的动态日期管控能力,真正实现“所见即所得”的业务规则驱动交互体验。











