文章目录[隐藏]
详细指南:开发网站会员签到打卡与连续奖励积分系统
摘要
在当今互联网时代,用户参与度和忠诚度是网站成功的关键因素之一。会员签到打卡与连续奖励积分系统作为一种有效的用户激励工具,已被广泛应用于各类网站和应用程序中。本文将详细介绍如何通过WordPress程序的代码二次开发,实现一个功能完善的会员签到打卡与连续奖励积分系统。我们将从系统设计、数据库结构、核心功能实现、用户界面设计到系统测试与优化,全面解析开发过程,帮助开发者快速掌握这一常用互联网小工具的实现方法。
第一章:系统概述与设计思路
1.1 签到打卡系统的价值
签到打卡系统是一种简单而有效的用户参与机制,通过每日签到获取积分奖励的方式,鼓励用户养成访问网站的习惯。这种系统不仅提高了用户粘性,还能促进内容消费和社区互动。结合连续签到奖励机制,用户为了获得更高的积分奖励,会努力保持连续签到记录,从而形成良性的用户行为循环。
1.2 系统功能需求分析
一个完整的会员签到打卡与连续奖励积分系统应包含以下核心功能:
- 用户签到功能:允许用户每日签到一次,记录签到时间
- 连续签到追踪:跟踪用户连续签到天数,并在中断时重置
- 积分奖励机制:根据签到情况奖励相应积分,连续签到天数越多奖励越高
- 签到日历展示:可视化展示用户的签到历史记录
- 奖励规则配置:管理员可灵活配置积分奖励规则
- 用户数据统计:展示用户的签到统计信息
- 积分兑换功能:允许用户使用积分兑换虚拟或实物奖励
1.3 WordPress二次开发的优势
WordPress作为全球最流行的内容管理系统,拥有以下优势:
- 成熟的用户管理系统
- 丰富的插件架构和钩子机制
- 庞大的开发者社区支持
- 灵活的主题和插件扩展能力
- 良好的安全性和稳定性
通过WordPress二次开发实现签到系统,可以充分利用现有用户体系,减少重复开发工作,提高开发效率。
第二章:数据库设计与数据模型
2.1 数据表结构设计
为了实现签到打卡系统,我们需要在WordPress数据库中添加以下自定义数据表:
-- 用户签到记录表
CREATE TABLE wp_signin_records (
id INT(11) NOT NULL AUTO_INCREMENT,
user_id INT(11) NOT NULL,
signin_date DATE NOT NULL,
signin_time DATETIME NOT NULL,
continuous_days INT(11) DEFAULT 1,
points_earned INT(11) DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY user_date (user_id, signin_date),
KEY user_id (user_id),
KEY signin_date (signin_date)
);
-- 用户积分总表
CREATE TABLE wp_user_points (
id INT(11) NOT NULL AUTO_INCREMENT,
user_id INT(11) NOT NULL,
total_points INT(11) DEFAULT 0,
available_points INT(11) DEFAULT 0,
last_updated DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY user_id (user_id)
);
-- 积分奖励规则表
CREATE TABLE wp_points_rules (
id INT(11) NOT NULL AUTO_INCREMENT,
rule_name VARCHAR(100) NOT NULL,
continuous_days INT(11) NOT NULL,
points_award INT(11) NOT NULL,
is_active TINYINT(1) DEFAULT 1,
PRIMARY KEY (id)
);
-- 积分兑换记录表
CREATE TABLE wp_points_redemption (
id INT(11) NOT NULL AUTO_INCREMENT,
user_id INT(11) NOT NULL,
redemption_date DATETIME NOT NULL,
points_used INT(11) NOT NULL,
item_name VARCHAR(200) NOT NULL,
status VARCHAR(50) DEFAULT 'pending',
PRIMARY KEY (id),
KEY user_id (user_id)
);
2.2 数据关系与索引优化
为了提高查询效率,我们在关键字段上建立了索引:
wp_signin_records表的user_id和signin_date字段建立联合唯一索引,防止重复签到- 各表的
user_id字段建立普通索引,加快用户相关查询 signin_date字段建立索引,便于按日期范围查询统计
2.3 与WordPress用户表的关联
我们的签到系统需要与WordPress核心用户表wp_users进行关联,通过user_id字段建立外键关系,确保数据一致性。同时,我们可以利用WordPress的用户元数据表wp_usermeta存储一些额外的用户签到相关信息。
第三章:核心功能实现
3.1 签到功能模块
3.1.1 签到逻辑实现
<?php
/**
* 处理用户签到
*/
function handle_user_signin($user_id) {
global $wpdb;
// 检查用户是否已签到
$today = date('Y-m-d');
$table_name = $wpdb->prefix . 'signin_records';
$already_signed = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE user_id = %d AND signin_date = %s",
$user_id, $today
));
if ($already_signed > 0) {
return array(
'success' => false,
'message' => '今日已签到,请明天再来!'
);
}
// 获取用户昨日签到记录,计算连续签到天数
$yesterday = date('Y-m-d', strtotime('-1 day'));
$yesterday_record = $wpdb->get_row($wpdb->prepare(
"SELECT continuous_days FROM $table_name WHERE user_id = %d AND signin_date = %s",
$user_id, $yesterday
));
$continuous_days = 1;
if ($yesterday_record) {
$continuous_days = $yesterday_record->continuous_days + 1;
}
// 根据连续签到天数计算奖励积分
$points_earned = calculate_signin_points($continuous_days);
// 插入签到记录
$result = $wpdb->insert(
$table_name,
array(
'user_id' => $user_id,
'signin_date' => $today,
'signin_time' => current_time('mysql'),
'continuous_days' => $continuous_days,
'points_earned' => $points_earned
),
array('%d', '%s', '%s', '%d', '%d')
);
if ($result) {
// 更新用户总积分
update_user_points($user_id, $points_earned);
return array(
'success' => true,
'message' => '签到成功!',
'continuous_days' => $continuous_days,
'points_earned' => $points_earned
);
} else {
return array(
'success' => false,
'message' => '签到失败,请稍后重试!'
);
}
}
/**
* 根据连续签到天数计算奖励积分
*/
function calculate_signin_points($continuous_days) {
$base_points = 10; // 基础积分
// 连续签到额外奖励
$extra_points = 0;
if ($continuous_days >= 7) {
$extra_points = 30;
} elseif ($continuous_days >= 30) {
$extra_points = 100;
} elseif ($continuous_days >= 100) {
$extra_points = 500;
}
// 特殊日期额外奖励(如节假日)
$special_bonus = check_special_date_bonus();
return $base_points + $extra_points + $special_bonus;
}
?>
3.1.2 AJAX签到接口
<?php
/**
* 注册AJAX签到接口
*/
add_action('wp_ajax_user_signin', 'ajax_handle_signin');
add_action('wp_ajax_nopriv_user_signin', 'ajax_handle_signin_no_priv');
function ajax_handle_signin() {
// 验证用户登录状态
if (!is_user_logged_in()) {
wp_send_json_error('请先登录!');
}
$user_id = get_current_user_id();
$result = handle_user_signin($user_id);
if ($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result['message']);
}
}
function ajax_handle_signin_no_priv() {
wp_send_json_error('请先登录!');
}
?>
3.2 连续签到追踪模块
<?php
/**
* 获取用户连续签到天数
*/
function get_user_continuous_days($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'signin_records';
$today = date('Y-m-d');
// 获取今日签到记录
$today_record = $wpdb->get_row($wpdb->prepare(
"SELECT continuous_days FROM $table_name WHERE user_id = %d AND signin_date = %s",
$user_id, $today
));
if ($today_record) {
return $today_record->continuous_days;
}
// 如果今日未签到,检查昨日是否签到
$yesterday = date('Y-m-d', strtotime('-1 day'));
$yesterday_record = $wpdb->get_row($wpdb->prepare(
"SELECT continuous_days FROM $table_name WHERE user_id = %d AND signin_date = %s",
$user_id, $yesterday
));
if ($yesterday_record) {
return 0; // 今日未签到,但昨日已签到,连续天数中断
}
// 检查连续签到是否中断
return check_continuous_break($user_id);
}
/**
* 检查连续签到是否中断
*/
function check_continuous_break($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'signin_records';
// 获取最近一次签到记录
$latest_record = $wpdb->get_row($wpdb->prepare(
"SELECT signin_date, continuous_days FROM $table_name
WHERE user_id = %d ORDER BY signin_date DESC LIMIT 1",
$user_id
));
if (!$latest_record) {
return 0; // 从未签到
}
$last_signin_date = $latest_record->signin_date;
$days_since_last = floor((time() - strtotime($last_signin_date)) / (60 * 60 * 24));
// 如果距离上次签到超过1天,则连续签到中断
if ($days_since_last > 1) {
return 0;
}
return $latest_record->continuous_days;
}
?>
3.3 积分管理系统
<?php
/**
* 更新用户积分
*/
function update_user_points($user_id, $points_to_add) {
global $wpdb;
$table_name = $wpdb->prefix . 'user_points';
// 检查用户积分记录是否存在
$existing_record = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE user_id = %d",
$user_id
));
if ($existing_record) {
// 更新现有记录
$new_total = $existing_record->total_points + $points_to_add;
$new_available = $existing_record->available_points + $points_to_add;
$wpdb->update(
$table_name,
array(
'total_points' => $new_total,
'available_points' => $new_available,
'last_updated' => current_time('mysql')
),
array('user_id' => $user_id),
array('%d', '%d', '%s'),
array('%d')
);
} else {
// 创建新记录
$wpdb->insert(
$table_name,
array(
'user_id' => $user_id,
'total_points' => $points_to_add,
'available_points' => $points_to_add,
'last_updated' => current_time('mysql')
),
array('%d', '%d', '%d', '%s')
);
}
// 记录积分变动日志
log_points_change($user_id, $points_to_add, 'signin_reward', '签到奖励');
}
/**
* 积分兑换功能
*/
function redeem_points($user_id, $item_id, $points_required) {
global $wpdb;
// 检查用户可用积分
$user_points = $wpdb->get_row($wpdb->prepare(
"SELECT available_points FROM {$wpdb->prefix}user_points WHERE user_id = %d",
$user_id
));
if (!$user_points || $user_points->available_points < $points_required) {
return array(
'success' => false,
'message' => '积分不足,无法兑换!'
);
}
// 获取兑换物品信息
$item_info = get_redemption_item($item_id);
if (!$item_info) {
return array(
'success' => false,
'message' => '兑换物品不存在!'
);
}
// 开始事务处理
$wpdb->query('START TRANSACTION');
try {
// 扣除用户积分
$wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}user_points
SET available_points = available_points - %d
WHERE user_id = %d",
$points_required, $user_id
));
// 记录兑换记录
$wpdb->insert(
$wpdb->prefix . 'points_redemption',
array(
'user_id' => $user_id,
'redemption_date' => current_time('mysql'),
'points_used' => $points_required,
'item_name' => $item_info['name'],
'status' => 'pending'
),
array('%d', '%s', '%d', '%s', '%s')
);
$wpdb->query('COMMIT');
return array(
'success' => true,
'message' => '兑换成功,请等待管理员处理!'
);
} catch (Exception $e) {
$wpdb->query('ROLLBACK');
return array(
'success' => false,
'message' => '兑换失败:' . $e->getMessage()
);
}
}
?>
第四章:用户界面设计与实现
4.1 签到界面设计
<?php
/**
* 签到界面短代码
*/
add_shortcode('signin_system', 'display_signin_system');
function display_signin_system() {
if (!is_user_logged_in()) {
return '<div class="signin-notice">请先登录以使用签到功能</div>';
}
$user_id = get_current_user_id();
$user_info = get_userdata($user_id);
$continuous_days = get_user_continuous_days($user_id);
$today_signed = check_today_signin($user_id);
ob_start();
?>
<div class="signin-container">
<div class="signin-header">
<h2>每日签到</h2>
<p>连续签到可获得更多积分奖励</p>
</div>
<div class="user-stats">
<div class="stat-item">
<span class="stat-label">用户名</span>
<span class="stat-value"><?php echo esc_html($user_info->display_name); ?></span>
</div>
<div class="stat-item">
<span class="stat-label">连续签到</span>
<span class="stat-value"><?php echo $continuous_days; ?> 天</span>
</div>
<div class="stat-item">
<span class="stat-label">总积分</span>
<span class="stat-value"><?php echo get_user_total_points($user_id); ?></span>
</div>
</div>
<div class="signin-main">
<?php if ($today_signed): ?>
<div class="signin-already">
<div class="signin-icon">✓</div>
<h3>今日已签到</h3>
<p>您已连续签到 <?php echo $continuous_days; ?> 天</p>
<p>明日再来可获得更多积分!</p>
</div>
<?php else: ?>
<div class="signin-available" id="signin-box">
<div class="signin-icon">📅</div>
<h3>立即签到</h3>
<p>今日签到可获得 <span class="points-preview"><?php echo calculate_signin_points($continuous_days + 1); ?></span> 积分</p>
<button class="signin-button" id="signin-button">立即签到</button>
<div class="signin-loading" id="signin-loading" style="display:none;">
处理中...
</div>
</div>
<?php endif; ?>
</div>
<div class="signin-calendar">
<h3>签到日历</h3>
<?php display_signin_calendar($user_id); ?>
</div>
<div class="rewards-info">
<h3>连续签到奖励规则</h3>
<ul class="rewards-list">
<li>第1-6天:每天10积分</li>
<li>第7天:额外奖励30积分</li>
<li>第30天:额外奖励100积分</li>
<li>第100天:额外奖励500积分</li>
<li>节假日:额外奖励20积分</li>
</ul>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('#signin-button').click(function() {
var button = $(this);
var loading = $('#signin-loading');
button.prop('disabled', true).text('签到中...');
loading.show();
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'user_signin',
nonce: '<?php echo wp_create_nonce('signin_nonce'); ?>'
},
success: function(response) {
if (response.success) {
// 更新界面显示
$('#signin-box').html(`
<div class="signin-success">
<div class="signin-icon">🎉</div>
<h3>签到成功!</h3>
<p>获得 <span class="points-earned">${response.data.points_earned}</span> 积分</p>
<p>连续签到天数:${response.data.continuous_days} 天</p>
<p>明日签到可获得更多积分!</p>
</div>
`);
// 更新用户统计信息
setTimeout(function() {
location.reload();
}, 2000);
} else {
alert(response.data);
button.prop('disabled', false).text('立即签到');
loading.hide();
}
},
error: function() {
alert('签到失败,请稍后重试!');
button.prop('disabled', false).text('立即签到');
loading.hide();
}
});
});
});
</script>
<?php
return ob_get_clean();
}
/**
- 显示签到日历
*/
function display_signin_calendar($user_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'signin_records';
$current_month = date('n');
$current_year = date('Y');
// 获取本月签到记录
$signin_records = $wpdb->get_results($wpdb->prepare(
"SELECT DAY(signin_date) as day, points_earned
FROM $table_name
WHERE user_id = %d
AND MONTH(signin_date) = %d
AND YEAR(signin_date) = %d",
$user_id, $current_month, $current_year
));
// 创建签到记录映射
$signin_map = array();
foreach ($signin_records as $record) {
$signin_map[$record->day] = $record->points_earned;
}
// 生成日历
$first_day = mktime(0, 0, 0, $current_month, 1, $current_year);
$days_in_month = date('t', $first_day);
$first_day_of_week = date('w', $first_day);
ob_start();
?>
<div class="calendar">
<div class="calendar-header">
<span class="month-year"><?php echo date('Y年n月'); ?></span>
</div>
<div class="calendar-weekdays">
<div>日</div>
<div>一</div>
<div>二</div>
<div>三</div>
<div>四</div>
<div>五</div>
<div>六</div>
</div>
<div class="calendar-days">
<?php
// 填充空白
for ($i = 0; $i < $first_day_of_week; $i++) {
echo '<div class="empty"></div>';
}
// 填充日期
for ($day = 1; $day <= $days_in_month; $day++) {
$is_today = ($day == date('j'));
$has_signed = isset($signin_map[$day]);
$class = 'day';
if ($is_today) $class .= ' today';
if ($has_signed) $class .= ' signed';
echo '<div class="' . $class . '">';
echo '<span class="day-number">' . $day . '</span>';
if ($has_signed) {
echo '<span class="signin-indicator" title="获得' . $signin_map[$day] . '积分">✓</span>';
}
echo '</div>';
}
?>
</div>
</div>
<?php
return ob_get_clean();
}
?>
### 4.2 用户积分中心
<?php
/**
- 用户积分中心短代码
*/
add_shortcode('points_center', 'display_points_center');
function display_points_center() {
if (!is_user_logged_in()) {
return '<div class="points-center-notice">请先登录以查看积分中心</div>';
}
$user_id = get_current_user_id();
$points_data = get_user_points_data($user_id);
$redemption_items = get_redemption_items();
$points_history = get_points_history($user_id, 10);
ob_start();
?>
<div class="points-center-container">
<div class="points-overview">
<div class="points-card">
<h3>我的积分</h3>
<div class="points-total">
<span class="points-number"><?php echo $points_data['available_points']; ?></span>
<span class="points-label">可用积分</span>
</div>
<div class="points-details">
<div class="detail-item">
<span>总获得积分</span>
<span><?php echo $points_data['total_points']; ?></span>
</div>
<div class="detail-item">
<span>已使用积分</span>
<span><?php echo $points_data['total_points'] - $points_data['available_points']; ?></span>
</div>
</div>
</div>
<div class="points-actions">
<h3>快速操作</h3>
<button class="btn-checkin" onclick="location.href='#signin-system'">每日签到</button>
<button class="btn-history" id="view-history">查看积分记录</button>
<button class="btn-redeem" id="show-redeem-modal">积分兑换</button>
</div>
</div>
<div class="points-history-section">
<h3>最近积分记录</h3>
<table class="points-history-table">
<thead>
<tr>
<th>日期</th>
<th>类型</th>
<th>积分变动</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<?php foreach ($points_history as $record): ?>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime($record->change_time)); ?></td>
<td><?php echo get_points_change_type_label($record->change_type); ?></td>
<td class="<?php echo $record->points_change >= 0 ? 'points-increase' : 'points-decrease'; ?>">
<?php echo ($record->points_change >= 0 ? '+' : '') . $record->points_change; ?>
</td>
<td><?php echo esc_html($record->description); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="redemption-section">
<h3>积分兑换商城</h3>
<div class="redemption-items">
<?php foreach ($redemption_items as $item): ?>
<div class="redemption-item">
<div class="item-image">
<img src="<?php echo esc_url($item['image']); ?>" alt="<?php echo esc_attr($item['name']); ?>">
</div>
<div class="item-info">
<h4><?php echo esc_html($item['name']); ?></h4>
<p class="item-description"><?php echo esc_html($item['description']); ?></p>
<div class="item-points">
<span class="points-required"><?php echo $item['points_required']; ?> 积分</span>
<?php if ($points_data['available_points'] >= $item['points_required']): ?>
<button class="btn-redeem-item"
data-item-id="<?php echo $item['id']; ?>"
data-item-name="<?php echo esc_attr($item['name']); ?>"
data-points-required="<?php echo $item['points_required']; ?>">
立即兑换
</button>
<?php else: ?>
<button class="btn-redeem-item disabled" disabled>积分不足</button>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- 兑换确认模态框 -->
<div id="redeem-modal" class="modal">
<div class="modal-content">
<span class="close-modal">×</span>
<h3>确认兑换</h3>
<div id="redeem-item-info"></div>
<div class="modal-actions">
<button id="confirm-redeem" class="btn-confirm">确认兑换</button>
<button id="cancel-redeem" class="btn-cancel">取消</button>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// 兑换按钮点击事件
$('.btn-redeem-item').click(function() {
var itemId = $(this).data('item-id');
var itemName = $(this).data('item-name');
var pointsRequired = $(this).data('points-required');
$('#redeem-item-info').html(`
<p>您要兑换:<strong>${itemName}</strong></p>
<p>需要积分:<strong>${pointsRequired}</strong></p>
<p>兑换后积分将立即扣除,兑换物品将在3个工作日内处理。</p>
`);
$('#redeem-modal').data('item-id', itemId);
$('#redeem-modal').data('points-required', pointsRequired);
$('#redeem-modal').show();
});
// 确认兑换
$('#confirm-redeem').click(function() {
var itemId = $('#redeem-modal').data('item-id');
var pointsRequired = $('#redeem-modal').data('points-required');
$.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'redeem_points',
item_id: itemId,
points_required: pointsRequired,
nonce: '<?php echo wp_create_nonce('redeem_nonce'); ?>'
},
success: function(response) {
if (response.success) {
alert('兑换成功!');
location.reload();
} else {
alert('兑换失败:' + response.data);
}
},
error: function() {
alert('兑换失败,请稍后重试!');
}
});
});
// 关闭模态框
$('.close-modal, #cancel-redeem').click(function() {
$('#redeem-modal').hide();
});
});
</script>
<?php
return ob_get_clean();
}
?>
---
## 第五章:后台管理功能
### 5.1 管理菜单与界面
<?php
/**
- 添加管理菜单
*/
add_action('admin_menu', 'register_signin_admin_menu');
function register_signin_admin_menu() {
add_menu_page(
'签到管理系统',
'签到管理',
'manage_options',
'signin-management',
'display_signin_admin_page',
'dashicons-calendar-alt',
30
);
add_submenu_page(
'signin-management',
'签到记录',
'签到记录',
'manage_options',
'signin-records',
'display_signin_records_page'
);
add_submenu_page(
'signin-management',
'积分规则',
'积分规则',
'manage_options',
'points-rules',
'display_points_rules_page'
);
add_submenu_page(
'signin-management',
'兑换管理',
'兑换管理',
'manage_options',
'redemption-management',
'display_redemption_management_page'
);
add_submenu_page(
'signin-management',
'数据统计',
'数据统计',
'manage_options',
'signin-statistics',
'display_signin_statistics_page'
);
}
/**
- 显示签到记录管理页面
*/
function display_signin_records_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'signin_records';
$users_table = $wpdb->users;
// 处理搜索和筛选
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
$date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
// 构建查询条件
$where = array('1=1');
$query_params = array();
if (!empty($search)) {
$where[] = "(u.user_login LIKE %s OR u.display_name LIKE %s)";
$query_params[] = '%' . $wpdb->esc_like($search) . '%';
$query_params[] = '%' . $wpdb->esc_like($search) . '%';
}
if (!empty($date_from)) {
$where[] = "s.signin_date >= %s";
$query_params[] = $date_from;
}
if (!empty($date_to)) {
$where[] = "s.signin_date <= %s";
$query_params[] = $date_to;
}
$where_clause = implode(' AND ', $where);
// 获取总记录数
$total_query = "SELECT COUNT(*) FROM $table_name s
INNER JOIN $users_table u ON s.user_id = u.ID
WHERE $where_clause";
if (!empty($query_params)) {
$total_query = $wpdb->prepare($total_query, $query_params);
}
$total_items = $wpdb->get_var($total_query);
// 分页设置
$per_page = 20;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
// 获取记录数据
$query = "SELECT s.*, u.user_login, u.display_name
FROM $table_name s
INNER JOIN $users_table u ON s.user_id = u.ID
WHERE $where_clause
ORDER BY s.signin_date DESC, s.signin_time DESC
LIMIT %d OFFSET %d";
$query_params[] = $per_page;
$query_params[] = $offset;
$records = $wpdb->get_results($wpdb->prepare($query, $query_params));
ob_start();
?>
<div class="wrap">
<h1 class="wp-heading-inline">签到记录管理</h1>
<form method="get" action="" class="search-form">
<input type="hidden" name="page" value="signin-records">
<div class="tablenav top">
<div class="alignleft actions">
<input type="text" name="s" value="<?php echo esc_attr($search); ?>"
placeholder="搜索用户名或显示名称">
<label>从:<input type="date" name="date_from" value="<?php echo esc_attr($date_from); ?>"></label>
<label>到:<input type="date" name="date_to" value="<?php echo esc_attr($date_to); ?>"></label>
<input type="submit" class="button" value="筛选">
<?php if (!empty($search) || !empty($date_from) || !empty($date_to)): ?>
<a href="?page=signin-records" class="button">清除筛选</a>
<?php endif; ?>
</div>
<div class="tablenav-pages">
<?php
$total_pages = ceil($total_items / $per_page);
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '«',
'next_text' => '»',
'total' => $total_pages,
'current' => $current_page
));
?>
</div>
</div>
</form>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>用户</th>
<th>签到日期</th>
<th>签到时间</th>
<th>连续天数</th>
<th>获得积分</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($records)): ?>
<tr>
<td colspan="6" style="text-align: center;">暂无签到记录</td>
</tr>
<?php else: ?>
<?php foreach ($records as $record): ?>
<tr>
<td>
<strong><?php echo esc_html($record->display_name); ?></strong><br>
<small><?php echo esc_html($record->user_login); ?></small>
</td>
<td><?php echo esc_html($record->signin_date); ?></td>
<td><?php echo esc_html($record->signin_time); ?></td>
<td><?php echo esc_html($record->continuous_days); ?></td>
<td><?php echo esc_html($record->points_earned); ?></td>
<td>
<button class="button button-small"
onclick="deleteSigninRecord(<?php echo $record->id; ?
