
我们可以通过算法来完成很多很多的功能,所以就有了一个想法,将各类工具都写出来,当然是尽可能的,毕竟未来无限可期,很多功能是我们当前还想不到的,为了最为靠谱的方法来完成,这里选择的语言为 HTML 来完成,别看只是简单的页面操作,但是里面都是各类算法来完成的,并且有很多的js库,做起来会很方便,能节约很多的时间,本系列文章会很多,有些标题命名可能不太合适,如果你用到了,感觉不好找可以在文章下面留言,我看到了后会进行对应的修改,希望本系列文章能给大家提供到各种各样的遍历。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>体重记录追踪</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<style>
:root {
--primary-color: #5d9cec;
--secondary-color: #f5f7fa;
--accent-color: #4a89dc;
--text-color: #333;
--light-text: #666;
--border-radius: 10px;
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
background-color: #f8f9fa;
color: var(--text-color);
line-height: 1.6;
}
.header {
background-color: white;
box-shadow: var(--box-shadow);
padding: 1.5rem 0;
margin-bottom: 2rem;
}
.header h1 {
color: var(--primary-color);
font-weight: 600;
}
.container {
max-width: 1200px;
}
.card {
border: none;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
transition: transform 0.3s ease;
margin-bottom: 1.5rem;
overflow: hidden;
}
.card:hover {
transform: translateY(-5px);
}
.card-header {
background-color: var(--primary-color);
color: white;
font-weight: 600;
padding: 0.8rem 1.2rem;
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: var(--accent-color);
border-color: var(--accent-color);
}
.btn-outline-primary {
color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-outline-primary:hover {
background-color: var(--primary-color);
color: white;
}
.weight-item {
border-left: 4px solid var(--primary-color);
background-color: white;
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: var(--border-radius);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
.weight-item:hover {
box-shadow: var(--box-shadow);
}
.weight-item.gain {
border-left-color: #dc3545;
}
.weight-item.loss {
border-left-color: #28a745;
}
.weight-item.maintain {
border-left-color: #ffc107;
}
.weight-value {
font-weight: 600;
font-size: 1.1rem;
}
.weight-change {
font-size: 0.9rem;
font-weight: 600;
}
.weight-change.gain {
color: #dc3545;
}
.weight-change.loss {
color: #28a745;
}
.weight-change.maintain {
color: #ffc107;
}
.footer {
background-color: white;
padding: 2rem 0;
margin-top: 3rem;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05);
}
.footer p {
margin-bottom: 0;
color: var(--light-text);
}
.summary-card {
text-align: center;
padding: 1.5rem;
}
.summary-value {
font-size: 1.8rem;
font-weight: bold;
display: block;
margin: 0.5rem 0;
}
.current-weight {
color: var(--primary-color);
}
.target-weight {
color: #28a745;
}
.initial-weight {
color: #6c757d;
}
.chart-container {
position: relative;
height: 300px;
margin-bottom: 1.5rem;
}
.date-filter {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
}
.date-filter .btn {
margin: 0 0.25rem;
}
.modal-content {
border-radius: var(--border-radius);
}
.modal-header {
background-color: var(--primary-color);
color: white;
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.modal-title {
font-weight: 600;
}
.btn-close {
filter: brightness(0) invert(1);
}
.progress {
height: 10px;
margin-bottom: 0.5rem;
}
.bmi-indicator {
position: relative;
height: 30px;
background: linear-gradient(to right, #28a745, #ffc107, #dc3545);
border-radius: 15px;
margin: 1rem 0;
}
.bmi-marker {
position: absolute;
top: -10px;
width: 10px;
height: 30px;
background-color: #333;
transform: translateX(-50%);
}
.bmi-labels {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 0.8rem;
color: var(--light-text);
}
.stats-item {
text-align: center;
padding: 1rem;
}
.stats-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
display: block;
}
.stats-label {
color: var(--light-text);
font-size: 0.9rem;
}
@media (max-width: 768px) {
.header {
padding: 1rem 0;
margin-bottom: 1rem;
}
.card {
margin-bottom: 1rem;
}
.summary-value {
font-size: 1.5rem;
}
.chart-container {
height: 250px;
}
.weight-item {
padding: 0.75rem;
}
.date-filter {
flex-wrap: wrap;
}
.date-filter .btn {
margin-bottom: 0.5rem;
}
.stats-value {
font-size: 1.3rem;
}
}
</style>
</head>
<body>
<header class="header">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<h1><i class="bi bi-clipboard2-pulse me-2"></i>体重记录追踪</h1>
<p class="text-muted">科学记录体重变化,健康管理更轻松</p>
</div>
<div class="col-md-6 text-md-end mt-3 mt-md-0">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addWeightModal">
<i class="bi bi-plus-circle me-1"></i>记录今日体重
</button>
</div>
</div>
</div>
</header>
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="card summary-card">
<div class="card-body">
<h5>当前体重</h5>
<span class="summary-value current-weight" id="currentWeight">0.0 kg</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card summary-card">
<div class="card-body">
<h5>目标体重</h5>
<span class="summary-value target-weight" id="targetWeight">0.0 kg</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card summary-card">
<div class="card-body">
<h5>初始体重</h5>
<span class="summary-value initial-weight" id="initialWeight">0.0 kg</span>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span>体重趋势</span>
<div class="dropdown">
<button class="btn btn-sm btn-light dropdown-toggle" type="button" id="chartTypeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
图表类型
</button>
<ul class="dropdown-menu" aria-labelledby="chartTypeDropdown">
<li><a class="dropdown-item chart-type-item" href="#" data-type="line">折线图</a></li>
<li><a class="dropdown-item chart-type-item" href="#" data-type="bar">柱状图</a></li>
</ul>
</div>
</div>
<div class="card-body">
<div class="date-filter">
<button class="btn btn-sm btn-outline-primary active" data-period="month">本月</button>
<button class="btn btn-sm btn-outline-primary" data-period="3months">近三个月</button>
<button class="btn btn-sm btn-outline-primary" data-period="6months">近半年</button>
<button class="btn btn-sm btn-outline-primary" data-period="year">近一年</button>
</div>
<div class="chart-container">
<canvas id="weightChart"></canvas>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<span>目标进度</span>
</div>
<div class="card-body">
<div class="progress" id="weightProgress">
<div class="progress-bar bg-success" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between">
<small id="progressStart">0.0 kg</small>
<small id="progressCurrent">当前: 0.0 kg</small>
<small id="progressTarget">目标: 0.0 kg</small>
</div>
<div class="mt-4">
<h6>BMI 指数: <span id="bmiValue">0.0</span> <small class="text-muted">(<span id="bmiCategory">未知</span>)</small></h6>
<div class="bmi-indicator">
<div class="bmi-marker" id="bmiMarker" style="left: 0%"></div>
</div>
<div class="bmi-labels">
<span>偏瘦</span>
<span>正常</span>
<span>超重</span>
<span>肥胖</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mb-4">
<div class="card-header">
<span>统计数据</span>
</div>
<div class="card-body">
<div class="row">
<div class="col-6">
<div class="stats-item">
<span class="stats-value" id="totalLoss">0.0</span>
<span class="stats-label">总减重 (kg)</span>
</div>
</div>
<div class="col-6">
<div class="stats-item">
<span class="stats-value" id="avgWeeklyChange">0.0</span>
<span class="stats-label">周平均变化 (kg)</span>
</div>
</div>
<div class="col-6">
<div class="stats-item">
<span class="stats-value" id="recordDays">0</span>
<span class="stats-label">记录天数</span>
</div>
</div>
<div class="col-6">
<div class="stats-item">
<span class="stats-value" id="daysToGoal">0</span>
<span class="stats-label">预计达标天数</span>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span>最近记录</span>
</div>
<div class="card-body p-2">
<div id="weightRecords">
<!-- 体重记录将通过JavaScript动态添加 -->
</div>
<div id="emptyState" class="text-center py-4">
<i class="bi bi-clipboard-plus" style="font-size: 2rem; color: #ccc;"></i>
<p class="mt-2 text-muted">暂无体重记录</p>
<button class="btn btn-primary btn-sm mt-2" data-bs-toggle="modal" data-bs-target="#addWeightModal">
添加第一条记录
</button>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<span>设置目标</span>
</div>
<div class="card-body">
<form id="targetForm">
<div class="mb-3">
<label for="targetWeightInput" class="form-label">目标体重 (kg)</label>
<input type="number" class="form-control" id="targetWeightInput" step="0.1" min="30" max="200">
</div>
<div class="mb-3">
<label for="targetDateInput" class="form-label">目标日期</label>
<input type="date" class="form-control" id="targetDateInput">
</div>
<div class="d-grid">
<button type="button" class="btn btn-primary" id="saveTargetBtn">保存目标</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 添加体重记录模态框 -->
<div class="modal fade" id="addWeightModal" tabindex="-1" aria-labelledby="addWeightModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addWeightModalLabel">记录体重</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="weightForm">
<div class="mb-3">
<label for="weightInput" class="form-label">体重 (kg)</label>
<input type="number" class="form-control" id="weightInput" step="0.1" min="30" max="200" required>
</div>
<div class="mb-3">
<label for="dateInput" class="form-label">日期</label>
<input type="date" class="form-control" id="dateInput" required>
</div>
<div class="mb-3">
<label for="noteInput" class="form-label">备注</label>
<textarea class="form-control" id="noteInput" rows="2"></textarea>
</div>
<div class="mb-3">
<label class="form-label">身体数据 (可选)</label>
<div class="row g-2">
<div class="col-6">
<div class="input-group">
<span class="input-group-text">身高</span>
<input type="number" class="form-control" id="heightInput" placeholder="cm" step="0.1" min="100" max="250">
</div>
</div>
<div class="col-6">
<div class="input-group">
<span class="input-group-text">体脂率</span>
<input type="number" class="form-control" id="bodyFatInput" placeholder="%" step="0.1" min="3" max="50">
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="saveWeightBtn">保存</button>
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-md-6">
<p>© 2025年8月 体重记录追踪系统</p>
</div>
<div class="col-md-6 text-md-end">
<p>科学管理体重,健康生活每一天</p>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 初始化日期为今天
document.getElementById('dateInput').valueAsDate = new Date();
// 设置目标日期默认为一个月后
const defaultTargetDate = new Date();
defaultTargetDate.setMonth(defaultTargetDate.getMonth() + 1);
document.getElementById('targetDateInput').valueAsDate = defaultTargetDate;
// 体重数据存储
let weightRecords = JSON.parse(localStorage.getItem('weightRecords')) || [];
let userSettings = JSON.parse(localStorage.getItem('userSettings')) || {
targetWeight: null,
targetDate: null,
height: null
};
// 更新UI
updateUI();
// 保存体重记录
document.getElementById('saveWeightBtn').addEventListener('click', function() {
const form = document.getElementById('weightForm');
const weight = parseFloat(document.getElementById('weightInput').value);
const date = document.getElementById('dateInput').value;
const note = document.getElementById('noteInput').value;
const height = parseFloat(document.getElementById('heightInput').value) || userSettings.height;
const bodyFat = parseFloat(document.getElementById('bodyFatInput').value) || null;
if (!weight || !date) {
alert('请填写必填字段');
return;
}
// 更新用户身高
if (height) {
userSettings.height = height;
localStorage.setItem('userSettings', JSON.stringify(userSettings));
}
// 检查是否已存在该日期的记录
const existingIndex = weightRecords.findIndex(r => r.date === date);
if (existingIndex !== -1) {
// 更新现有记录
weightRecords[existingIndex] = {
id: weightRecords[existingIndex].id,
weight,
date,
note,
bodyFat,
timestamp: new Date().toISOString()
};
} else {
// 添加新记录
const record = {
id: Date.now(),
weight,
date,
note,
bodyFat,
timestamp: new Date().toISOString()
};
weightRecords.push(record);
}
// 保存数据
localStorage.setItem('weightRecords', JSON.stringify(weightRecords));
// 重置表单
form.reset();
document.getElementById('dateInput').valueAsDate = new Date();
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('addWeightModal'));
modal.hide();
// 更新UI
updateUI();
});
// 保存目标
document.getElementById('saveTargetBtn').addEventListener('click', function() {
const targetWeight = parseFloat(document.getElementById('targetWeightInput').value);
const targetDate = document.getElementById('targetDateInput').value;
if (!targetWeight || !targetDate) {
alert('请填写目标体重和日期');
return;
}
userSettings.targetWeight = targetWeight;
userSettings.targetDate = targetDate;
localStorage.setItem('userSettings', JSON.stringify(userSettings));
updateUI();
alert('目标设置已保存');
});
// 图表类型切换
document.querySelectorAll('.chart-type-item').forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
const chartType = this.getAttribute('data-type');
document.getElementById('chartTypeDropdown').textContent = chartType === 'line' ? '折线图' : '柱状图';
updateWeightChart(weightRecords, chartType);
});
});
// 日期筛选
document.querySelectorAll('.date-filter .btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.date-filter .btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
const period = this.getAttribute('data-period');
filterByDate(period);
});
});
// 删除体重记录
document.addEventListener('click', function(e) {
if (e.target && e.target.classList.contains('delete-btn')) {
const id = parseInt(e.target.getAttribute('data-id'));
if (confirm('确定要删除这条记录吗?')) {
weightRecords = weightRecords.filter(r => r.id !== id);
localStorage.setItem('weightRecords', JSON.stringify(weightRecords));
updateUI();
}
}
});
// 更新UI函数
function updateUI() {
updateWeightRecords(weightRecords);
updateSummary(weightRecords);
updateWeightChart(weightRecords, 'line');
updateProgress(weightRecords);
updateStats(weightRecords);
toggleEmptyState();
// 填充目标设置表单
if (userSettings.targetWeight) {
document.getElementById('targetWeightInput').value = userSettings.targetWeight;
}
if (userSettings.targetDate) {
document.getElementById('targetDateInput').value = userSettings.targetDate;
}
}
// 更新体重记录列表
function updateWeightRecords(data) {
const recordsContainer = document.getElementById('weightRecords');
recordsContainer.innerHTML = '';
// 按日期降序排序
data.sort((a, b) => new Date(b.date) - new Date(a.date));
// 只显示最近10条记录
const recentRecords = data.slice(0, 10);
recentRecords.forEach((record, index) => {
const item = document.createElement('div');
// 计算与上一条记录的体重差异
let changeClass = 'maintain';
let changeText = '维持';
let changeSymbol = '';
if (index < recentRecords.length - 1) {
const prevWeight = recentRecords[index + 1].weight;
const weightDiff = record.weight - prevWeight;
if (weightDiff > 0) {
changeClass = 'gain';
changeText = `增加 ${weightDiff.toFixed(1)}kg`;
changeSymbol = '↑';
} else if (weightDiff < 0) {
changeClass = 'loss';
changeText = `减少 ${Math.abs(weightDiff).toFixed(1)}kg`;
changeSymbol = '↓';
}
}
item.className = `weight-item ${changeClass}`;
const formattedDate = formatDate(record.date);
item.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="weight-value">${record.weight.toFixed(1)} kg</div>
<div class="text-muted small">${formattedDate} ${record.note ? '· ' + record.note : ''}</div>
</div>
<div class="d-flex align-items-center">
<span class="weight-change ${changeClass} me-3">${changeSymbol} ${changeText}</span>
<button class="btn btn-sm btn-outline-danger delete-btn" data-id="${record.id}">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
`;
recordsContainer.appendChild(item);
});
}
// 更新汇总信息
function updateSummary(data) {
// 按日期排序
data.sort((a, b) => new Date(a.date) - new Date(b.date));
const currentWeight = data.length > 0 ? data[data.length - 1].weight : 0;
const initialWeight = data.length > 0 ? data[0].weight : 0;
const targetWeight = userSettings.targetWeight || 0;
document.getElementById('currentWeight').textContent = `${currentWeight.toFixed(1)} kg`;
document.getElementById('initialWeight').textContent = `${initialWeight.toFixed(1)} kg`;
document.getElementById('targetWeight').textContent = `${targetWeight.toFixed(1)} kg`;
// 计算BMI
if (currentWeight > 0 && userSettings.height) {
const heightInMeters = userSettings.height / 100;
const bmi = currentWeight / (heightInMeters * heightInMeters);
document.getElementById('bmiValue').textContent = bmi.toFixed(1);
// 设置BMI分类
let category = '';
let markerPosition = 0;
if (bmi < 18.5) {
category = '偏瘦';
markerPosition = (bmi / 18.5) * 25;
} else if (bmi < 24) {
category = '正常';
markerPosition = 25 + ((bmi - 18.5) / 5.5) * 25;
} else if (bmi < 28) {
category = '超重';
markerPosition = 50 + ((bmi - 24) / 4) * 25;
} else {
category = '肥胖';
markerPosition = 75 + Math.min(((bmi - 28) / 12) * 25, 25);
}
document.getElementById('bmiCategory').textContent = category;
document.getElementById('bmiMarker').style.left = `${markerPosition}%`;
} else {
document.getElementById('bmiValue').textContent = '0.0';
document.getElementById('bmiCategory').textContent = '未知';
document.getElementById('bmiMarker').style.left = '0%';
}
}
// 更新体重图表
function updateWeightChart(data, chartType = 'line') {
const ctx = document.getElementById('weightChart').getContext('2d');
// 按日期排序
data.sort((a, b) => new Date(a.date) - new Date(b.date));
// 准备图表数据
const labels = data.map(r => formatDate(r.date));
const weights = data.map(r => r.weight);
// 销毁旧图表
if (window.weightChart) {
window.weightChart.destroy();
}
// 创建新图表
window.weightChart = new Chart(ctx, {
type: chartType,
data: {
labels: labels,
datasets: [{
label: '体重 (kg)',
data: weights,
borderColor: '#5d9cec',
backgroundColor: 'rgba(93, 156, 236, 0.1)',
borderWidth: 2,
fill: chartType === 'line',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: false,
ticks: {
callback: function(value) {
return value + ' kg';
}
}
}
}
}
});
// 如果没有数据,显示无数据提示
if (data.length === 0) {
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('暂无数据', ctx.canvas.width / 2, ctx.canvas.height / 2);
}
}
// 更新进度条
function updateProgress(data) {
if (data.length === 0 || !userSettings.targetWeight) {
return;
}
// 按日期排序
data.sort((a, b) => new Date(a.date) - new Date(b.date));
const initialWeight = data[0].weight;
const currentWeight = data[data.length - 1].weight;
const targetWeight = userSettings.targetWeight;
// 计算进度
let progress = 0;
// 如果目标是减重
if (initialWeight > targetWeight) {
progress = Math.min(100, Math.max(0,
((initialWeight - currentWeight) / (initialWeight - targetWeight)) * 100
));
}
// 如果目标是增重
else if (initialWeight < targetWeight) {
progress = Math.min(100, Math.max(0,
((currentWeight - initialWeight) / (targetWeight - initialWeight)) * 100
));
}
// 如果初始体重等于目标体重
else {
progress = currentWeight === targetWeight ? 100 : 0;
}
// 更新进度条
const progressBar = document.querySelector('#weightProgress .progress-bar');
progressBar.style.width = `${progress}%`;
progressBar.setAttribute('aria-valuenow', progress);
// 更新进度文本
document.getElementById('progressStart').textContent = `${initialWeight.toFixed(1)} kg`;
document.getElementById('progressCurrent').textContent = `当前: ${currentWeight.toFixed(1)} kg`;
document.getElementById('progressTarget').textContent = `目标: ${targetWeight.toFixed(1)} kg`;
}
// 更新统计数据
function updateStats(data) {
if (data.length === 0) {
return;
}
// 按日期排序
data.sort((a, b) => new Date(a.date) - new Date(b.date));
const initialWeight = data[0].weight;
const currentWeight = data[data.length - 1].weight;
const totalLoss = initialWeight - currentWeight;
// 计算周平均变化
let weeklyChange = 0;
if (data.length > 1) {
const firstDate = new Date(data[0].date);
const lastDate = new Date(data[data.length - 1].date);
const daysDiff = (lastDate - firstDate) / (1000 * 60 * 60 * 24);
const weeksDiff = daysDiff / 7;
if (weeksDiff > 0) {
weeklyChange = totalLoss / weeksDiff;
}
}
// 计算预计达标天数
let daysToGoal = 0;
if (userSettings.targetWeight && weeklyChange !== 0) {
const remainingLoss = currentWeight - userSettings.targetWeight;
const dailyChange = weeklyChange / 7;
if (dailyChange !== 0) {
daysToGoal = Math.abs(Math.ceil(remainingLoss / dailyChange));
}
}
// 更新UI
document.getElementById('totalLoss').textContent = Math.abs(totalLoss).toFixed(1);
document.getElementById('avgWeeklyChange').textContent = Math.abs(weeklyChange).toFixed(1);
document.getElementById('recordDays').textContent = data.length;
document.getElementById('daysToGoal').textContent = daysToGoal > 0 ? daysToGoal : '-';
}
// 按日期筛选
function filterByDate(period) {
const now = new Date();
let startDate;
switch (period) {
case 'month':
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
break;
case '3months':
startDate = new Date(now);
startDate.setMonth(now.getMonth() - 3);
break;
case '6months':
startDate = new Date(now);
startDate.setMonth(now.getMonth() - 6);
break;
case 'year':
startDate = new Date(now);
startDate.setFullYear(now.getFullYear() - 1);
break;
default:
startDate = new Date(0); // 从1970年开始
}
// 筛选数据
const filteredData = weightRecords.filter(r => new Date(r.date) >= startDate);
// 更新图表
updateWeightChart(filteredData, window.weightChart ? window.weightChart.config.type : 'line');
}
// 切换空状态显示
function toggleEmptyState() {
const emptyState = document.getElementById('emptyState');
const recordsContainer = document.getElementById('weightRecords');
if (weightRecords.length === 0) {
emptyState.style.display = 'block';
recordsContainer.style.display = 'none';
} else {
emptyState.style.display = 'none';
recordsContainer.style.display = 'block';
}
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
// 添加示例数据(仅首次使用)
function addSampleData() {
if (weightRecords.length === 0 && !localStorage.getItem('sampleDataAdded')) {
const today = new Date();
const sampleData = [];
// 生成过去30天的示例数据
for (let i = 30; i >= 0; i -= 2) {
const date = new Date(today);
date.setDate(today.getDate() - i);
// 模拟体重变化(从75kg开始,逐渐减少到72kg)
const weight = 75 - (i * 0.1);
sampleData.push({
id: Date.now() - i * 100000,
weight: parseFloat(weight.toFixed(1)),
date: date.toISOString().split('T')[0],
note: i === 0 ? '今日记录' : '',
timestamp: date.toISOString()
});
}
weightRecords = sampleData;
localStorage.setItem('weightRecords', JSON.stringify(weightRecords));
localStorage.setItem('sampleDataAdded', 'true');
// 设置示例目标和身高
userSettings.targetWeight = 70;
userSettings.height = 175;
userSettings.targetDate = new Date(today.setMonth(today.getMonth() + 2)).toISOString().split('T')[0];
localStorage.setItem('userSettings', JSON.stringify(userSettings));
updateUI();
}
}
// 添加示例数据
addSampleData();
});
</script>

体重记录追踪系统是一款专为用户提供体重管理和健康监测的Web应用。通过直观的界面和丰富的数据可视化功能,帮助用户科学地记录、分析和管理自己的体重变化,从而实现健康的体重管理目标。