html记账本改写:保存数据 localStorage。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>记账本改写</title><style>table {user-select: none;/* width: 100%; */border-collapse: collapse;}table,th,td {border: 1px solid black;}th,td {padding: 8px;text-align: center;}</style>
</head>
<body><table id="ledgerTable"><thead><tr><th style="width: 50px;"><input type="month" id="monthPicker"></th><th colspan="3">日常开销</th></tr><tr><th>日期</th><th>项目</th><th>支出</th><th>收入</th></tr></thead><tbody></tbody><tfoot><tr><td><strong>总和</strong></td><td id="totalAmount">0</td><td id="totalExpense">0</td><td id="totalIncome">0</td></tr></tfoot></table><script>let currentYear, currentMonth;document.getElementById('monthPicker').addEventListener('change', function () {let selectedDate = new Date(this.value);currentYear = selectedDate.getFullYear();currentMonth = selectedDate.getMonth() + 1; // 月份从0开始,需要加1let daysInMonth = new Date(currentYear, currentMonth, 0).getDate();let tbody = document.querySelector('#ledgerTable tbody');// 清空tbody中的所有行tbody.innerHTML = '';// 为每一天添加行for (let day = 1; day <= daysInMonth; day++) {let newRow = document.createElement('tr');newRow.innerHTML = `<td>${day}</td><td><button class="add-item">+</button></td><td>0</td><td>0</td>`;newRow.querySelector('.add-item').addEventListener('click', addItemHandler);tbody.appendChild(newRow);}// 从localStorage加载数据loadData();// 初始化总和updateTotals();});// 添加项目的事件处理函数function addItemHandler() {const newItem = createItemElement({ name: '', expense: '', income: '' });this.parentNode.insertBefore(newItem, this);updateDailyTotals(this.closest('tr'));updateTotals(); // 更新总和saveData(); // 保存数据}// 创建项目元素的函数function createItemElement(item) {const newItem = document.createElement('div');newItem.innerHTML = `<input type="text" placeholder="项目" value="${item.name}"><select class="type-select"><option value="expense">支出</option><option value="income">收入</option></select><input type="number" placeholder="金额" style="width: 70px;" value="${item.expense || item.income || ''}"><button class="remove-item">-</button>`;// 获取金额输入框和类型选择框const amountInput = newItem.querySelector('input[type="number"]');const typeSelect = newItem.querySelector('.type-select');// 添加事件监听器addEventListeners(amountInput, typeSelect, newItem);// 给“-”按钮添加事件监听newItem.querySelector('.remove-item').addEventListener('click', function () {const row = this.closest('tr');// 弹出确认对话框const confirmDelete = confirm("确定要删除此项目吗?");if (confirmDelete) {this.parentNode.remove();updateDailyTotals(row);updateTotals(); // 更新总和saveData(); // 保存数据}});return newItem;}// 添加事件监听器的函数function addEventListeners(amountInput, typeSelect, newItem) {// 监听金额输入框的变化amountInput.addEventListener('input', function () {if (typeSelect.value === 'expense' && this.value > 0) {this.value = -this.value; // 如果选择“支出”且金额为正数,自动转换为负数}updateDailyTotals(newItem.closest('tr')); // 更新每日总和updateTotals(); // 更新总和saveData(); // 保存数据});// 监听类型选择框的变化typeSelect.addEventListener('change', function () {if (this.value === 'expense') {// 如果选择“支出”,确保金额为负数if (amountInput.value > 0) {amountInput.value = -amountInput.value;}amountInput.min = '-999999';amountInput.max = '0';} else if (this.value === 'income') {// 如果选择“收入”,确保金额为正数if (amountInput.value < 0) {amountInput.value = -amountInput.value;}amountInput.min = '0';amountInput.max = '999999';}updateDailyTotals(newItem.closest('tr')); // 更新每日总和updateTotals(); // 更新总和saveData(); // 保存数据});}// 更新每日总和的函数function updateDailyTotals(row) {const items = row.querySelectorAll('div');let expenseTotal = 0;let incomeTotal = 0;items.forEach(item => {const amountInput = item.querySelector('input[type="number"]');const amount = parseFloat(amountInput.value) || 0;if (amount < 0) {expenseTotal += amount;} else {incomeTotal += amount;}});row.querySelectorAll('td')[2].textContent = expenseTotal;row.querySelectorAll('td')[3].textContent = incomeTotal;}// 更新总和的函数function updateTotals() {const rows = document.querySelectorAll('#ledgerTable tbody tr');let totalExpense = 0;let totalIncome = 0;rows.forEach(row => {const expenseCell = row.querySelectorAll('td')[2];const incomeCell = row.querySelectorAll('td')[3];totalExpense += parseFloat(expenseCell.textContent) || 0;totalIncome += parseFloat(incomeCell.textContent) || 0;});document.getElementById('totalExpense').textContent = totalExpense;document.getElementById('totalIncome').textContent = totalIncome;document.getElementById('totalAmount').textContent = totalIncome + totalExpense;}// 保存数据的函数function saveData() {if (!currentYear || !currentMonth) return;const key = `ledger_${currentYear}_${currentMonth}`;const data = [];const rows = document.querySelectorAll('#ledgerTable tbody tr');rows.forEach(row => {const day = row.querySelector('td').textContent;const items = row.querySelectorAll('div');items.forEach(item => {const name = item.querySelector('input[type="text"]').value;const type = item.querySelector('.type-select').value;const amount = item.querySelector('input[type="number"]').value;data.push({ day, name, type, amount });});});localStorage.setItem(key, JSON.stringify(data));}// 加载数据的函数function loadData() {if (!currentYear || !currentMonth) return;const key = `ledger_${currentYear}_${currentMonth}`;const data = JSON.parse(localStorage.getItem(key)) || [];const rows = document.querySelectorAll('#ledgerTable tbody tr');data.forEach(item => {const row = Array.from(rows).find(row => row.querySelector('td').textContent === item.day);if (row) {const newItem = createItemElement({ name: item.name, expense: item.type === 'expense' ? item.amount : '', income: item.type === 'income' ? item.amount : '' });row.querySelector('td:nth-child(2)').insertBefore(newItem, row.querySelector('.add-item'));}});rows.forEach(row => updateDailyTotals(row));updateTotals();}</script>
</body>
</html>
相关文章:

html记账本改写:保存数据 localStorage。
<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>记账本改写</title><style>table {user-select: none;/* width: 100%; */border-collapse: collapse;}table,th,td {border: 1px solid…...

frida检测绕过-libmsaoaidsec.so
libmsaoaidsec.so 部分检测手段 检测机制在native层实现一般在init_proc()函数中触发使用 pthread_create 创建2个检测线程 绕过: nop pthread_create 的调用 eg: 在 bilibil1 - v7.26.1版本中, 在got表导入了pthread_create 绕过: 替换dlsym(xx, "pthread_create "…...

Splasthop 安全远程访问帮助企业对抗 Cobalt Strike 载荷网络攻击
一、背景 根据 FreeBuf(标题为:潜藏系统2个月未被发现,新型网络攻击瞄准中国高价值目标)和 The Hacker News(标题为:New Cyberattack Targets Chinese-Speaking Businesses with Cobalt Strike Payloads&a…...

Rust:Restful API 服务程序开发详述
0. 关于异步程序设计 0.1 对异步机制的理解 运行效率对于后端程序来讲很重要。我曾经以为,多线程机制是后端设计的终极方法,后来才发现,异步机制才是榨干 CPU 运行效率资源的关键所在。 我最初对于异步程序设计有误解,以为多线…...

《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 09部署OSPF
本章的目的是帮助网络工程师确定网络的理想 OSPF 配置。本章将回答以下问题 应何时在数据中使用OSPF ?配置 OSPF 的关键设计原则是什么?OSPFv2 和 OSPFv3 之间有什么区别,应如何使用?如何在路由协议栈中配置 OSPF ?如何在服务器上配置 OSPF,例如为容…...

【Visual Studio 报错】未加载 wntdll.pdb(一种可行的解决办法)
调试程序时,会出现下面这个报错 分析原因: 出现未加载 wntdll.pdb 报错大概率是你的指针使用错误 ,比如使用野指针、越界访问、或者堆区空间释放方式错误等。 这里以 堆区空间释放方式错误 为例子 1、堆区开辟的数组空间使用 delete 释放 …...

P1332 血色先锋队
[题目通道](血色先锋队 - 洛谷) #include<bits/stdc.h> using namespace std; int n,m,a,b,xa[114514],ya[114514],xb[114514],yb[114514],maxx[114514]; int main() {cin>>n>>m>>a>>b;for(int i1;i<a;i)cin>>xa[i]>>ya[i];for(…...

HarmonyOS】ArkTS学习之基于TextTimer的简易计时器的elapsedTime最小时间单位问题
本文旨在纪录自己对TextTimer使用过程的疑惑问题 我在查看教程时候,发现很多博客在onTimer(event: (utc: number, elapsedTime: number) > void) 这里提到elapsedTime:计时器经过的时间,单位为毫秒。我不清楚是否为版本问题。 在我查看ver…...

函数指针学习
认识函数指针: 函数指针的用处: 回调函数(以函数指针为参数的参数) 定义带标签的结构体 typedef struct mode { int data; } Node; 标签(mode): mode 是结构体的标签名。在这种定义中,mo…...

『功能项目』武器的切换实例【34】
本章项目成果展示 我们打开上一篇33战士的A键连击的项目, 本章要做的事情是按键盘E键切换职业时切换手中的武器 首先在资源商店下载免费的武器模型 创建一个空物体 命名为WeaponPos 将武器预制体拖拽至WeaponPos (注意调整空物体位置就可以后续文章会更…...

github中action作用和讲解
1,简介 GitHub Actions 是 GitHub 的一个自动化功能,它允许你在 GitHub 仓库中自动执行软件开发工作流程。你可以使用 GitHub Actions 来执行各种任务,比如: 自动测试:每当代码被推送到仓库时,自动运行测试…...

数据库管理-第238期 23ai:全球分布式数据库-架构与组件(20240904)
数据库管理238期 2024-09-04 数据库管理-第238期 23ai:全球分布式数据库-架构与组件(20240904)1 架构图2 分片数据库与分片3 Shard Catalog4 Shard Director5 Global Service6 管理界面总结 数据库管理-第238期 23ai:全球分布式数…...

GIT | git提交注释自动添加信息头
GIT | git提交注释自动添加信息头 时间:2024年9月6日10:20:11 文章目录 GIT | git提交注释自动添加信息头1.操作2.commit-msg文件 1.操作 2.commit-msg文件 #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit&q…...

React 全屏问题解决方案
1、全屏下弹窗被遮挡的问题 参考:https://www.jianshu.com/p/b22d1ad9533e 原因: 需要全屏的节点部分被传入 screenfull 中,弹窗的层级永远低于全屏,所以被遮挡。 解决方法: 方式1:把整个 body 全屏&…...

Java JVM 垃圾回收算法详解
Java 虚拟机(JVM)是运行 Java 应用程序的核心,它的垃圾回收(Garbage Collection, GC)机制是 JVM 中非常重要的一个部分。垃圾回收的主要任务是自动管理内存,回收那些不再被使用的对象,从而释放内…...

hadoop dfs web页面访问增加鉴权
前言 装好了Hadoop,通过浏览器访问,发现竟然不需要鉴权就能访问,且暴露了很多服务器层文件路径信息,基于多年积累的安全意识,必须得配置些鉴权信息,就有了该文,仅做学习记录,下次自…...

LCP 485. 最大连续 1 的个数[lleetcode -11]
从今天起,我们的算法开始研究搜索,首先就是DFS深度优先搜索(depth-first seach,DFS)在搜索到一个新的节点时,立即对该新节点进行遍 历;因此遍历需要用先入后出的栈来实现,也可以通过…...

关于宏任务的说法已经过时
关于宏任务w3c的最新解释,(mdn已经搜不到宏任务队列) ● 每个任务都有一个任务类型,用一个类型的任务必须在一个队列,不同类型的任务可以分属不同的队列。在一次事件循环当中,浏览器可以根据实际情况从不同…...

Java箱与泛型
大O的渐进表示法 大 O 的渐进表示法 去掉了那些对结果影响不大的项 ,简洁明了的表示出了执行次数。 void func1(int N){ int count 0; for (int i 0; i < N ; i) { for (int j 0; j < N ; j) { count; } } for (int k 0; k < 2 * N ; k) { count; } in…...

QT如何判断一个文件是否存在
在Qt中,判断一个文件是否存在是一个常见的操作,可以通过QFile类和QDir类来实现。不过,对于简单的文件存在性检查,QFile类提供的接口更为直接。下面是一个使用QFile类来判断文件是否存在的例子: #include <QFile>…...

Vim笔记
【指尖飞舞:vscode vim 高效开发(系列视频)】https://www.bilibili.com/video/BV1z541177Jyp16&vd_source23e4761174881d73295e362ffd706749 Vscode vim插件配置-CSDN博客 g_跳到行尾最后一个非空字符 gd :go to definition ctrl (o): …...

宝塔部署Vue项目解决跨域问题
一、前言 使用宝塔面板部署前端后端项目相比用命令行进行部署要简单许多,宝塔的可视化操作对那些对Linux不熟悉的人很友好。使用宝塔部署SpringBoot后端项目和Vue前端项目的方法如下: 1、视频教程 2、文字教程1 3、文字教程2 以上的教程完全可以按照步骤…...

C++智能指针简述
auto_ptr 设计目的:超出作用域,自动释放内存。所有权传递:拷贝auto_ptr时,原指针失效,释放所有权;新指针获得所有权。总而言之,允许通过拷贝转移所有权,独占所有权。缺陷࿱…...

龙芯+FreeRTOS+LVGL实战笔记(新)——05部署主按钮
本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了改进和优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…...

Android Camera系列(二):TextureView+Camera
两岸猿声啼不住,轻舟已过万重山—李白 Android Camera系列(一):SurfaceViewCamera Android Camera系列(二):TextureViewCamera Android Camera系列(三):GLS…...

DFS算法专题(一)——二叉树中的深搜【回溯与剪枝的初步注入】
目录 1、DFS算法简介 2、算法实战应用【leetcode】 2.1 计算布尔二叉树的值 2.1.1 算法原理 2.1.2 算法代码 2.2 求根节点到叶节点数字之和 2.2.1 算法原理 2.2.2 算法代码 2.3 二叉树剪枝 2.3.1 算法原理 2.3.2 算法代码 2.4 验证二叉搜索树 2.4.1 算法原理 …...

AWS SES服务 Golang接入教程(排坑版)
因为刚来看的时候 也迷迷糊糊的 所以 先讲概念 再上代码 一 基础设置 这里需要完成两个最基础的设置任务 1 是验证至少一个收件电子邮箱 2 【很关键】是验证发送域。即身份里的域类型的身份。(可以理解为配置你的域名邮箱服务器(SMPT)为亚马…...

Vite + Vue3 +Vant4出现Toast is not a function
今天写前端的时候出现了这个问题搞了我一会 搜集原因: 1:是vant版本的问题,Toast()的方法是vant3版本的写法,而我用的是vant4,vant4中的写法改成了showToast()方法,改正过来 import {showToast} from "vant"; 发现还是…...

【MATLAB】模拟退火算法
模拟退火算法的MATLAB实现 模拟退火算法简介模拟退火算法应用实例关于计算结果 模拟退火算法简介 1982年,Kirkpatrick 将退火思想引入组合优化领域,提出了一种能够有效解决大规模组合优化问题的算法,尤其对 NP 完全问题表现出显著优势。模拟…...

什么是Kubernetes RBAC?
什么是Kubernetes RBAC? 1、什么是RBAC?2、核心组件3、优势💖The Begin💖点点关注,收藏不迷路💖 在Kubernetes集群中,RBAC(基于角色的访问控制)是保障系统安全的关键。它通过角色和绑定管理不同实体对资源的访问权限,具有显著优势: 1、什么是RBAC? RBAC是Kube…...