WP 插件开发实战:自定义数据表 + CRUD + 后台列表页
WP 插件开发实战:自定义数据表 + CRUD + 后台列表页(含完整示例代码)
很多 WordPress 插件写到后面都会遇到同一个现实:options 只适合存少量配置,不适合存“记录型数据”(例如:线索、日志、任务队列、表单提交、同步结果)。一旦你开始把大量记录塞进 postmeta 或 options,性能和维护都会越来越痛苦。
这篇文章我写一个“最小可用”的记录型插件:创建自定义表 wp_mwt_leads,提供后台菜单页,支持新增、删除、分页列表。重点还是:结构、规范、安全和可维护性。
1)插件目标与结构
目标:
- 插件激活时创建数据表
- 后台菜单“Leads”,展示列表(分页)
- 可新增记录(表单 + nonce)
- 可删除记录(GET 操作 + nonce)
目录结构建议:
mwt-leads/
mwt-leads.php
(先单文件实现,后续再拆类更清晰)
2)完整示例代码(可直接运行)
新建:wp-content/plugins/mwt-leads/mwt-leads.php
```php prefix . 'mwt_leads'; $charset_collate = $wpdb->get_charset_collate();
// 用 dbDelta 创建/升级表结构
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$sql = "CREATE TABLE $table (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(120) NOT NULL,
email VARCHAR(190) NOT NULL,
note TEXT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY email (email)
) $charset_collate;";
dbDelta($sql);
add_option('mwt_leads_db_version', $mwt_leads_db_version);
}
add_action('admin_menu', function() { add_menu_page( 'MWT Leads', 'MWT Leads', 'manage_options', 'mwt-leads', 'mwt_leads_render_page', 'dashicons-id', 56 ); });
function mwt_leads_render_page() { if (!current_user_can('manage_options')) return;
global $wpdb;
$table = $wpdb->prefix . 'mwt_leads';
// 1) 处理新增
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mwt_action']) && $_POST['mwt_action'] === 'add') {
check_admin_referer('mwt_leads_add');
$name = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$note = sanitize_textarea_field($_POST['note'] ?? '');
if ($name && $email) {
$wpdb->insert(
$table,
[
'name' => $name,
'email' => $email,
'note' => $note,
'created_at' => current_time('mysql')
],
['%s','%s','%s','%s']
);
echo '<div class="notice notice-success"><p>Lead 已添加。</p></div>';
} else {
echo '<div class="notice notice-error"><p>name 和 email 不能为空。</p></div>';
}
}
// 2) 处理删除
if (isset($_GET['action'], $_GET['lead_id']) && $_GET['action'] === 'delete') {
$lead_id = absint($_GET['lead_id']);
check_admin_referer('mwt_leads_delete_' . $lead_id);
if ($lead_id > 0) {
$wpdb->delete($table, ['id' => $lead_id], ['%d']);
echo '<div class="notice notice-success"><p>Lead 已删除。</p></div>';
}
}
// 3) 分页查询
$per_page = 10;
$paged = max(1, absint($_GET['paged'] ?? 1));
$offset = ($paged - 1) * $per_page;
$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table");
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table ORDER BY id DESC LIMIT %d OFFSET %d",
$per_page,
$offset
));
$total_pages = (int) ceil($total / $per_page);
?>
<div class="wrap">
<h1>MWT Leads</h1>
<h2>新增 Lead</h2>
<form method="post">
<input type="hidden" name="mwt_action" value="add" />
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="name">Name</label></th>
<td><input class="regular-text" id="name" name="name" type="text" required></td>
</tr>
<tr>
<th scope="row"><label for="email">Email</label></th>
<td><input class="regular-text" id="email" name="email" type="email" required></td>
</tr>
<tr>
<th scope="row"><label for="note">Note</label></th>
<td><textarea class="large-text" id="note" name="note" rows="3"></textarea></td>
</tr>
</table>
</form>
<hr />
<h2>列表()</h2>
<table class="widefat fixed striped">
<thead>
<tr>
<th width="60">ID</th>
<th>Name</th>
<th>Email</th>
<th>Note</th>
<th width="160">Created</th>
<th width="90">Action</th>
</tr>
</thead>
<tbody>
<tr><td colspan="6">暂无数据</td></tr>
<tr>
<td>id; ?></td>
<td>name); ?></td>
<td>email); ?></td>
<td>note, 12, '...')); ?></td>
<td>created_at); ?></td>
<td>
id),
'mwt_leads_delete_' . (int)$r->id
);
?>
<a href="<?php echo esc_url($del_url); ?>">Delete</a>
</td>
</tr>
</tbody>
</table>
1): ?>
<div class="tablenav">
<div class="tablenav-pages">
$base,
'format' => '&paged=%#%',
'current' => $paged,
'total' => $total_pages,
'prev_text' => '«',
'next_text' => '»',
]);
?>
</div>
</div>
<p style="color:#666;margin-top:12px;">
说明:这是最小可用示例,真实项目建议用 WP_List_Table 封装列表与批量操作。
</p>
</div>
评论 0