文章目录[隐藏]
实战教程:在WordPress网站中添加在线个人习惯追踪与目标管理小程序
引言:为什么网站需要个人习惯追踪功能?
在当今快节奏的数字时代,个人效率管理和习惯养成已成为许多人关注的焦点。研究表明,习惯追踪能够提高目标达成率高达42%,而将这一功能集成到个人或专业网站中,不仅能提升用户体验,还能增加网站粘性和实用价值。
WordPress作为全球最流行的内容管理系统,其强大的可扩展性使其成为实现此类功能的理想平台。本教程将指导您通过代码二次开发,在WordPress网站中集成一个完整的在线个人习惯追踪与目标管理小程序,让您的网站从单纯的内容展示平台转变为实用的个人成长工具。
第一部分:项目规划与准备工作
1.1 功能需求分析
在开始编码之前,我们需要明确小程序的核心功能:
- 用户习惯管理:创建、编辑、删除个人习惯
- 进度追踪:每日习惯打卡与进度可视化
- 目标设定:短期与长期目标管理
- 数据统计:习惯坚持率、完成趋势分析
- 提醒功能:邮件或站内消息提醒
- 社交分享:成就分享到社交媒体
- 数据导出:支持CSV格式数据导出
1.2 技术栈选择
- 前端:HTML5、CSS3、JavaScript (使用Vue.js或React简化开发)
- 后端:PHP (WordPress原生支持)
- 数据库:MySQL (WordPress数据库)
- 图表库:Chart.js 或 Google Charts
- 日期处理:Moment.js
- 开发环境:本地WordPress安装或开发服务器
1.3 开发环境搭建
- 安装本地服务器环境(如XAMPP、MAMP或Local by Flywheel)
- 下载最新版WordPress并完成安装
- 创建子主题或自定义插件目录结构
- 安装代码编辑器(如VS Code)并配置PHP开发环境
第二部分:数据库设计与数据模型
2.1 自定义数据库表设计
为了存储习惯追踪数据,我们需要在WordPress数据库中添加几个自定义表:
-- 习惯表
CREATE TABLE wp_habits (
habit_id INT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(20) UNSIGNED NOT NULL,
habit_name VARCHAR(255) NOT NULL,
habit_description TEXT,
habit_category VARCHAR(100),
frequency ENUM('daily', 'weekly', 'monthly') DEFAULT 'daily',
target_days INT DEFAULT 7,
start_date DATE NOT NULL,
end_date DATE,
color_code VARCHAR(7) DEFAULT '#3498db',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES wp_users(ID)
);
-- 习惯记录表
CREATE TABLE wp_habit_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
habit_id INT NOT NULL,
user_id BIGINT(20) UNSIGNED NOT NULL,
log_date DATE NOT NULL,
status ENUM('completed', 'skipped', 'failed') DEFAULT 'completed',
notes TEXT,
duration INT COMMENT '完成习惯所用时间(分钟)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_habit_date (habit_id, log_date),
FOREIGN KEY (habit_id) REFERENCES wp_habits(habit_id),
FOREIGN KEY (user_id) REFERENCES wp_users(ID)
);
-- 目标表
CREATE TABLE wp_goals (
goal_id INT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(20) UNSIGNED NOT NULL,
goal_name VARCHAR(255) NOT NULL,
goal_description TEXT,
goal_type ENUM('habit_based', 'milestone', 'quantitative') DEFAULT 'habit_based',
target_value DECIMAL(10,2),
current_value DECIMAL(10,2) DEFAULT 0,
start_date DATE NOT NULL,
target_date DATE,
status ENUM('active', 'completed', 'abandoned') DEFAULT 'active',
related_habit_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES wp_users(ID),
FOREIGN KEY (related_habit_id) REFERENCES wp_habits(habit_id)
);
2.2 WordPress数据表集成策略
虽然我们创建了自定义表,但仍需确保与WordPress用户系统的无缝集成:
- 使用WordPress的
$wpdb类进行数据库操作 - 利用WordPress的用户认证系统
- 遵循WordPress的数据转义和安全规范
- 考虑使用自定义Post Type作为替代方案(适合简单需求)
第三部分:创建WordPress插件框架
3.1 插件基础结构
创建插件目录wp-content/plugins/habit-tracker/,并添加以下文件:
<?php
/*
Plugin Name: 个人习惯追踪与目标管理
Plugin URI: https://yourwebsite.com/habit-tracker
Description: 在WordPress网站中添加在线个人习惯追踪与目标管理功能
Version: 1.0.0
Author: 您的名称
License: GPL v2 or later
Text Domain: habit-tracker
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('HABIT_TRACKER_VERSION', '1.0.0');
define('HABIT_TRACKER_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('HABIT_TRACKER_PLUGIN_URL', plugin_dir_url(__FILE__));
// 数据库表版本
define('HABIT_TRACKER_DB_VERSION', '1.0');
// 包含必要文件
require_once HABIT_TRACKER_PLUGIN_DIR . 'includes/class-database.php';
require_once HABIT_TRACKER_PLUGIN_DIR . 'includes/class-habit-tracker.php';
require_once HABIT_TRACKER_PLUGIN_DIR . 'includes/class-shortcodes.php';
require_once HABIT_TRACKER_PLUGIN_DIR . 'includes/class-ajax-handler.php';
// 初始化插件
function habit_tracker_init() {
$plugin = new Habit_Tracker();
$plugin->run();
}
add_action('plugins_loaded', 'habit_tracker_init');
// 激活插件时创建数据库表
register_activation_hook(__FILE__, array('Habit_Tracker_Database', 'create_tables'));
// 停用插件时的清理操作
register_deactivation_hook(__FILE__, array('Habit_Tracker_Database', 'cleanup'));
3.2 数据库操作类
创建includes/class-database.php文件:
<?php
class Habit_Tracker_Database {
private static $table_prefix;
public static function init() {
global $wpdb;
self::$table_prefix = $wpdb->prefix . 'habit_';
}
// 创建数据库表
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = array();
// 创建习惯表
$sql[] = "CREATE TABLE " . self::$table_prefix . "habits (
habit_id INT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(20) UNSIGNED NOT NULL,
habit_name VARCHAR(255) NOT NULL,
habit_description TEXT,
habit_category VARCHAR(100),
frequency ENUM('daily', 'weekly', 'monthly') DEFAULT 'daily',
target_days INT DEFAULT 7,
start_date DATE NOT NULL,
end_date DATE,
color_code VARCHAR(7) DEFAULT '#3498db',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) $charset_collate;";
// 创建习惯记录表
$sql[] = "CREATE TABLE " . self::$table_prefix . "logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
habit_id INT NOT NULL,
user_id BIGINT(20) UNSIGNED NOT NULL,
log_date DATE NOT NULL,
status ENUM('completed', 'skipped', 'failed') DEFAULT 'completed',
notes TEXT,
duration INT COMMENT '完成习惯所用时间(分钟)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_habit_date (habit_id, log_date)
) $charset_collate;";
// 创建目标表
$sql[] = "CREATE TABLE " . self::$table_prefix . "goals (
goal_id INT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(20) UNSIGNED NOT NULL,
goal_name VARCHAR(255) NOT NULL,
goal_description TEXT,
goal_type ENUM('habit_based', 'milestone', 'quantitative') DEFAULT 'habit_based',
target_value DECIMAL(10,2),
current_value DECIMAL(10,2) DEFAULT 0,
start_date DATE NOT NULL,
target_date DATE,
status ENUM('active', 'completed', 'abandoned') DEFAULT 'active',
related_habit_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
foreach ($sql as $query) {
dbDelta($query);
}
add_option('habit_tracker_db_version', HABIT_TRACKER_DB_VERSION);
}
// 获取用户的所有习惯
public static function get_user_habits($user_id, $active_only = true) {
global $wpdb;
$table_name = self::$table_prefix . 'habits';
$condition = $active_only ? " AND is_active = 1" : "";
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $table_name WHERE user_id = %d $condition ORDER BY created_at DESC",
$user_id
)
);
}
// 添加更多数据库操作方法...
}
第四部分:前端界面开发
4.1 主界面HTML结构
创建templates/dashboard.php文件:
<div class="habit-tracker-container">
<div class="habit-tracker-header">
<h1>个人习惯追踪与目标管理</h1>
<div class="date-navigation">
<button class="prev-date">← 前一天</button>
<span class="current-date"><?php echo date('Y年m月d日'); ?></span>
<button class="next-date">后一天 →</button>
</div>
</div>
<div class="habit-tracker-main">
<!-- 左侧习惯管理区域 -->
<div class="habits-section">
<div class="section-header">
<h2>我的习惯</h2>
<button class="btn-add-habit">+ 添加新习惯</button>
</div>
<div class="habits-list">
<!-- 习惯项将通过JavaScript动态加载 -->
<div class="loading-habits">加载习惯中...</div>
</div>
<div class="habits-stats">
<div class="stat-card">
<div class="stat-value" id="current-streak">0</div>
<div class="stat-label">当前连续天数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="completion-rate">0%</div>
<div class="stat-label">本月完成率</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-habits">0</div>
<div class="stat-label">活跃习惯</div>
</div>
</div>
</div>
<!-- 右侧目标与统计区域 -->
<div class="goals-stats-section">
<div class="goals-section">
<div class="section-header">
<h2>我的目标</h2>
<button class="btn-add-goal">+ 添加新目标</button>
</div>
<div class="goals-list">
<!-- 目标项将通过JavaScript动态加载 -->
</div>
</div>
<div class="stats-section">
<div class="section-header">
<h2>习惯统计</h2>
<select id="stats-period">
<option value="week">本周</option>
<option value="month" selected>本月</option>
<option value="year">今年</option>
<option value="all">全部</option>
</select>
</div>
<div class="charts-container">
<canvas id="completionChart" width="400" height="200"></canvas>
<canvas id="categoryChart" width="400" height="200"></canvas>
</div>
</div>
</div>
</div>
<!-- 添加习惯模态框 -->
<div class="modal" id="addHabitModal">
<div class="modal-content">
<span class="close-modal">×</span>
<h3>添加新习惯</h3>
<form id="habitForm">
<div class="form-group">
<label for="habitName">习惯名称 *</label>
<input type="text" id="habitName" name="habit_name" required>
</div>
<div class="form-group">
<label for="habitDescription">习惯描述</label>
<textarea id="habitDescription" name="habit_description" rows="3"></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="habitCategory">分类</label>
<select id="habitCategory" name="habit_category">
<option value="health">健康</option>
<option value="work">工作</option>
<option value="learning">学习</option>
<option value="finance">财务</option>
<option value="relationship">人际关系</option>
<option value="hobby">兴趣爱好</option>
</select>
</div>
<div class="form-group">
<label for="habitFrequency">频率</label>
<select id="habitFrequency" name="frequency">
<option value="daily">每日</option>
<option value="weekly">每周</option>
<option value="monthly">每月</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="targetDays">目标天数 (周)</label>
<input type="number" id="targetDays" name="target_days" min="1" max="7" value="7">
</div>
<div class="form-group">
<label for="habitColor">颜色标识</label>
<input type="color" id="habitColor" name="color_code" value="#3498db">
</div>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel">取消</button>
<button type="submit" class="btn-submit">创建习惯</button>
</div>
</form>
</div>
</div>
</div>
4.2 CSS样式设计
创建assets/css/habit-tracker.css文件:
/* 主容器样式 */
.habit-tracker-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
.habit-tracker-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #eaeaea;
}
.habit-tracker-header h1 {
margin: 0;
color: #2c3e50;
font-size: 28px;
}
.date-navigation {
display: flex;
align-items: center;
gap: 15px;
}
.date-navigation button {
background: #f8f9fa;
border: 1px solid #ddd;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.date-navigation button:hover {
background: #e9ecef;
}
.current-date {
font-weight: 600;
color: #495057;
}
/* 主内容区域 */
.habit-tracker-main {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
@media (max-width: 992px) {
.habit-tracker-main {
grid-template-columns: 1fr;
}
}
/* 习惯列表样式 */
.habits-section, .goals-section, .stats-section {
background: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.section-header h2 {
margin: 0;
color: #2c3e50;
font-size: 22px;
}
.btn-add-habit, .btn-add-goal {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: background 0.3s;
}
.btn-add-habit:hover, .btn-add-goal:hover {
background: #2980b9;
}
/* 习惯项样式 */
.habit-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
margin-bottom: 15px;
border-radius: 6px;
background: #f8f9fa;
border-left: 4px solid #3498db;
transition: transform 0.2s;
}
.habit-item:hover {
transform: translateY(-2px);
(接上文)
.habit-info {
flex: 1;
}
.habit-name {
font-weight: 600;
color: #2c3e50;
margin-bottom: 5px;
font-size: 16px;
}
.habit-meta {
display: flex;
gap: 15px;
font-size: 14px;
color: #6c757d;
}
.habit-category {
background: #e9ecef;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
}
.habit-actions {
display: flex;
gap: 10px;
align-items: center;
}
.check-habit {
width: 24px;
height: 24px;
border: 2px solid #dee2e6;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.check-habit.checked {
background: #28a745;
border-color: #28a745;
color: white;
}
.check-habit.checked::after {
content: "✓";
font-size: 14px;
}
/* 统计卡片样式 */
.habits-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 25px;
}
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
text-align: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
border: 1px solid #eaeaea;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #3498db;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
color: #6c757d;
}
/* 目标项样式 */
.goal-item {
padding: 15px;
margin-bottom: 15px;
background: #f8f9fa;
border-radius: 6px;
border-left: 4px solid #9b59b6;
}
.goal-progress {
margin-top: 10px;
}
.progress-bar {
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #9b59b6, #8e44ad);
border-radius: 4px;
transition: width 0.5s ease;
}
.goal-details {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 14px;
color: #6c757d;
}
/* 图表容器 */
.charts-container {
margin-top: 20px;
}
.charts-container canvas {
max-width: 100%;
margin-bottom: 20px;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
padding: 30px;
position: relative;
animation: modalSlideIn 0.3s ease;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.close-modal {
position: absolute;
top: 15px;
right: 20px;
font-size: 24px;
cursor: pointer;
color: #6c757d;
}
.close-modal:hover {
color: #343a40;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
}
.form-group input[type="text"],
.form-group input[type="number"],
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52,152,219,0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 15px;
margin-top: 30px;
}
.btn-cancel, .btn-submit {
padding: 12px 25px;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.3s;
}
.btn-cancel {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #dee2e6;
}
.btn-cancel:hover {
background: #e9ecef;
}
.btn-submit {
background: #3498db;
color: white;
}
.btn-submit:hover {
background: #2980b9;
}
/* 响应式调整 */
@media (max-width: 768px) {
.habit-tracker-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.form-row {
grid-template-columns: 1fr;
}
.habits-stats {
grid-template-columns: 1fr;
}
}
第五部分:JavaScript交互功能
5.1 主JavaScript文件
创建assets/js/habit-tracker.js文件:
// 习惯追踪器主应用
class HabitTracker {
constructor() {
this.currentDate = new Date();
this.habits = [];
this.goals = [];
this.stats = {};
this.init();
}
init() {
// 绑定事件监听器
this.bindEvents();
// 加载初始数据
this.loadUserData();
// 初始化图表
this.initCharts();
}
bindEvents() {
// 日期导航
document.querySelector('.prev-date')?.addEventListener('click', () => this.changeDate(-1));
document.querySelector('.next-date')?.addEventListener('click', () => this.changeDate(1));
// 添加习惯按钮
document.querySelector('.btn-add-habit')?.addEventListener('click', () => this.showAddHabitModal());
// 模态框关闭
document.querySelector('.close-modal')?.addEventListener('click', () => this.hideModal());
document.querySelector('.btn-cancel')?.addEventListener('click', () => this.hideModal());
// 习惯表单提交
document.getElementById('habitForm')?.addEventListener('submit', (e) => this.handleAddHabit(e));
// 统计周期选择
document.getElementById('stats-period')?.addEventListener('change', (e) => this.updateStats(e.target.value));
// 点击模态框外部关闭
document.querySelector('.modal')?.addEventListener('click', (e) => {
if (e.target.classList.contains('modal')) {
this.hideModal();
}
});
}
// 加载用户数据
async loadUserData() {
try {
// 加载习惯
const habitsResponse = await this.ajaxRequest('get_user_habits');
this.habits = habitsResponse.data || [];
// 加载目标
const goalsResponse = await this.ajaxRequest('get_user_goals');
this.goals = goalsResponse.data || [];
// 加载今日习惯状态
const todayStatus = await this.ajaxRequest('get_today_status');
// 渲染数据
this.renderHabits();
this.renderGoals();
this.updateStats('month');
} catch (error) {
console.error('加载数据失败:', error);
this.showMessage('加载数据失败,请刷新页面重试', 'error');
}
}
// 渲染习惯列表
renderHabits() {
const habitsList = document.querySelector('.habits-list');
if (!habitsList) return;
if (this.habits.length === 0) {
habitsList.innerHTML = `
<div class="empty-state">
<p>还没有添加任何习惯</p>
<button class="btn-add-habit">添加第一个习惯</button>
</div>
`;
return;
}
let html = '';
this.habits.forEach(habit => {
const isChecked = habit.today_status === 'completed';
html += `
<div class="habit-item" data-habit-id="${habit.habit_id}" style="border-left-color: ${habit.color_code}">
<div class="habit-info">
<div class="habit-name">${habit.habit_name}</div>
<div class="habit-meta">
<span class="habit-category">${this.getCategoryName(habit.habit_category)}</span>
<span>已坚持 ${habit.current_streak || 0} 天</span>
<span>${habit.completion_rate || 0}% 完成率</span>
</div>
</div>
<div class="habit-actions">
<div class="check-habit ${isChecked ? 'checked' : ''}"
onclick="habitTracker.toggleHabit(${habit.habit_id})">
</div>
<button class="btn-habit-detail" onclick="habitTracker.showHabitDetail(${habit.habit_id})">
详情
</button>
</div>
</div>
`;
});
habitsList.innerHTML = html;
// 更新统计卡片
this.updateStatCards();
}
// 渲染目标列表
renderGoals() {
const goalsList = document.querySelector('.goals-list');
if (!goalsList) return;
if (this.goals.length === 0) {
goalsList.innerHTML = `
<div class="empty-state">
<p>还没有设置任何目标</p>
<button class="btn-add-goal">添加第一个目标</button>
</div>
`;
return;
}
let html = '';
this.goals.forEach(goal => {
const progress = goal.target_value > 0 ? (goal.current_value / goal.target_value * 100) : 0;
const progressPercent = Math.min(100, Math.round(progress));
html += `
<div class="goal-item" data-goal-id="${goal.goal_id}">
<div class="goal-info">
<div class="goal-name">${goal.goal_name}</div>
<div class="goal-description">${goal.goal_description || ''}</div>
</div>
<div class="goal-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: ${progressPercent}%"></div>
</div>
<div class="goal-details">
<span>${goal.current_value} / ${goal.target_value}</span>
<span>${progressPercent}%</span>
</div>
</div>
</div>
`;
});
goalsList.innerHTML = html;
}
// 切换习惯完成状态
async toggleHabit(habitId) {
try {
const response = await this.ajaxRequest('toggle_habit_status', {
habit_id: habitId,
date: this.formatDate(this.currentDate)
});
if (response.success) {
// 更新本地数据
const habit = this.habits.find(h => h.habit_id == habitId);
if (habit) {
habit.today_status = habit.today_status === 'completed' ? null : 'completed';
habit.current_streak = response.data.current_streak;
habit.completion_rate = response.data.completion_rate;
}
// 重新渲染
this.renderHabits();
this.updateStats();
this.showMessage('习惯状态已更新', 'success');
}
} catch (error) {
console.error('更新习惯状态失败:', error);
this.showMessage('更新失败,请重试', 'error');
}
}
// 显示添加习惯模态框
showAddHabitModal() {
const modal = document.getElementById('addHabitModal');
if (modal) {
modal.style.display = 'flex';
document.getElementById('habitName').focus();
}
}
// 隐藏模态框
hideModal() {
const modal = document.getElementById('addHabitModal');
if (modal) {
modal.style.display = 'none';
document.getElementById('habitForm').reset();
}
}
// 处理添加习惯表单提交
async handleAddHabit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const habitData = {
habit_name: formData.get('habit_name'),
habit_description: formData.get('habit_description'),
habit_category: formData.get('habit_category'),
frequency: formData.get('frequency'),
target_days: parseInt(formData.get('target_days')),
color_code: formData.get('color_code'),
start_date: this.formatDate(new Date())
};
try {
const response = await this.ajaxRequest('add_habit', habitData);
if (response.success) {
// 添加新习惯到列表
this.habits.unshift(response.data);
// 重新渲染
this.renderHabits();
this.hideModal();
this.showMessage('习惯添加成功', 'success');
}
} catch (error) {
console.error('添加习惯失败:', error);
this.showMessage('添加失败,请重试', 'error');
}
}
// 更新统计卡片
updateStatCards() {
if (this.habits.length === 0) return;
// 计算最长连续天数
const longestStreak = Math.max(...this.habits.map(h => h.current_streak || 0));
// 计算本月完成率
const completedHabits = this.habits.filter(h => h.today_status === 'completed').length;
const completionRate = this.habits.length > 0 ?
Math.round((completedHabits / this.habits.length) * 100) : 0;
// 更新DOM
document.getElementById('current-streak').textContent = longestStreak;
document.getElementById('completion-rate').textContent = `${completionRate}%`;
document.getElementById('total-habits').textContent = this.habits.length;
}
// 更新统计图表
async updateStats(period = 'month') {
try {
const response = await this.ajaxRequest('get_stats', { period });
this.stats = response.data || {};
this.updateCharts();
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
// 初始化图表
initCharts() {
// 完成率图表
const completionCtx = document.getElementById('completionChart')?.getContext('2d');
if (completionCtx) {
this.completionChart = new Chart(completionCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '完成率',
data: [],
borderColor: '#3498db',
backgroundColor: 'rgba(52, 152, 219, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
}
});
}
// 分类分布图表
const categoryCtx = document.getElementById('categoryChart')?.getContext('2d');
if (categoryCtx) {
this.categoryChart = new Chart(categoryCtx, {
type: 'doughnut',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: [
'#3498db', '#2ecc71', '#9b59b6',
'#e74c3c', '#f39c12', '#1abc9c'
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
}
// 更新图表数据
updateCharts() {
// 更新完成率图表
if (this.completionChart && this.stats.completion_trend) {
const trend = this.stats.completion_trend;
this.completionChart.data.labels = trend.labels;
this.completionChart.data.datasets[0].data = trend.data;
this.completionChart.update();
}
// 更新分类分布图表
if (this.categoryChart && this.stats.category_distribution) {
const distribution = this.stats.category_distribution;
this.categoryChart.data.labels = distribution.labels;
this.categoryChart.data.datasets[0].data = distribution.data;
this.categoryChart.update();
}
}
// 辅助方法:AJAX请求
async ajaxRequest(action, data = {}) {
return new Promise((resolve, reject) => {
jQuery.ajax({
url: habitTrackerAjax.ajax_url,
