吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 250|回复: 3
收起左侧

[其他原创] 可用金额月度预算日历手机版

[复制链接]
zongyunxu 发表于 2026-1-13 15:58
本帖最后由 zongyunxu 于 2026-1-13 16:00 编辑

因本人每月工资除去上交还房贷以后,每个月都会剩下一点钱做为自己的零花钱,想计划一下每天可以用多少钱,怕花超了预算。导致月后面就没钱可用了。所以根据自己的实际情况,制作了一个这样的网页。
设想:输入每月可用预算金额,固定开支选项用+号添加,类别包括电费、燃气费、水费、加油费、物业费、停车费、预存款、其他项(可自定义输入文字,长度限制在10个汉字以内)。点击计算后,显示一张日历,对应的日期下面显示每天可用金额,日历应当自2026年1月开始。根据2025年的银行卡账单的分析后,对自己的消费习惯做出如下预算规则:周五固定金额100元,周六、周日每天可用金额是周一至周四可用金额的1.5倍。
有需要的可以自行调整固定金额和倍数。


运行后界面是这样的:


如果有重复项添加,则提示已有重复项,需删除:


完整版代码如下,保存为XXX.html存放到手机里,点击即可使用。为了怕数据重复,顶部添加了清除缓存刷新按钮。
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <title>2026预算日历</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }
        
        body {
            padding: 10px; /* 减少body内边距,适配小屏 */
            background-color: #f5f5f5;
            overflow-x: hidden; /* 禁止页面整体横向滚动 */
        }
        
        .container {
            width: 100%; /* 改为100%宽度,适配所有屏幕 */
            max-width: 500px;
            margin: 0 auto;
            background: white;
            padding: 15px; /* 减少内边距 */
            border-radius: 12px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        h1 {
            font-size: 18px; /* 缩小标题字体 */
            text-align: center;
            margin-bottom: 15px;
            color: #333;
        }
        
        .refresh-btn {
            width: 100%;
            background: #ff9800;
            color: white;
            border: none;
            padding: 8px; /* 减少按钮内边距 */
            border-radius: 6px;
            font-size: 15px;
            cursor: pointer;
            margin-bottom: 12px;
        }
        
        .input-group {
            margin-bottom: 12px;
        }
        
        label {
            display: block;
            margin-bottom: 4px;
            font-weight: 500;
            color: #555;
            font-size: 14px;
        }
        
        input[type="number"], 
        select, 
        .custom-input {
            width: 100%;
            padding: 8px; /* 减少输入框内边距 */
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
        }
        
        .expense-item {
            display: flex;
            gap: 8px; /* 缩小间距 */
            align-items: center;
            margin-bottom: 8px;
            padding: 8px;
            background: #f9f9f9;
            border-radius: 6px;
        }
        
        .expense-item select, 
        .expense-item input {
            flex: 1;
        }
        
        .expense-amount {
            width: 70px !important; /* 缩小金额输入框 */
        }
        
        .remove-btn {
            background: #ff4444;
            color: white;
            border: none;
            width: 28px; /* 缩小删除按钮 */
            height: 28px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .add-btn {
            background: #2196F3;
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 4px;
        }
        
        .calculate-btn {
            width: 100%;
            background: #4CAF50;
            color: white;
            border: none;
            padding: 10px;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
            margin-bottom: 15px;
        }
        
        .calendar {
            display: none;
            margin-top: 15px;
            width: 100%; /* 日历容器占满宽度 */
            overflow-x: auto; /* 小屏时允许横向滚动日历 */
            -webkit-overflow-scrolling: touch; /* 适配iOS顺滑滚动 */
        }
        
        .calendar h2 {
            font-size: 16px;
            text-align: center;
            margin-bottom: 8px;
            color: #333;
        }
        
        .calendar-header {
            display: grid;
            grid-template-columns: repeat(7, minmax(30px, 1fr)); /* 关键:列宽自适应,最小30px */
            text-align: center;
            font-weight: bold;
            margin-bottom: 8px;
            color: #666;
            font-size: 12px; /* 缩小表头字体 */
        }
        
        .calendar-days {
            display: grid;
            grid-template-columns: repeat(7, minmax(30px, 1fr)); /* 关键:列宽自适应 */
            gap: 3px; /* 缩小日期间距 */
        }
        
        .calendar-day {
            min-height: 35px; /* 最小高度,保证可点击 */
            aspect-ratio: 1; /* 保持正方形 */
            border: 1px solid #eee;
            padding: 2px; /* 大幅减少内边距 */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            border-radius: 4px;
            background: #f9f9f9;
        }
        
        .day-number {
            font-weight: bold;
            margin-bottom: 1px;
            font-size: 11px; /* 缩小日期数字 */
        }
        
        .day-amount {
            font-size: 9px; /* 缩小金额字体 */
            color: #2196F3;
        }
        
        .empty-day {
            border: none;
            background: transparent;
        }
        
        .friday {
            background: #ffebee;
        }
        
        .weekend {
            background: #e3f2fd;
        }
        
        .error {
            color: #ff4444;
            margin-bottom: 12px;
            display: none;
            font-size: 14px;
        }
        
        .custom-category-container {
            display: none;
            margin-top: 4px;
        }
        
        .custom-category-input {
            width: 100%;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 13px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>2026年月度预算日历</h1>
        
        <button class="refresh-btn" id="refresh-btn">&#128260; 清除缓存并刷新</button>
        
        <div class="input-group">
            <label for="total-budget">每月可用预算(元)</label>
            <input type="number" id="total-budget" min="0" step="0.01" required>
        </div>
        
        <div class="input-group">
            <label>固定开支</label>
            <div id="expense-list">
                <!-- 动态添加开支项 -->
            </div>
            <button class="add-btn" id="add-expense">+ 添加固定开支</button>
        </div>
        
        <div class="error" id="error-message"></div>
        
        <button class="calculate-btn" id="calculate-btn">计算并显示日历</button>
        
        <div class="calendar" id="calendar-container">
            <h2>2026年1月预算日历</h2>
            <div class="calendar-header">
                <div>周一</div>
                <div>周二</div>
                <div>周三</div>
                <div>周四</div>
                <div>周五</div>
                <div>周六</div>
                <div>周日</div>
            </div>
            <div class="calendar-days" id="calendar-days">
                <!-- 动态生成日历 -->
            </div>
        </div>
    </div>

    <script>
        // 初始化开支项
        document.addEventListener('DOMContentLoaded', function() {
            const expenseList = document.getElementById('expense-list');
            const addExpenseBtn = document.getElementById('add-expense');
            const calculateBtn = document.getElementById('calculate-btn');
            const calendarContainer = document.getElementById('calendar-container');
            const calendarDays = document.getElementById('calendar-days');
            const errorMessage = document.getElementById('error-message');
            const refreshBtn = document.getElementById('refresh-btn');
            
            // 开支类别
            const expenseCategories = [
                '电费', '燃气费', '水费', '加油费', 
                '物业费', '停车费', '预存款', '其他项'
            ];
            
            // 强制刷新逻辑
            refreshBtn.addEventListener('click', function() {
                if (confirm('确定要清除缓存并刷新页面吗?所有未提交的输入会丢失!')) {
                    window.location.reload(true); 
                }
            });
            
            // 添加按钮仅创建项,不立即检查重复
            addExpenseBtn.addEventListener('click', function() {
                addExpenseItem(); // 直接创建,不检查重复
            });
            
            // 初始化添加一个开支项
            addExpenseItem();
            
            // 计算按钮点击事件:先检查所有项是否重复,再计算
            calculateBtn.addEventListener('click', function() {
                // 第一步:检查所有开支项是否有重复类别
                const duplicateCategory = checkAllExpenseDuplicate();
                if (duplicateCategory) {
                    errorMessage.textContent = `发现重复的开支类别:${duplicateCategory},请修改后再计算!`;
                    errorMessage.style.display = 'block';
                    return; // 阻止后续计算
                }
                
                // 第二步:执行正常计算逻辑
                calculateAndDisplayCalendar();
            });
            
            // 新增:检查所有开支项是否有重复类别(全局校验)
            function checkAllExpenseDuplicate() {
                const categoryList = [];
                const expenseItems = document.querySelectorAll('.expense-item');
                
                // 遍历所有项,收集类别
                expenseItems.forEach(item => {
                    const categorySelect = item.querySelector('.expense-category');
                    let category = categorySelect.value;
                    if (category === '其他项') {
                        const customInput = item.querySelector('.custom-category-input');
                        category = customInput.value.trim() || '其他项';
                    }
                    // 空类别跳过(未输入的其他项)
                    if (category) {
                        categoryList.push(category);
                    }
                });
                
                // 检查是否有重复
                const uniqueCategories = new Set(categoryList);
                if (categoryList.length !== uniqueCategories.size) {
                    // 找到重复的类别
                    for (let cat of categoryList) {
                        if (categoryList.filter(c => c === cat).length > 1) {
                            return cat; // 返回第一个重复的类别
                        }
                    }
                }
                return null; // 无重复
            }
            
            // 检查单个项重复的函数(保留,备用)
            function checkExpenseDuplicate(newItem) {
                if (!newItem) return false;
                
                const newCategorySelect = newItem.querySelector('.expense-category');
                let newCategory = newCategorySelect.value;
                if (newCategory === '其他项') {
                    const customInput = newItem.querySelector('.custom-category-input');
                    newCategory = customInput.value.trim() || '其他项';
                }
                if (!newCategory) return false;
                
                const existingCategories = [];
                const expenseItems = document.querySelectorAll('.expense-item');
                
                expenseItems.forEach(item => {
                    if (item === newItem) return;
                    const categorySelect = item.querySelector('.expense-category');
                    let category = categorySelect.value;
                    if (category === '其他项') {
                        const customInput = item.querySelector('.custom-category-input');
                        category = customInput.value.trim() || '其他项';
                    }
                    existingCategories.push(category);
                });
                
                return existingCategories.includes(newCategory);
            }
            
            // 添加开支项函数
            function addExpenseItem() {
                const expenseItem = document.createElement('div');
                expenseItem.className = 'expense-item';
                
                const categorySelect = document.createElement('select');
                categorySelect.className = 'expense-category';
                expenseCategories.forEach(category => {
                    const option = document.createElement('option');
                    option.value = category;
                    option.textContent = category;
                    categorySelect.appendChild(option);
                });
                
                const customCategoryContainer = document.createElement('div');
                customCategoryContainer.className = 'custom-category-container';
                const customCategoryInput = document.createElement('input');
                customCategoryInput.type = 'text';
                customCategoryInput.className = 'custom-category-input';
                customCategoryInput.placeholder = '请输入自定义类别(10字以内)';
                customCategoryInput.maxLength = 10;
                customCategoryContainer.appendChild(customCategoryInput);
                
                const amountInput = document.createElement('input');
                amountInput.type = 'number';
                amountInput.className = 'expense-amount';
                amountInput.min = '0';
                amountInput.step = '0.01';
                amountInput.placeholder = '金额';
                
                const removeBtn = document.createElement('button');
                removeBtn.className = 'remove-btn';
                removeBtn.textContent = '-';
                removeBtn.addEventListener('click', function() {
                    if (expenseList.children.length > 1) {
                        expenseItem.remove();
                    } else {
                        alert('至少保留一个开支项,可以将金额设为0');
                    }
                });
                
                // 监听类别选择变化
                categorySelect.addEventListener('change', function() {
                    if (this.value === '其他项') {
                        customCategoryContainer.style.display = 'block';
                    } else {
                        customCategoryContainer.style.display = 'none';
                    }
                });
                
                // 组装开支项
                expenseItem.appendChild(categorySelect);
                expenseItem.appendChild(customCategoryContainer);
                expenseItem.appendChild(amountInput);
                expenseItem.appendChild(removeBtn);
                
                expenseList.appendChild(expenseItem);
                
                return expenseItem;
            }
            
            // 计算并显示日历
            function calculateAndDisplayCalendar() {
                try {
                    errorMessage.style.display = 'none';
                    
                    const totalBudget = parseFloat(document.getElementById('total-budget').value);
                    if (isNaN(totalBudget) || totalBudget < 0) {
                        throw new Error('请输入有效的月度预算金额(大于等于0)');
                    }
                    
                    let totalExpenses = 0;
                    const expenseItems = document.querySelectorAll('.expense-item');
                    
                    expenseItems.forEach(item => {
                        const amountInput = item.querySelector('.expense-amount');
                        const amount = parseFloat(amountInput.value) || 0;
                        if (amount < 0) {
                            throw new Error('固定开支金额不能为负数');
                        }
                        totalExpenses += amount;
                    });
                    
                    if (totalExpenses > totalBudget) {
                        throw new Error(`固定开支总额(${totalExpenses.toFixed(2)}元)超过了月度预算(${totalBudget.toFixed(2)}元)`);
                    }
                    
                    const availableBudget = Math.round(totalBudget - totalExpenses);
                    
                    const monthStats = {
                        weekdays: 0,    
                        fridays: 0,     
                        weekends: 0     
                    };
                    
                    for (let day = 1; day <= 31; day++) {
                        const date = new Date(2026, 0, day);
                        const dayOfWeek = date.getDay();
                        
                        if (dayOfWeek === 5) { 
                            monthStats.fridays++;
                        } else if (dayOfWeek === 0 || dayOfWeek === 6) { 
                            monthStats.weekends++;
                        } else { 
                            monthStats.weekdays++;
                        }
                    }
                    
                    const fridayFixedAmount = 100;
                    const weekendMultiplier = 1.5;
                    
                    const fridayTotal = fridayFixedAmount * monthStats.fridays;
                    if (fridayTotal > availableBudget) {
                        throw new Error(`预算不足,仅周五固定支出就需要${fridayTotal}元`);
                    }
                    
                    let baseAmount = (availableBudget - fridayTotal) / 
                                      (monthStats.weekdays + weekendMultiplier * monthStats.weekends);
                    
                    let dailyAmounts = [];
                    for (let day = 1; day <= 31; day++) {
                        const date = new Date(2026, 0, day);
                        const dayOfWeek = date.getDay();
                        let amount;
                        
                        if (dayOfWeek === 5) {
                            amount = fridayFixedAmount;
                        } else if (dayOfWeek === 0 || dayOfWeek === 6) {
                            amount = baseAmount * weekendMultiplier;
                        } else {
                            amount = baseAmount;
                        }
                        dailyAmounts.push({ day, dayOfWeek, amount });
                    }
                    
                    dailyAmounts = dailyAmounts.map(item => ({
                        ...item,
                        amount: Math.floor(item.amount)
                    }));
                    
                    let totalAllocated = dailyAmounts.reduce((sum, item) => sum + item.amount, 0);
                    let diff = availableBudget - totalAllocated;
                    
                    if (diff > 0) {
                        const sortedIndices = dailyAmounts
                            .map((_, idx) => idx)
                            .sort((a, b) => dailyAmounts[a].amount - dailyAmounts[b].amount);
                        
                        for (let i = 0; i < diff && i < sortedIndices.length; i++) {
                            dailyAmounts[sortedIndices[i]].amount += 1;
                        }
                    }
                    
                    generateCalendar(dailyAmounts);
                    calendarContainer.style.display = 'block';
                    
                } catch (error) {
                    errorMessage.textContent = error.message;
                    errorMessage.style.display = 'block';
                    calendarContainer.style.display = 'none';
                }
            }
            
            // 生成日历
            function generateCalendar(dailyAmounts) {
                calendarDays.innerHTML = '';
                
                const firstDayOfMonth = new Date(2026, 0, 1).getDay();
                const daysInMonth = 31;
                
                let emptyDays = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;
                
                for (let i = 0; i < emptyDays; i++) {
                    const emptyDay = document.createElement('div');
                    emptyDay.className = 'calendar-day empty-day';
                    calendarDays.appendChild(emptyDay);
                }
                
                dailyAmounts.forEach(item => {
                    const { day, dayOfWeek, amount } = item;
                    
                    const dayElement = document.createElement('div');
                    dayElement.className = 'calendar-day';
                    
                    if (dayOfWeek === 5) { 
                        dayElement.classList.add('friday');
                    } else if (dayOfWeek === 0 || dayOfWeek === 6) { 
                        dayElement.classList.add('weekend');
                    }
                    
                    const dayNumber = document.createElement('div');
                    dayNumber.className = 'day-number';
                    dayNumber.textContent = day;
                    
                    const dayAmountElement = document.createElement('div');
                    dayAmountElement.className = 'day-amount';
                    dayAmountElement.textContent = amount + '元';
                    
                    dayElement.appendChild(dayNumber);
                    dayElement.appendChild(dayAmountElement);
                    
                    calendarDays.appendChild(dayElement);
                });
            }
        });
    </script>
</body>
</html>


微信图片_20260113155324_304_685.png
微信图片_20260113155353_305_685.png

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

wi_xue2008 发表于 2026-1-13 22:44
负债开销太多,余钱少,花费心中有数,谢谢分享!
lfgzs 发表于 2026-1-14 09:46
qiufengqiusi 发表于 2026-1-14 21:38
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-1-17 05:43

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表