现在学校、小饭桌,都采取积分奖励式带娃,之前dy也看到有大神做了app,不过是私用的。
前两天在公众号看到一个网页版的积分小管家,便用这个模板,用AI优化了一下,纯离线运行,历史数据可以保存,可以清控,设置可以重置、自定义,非常轻便,发到手机上,点开就能用,不到100kb。

txt改成html就行,要是有大神能封装成一个app就好了,不过估计app安装包可能就不止100km了
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta name="robots" content="noindex,nofollow"><meta name="generator" content="ShipPage"><link rel="canonical" href="https://shippage.ai/p/jifen">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>积分小管家</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
min-height: 100vh;
color: #333;
padding-bottom: 80px;
}
/* 顶部导航 */
.header {
background: rgba(255, 255, 255, 0.95);
padding: 15px 20px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 周积分网格样式 */
.day-item {
cursor: pointer;
transition: all 0.2s ease;
background: #f8f9fa;
border-radius: 10px;
padding: 10px 5px;
text-align: center;
}
.day-item:hover {
transform: translateY(-2px);
}
.day-item:active {
transform: scale(0.95);
}
.day-item.selected {
background: rgba(34, 197, 94, 0.1);
}
.day-points.today-points {
font-weight: 800;
font-size: 18px;
}
.day-points.selected-points {
font-weight: 600;
font-size: 16px;
opacity: 0.8;
}
.header h1 {
font-size: 20px;
font-weight: 700;
color: #22c55e;
text-align: center;
}
/* 积分卡片 */
.points-card {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
margin: 20px;
padding: 24px 20px;
border-radius: 20px;
color: white;
box-shadow: 0 8px 20px rgba(34, 197, 94, 0.4);
text-align: center;
}
.points-header {
display: flex;
align-items: baseline;
justify-content: center;
gap: 12px;
margin-bottom: 8px;
}
.points-title {
font-size: 16px;
font-weight: 600;
opacity: 0.85;
letter-spacing: 2px;
}
.points-amount {
font-size: 56px;
font-weight: 900;
text-shadow: 0 2px 8px rgba(0,0,0,0.2);
letter-spacing: 2px;
}
.points-today {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 14px;
background: rgba(255,255,255,0.15);
border-radius: 12px;
padding: 8px 16px;
margin: 0 auto;
width: fit-content;
}
.today-divider {
width: 1px;
height: 18px;
background: rgba(255, 255, 255, 0.5);
}
.today-value {
font-weight: 800;
font-size: 15px;
}
.points-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.points-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: 12px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.points-btn.primary {
background: white;
color: #22c55e;
}
.points-btn.danger {
background: rgba(239, 68, 68, 0.9);
color: white;
}
.points-btn:active {
transform: scale(0.95);
}
/* 功能入口 */
.features {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
padding: 0 20px;
margin-bottom: 15px;
}
.feature-item {
background: rgba(255, 255, 255, 0.95);
border-radius: 14px;
padding: 12px 8px;
text-align: center;
cursor: pointer;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
}
.feature-item:active {
transform: scale(0.95);
}
.feature-icon {
font-size: 24px;
margin-bottom: 6px;
}
.feature-name {
font-size: 11px;
color: #333;
font-weight: 600;
}
/* 内容区域 */
.content-section {
background: white;
margin: 0 20px 15px;
border-radius: 16px;
padding: 15px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
}
.section-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 15px;
}
.section-title > span {
font-size: 18px;
font-weight: 700;
color: #333;
}
.section-actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
/* 手机端兼容性优化 */
@media (max-width: 480px) {
.action-btn {
font-size: 11px;
padding: 5px 10px;
}
.list-item {
flex-wrap: wrap;
gap: 10px;
}
.list-item .section-actions {
width: 100%;
justify-content: flex-end;
margin-top: 5px;
}
}
.action-btn.add {
background: #22c55e;
color: white;
}
.action-btn.edit {
background: #f59e0b;
color: white;
}
.action-btn.delete {
background: #ef4444;
color: white;
}
/* 列表项 */
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f8f9fa;
border-radius: 12px;
margin-bottom: 10px;
}
.list-item:last-child {
margin-bottom: 0;
}
.item-info {
flex: 1;
}
.item-name {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.item-points {
font-size: 16px;
font-weight: 700;
}
.item-points.income {
color: #10b981;
}
.item-points.expense {
color: #ef4444;
}
.item-actions {
display: flex;
gap: 8px;
}
.item-btn {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.item-btn.earn {
background: #10b981;
color: white;
}
.item-btn.deduct {
background: #ef4444;
color: white;
}
.item-btn.exchange {
background: #667eea;
color: white;
}
/* 快速添加行 */
.quick-add {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
background: #f0fdf4;
border: 1px dashed #22c55e;
border-radius: 12px;
margin-bottom: 12px;
}
.quick-add input {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 8px 10px;
font-size: 13px;
outline: none;
}
.quick-add input:focus {
border-color: #22c55e;
}
.quick-add .qa-name {
flex: 1;
max-width: 50%;
}
.quick-add .qa-points {
width: 80px;
text-align: center;
}
.quick-add .qa-btn {
padding: 8px 16px;
background: #22c55e;
color: white;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
margin-left: auto;
}
.quick-add .qa-btn:active {
background: #16a34a;
}
.quick-add.deduct {
background: #fef2f2;
border-color: #fca5a5;
}
.quick-add.deduct input:focus {
border-color: #ef4444;
}
.quick-add.deduct .qa-btn {
background: #ef4444;
}
.quick-add.deduct .qa-btn:active {
background: #dc2626;
}
.quick-add.exchange {
background: #f5f3ff;
border-color: #c4b5fd;
}
.quick-add.exchange input:focus {
border-color: #667eea;
}
.quick-add.exchange .qa-btn {
background: #667eea;
}
.quick-add.exchange .qa-btn:active {
background: #4f46e5;
}
/* 周积分统计 */
.week-stats {
margin-bottom: 20px;
}
.week-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
}
.day-item.today {
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
color: white;
}
.day-name {
font-size: 10px;
margin-bottom: 4px;
color: #666;
}
.day-item.today .day-name {
color: rgba(255, 255, 255, 0.9);
}
.day-points {
font-size: 14px;
font-weight: 700;
}
.day-item.today .day-points {
color: white;
}
/* 历史积分月日历 */
.month-calendar {
background: #f8f9fa;
border-radius: 12px;
padding: 12px;
margin-bottom: 12px;
}
.month-header {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.month-title {
font-size: 14px;
font-weight: 700;
}
.month-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
margin-bottom: 4px;
}
.month-weekday {
text-align: center;
font-size: 10px;
color: #999;
padding: 2px 0;
}
.month-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.month-day {
text-align: center;
padding: 6px 2px;
background: white;
border-radius: 6px;
min-height: 38px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.month-day.empty {
background: transparent;
}
.month-day.today {
background: rgba(34, 197, 94, 0.15);
}
.month-day.today .month-day-num {
font-weight: 700;
color: #22c55e;
}
.month-day-num {
font-size: 11px;
color: #333;
line-height: 1;
}
.month-day-pts {
font-size: 10px;
font-weight: 600;
color: #22c55e;
line-height: 1;
margin-top: 1px;
}
/* 年历卡片 */
.year-calendar {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
}
.year-month-card {
background: #f8f9fa;
border-radius: 10px;
padding: 10px 6px;
text-align: center;
}
.year-month-card.current {
background: rgba(34, 197, 94, 0.1);
border: 1.5px solid #22c55e;
}
.year-month-name {
font-size: 11px;
color: #666;
margin-bottom: 4px;
}
.year-month-total {
font-size: 16px;
font-weight: 700;
color: #22c55e;
}
/* 积分记录列表 */
.record-list {
max-height: 400px;
overflow-y: auto;
}
.record-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
}
.record-item:last-child {
border-bottom: none;
}
.record-info {
flex: 1;
}
.record-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.record-time {
font-size: 12px;
color: #999;
}
.record-amount {
font-size: 16px;
font-weight: 700;
padding-left: 10px;
padding-right: 15px;
margin: 5px 0;
}
.record-amount.income {
color: #10b981;
}
.record-amount.expense {
color: #ef4444;
}
/* 底部导航菜单 */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
display: flex;
justify-content: space-around;
padding: 10px 0;
padding-bottom: calc(10px + env(safe-area-inset-bottom));
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 100;
}
.nav-item {
text-align: center;
cursor: pointer;
flex: 1;
}
.nav-icon {
font-size: 22px;
margin-bottom: 2px;
}
.nav-text {
font-size: 11px;
color: #999;
}
.nav-item.active .nav-text {
color: #22c55e;
}
.nav-item.active .nav-icon {
color: #22c55e;
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
border-radius: 20px;
padding: 25px;
width: 90%;
max-width: 400px;
animation: modalSlide 0.3s ease;
}
@keyframes modalSlide {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-title {
font-size: 18px;
font-weight: 700;
margin-bottom: 20px;
text-align: center;
}
.modal-body {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.form-input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 14px;
transition: all 0.3s;
}
.form-input:focus {
outline: none;
border-color: #22c55e;
}
.form-input[type="number"] {
-moz-appearance: textfield;
}
.form-input[type="number"]::-webkit-outer-spin-button,
.form-input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.modal-footer {
display: flex;
gap: 10px;
}
.modal-btn {
flex: 1;
padding: 12px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.modal-btn.confirm {
background: #22c55e;
color: white;
}
.modal-btn.cancel {
background: #f0f0f0;
color: #666;
}
.modal-btn.danger {
background: #ef4444;
color: white;
}
/* 页面切换 */
.page {
display: none;
}
.page.active {
display: block;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.empty-icon {
font-size: 48px;
margin-bottom: 12px;
}
.empty-text {
font-size: 14px;
}
/* 响应式适配 */
@media (max-width: 420px) {
.points-amount {
font-size: 48px;
}
.features {
gap: 8px;
}
.week-grid {
gap: 4px;
}
.day-item {
padding: 8px 3px;
}
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="header">
<h1>积分小管家</h1>
</div>
<!-- 底部导航菜单 -->
<div class="bottom-nav">
<div class="nav-item active" onclick="switchPage('home')">
<div class="nav-icon">🏠</div>
<div class="nav-text">首页</div>
</div>
</div>
<!-- 首页 -->
<div class="page active" id="home-page">
<!-- 积分卡片(总积分 + 今日积分合并) -->
<div class="points-card">
<div class="points-header">
<div class="points-title">总积分</div>
<div class="points-amount" id="total-points">0</div>
</div>
<div class="points-today">
<span>📅 今日积分</span>
<span class="today-divider"></span>
<span class="today-value" id="daily-challenge">赚0分 - 扣0分 = 0分</span>
</div>
</div>
<!-- 功能入口 -->
<div class="features">
<div class="feature-item" onclick="switchPage('earn')">
<div class="feature-icon">➕</div>
<div class="feature-name">赚取积分</div>
</div>
<div class="feature-item" onclick="switchPage('deduct')">
<div class="feature-icon">➖</div>
<div class="feature-name">扣取积分</div>
</div>
<div class="feature-item" onclick="switchPage('exchange')">
<div class="feature-icon">🏪</div>
<div class="feature-name">奖励兑换中心</div>
</div>
<div class="feature-item" onclick="switchPage('earn-manage')">
<div class="feature-icon">💰</div>
<div class="feature-name">积分项设置</div>
</div>
<div class="feature-item" onclick="switchPage('deduct-manage')">
<div class="feature-icon">⚠️</div>
<div class="feature-name">扣分项设置</div>
</div>
<div class="feature-item" onclick="switchPage('exchange-manage')">
<div class="feature-icon">🎁</div>
<div class="feature-name">兑换奖励设置</div>
</div>
</div>
<!-- 本周积分 -->
<div class="content-section">
<div class="section-title">
<span id="week-score-text">本周积分</span>
</div>
<div class="week-stats">
<div class="week-grid" id="current-week-grid">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 快捷工具 -->
<div class="features">
<div class="feature-item" onclick="switchPage('records')">
<div class="feature-icon">📊</div>
<div class="feature-name">积分记录</div>
</div>
<div class="feature-item" onclick="showClearPointsModal()">
<div class="feature-icon">🗑️</div>
<div class="feature-name">积分清空</div>
</div>
<div class="feature-item" onclick="systemReset()">
<div class="feature-icon">🔄</div>
<div class="feature-name">系统重置</div>
</div>
</div>
</div>
<!-- 赚取积分页面 -->
<div class="page" id="earn-page">
<div class="content-section">
<div class="section-title">
<span>赚取积分</span>
</div>
<div id="earn-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 扣取积分页面 -->
<div class="page" id="deduct-page">
<div class="content-section">
<div class="section-title">
<span>扣取积分</span>
</div>
<div id="deduct-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 兑换中心页面 -->
<div class="page" id="exchange-page">
<div class="content-section">
<div class="section-title">
<span>兑换中心</span>
</div>
<div id="exchange-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 赚取积分项管理页面 -->
<div class="page" id="earn-manage-page">
<div class="content-section">
<div class="section-title">
<span>赚取积分项管理</span>
<div class="section-actions">
<button class="action-btn add" onclick="showAddEarnItemModal()">+ 添加</button>
</div>
</div>
<div id="earn-manage-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 扣取积分项管理页面 -->
<div class="page" id="deduct-manage-page">
<div class="content-section">
<div class="section-title">
<span>扣取积分项管理</span>
<div class="section-actions">
<button class="action-btn add" onclick="showAddDeductItemModal()">+ 添加</button>
</div>
</div>
<div id="deduct-manage-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 兑换奖励管理页面 -->
<div class="page" id="exchange-manage-page">
<div class="content-section">
<div class="section-title">
<span>兑换奖励管理</span>
<div class="section-actions">
<button class="action-btn add" onclick="showAddRewardModal()">+ 添加</button>
</div>
</div>
<div id="reward-manage-list">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 积分记录页面 -->
<div class="page" id="records-page">
<div class="content-section">
<div class="section-title">
<span id="records-week-score-text">本周积分</span>
<div class="section-actions">
<button class="action-btn add" onclick="showAddPointsModal()">补积分</button>
</div>
</div>
<div class="week-stats">
<div class="week-grid" id="records-week-grid">
<!-- 动态加载 -->
</div>
</div>
</div>
<div class="content-section">
<div class="section-title">每日积分记录</div>
<div class="record-list" id="day-records">
<!-- 动态加载 -->
</div>
</div>
<div class="content-section">
<div class="section-title">历史积分</div>
<div id="history-weeks">
<!-- 动态加载 -->
</div>
</div>
</div>
<!-- 添加赚取积分项弹窗 -->
<div class="modal" id="add-earn-item-modal">
<div class="modal-content">
<div class="modal-title" id="earn-item-modal-title">添加赚取积分项</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">项目名称</label>
<input type="text" class="form-input" id="earn-item-name" placeholder="例如:完成作业">
</div>
<div class="form-group">
<label class="form-label">积分数量</label>
<input type="number" class="form-input" id="earn-item-points" placeholder="例如:10">
</div>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('add-earn-item-modal')">取消</button>
<button class="modal-btn confirm" onclick="saveEarnItem()">保存</button>
</div>
</div>
</div>
<!-- 添加扣取积分项弹窗 -->
<div class="modal" id="add-deduct-item-modal">
<div class="modal-content">
<div class="modal-title" id="deduct-item-modal-title">添加扣取积分项</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">项目名称</label>
<input type="text" class="form-input" id="deduct-item-name" placeholder="例如:未完成作业">
</div>
<div class="form-group">
<label class="form-label">积分数量</label>
<input type="number" class="form-input" id="deduct-item-points" placeholder="例如:10">
</div>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('add-deduct-item-modal')">取消</button>
<button class="modal-btn confirm" onclick="saveDeductItem()">保存</button>
</div>
</div>
</div>
<!-- 添加兑换奖励弹窗 -->
<div class="modal" id="add-reward-modal">
<div class="modal-content">
<div class="modal-title" id="reward-modal-title">添加兑换奖励</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">奖励名称</label>
<input type="text" class="form-input" id="reward-name" placeholder="例如:看电视30分钟">
</div>
<div class="form-group">
<label class="form-label">所需积分</label>
<input type="number" class="form-input" id="reward-points" placeholder="例如:50">
</div>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('add-reward-modal')">取消</button>
<button class="modal-btn confirm" onclick="saveReward()">保存</button>
</div>
</div>
</div>
<!-- 兑换确认弹窗 -->
<div class="modal" id="exchange-confirm-modal">
<div class="modal-content">
<div class="modal-title">确认兑换</div>
<div class="modal-body">
<p id="exchange-reward-name">看电视30分钟</p>
<p style="color: #667eea; font-weight: 700; font-size: 20px; margin-top: 10px;" id="exchange-reward-points">50积分</p>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('exchange-confirm-modal')">取消</button>
<button class="modal-btn confirm" onclick="confirmExchange()">确认兑换</button>
</div>
</div>
</div>
<!-- 补积分弹窗 -->
<div class="modal" id="add-points-modal">
<div class="modal-content">
<div class="modal-title">补积分</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label">补积分原因</label>
<input type="text" class="form-input" id="add-points-reason" placeholder="例如:忘记记录的作业完成">
</div>
<div class="form-group">
<label class="form-label">积分数值</label>
<input type="number" class="form-input" id="add-points-value" placeholder="例如:10">
</div>
<div class="form-group">
<label class="form-label">选择日期</label>
<input type="date" class="form-input" id="add-points-date">
</div>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('add-points-modal')">取消</button>
<button class="modal-btn confirm" onclick="addPoints()">确认补积分</button>
</div>
</div>
</div>
<!-- 清空积分确认弹窗 -->
<div class="modal" id="clear-points-modal">
<div class="modal-content">
<div class="modal-title">确认清空积分</div>
<div class="modal-body">
<p>确定要清空所有积分吗?此操作不可恢复!</p>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('clear-points-modal')">取消</button>
<button class="modal-btn danger" onclick="confirmClearPoints()">确认清空</button>
</div>
</div>
</div>
<!-- 删除确认弹窗 -->
<div class="modal" id="delete-confirm-modal">
<div class="modal-content">
<div class="modal-title">确认删除</div>
<div class="modal-body">
<p>确定要删除此项目吗?</p>
</div>
<div class="modal-footer">
<button class="modal-btn cancel" onclick="closeModal('delete-confirm-modal')">取消</button>
<button class="modal-btn danger" onclick="confirmDelete()">确认删除</button>
</div>
</div>
</div>
<script>
// 数据存储键
const STORAGE_KEYS = {
TOTAL_POINTS: 'pointsDriver_totalPoints',
EARN_ITEMS: 'pointsDriver_earnItems',
DEDUCT_ITEMS: 'pointsDriver_deductItems',
REWARDS: 'pointsDriver_rewards',
RECORDS: 'pointsDriver_records',
RECORD_ID_COUNTER: 'pointsDriver_recordIdCounter'
};
// 默认数据
const DEFAULT_EARN_ITEMS = [
{ id: 1, name: '完成作业', points: 10 },
{ id: 2, name: '阅读30分钟', points: 5 },
{ id: 3, name: '做家务', points: 15 },
{ id: 4, name: '锻炼身体', points: 8 },
{ id: 5, name: '早睡早起', points: 5 }
];
const DEFAULT_DEDUCT_ITEMS = [
{ id: 1, name: '未完成作业', points: -10 },
{ id: 2, name: '迟到', points: -5 },
{ id: 3, name: '说脏话', points: -8 },
{ id: 4, name: '不整理房间', points: -5 }
];
const DEFAULT_REWARDS = [
{ id: 1, name: '看电视30分钟', points: 50 },
{ id: 2, name: '玩游戏1小时', points: 100 },
{ id: 3, name: '买零食', points: 30 },
{ id: 4, name: '周末外出', points: 200 }
];
// 全局数据
let totalPoints = 0;
let earnItems = [];
let deductItems = [];
let rewards = [];
let records = [];
let recordIdCounter = 0;
// 当前操作项
let currentEditItem = null;
let currentDeleteItem = null;
let currentDeleteType = '';
let currentExchangeReward = null;
let selectedDate = null;
let displayYear = null;
let displayMonth = null;
// 初始化数据
function initData() {
totalPoints = parseInt(localStorage.getItem(STORAGE_KEYS.TOTAL_POINTS)) || 0;
earnItems = JSON.parse(localStorage.getItem(STORAGE_KEYS.EARN_ITEMS)) || JSON.parse(JSON.stringify(DEFAULT_EARN_ITEMS));
deductItems = JSON.parse(localStorage.getItem(STORAGE_KEYS.DEDUCT_ITEMS)) || JSON.parse(JSON.stringify(DEFAULT_DEDUCT_ITEMS));
rewards = JSON.parse(localStorage.getItem(STORAGE_KEYS.REWARDS)) || JSON.parse(JSON.stringify(DEFAULT_REWARDS));
records = JSON.parse(localStorage.getItem(STORAGE_KEYS.RECORDS)) || [];
recordIdCounter = parseInt(localStorage.getItem(STORAGE_KEYS.RECORD_ID_COUNTER)) || 0;
}
// 保存数据
function saveData() {
localStorage.setItem(STORAGE_KEYS.TOTAL_POINTS, totalPoints);
localStorage.setItem(STORAGE_KEYS.EARN_ITEMS, JSON.stringify(earnItems));
localStorage.setItem(STORAGE_KEYS.DEDUCT_ITEMS, JSON.stringify(deductItems));
localStorage.setItem(STORAGE_KEYS.REWARDS, JSON.stringify(rewards));
localStorage.setItem(STORAGE_KEYS.RECORDS, JSON.stringify(records));
localStorage.setItem(STORAGE_KEYS.RECORD_ID_COUNTER, recordIdCounter);
}
// 更新积分显示
function updatePointsDisplay() {
document.getElementById('total-points').textContent = totalPoints.toLocaleString();
}
// 页面切换
function switchPage(pageName) {
// 隐藏所有页面
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
// 显示目标页面
const targetPage = document.getElementById(pageName + '-page');
if (targetPage) {
targetPage.classList.add('active');
}
// 更新底部导航
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
// 根据页面名称激活对应导航
const navMap = { 'home': 0 };
const navItems = document.querySelectorAll('.nav-item');
if (navMap[pageName] !== undefined) {
navItems[navMap[pageName]].classList.add('active');
}
// 加载页面数据
if (pageName === 'home') {
loadHomeData();
} else if (pageName === 'earn') {
loadEarnList();
} else if (pageName === 'deduct') {
loadDeductList();
} else if (pageName === 'exchange') {
loadExchangeList();
} else if (pageName === 'earn-manage') {
loadEarnManageList();
} else if (pageName === 'deduct-manage') {
loadDeductManageList();
} else if (pageName === 'exchange-manage') {
loadRewardManageList();
} else if (pageName === 'records') {
loadRecordsData();
}
}
// 更新当天积分显示
function updateTodayPointsDisplay() {
const today = formatDate(new Date());
const dayRecords = records.filter(r => r.date === today);
const earnPoints = dayRecords.filter(r => r.type === 'income').reduce((sum, r) => sum + r.amount, 0);
const deductPoints = Math.abs(dayRecords.filter(r => r.type === 'expense').reduce((sum, r) => sum + r.amount, 0));
const netPoints = earnPoints - deductPoints;
document.getElementById('daily-challenge').textContent = `赚${earnPoints}分 - 扣${deductPoints}分 = ${netPoints}分`;
}
// 加载首页数据
function loadHomeData() {
updatePointsDisplay();
updateTodayPointsDisplay();
loadCurrentWeekGrid('current-week-grid', 'week-score-text');
}
// 加载赚取积分列表
function loadEarnList() {
const listContainer = document.getElementById('earn-list');
let html = `
<div class="quick-add">
<input class="qa-name" id="quick-earn-name" placeholder="名称(留空则用日期)">
<input class="qa-points" id="quick-earn-points" type="number" placeholder="积分">
<button class="qa-btn" onclick="quickEarn()">赚取</button>
</div>
`;
if (earnItems.length === 0) {
html += `
<div class="empty-state">
<div class="empty-icon">💰</div>
<div class="empty-text">暂无赚取积分项</div>
</div>
`;
} else {
html += earnItems.map(item => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${item.name}</div>
<div class="item-points income">+${item.points}积分</div>
</div>
<button class="item-btn earn" onclick="earnPoints(${item.id})">赚取</button>
</div>
`).join('');
}
listContainer.innerHTML = html;
}
// 加载扣取积分列表
function loadDeductList() {
const listContainer = document.getElementById('deduct-list');
let html = `
<div class="quick-add deduct">
<input class="qa-name" id="quick-deduct-name" placeholder="名称(留空则用日期)">
<input class="qa-points" id="quick-deduct-points" type="number" placeholder="积分">
<button class="qa-btn" onclick="quickDeduct()">扣取</button>
</div>
`;
if (deductItems.length === 0) {
html += `
<div class="empty-state">
<div class="empty-icon">⚠️</div>
<div class="empty-text">暂无扣取积分项</div>
</div>
`;
} else {
html += deductItems.map(item => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${item.name}</div>
<div class="item-points expense">${item.points}积分</div>
</div>
<button class="item-btn deduct" onclick="deductPoints(${item.id})">扣取</button>
</div>
`).join('');
}
listContainer.innerHTML = html;
}
// 加载兑换列表
function loadExchangeList() {
const listContainer = document.getElementById('exchange-list');
let html = `
<div class="quick-add exchange">
<input class="qa-name" id="quick-exchange-name" placeholder="名称(留空则用日期)">
<input class="qa-points" id="quick-exchange-points" type="number" placeholder="积分">
<button class="qa-btn" onclick="quickExchange()">兑换</button>
</div>
`;
if (rewards.length === 0) {
html += `
<div class="empty-state">
<div class="empty-icon">🎁</div>
<div class="empty-text">暂无兑换奖励</div>
</div>
`;
} else {
html += rewards.map(reward => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${reward.name}</div>
<div class="item-points">${reward.points}积分</div>
</div>
<button class="item-btn exchange" onclick="showExchangeModal(${reward.id})">兑换</button>
</div>
`).join('');
}
listContainer.innerHTML = html;
}
// 加载赚取积分项管理列表
function loadEarnManageList() {
const listContainer = document.getElementById('earn-manage-list');
if (earnItems.length === 0) {
listContainer.innerHTML = `
<div class="empty-state">
<div class="empty-icon">💰</div>
<div class="empty-text">暂无赚取积分项</div>
</div>
`;
return;
}
listContainer.innerHTML = earnItems.map(item => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${item.name}</div>
<div class="item-points income">+${item.points}积分</div>
</div>
<div class="item-actions">
<button class="action-btn edit" onclick="editEarnItem(${item.id})">编辑</button>
<button class="action-btn delete" onclick="deleteEarnItem(${item.id})">删除</button>
</div>
</div>
`).join('');
}
// 加载扣取积分项管理列表
function loadDeductManageList() {
const listContainer = document.getElementById('deduct-manage-list');
if (deductItems.length === 0) {
listContainer.innerHTML = `
<div class="empty-state">
<div class="empty-icon">⚠️</div>
<div class="empty-text">暂无扣取积分项</div>
</div>
`;
return;
}
listContainer.innerHTML = deductItems.map(item => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${item.name}</div>
<div class="item-points expense">${item.points}积分</div>
</div>
<div class="item-actions">
<button class="action-btn edit" onclick="editDeductItem(${item.id})">编辑</button>
<button class="action-btn delete" onclick="deleteDeductItem(${item.id})">删除</button>
</div>
</div>
`).join('');
}
// 加载奖励管理列表
function loadRewardManageList() {
const listContainer = document.getElementById('reward-manage-list');
if (rewards.length === 0) {
listContainer.innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎁</div>
<div class="empty-text">暂无兑换奖励</div>
</div>
`;
return;
}
listContainer.innerHTML = rewards.map(reward => `
<div class="list-item">
<div class="item-info">
<div class="item-name">${reward.name}</div>
<div class="item-points">${reward.points}积分</div>
</div>
<div class="item-actions">
<button class="action-btn edit" onclick="editReward(${reward.id})">编辑</button>
<button class="action-btn delete" onclick="deleteReward(${reward.id})">删除</button>
</div>
</div>
`).join('');
}
// 加载积分记录数据
function loadRecordsData() {
// 如果没有选中日期,默认选中当天
if (!selectedDate) {
selectedDate = formatDate(new Date());
}
loadCurrentWeekGrid('records-week-grid', 'records-week-score-text');
loadDayRecords();
loadHistoryWeeks();
}
// 加载当前周积分网格
function loadCurrentWeekGrid(gridId, titleId) {
const grid = document.getElementById(gridId);
const now = new Date();
const currentDay = now.getDay();
const weekStart = new Date(now);
weekStart.setDate(now.getDate() - (currentDay === 0 ? 6 : currentDay - 1)); // 周一
let weekTotal = 0;
const dayNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
let cellsHtml = '';
dayNames.forEach((dayName, index) => {
const date = new Date(weekStart);
date.setDate(weekStart.getDate() + index);
const dateStr = formatDate(date);
const dayRecords = records.filter(r => r.date === dateStr);
const dayTotal = dayRecords.reduce((sum, r) => sum + r.amount, 0);
weekTotal += dayTotal;
const isToday = date.toDateString() === now.toDateString();
const isSelected = selectedDate === dateStr;
let pointsClass = '';
if (isToday) {
pointsClass = 'today-points';
} else if (isSelected) {
pointsClass = 'selected-points';
}
cellsHtml += `
<div class="day-item ${isToday ? 'today' : ''} ${isSelected ? 'selected' : ''}" onclick="switchToDayRecords('${dateStr}')">
<div class="day-name">${dayName}</div>
<div class="day-points ${pointsClass}">${dayTotal > 0 ? '+' : ''}${dayTotal}</div>
</div>
`;
});
grid.innerHTML = cellsHtml;
// 更新本周积分标题
const scoreEl = document.getElementById(titleId);
if (scoreEl) {
const sign = weekTotal >= 0 ? '+' : '';
scoreEl.textContent = `本周积分 (${sign}${weekTotal})`;
}
}
// 切换到记录页面并显示当天详情
function switchToDayRecords(dateStr) {
selectedDate = dateStr;
switchPage('records');
loadDayRecords();
// 重新加载周积分网格,以更新选中状态
loadCurrentWeekGrid('records-week-grid', 'records-week-score-text');
}
// 加载每日积分记录
function loadDayRecords() {
const container = document.getElementById('day-records');
if (selectedDate) {
const dayRecords = records.filter(r => r.date === selectedDate);
if (dayRecords.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📊</div>
<div class="empty-text">该日期暂无积分记录</div>
</div>
`;
} else {
container.innerHTML = dayRecords.map(record => `
<div class="record-item">
<div class="record-info">
<div class="record-title">${record.title}</div>
<div class="record-time">${record.time}</div>
</div>
<div class="record-amount ${record.type}">${record.type === 'income' ? '+' : ''}${record.amount}</div>
<button class="action-btn delete" onclick="undoRecord(${record.id})" style="font-size:10px;padding:3px 8px;flex-shrink:0;">撤销</button>
</div>
`).join('');
}
} else {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📅</div>
<div class="empty-text">点击上方某天查看详细记录</div>
</div>
`;
}
}
// 加载历史积分(月日历 + 年历)
function loadHistoryWeeks() {
const container = document.getElementById('history-weeks');
const now = new Date();
const curYear = now.getFullYear();
const curMonth = now.getMonth();
const weekdayNames = ['一', '二', '三', '四', '五', '六', '日'];
const monthNames = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
// 确定显示的月份
if (displayYear === null) displayYear = curYear;
if (displayMonth === null) displayMonth = curMonth;
const dYear = displayYear;
const dMonth = displayMonth;
// 年度总积分
const yearStart = formatDate(new Date(dYear, 0, 1));
const yearEnd = formatDate(new Date(dYear, 11, 31));
const yearRecords = records.filter(r => r.date >= yearStart && r.date <= yearEnd);
const yearTotal = yearRecords.reduce((sum, r) => sum + r.amount, 0);
const ySign = yearTotal > 0 ? '+' : '';
// 当月月历
const daysInMonth = new Date(dYear, dMonth + 1, 0).getDate();
const monthStart = formatDate(new Date(dYear, dMonth, 1));
const monthEnd = formatDate(new Date(dYear, dMonth, daysInMonth));
const monthRecords = records.filter(r => r.date >= monthStart && r.date <= monthEnd);
const monthTotal = monthRecords.reduce((sum, r) => sum + r.amount, 0);
const totalSign = monthTotal > 0 ? '+' : '';
const firstDay = new Date(dYear, dMonth, 1).getDay();
const startPadding = firstDay === 0 ? 6 : firstDay - 1;
let html = `<div class="month-calendar">
<div class="month-header">
<div class="month-title">${dYear}年${dMonth + 1}月 (${totalSign}${monthTotal})</div>
</div>
<div class="month-weekdays">${weekdayNames.map(w => `<div class="month-weekday">${w}</div>`).join('')}</div>
<div class="month-days">`;
for (let p = 0; p < startPadding; p++) {
html += `<div class="month-day empty"></div>`;
}
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = formatDate(new Date(dYear, dMonth, d));
const dayRecords = records.filter(r => r.date === dateStr);
const dayTotal = dayRecords.reduce((sum, r) => sum + r.amount, 0);
const ptsStr = dayTotal !== 0 ? ((dayTotal > 0 ? '+' : '') + dayTotal) : '';
const isToday = dateStr === formatDate(now) ? ' today' : '';
html += `<div class="month-day${isToday}">
<div class="month-day-num">${d}</div>
<div class="month-day-pts">${ptsStr}</div>
</div>`;
}
html += `</div></div>`;
// 年历
html += `<div class="section-title">${dYear}年总积分 (${ySign}${yearTotal})</div>`;
// 年历 6×2 宫格
html += `<div class="year-calendar">`;
for (let m = 0; m < 12; m++) {
const mStart = formatDate(new Date(dYear, m, 1));
const mEnd = formatDate(new Date(dYear, m, new Date(dYear, m + 1, 0).getDate()));
const mRecords = records.filter(r => r.date >= mStart && r.date <= mEnd);
const mTotal = mRecords.reduce((sum, r) => sum + r.amount, 0);
const mSign = mTotal > 0 ? '+' : '';
const isCur = (dYear === curYear && m === curMonth) ? ' current' : '';
html += `<div class="year-month-card${isCur}" onclick="switchMonthCalendar(${m})">
<div class="year-month-name">${monthNames[m]}</div>
<div class="year-month-total">${mSign}${mTotal}</div>
</div>`;
}
html += `</div>`;
container.innerHTML = html;
}
// 点击年历卡片切换月日历
function switchMonthCalendar(month) {
displayYear = displayYear !== null ? displayYear : new Date().getFullYear();
displayMonth = month;
loadHistoryWeeks();
}
// 格式化日期
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 赚取积分
// 快速赚取积分(自定义填入)
function quickEarn() {
const nameEl = document.getElementById('quick-earn-name');
const pointsEl = document.getElementById('quick-earn-points');
const name = nameEl.value.trim() || formatDate(new Date());
const points = parseInt(pointsEl.value);
if (!points || points <= 0) { showAlert('请输入有效积分'); return; }
totalPoints += points;
addRecord(name, points, 'income');
nameEl.value = '';
pointsEl.value = '';
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
showAlert(`赚取 +${points} 积分`);
}
// 快速扣取积分(自定义填入)
function quickDeduct() {
const nameEl = document.getElementById('quick-deduct-name');
const pointsEl = document.getElementById('quick-deduct-points');
const name = nameEl.value.trim() || formatDate(new Date());
const points = parseInt(pointsEl.value);
if (!points || points <= 0) { showAlert('请输入有效积分'); return; }
totalPoints -= points;
addRecord(name, -points, 'expense');
nameEl.value = '';
pointsEl.value = '';
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
showAlert(`扣取 -${points} 积分`);
}
// 快速兑换(自定义填入)
function quickExchange() {
const nameEl = document.getElementById('quick-exchange-name');
const pointsEl = document.getElementById('quick-exchange-points');
const name = nameEl.value.trim() || formatDate(new Date());
const points = parseInt(pointsEl.value);
if (!points || points <= 0) { showAlert('请输入有效积分'); return; }
if (totalPoints < points) { showAlert('积分不足'); return; }
totalPoints -= points;
addRecord(name, -points, 'expense');
nameEl.value = '';
pointsEl.value = '';
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
showAlert(`兑换成功!消耗 ${points} 积分`);
}
function earnPoints(itemId) {
const item = earnItems.find(i => i.id === itemId);
if (item) {
totalPoints += item.points;
addRecord(item.name, item.points, 'income');
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
showAlert(`成功赚取${item.points}积分!`);
}
}
// 显示自定义提示弹窗
function showAlert(message) {
const modal = document.createElement('div');
modal.className = 'modal show';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-title">提示</div>
<div class="modal-body">
<p>${message}</p>
</div>
<div class="modal-footer">
<button class="modal-btn confirm" onclick="this.closest('.modal').remove()">确定</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 点击弹窗外部关闭
modal.addEventListener('click', function(e) {
if (e.target === this) {
this.remove();
}
});
}
// 扣取积分
function deductPoints(itemId) {
const item = deductItems.find(i => i.id === itemId);
if (item) {
totalPoints += item.points;
addRecord(item.name, item.points, 'expense');
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
showAlert(`成功扣取${Math.abs(item.points)}积分!`);
}
}
// 添加积分记录
function addRecord(title, amount, type, date = null, timeStr = null) {
const now = new Date();
const recordDate = date ? new Date(date) : now;
recordIdCounter++;
records.unshift({
id: recordIdCounter,
title: title,
amount: amount,
type: type,
date: formatDate(recordDate),
time: timeStr || now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
});
}
// 显示添加赚取积分项弹窗
function showAddEarnItemModal() {
currentEditItem = null;
document.getElementById('earn-item-modal-title').textContent = '添加赚取积分项';
document.getElementById('earn-item-name').value = '';
document.getElementById('earn-item-points').value = '';
document.getElementById('add-earn-item-modal').classList.add('show');
}
// 编辑赚取积分项
function editEarnItem(itemId) {
const item = earnItems.find(i => i.id === itemId);
if (item) {
currentEditItem = item;
document.getElementById('earn-item-modal-title').textContent = '编辑赚取积分项';
document.getElementById('earn-item-name').value = item.name;
document.getElementById('earn-item-points').value = item.points;
document.getElementById('add-earn-item-modal').classList.add('show');
}
}
// 保存赚取积分项
function saveEarnItem() {
const name = document.getElementById('earn-item-name').value.trim();
const points = parseInt(document.getElementById('earn-item-points').value);
if (!name || isNaN(points)) {
showAlert('请填写完整信息');
return;
}
if (currentEditItem) {
// 编辑
currentEditItem.name = name;
currentEditItem.points = points;
} else {
// 添加
const newId = earnItems.length > 0 ? Math.max(...earnItems.map(i => i.id)) + 1 : 1;
earnItems.push({ id: newId, name: name, points: points });
}
saveData();
loadEarnManageList();
closeModal('add-earn-item-modal');
}
// 删除赚取积分项
function deleteEarnItem(itemId) {
currentDeleteItem = itemId;
currentDeleteType = 'earn';
document.getElementById('delete-confirm-modal').classList.add('show');
}
// 显示添加扣取积分项弹窗
function showAddDeductItemModal() {
currentEditItem = null;
document.getElementById('deduct-item-modal-title').textContent = '添加扣取积分项';
document.getElementById('deduct-item-name').value = '';
document.getElementById('deduct-item-points').value = '';
document.getElementById('add-deduct-item-modal').classList.add('show');
}
// 编辑扣取积分项
function editDeductItem(itemId) {
const item = deductItems.find(i => i.id === itemId);
if (item) {
currentEditItem = item;
document.getElementById('deduct-item-modal-title').textContent = '编辑扣取积分项';
document.getElementById('deduct-item-name').value = item.name;
document.getElementById('deduct-item-points').value = Math.abs(item.points);
document.getElementById('add-deduct-item-modal').classList.add('show');
}
}
// 保存扣取积分项
function saveDeductItem() {
const name = document.getElementById('deduct-item-name').value.trim();
const points = parseInt(document.getElementById('deduct-item-points').value);
if (!name || isNaN(points)) {
showAlert('请填写完整信息');
return;
}
if (currentEditItem) {
// 编辑
currentEditItem.name = name;
currentEditItem.points = -points;
} else {
// 添加
const newId = deductItems.length > 0 ? Math.max(...deductItems.map(i => i.id)) + 1 : 1;
deductItems.push({ id: newId, name: name, points: -points });
}
saveData();
loadDeductManageList();
closeModal('add-deduct-item-modal');
}
// 删除扣取积分项
function deleteDeductItem(itemId) {
currentDeleteItem = itemId;
currentDeleteType = 'deduct';
document.getElementById('delete-confirm-modal').classList.add('show');
}
// 显示添加奖励弹窗
function showAddRewardModal() {
currentEditItem = null;
document.getElementById('reward-modal-title').textContent = '添加兑换奖励';
document.getElementById('reward-name').value = '';
document.getElementById('reward-points').value = '';
document.getElementById('add-reward-modal').classList.add('show');
}
// 编辑奖励
function editReward(rewardId) {
const reward = rewards.find(r => r.id === rewardId);
if (reward) {
currentEditItem = reward;
document.getElementById('reward-modal-title').textContent = '编辑兑换奖励';
document.getElementById('reward-name').value = reward.name;
document.getElementById('reward-points').value = reward.points;
document.getElementById('add-reward-modal').classList.add('show');
}
}
// 保存奖励
function saveReward() {
const name = document.getElementById('reward-name').value.trim();
const points = parseInt(document.getElementById('reward-points').value);
if (!name || isNaN(points)) {
showAlert('请填写完整信息');
return;
}
if (currentEditItem) {
// 编辑
currentEditItem.name = name;
currentEditItem.points = points;
} else {
// 添加
const newId = rewards.length > 0 ? Math.max(...rewards.map(r => r.id)) + 1 : 1;
rewards.push({ id: newId, name: name, points: points });
}
saveData();
loadRewardManageList();
closeModal('add-reward-modal');
}
// 删除奖励
function deleteReward(rewardId) {
currentDeleteItem = rewardId;
currentDeleteType = 'reward';
document.getElementById('delete-confirm-modal').classList.add('show');
}
// 显示兑换弹窗
function showExchangeModal(rewardId) {
const reward = rewards.find(r => r.id === rewardId);
if (reward) {
currentExchangeReward = reward;
document.getElementById('exchange-reward-name').textContent = reward.name;
document.getElementById('exchange-reward-points').textContent = reward.points + '积分';
document.getElementById('exchange-confirm-modal').classList.add('show');
}
}
// 确认兑换
function confirmExchange() {
if (currentExchangeReward && totalPoints >= currentExchangeReward.points) {
totalPoints -= currentExchangeReward.points;
addRecord('兑换:' + currentExchangeReward.name, -currentExchangeReward.points, 'expense');
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
closeModal('exchange-confirm-modal');
showAlert('兑换成功!');
} else {
closeModal('exchange-confirm-modal');
showAlert('积分不足!');
}
}
// 显示补积分弹窗
function showAddPointsModal() {
// 设置默认日期为今天
const today = new Date();
const formattedDate = today.toISOString().split('T')[0];
document.getElementById('add-points-date').value = formattedDate;
document.getElementById('add-points-reason').value = '';
document.getElementById('add-points-value').value = '';
document.getElementById('add-points-modal').classList.add('show');
}
// 补积分
function addPoints() {
const reason = document.getElementById('add-points-reason').value.trim();
const value = parseInt(document.getElementById('add-points-value').value);
const dateStr = document.getElementById('add-points-date').value;
if (!reason || isNaN(value) || !dateStr) {
showAlert('请填写完整信息');
return;
}
// 添加积分记录
totalPoints += value;
const recordTime = new Date(dateStr + 'T12:00:00').toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
addRecord('补积分:' + reason, value, 'income', dateStr, recordTime);
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
// 关闭弹窗
closeModal('add-points-modal');
// 重新加载记录数据
loadRecordsData();
showAlert('补积分成功!');
}
// 显示清空积分弹窗
function showClearPointsModal() {
document.getElementById('clear-points-modal').classList.add('show');
}
// 确认清空积分
function confirmClearPoints() {
totalPoints = 0;
records = [];
recordIdCounter = 0;
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
closeModal('clear-points-modal');
loadCurrentPageData();
showAlert('积分已清空!');
}
// 系统重置
function systemReset() {
if (!confirm('确定要系统重置吗?\n\n将清空所有积分、记录,恢复默认设置项。\n此操作不可恢复!')) return;
totalPoints = 0;
records = [];
recordIdCounter = 0;
earnItems = JSON.parse(JSON.stringify(DEFAULT_EARN_ITEMS));
deductItems = JSON.parse(JSON.stringify(DEFAULT_DEDUCT_ITEMS));
rewards = JSON.parse(JSON.stringify(DEFAULT_REWARDS));
saveData();
updatePointsDisplay();
updateTodayPointsDisplay();
closeModal('clear-points-modal');
loadCurrentPageData();
showAlert('系统已重置为默认状态');
}
// 确认删除
function confirmDelete() {
if (currentDeleteType === 'earn') {
earnItems = earnItems.filter(i => i.id !== currentDeleteItem);
loadEarnManageList();
} else if (currentDeleteType === 'deduct') {
deductItems = deductItems.filter(i => i.id !== currentDeleteItem);
loadDeductManageList();
} else if (currentDeleteType === 'reward') {
rewards = rewards.filter(r => r.id !== currentDeleteItem);
loadRewardManageList();
}
saveData();
closeModal('delete-confirm-modal');
}
// 撤销记录
function undoRecord(recordId) {
const idx = records.findIndex(r => r.id === recordId);
if (idx !== -1) {
totalPoints -= records[idx].amount;
records.splice(idx, 1);
updatePointsDisplay();
updateTodayPointsDisplay();
saveData();
loadDayRecords();
if (document.getElementById('records-page').classList.contains('active')) {
loadCurrentWeekGrid('records-week-grid', 'records-week-score-text');
loadHistoryWeeks();
}
}
}
// 关闭弹窗
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('show');
}
// 重载当前活动页面数据
function loadCurrentPageData() {
if (document.getElementById('home-page').classList.contains('active')) {
loadHomeData();
} else if (document.getElementById('records-page').classList.contains('active')) {
loadRecordsData();
}
}
// 点击弹窗外部关闭
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', function(e) {
if (e.target === this) {
this.classList.remove('show');
}
});
});
// 初始化
document.addEventListener('DOMContentLoaded', function() {
initData();
loadHomeData();
});
</script>
</body>
</html>

