如何利用`WP_List_Table`类构建复杂的后台数据列表?

利用 WP_List_Table 类构建复杂的后台数据列表

大家好,今天我们来深入探讨如何使用 WordPress 的 WP_List_Table 类来构建复杂的后台数据列表。WP_List_Table 提供了一个强大的框架,允许我们以结构化和可定制的方式在 WordPress 后台展示数据。虽然它本身可能有些复杂,但掌握了它之后,就能创建出功能丰富的管理界面。

1. WP_List_Table 的基础

WP_List_Table 是一个抽象类,这意味着我们不能直接实例化它。我们需要创建一个子类并实现一些必要的方法,才能使用它。

首先,我们需要包含 WP_List_Table 类。通常,WordPress 不会自动加载它,因此我们需要手动包含:

if ( ! class_exists( 'WP_List_Table' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

这段代码检查 WP_List_Table 是否已经定义,如果没有,则包含 WordPress 核心文件 class-wp-list-table.php

2. 创建自定义列表类

现在,让我们创建一个自定义类,继承 WP_List_Table

class My_Custom_List_Table extends WP_List_Table {

    function __construct() {
        global $status, $page;

        parent::__construct( array(
            'singular' => 'book',     // 单数形式的名称,用于各种消息
            'plural'   => 'books',    // 复数形式的名称,用于表格标题
            'ajax'     => false        // 是否使用 AJAX
        ) );
    }

    // ... 更多方法将在后面定义 ...
}

在构造函数中,我们调用了父类的构造函数 parent::__construct()。这里我们传递一个数组,其中包含一些重要的参数:

  • singular: 单数形式的名称,用于各种消息提示,例如“已删除 book”。
  • plural: 复数形式的名称,用于表格标题,例如“Books”。
  • ajax: 指定是否使用 AJAX 来加载数据。如果设置为 true,则需要实现 ajax_response() 方法。

3. 定义列(get_columns()

接下来,我们需要定义列表中的列。这是通过 get_columns() 方法完成的:

function get_columns() {
    $columns = array(
        'cb'        => '<input type="checkbox" />', // 用于批量操作
        'title'     => 'Title',
        'author'    => 'Author',
        'isbn'      => 'ISBN',
        'price'     => 'Price'
    );
    return $columns;
}

这个方法返回一个关联数组,键是列的 ID(用于在其他方法中引用),值是列的标题(显示在表格头部)。cb 列是特殊的,用于显示复选框,以便进行批量操作。

4. 可排序的列(get_sortable_columns()

如果希望某些列可以排序,我们需要实现 get_sortable_columns() 方法:

function get_sortable_columns() {
    $sortable_columns = array(
        'title'     => array('title', true),     // column_id => array(orderby, asc/desc)
        'author'    => array('author', false),    // true means it's already sorted
        'price'     => array('price', false)
    );
    return $sortable_columns;
}

这个方法返回一个关联数组,键是列的 ID,值是一个包含两个元素的数组:

  • orderby: 用于排序的字段名(通常与列 ID 相同,但也可以不同)。
  • asc/desc: 一个布尔值,指示列是否默认已排序(true 表示已排序,false 表示未排序)。

5. 准备数据(prepare_items()

prepare_items() 方法是 WP_List_Table 中最重要的一个方法。它负责获取数据、进行排序、分页等操作,并将数据传递给表格。

function prepare_items() {
    global $wpdb;

    $per_page = 5; // 每页显示的记录数

    $columns = $this->get_columns();
    $hidden = array();
    $sortable = $this->get_sortable_columns();

    $this->_column_headers = array($columns, $hidden, $sortable);

    // 处理排序
    $orderby = (!empty($_GET["orderby"])) ? esc_sql($_GET["orderby"]) : 'title';
    $order = (!empty($_GET["order"])) ? esc_sql($_GET["order"]) : 'asc';

    // 获取总记录数
    $total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}my_books");

    // 分页
    $paged = (!empty($_GET["paged"])) ? esc_sql($_GET["paged"]) : '';
    if (empty($paged) || !is_numeric($paged) || $paged <= 0) {
        $paged = 1;
    }

    $total_pages = ceil($total_items / $per_page);

    // 获取数据
    $offset = ($paged - 1) * $per_page;
    $this->items = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}my_books ORDER BY $orderby $order LIMIT %d OFFSET %d", $per_page, $offset), ARRAY_A);

    // 配置分页
    $this->set_pagination_args(array(
        'total_items' => $total_items,
        'per_page'    => $per_page,
        'total_pages' => $total_pages
    ));
}

让我们分解一下这个方法:

  1. 定义变量: 定义了每页显示的记录数 $per_page
  2. 设置列头: 使用 get_columns(), get_hidden_columns() (未实现,默认为空数组) 和 get_sortable_columns() 方法获取列的定义,并将它们存储在 $this->_column_headers 属性中。这是 WP_List_Table 类用来呈现表格头部的必要步骤。
  3. 处理排序:$_GET 变量中获取排序字段和排序方式。esc_sql() 用于防止 SQL 注入。如果 $_GET 中没有排序信息,则设置默认排序字段为 title,排序方式为 asc
  4. 获取总记录数: 使用 $wpdb 对象查询数据库,获取总记录数。
  5. 分页:$_GET 变量中获取当前页码。如果 $_GET 中没有页码信息,或者页码无效,则设置当前页码为 1。计算总页数。
  6. 获取数据: 使用 $wpdb 对象查询数据库,获取当前页的数据。$wpdb->prepare() 用于防止 SQL 注入。
  7. 配置分页: 使用 set_pagination_args() 方法配置分页参数。total_items 是总记录数,per_page 是每页显示的记录数,total_pages 是总页数。

6. 显示数据(column_default() 和自定义列方法)

column_default() 方法用于显示没有自定义处理的列的数据:

function column_default( $item, $column_name ) {
    switch( $column_name ) {
        case 'isbn':
        case 'price':
            return $item[ $column_name ];
        default:
            return '<i>' . $column_name . '</i>'; // 显示列 ID,用于调试
    }
}

如果需要自定义显示某些列的数据,可以定义以 column_ 开头的方法,例如 column_title()

function column_title( $item ) {
    $title = '<strong>' . $item['title'] . '</strong>';

    $actions = array(
        'edit'      => sprintf('<a href="?page=%s&action=%s&book=%s">Edit</a>', $_REQUEST['page'], 'edit', $item['id']),
        'delete'    => sprintf('<a href="?page=%s&action=%s&book=%s">Delete</a>', $_REQUEST['page'], 'delete', $item['id']),
    );

    return $title . $this->row_actions($actions);
}

在这个方法中,我们自定义显示了 title 列的数据,并添加了编辑和删除操作链接。$this->row_actions() 方法用于生成操作链接。

7. 复选框列(column_cb()

为了支持批量操作,我们需要实现 column_cb() 方法来显示复选框:

function column_cb( $item ) {
    return sprintf(
        '<input type="checkbox" name="book[]" value="%s" />', $item['id']
    );
}

这个方法返回一个复选框,其值为当前行的 ID。

8. 批量操作(get_bulk_actions()process_bulk_action()

为了支持批量操作,我们需要实现 get_bulk_actions() 方法来定义批量操作的选项:

function get_bulk_actions() {
    $actions = array(
        'delete'    => 'Delete'
    );
    return $actions;
}

这个方法返回一个关联数组,键是操作的 ID,值是操作的名称(显示在下拉菜单中)。

我们还需要实现一个函数来处理批量操作,例如 process_bulk_action()。这个函数需要在 prepare_items() 方法之前调用。

function process_bulk_action() {

    if ( 'delete' === $this->current_action() ) {
        $ids = isset($_REQUEST['book']) ? $_REQUEST['book'] : array();
        if ( is_array($ids) ) {
            $ids = array_map( 'absint', $ids );
        }
        if ( ! empty( $ids ) ) {
            global $wpdb;
            $ids_string = implode( ',', $ids );
            $wpdb->query( "DELETE FROM {$wpdb->prefix}my_books WHERE id IN($ids_string)" );
        }
    }
}

9. 显示列表(display()

最后,我们需要在 WordPress 后台页面中显示列表。首先,创建一个函数来实例化并显示列表:

function my_custom_list_table_page() {
    ?>
    <div class="wrap">
        <h2>My Books</h2>
        <form method="post">
            <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
            <?php
            $myListTable = new My_Custom_List_Table();
            $myListTable->process_bulk_action(); // 处理批量操作
            $myListTable->prepare_items();
            $myListTable->display();
            ?>
        </form>
    </div>
    <?php
}

然后,将这个函数添加到 WordPress 后台菜单:

add_action('admin_menu', 'my_custom_menu');

function my_custom_menu() {
    add_menu_page(
        'My Books',
        'My Books',
        'manage_options',
        'my_books',
        'my_custom_list_table_page'
    );
}

完整的示例代码

下面是一个完整的示例代码,展示了如何使用 WP_List_Table 类构建一个简单的后台数据列表:

<?php

if ( ! class_exists( 'WP_List_Table' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class My_Custom_List_Table extends WP_List_Table {

    function __construct() {
        global $status, $page;

        parent::__construct( array(
            'singular' => 'book',     // 单数形式的名称,用于各种消息
            'plural'   => 'books',    // 复数形式的名称,用于表格标题
            'ajax'     => false        // 是否使用 AJAX
        ) );
    }

    function get_columns() {
        $columns = array(
            'cb'        => '<input type="checkbox" />', // 用于批量操作
            'title'     => 'Title',
            'author'    => 'Author',
            'isbn'      => 'ISBN',
            'price'     => 'Price'
        );
        return $columns;
    }

    function get_sortable_columns() {
        $sortable_columns = array(
            'title'     => array('title', true),     // column_id => array(orderby, asc/desc)
            'author'    => array('author', false),    // true means it's already sorted
            'price'     => array('price', false)
        );
        return $sortable_columns;
    }

    function column_default( $item, $column_name ) {
        switch( $column_name ) {
            case 'isbn':
            case 'price':
                return $item[ $column_name ];
            default:
                return '<i>' . $column_name . '</i>'; // 显示列 ID,用于调试
        }
    }

    function column_title( $item ) {
        $title = '<strong>' . $item['title'] . '</strong>';

        $actions = array(
            'edit'      => sprintf('<a href="?page=%s&action=%s&book=%s">Edit</a>', $_REQUEST['page'], 'edit', $item['id']),
            'delete'    => sprintf('<a href="?page=%s&action=%s&book=%s">Delete</a>', $_REQUEST['page'], 'delete', $item['id']),
        );

        return $title . $this->row_actions($actions);
    }

    function column_cb( $item ) {
        return sprintf(
            '<input type="checkbox" name="book[]" value="%s" />', $item['id']
        );
    }

    function get_bulk_actions() {
        $actions = array(
            'delete'    => 'Delete'
        );
        return $actions;
    }

    function process_bulk_action() {

        if ( 'delete' === $this->current_action() ) {
            $ids = isset($_REQUEST['book']) ? $_REQUEST['book'] : array();
            if ( is_array($ids) ) {
                $ids = array_map( 'absint', $ids );
            }
            if ( ! empty( $ids ) ) {
                global $wpdb;
                $ids_string = implode( ',', $ids );
                $wpdb->query( "DELETE FROM {$wpdb->prefix}my_books WHERE id IN($ids_string)" );
            }
        }
    }

    function prepare_items() {
        global $wpdb;

        $per_page = 5; // 每页显示的记录数

        $columns = $this->get_columns();
        $hidden = array();
        $sortable = $this->get_sortable_columns();

        $this->_column_headers = array($columns, $hidden, $sortable);

        // 处理批量操作
        $this->process_bulk_action();

        // 处理排序
        $orderby = (!empty($_GET["orderby"])) ? esc_sql($_GET["orderby"]) : 'title';
        $order = (!empty($_GET["order"])) ? esc_sql($_GET["order"]) : 'asc';

        // 获取总记录数
        $total_items = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}my_books");

        // 分页
        $paged = (!empty($_GET["paged"])) ? esc_sql($_GET["paged"]) : '';
        if (empty($paged) || !is_numeric($paged) || $paged <= 0) {
            $paged = 1;
        }

        $total_pages = ceil($total_items / $per_page);

        // 获取数据
        $offset = ($paged - 1) * $per_page;
        $this->items = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}my_books ORDER BY $orderby $order LIMIT %d OFFSET %d", $per_page, $offset), ARRAY_A);

        // 配置分页
        $this->set_pagination_args(array(
            'total_items' => $total_items,
            'per_page'    => $per_page,
            'total_pages' => $total_pages
        ));
    }
}

function my_custom_list_table_page() {
    ?>
    <div class="wrap">
        <h2>My Books</h2>
        <form method="post">
            <input type="hidden" name="page" value="<?php echo $_REQUEST['page'] ?>" />
            <?php
            $myListTable = new My_Custom_List_Table();
            $myListTable->prepare_items();
            $myListTable->display();
            ?>
        </form>
    </div>
    <?php
}

add_action('admin_menu', 'my_custom_menu');

function my_custom_menu() {
    add_menu_page(
        'My Books',
        'My Books',
        'manage_options',
        'my_books',
        'my_custom_list_table_page'
    );
}

// 示例数据,用于测试
add_action( 'admin_init', 'my_plugin_create_db' );
function my_plugin_create_db() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'my_books';

    if ( $wpdb->get_var("show tables like '$table_name'") != $table_name ) {

        $sql = "CREATE TABLE " . $table_name . " (
          id mediumint(9) NOT NULL AUTO_INCREMENT,
          title varchar(255) NOT NULL,
          author varchar(255) NOT NULL,
          isbn varchar(20) NOT NULL,
          price decimal(10,2) NOT NULL,
          UNIQUE KEY id (id)
        );";

        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        dbDelta( $sql );

        // 插入一些示例数据
        $wpdb->insert(
            $table_name,
            array(
                'title' => 'The Lord of the Rings',
                'author' => 'J.R.R. Tolkien',
                'isbn' => '978-0618260221',
                'price' => 25.00,
            )
        );
        $wpdb->insert(
            $table_name,
            array(
                'title' => 'The Hobbit',
                'author' => 'J.R.R. Tolkien',
                'isbn' => '978-0547928227',
                'price' => 18.00,
            )
        );
        $wpdb->insert(
            $table_name,
            array(
                'title' => 'Pride and Prejudice',
                'author' => 'Jane Austen',
                'isbn' => '978-0141439518',
                'price' => 12.00,
            )
        );
        $wpdb->insert(
            $table_name,
            array(
                'title' => '1984',
                'author' => 'George Orwell',
                'isbn' => '978-0451524935',
                'price' => 15.00,
            )
        );
        $wpdb->insert(
            $table_name,
            array(
                'title' => 'To Kill a Mockingbird',
                'author' => 'Harper Lee',
                'isbn' => '978-0446310789',
                'price' => 20.00,
            )
        );
        $wpdb->insert(
            $table_name,
            array(
                'title' => 'The Catcher in the Rye',
                'author' => 'J.D. Salinger',
                'isbn' => '978-0316769532',
                'price' => 16.00,
            )
        );
    }
}
?>

总结

本文介绍了如何使用WP_List_Table类在WordPress后台构建复杂的列表页面。我们需要继承WP_List_Table类,实现必要的方法,如get_columns()prepare_items()column_default(),以定义列、准备数据并显示数据。掌握这些知识,可以更灵活地定制WordPress后台界面。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注