D8中如何通过编程实现视图的日、月、年份筛选?
我太懂这种刚需的痛苦了——Drupal 8确实把D7视图里那种直接按日/月/年拆分筛选的原生功能给砍掉了,折腾了好一阵才找到靠谱的编程实现方式,给你分享两种亲测有效的方案:
方案一:自定义模块添加专属的日/月/年视图筛选器
这是最灵活的方案,能完全自定义筛选逻辑,而且可以复用在多个视图里,适合长期项目使用。
- 先创建一个自定义模块(比如叫
date_part_filter),在模块目录下新建date_part_filter.views.inc文件,实现hook_views_data()来注册我们的筛选器:
<?php /** * Implements hook_views_data(). */ function date_part_filter_views_data() { $data = []; // 这里以节点的创建日期为例,你可以替换成自己的自定义日期字段机器名 $data['node_field_data']['created_month'] = [ 'title' => t('Created month'), 'help' => t('Filter nodes by the month of their creation date.'), 'filter' => [ 'id' => 'date_part_month', 'field' => 'created', ], ]; $data['node_field_data']['created_year'] = [ 'title' => t('Created year'), 'help' => t('Filter nodes by the year of their creation date.'), 'filter' => [ 'id' => 'date_part_year', 'field' => 'created', ], ]; $data['node_field_data']['created_day'] = [ 'title' => t('Created day'), 'help' => t('Filter nodes by the day of their creation date.'), 'filter' => [ 'id' => 'date_part_day', 'field' => 'created', ], ]; return $data; }
- 接着在模块目录下创建
src/Plugin/views/filter/DatePartFilter.php文件,写筛选器的核心处理类:
<?php namespace Drupal\date_part_filter\Plugin\views\filter; use Drupal\views\Plugin\views\filter\NumericFilter; use Drupal\Core\Form\FormStateInterface; /** * Base filter class for date parts (month/year/day). */ abstract class DatePartFilter extends NumericFilter { /** * Get the date part we're filtering on (month/year/day). */ abstract protected function getDatePart(); /** * {@inheritdoc} */ public function query() { $this->ensureMyTable(); $field = "$this->tableAlias.$this->realField"; // 这里适配MySQL,如果你用PostgreSQL,把DATE_FORMAT换成EXTRACT(MONTH FROM $field)这类写法 $date_part = $this->getDatePart(); $expression = "DATE_FORMAT($field, '%$date_part')"; $this->query->addWhereExpression($this->options['group'], "$expression = :value", [':value' => $this->value['value']]); } } /** * Filter by month of a date field. * * @ViewsFilter("date_part_month") */ class DatePartMonth extends DatePartFilter { protected function getDatePart() { return 'm'; } } /** * Filter by year of a date field. * * @ViewsFilter("date_part_year") */ class DatePartYear extends DatePartFilter { protected function getDatePart() { return 'Y'; } } /** * Filter by day of a date field. * * @ViewsFilter("date_part_day") */ class DatePartDay extends DatePartFilter { protected function getDatePart() { return 'd'; } }
注意:如果你的站点用PostgreSQL数据库,记得把DATE_FORMAT函数替换成PostgreSQL的EXTRACT语法,比如筛选月份就用EXTRACT(MONTH FROM $field),对应的格式符也要调整哦。
- 启用这个自定义模块后,去视图的「Filter Criteria」里就能找到「Created month」「Created year」「Created day」这几个新筛选器了,添加之后直接输入数字就能筛选(比如月份填1代表1月,年份填2024就行)。
方案二:用hook_form_alter扩展现有日期筛选器
如果不想写太多类,这个轻量方案更适合快速上手——直接改造视图里已有的日期筛选表单,添加下拉选项让用户选择按日/月/年筛选。
在你的自定义模块的.module文件里添加以下代码:
<?php /** * Implements hook_form_alter(). */ function custom_date_filter_form_alter(&$form, FormStateInterface $form_state, $form_id) { // 匹配视图的暴露筛选表单ID,格式一般是views_exposed_form_[视图名]_[显示名] if (strpos($form_id, 'views_exposed_form') !== FALSE) { foreach ($form as $key => $element) { // 找到日期类型的筛选元素,这里以创建日期为例,你可以换成自己的字段名 if (isset($element['#type']) && $element['#type'] == 'date' && strpos($key, 'created') !== FALSE) { // 添加筛选类型下拉菜单 $form[$key . '_part'] = [ '#type' => 'select', '#title' => t('Filter by'), '#options' => [ 'full' => t('Full date'), 'year' => t('Year'), 'month' => t('Month'), 'day' => t('Day'), ], '#default_value' => 'full', '#weight' => $element['#weight'] - 0.1, ]; // 添加自定义提交处理函数,修改视图查询逻辑 $form['#submit'][] = 'custom_date_filter_form_submit'; } } } } /** * Custom submit handler for the date filter form. */ function custom_date_filter_form_submit(&$form, FormStateInterface $form_state) { $values = $form_state->getValues(); $view = $form_state->get('view'); foreach ($values as $key => $value) { // 匹配日期字段的筛选控件,排除我们添加的_part后缀字段 if (strpos($key, 'created') !== FALSE && strpos($key, '_part') === FALSE && !empty($value)) { $part_key = $key . '_part'; $date_part = $values[$part_key] ?? 'full'; if ($date_part != 'full') { $date = new \DateTime($value); // 找到对应的视图筛选器 foreach ($view->filter as $filter) { if ($filter->field == str_replace('filter_', '', $key)) { // 清空原筛选条件,添加日期部分筛选逻辑 $filter->value = []; $field = "$filter->tableAlias.$filter->realField"; switch ($date_part) { case 'year': $expression = "DATE_FORMAT($field, '%Y') = :year"; $args = [':year' => $date->format('Y')]; break; case 'month': $expression = "DATE_FORMAT($field, '%m') = :month"; $args = [':month' => $date->format('m')]; break; case 'day': $expression = "DATE_FORMAT($field, '%d') = :day"; $args = [':day' => $date->format('d')]; break; default: return; } $view->query->addWhereExpression($filter->options['group'], $expression, $args); // 阻止原筛选器执行默认逻辑 $filter->setWhereGroup('OR'); $filter->value = ['min' => '', 'max' => '']; } } } } } }
这个方案的好处是不用额外注册视图数据,直接修改现有表单——用户在前端选择按年/月/日筛选后,输入任意一个对应日期(比如选「Month」后输入2024-05-01,系统会自动提取5月来筛选)。
两种方案都能完美满足你的需求,方案一更适合需要长期复用的场景,方案二更适合快速解决问题。如果是自定义日期字段,只需要把代码里的created替换成你的字段机器名就可以了。
内容的提问来源于stack exchange,提问作者Alan Yong
相关产品推荐
相关产品推荐

