基于 Cloudflare Workers 和 D1 数据库构建的单文件无服务器信用卡管理应用。当前版本:V3.3。
“信用卡掌柜”是一个轻量级、响应式的网页应用程序,专为管理用户名下多张信用卡的关键日期信息而设计。除了还款功能,可以完全替代云闪付,且因为不存储敏感信息而无安全之忧。
该应用的核心优势在于其单文件架构和无服务器部署:
- 技术栈: 采用 Cloudflare Workers(作为后端逻辑和前端服务)结合 D1 数据库(持久化存储)。整个应用(HTML/Tailwind CSS/JavaScript/Worker 逻辑)仅存在于一个
worker.js文件中。 - 功能目标: 帮助用户快速查询每张卡的账单日、还款日以及剩余免息期等核心信息,避免错过还款日期。
- 部署环境: 部署在 Cloudflare Workers 上,具备极高的运行速度和可用性。
- 实时日期计算: 自动根据当前日期、账单日、还款规则和宽限期,计算出下一个还款日和剩余免息天数。
- 管理面板: 提供管理员登录入口,实现卡片信息的添加、修改和删除。
- 多模式视图: 支持按“还款日”或“账单日”排序的列表,以及可切换标注模式的日历小控件。
- 微信提醒: 支持检测到存在还剩1天还款日的信用卡时,则调用PUSHPLUS的接口实现信息推送(V3.0版本新增功能)。
- 数据导出: 支持数据导出为excel表格,随时备份。
应用包含四个主要视图,并通过客户端路由在单个页面内切换:
这是应用的默认主界面,面向所有用户。
| 组件 | 功能描述 |
|---|---|
| 顶部统计卡 | 实时显示 卡片总数、7 天内待还卡片数(高亮提醒)和 总授信额度。 |
| 搜索栏 | 支持通过银行名称或卡号后 4 位进行模糊搜索。 |
| 日历小控件 | 显示当前月份的日历。 - 左右箭头: 切换月份。 - 点击标题/标签: 切换标注模式(还款日/账单日)。 - 标注: 红色标记还款日,绿色标记账单日。 |
| 列表排序切换 | 位于列表标题右侧的按钮,可切换列表的排序依据:默认按还款日由近及远,点击后切换为按账单日由小及大。 |
| 信用卡列表 | 默认显示卡片信息,排版精简,优化了手机端显示效果。 |
| 添加入口 | 底部显示 + 添加信用卡信息 按钮,点击后自动检测登录状态。 |
位于页面右上角的👤图标是登录入口。
- 登录: 输入用户名和密码进行验证。成功后,图标旁会显示您的账号名。
- 退出: 登录后再次点击图标,即可安全退出,并清除临时存储的登录状态。
- 管理卡片: 登录成功后,仪表板中的信用卡列表行变为可点击状态,点击任一行即可进入 卡片信息管理 界面。
提供统一的表单用于输入和编辑卡片信息,包含严格的输入校验。
| 字段 | 校验规则 | 特殊交互 |
|---|---|---|
| 发卡银行 | 1~10 个汉字 | - |
| 卡号后4位 | 4 位阿拉伯数字 | - |
| 卡片额度 | 1~1,000,000 的整数 | - |
| 账单日 / 宽限期 | 1~31 的整数 | - |
| 还款日 | 1~31 的整数 | 包含一个切换按钮,可在 “账单日后 X 天” 或 “每月固定 X 日” 两种模式间切换。 |
| 年费 | 1~1,000,000 的整数 | 有年费的卡片在列表中被标注* |
| 备注 | 100 字以内 | - |
| 新增界面 | 提供 确认 和 取消 按钮。 |
- |
| 管理界面 | 预加载选中数据,提供 更新 和 删除 按钮。 |
- |
本应用是为 Cloudflare Workers 定制的单文件应用。请按以下步骤部署:
-
创建数据库:
- 登录 Cloudflare 仪表板,导航到 Workers & Pages -> D1。
- 点击 创建数据库,数据库名称建议使用小写字母和连字符,例如
credit-card-db。
-
创建表结构:
- 进入您创建的 D1 数据库,转到 控制台 (Console) 选项卡。
- 粘贴以下 SQL 代码来创建
credit_cards表:
CREATE TABLE IF NOT EXISTS credit_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bank_name TEXT NOT NULL,
last_4_digits TEXT NOT NULL,
card_limit INTEGER,
billing_day INTEGER NOT NULL,
payment_type TEXT NOT NULL,
payment_value INTEGER NOT NULL,
grace_days INTEGER DEFAULT 0,
max_grace_period INTEGER,
annual_fee INTEGER DEFAULT 0,
notes TEXT
); * 点击 **执行 (Execute)**。
-
插入初始数据 (可选):
- 等待上一步成功后,清除输入框。粘贴以下代码以预设两条卡片信息:
INSERT INTO credit_cards (bank_name, last_4_digits, card_limit, billing_day, payment_type, payment_value, grace_days, max_grace_period, annual_fee, notes)
VALUES
('示例银行A', '1234', 50000, 10, 'days_after_billing', 20, 3, 53, 0, '这是第一张示例卡'),
('示例银行B', '5678', 100000, 15, 'fixed_day', 5, 0, 35, 200, '这是第二张示例卡'); * 点击 **执行 (Execute)**。
-
创建 Worker:
- 导航到 Workers & Pages,点击 创建应用 -> 创建 Worker。
- 在 Worker 编辑器中,将默认代码替换为上面提供的
worker.js完整代码。
-
绑定 D1 数据库:
- 在 Worker 设置页面,转到 设置 (Settings) -> 变量 (Variables) -> D1 数据库绑定 (D1 Database Bindings)。
- 点击 编辑变量 (Edit variables)。
- 变量名: 填写
DB(这是代码中使用的绑定变量名)。 - D1 数据库: 选择您在步骤 A 中创建的数据库(例如
credit-card-db)。
-
配置环境变量:
- 在 Worker 设置 -> 变量 (Variables) -> 环境变量 (Environment Variables) 下,添加以下两个变量(用于管理员登录):
- 变量名:
ADMIN_USERNAME - 值: 设置您想要的管理员用户名(例如
admin) - 变量名:
ADMIN_PASSWORD - 值: 设置您想要的管理员密码(例如
123456) - 变量名:
PUSHPLUS_API - 值: 设置您申请的推送加账号的api,推送加的网站说明提供(例如
https://www.pushplus.plus/send) - 变量名:
PUSHPLUS_TOKEN - 值: 设置您申请的推送加账号的token,可以是账号token或者消息token(例如
3f2afea5235347568f9d22f1552cafbe85)
- 变量名:
- 在 Worker 设置 -> 变量 (Variables) -> 环境变量 (Environment Variables) 下,添加以下两个变量(用于管理员登录):
-
新增触发事件(定时器):
- workers项目的设置中设置触发事件,用以定义调用 Worker 的事件,类型为Cron,处理程序为scheduled(),详细信息为0 1 * * *,也就是固定每天的北京时间9:00开始检查还款日临近情况。
- 保存。
-
保存并部署:
- 保存您的 Worker 代码并部署。
- 访问您的 Worker URL 即可看到应用运行效果,最好是绑定自有域名。
- 您也可以将您的 Worker 连接到 Git 存储库来进行自动构建和部署。
项目总结:本项目包含所有信用卡管理功能、美化和交互逻辑的 worker.js 文件,您可以10分钟部署。记得修改const ADMIN_TOKEN = "secret-admin-token-12345";这一行。
worker.js 代码特点:
- 实现了 Cloudflare Workers 路由和 D1 数据库 CRUD 操作。
- 在前端实现了完整的客户端路由(
dashboard,login,add,manage)。 - 优化了登录界面和表单的美观度和排版。
- 优化了列表视图,卡片信息更紧凑,避免了在移动端换行,并将“账单日后”压缩为“账后”。
- 日历组件支持点击标题切换标注模式,并移除了额外的注释文字。
- 实现了还款日计算方式的切换交互和表单校验。
- 实现了还款日临近时的微信提醒。
您可以按照 README 中的详细部署步骤,将这个单文件应用部署到您的 Cloudflare 环境中。
一、注意,如果你是2025年11月10日之前部署的1.0版本升级,请先: 粘贴以下 SQL 代码来插入新列,因为数据库中新增了年费:
ALTER TABLE credit_cards ADD COLUMN annual_fee INTEGER DEFAULT 0; * 点击 **执行 (Execute)**。
验证。控制台输入
PRAGMA table_info(credit_cards);后得到如下信息:
| cid | name | type | notnull | dflt_value | pk |
|---|---|---|---|---|---|
| 0 | id | INTEGER | 0 | 1 | |
| 1 | bank_name | TEXT | 1 | 0 | |
| 2 | last_4_digits | TEXT | 1 | 0 | |
| 3 | card_limit | INTEGER | 0 | 0 | |
| 4 | billing_day | INTEGER | 1 | 0 | |
| 5 | payment_type | TEXT | 1 | 0 | |
| 6 | payment_value | INTEGER | 1 | 0 | |
| 7 | grace_days | INTEGER | 0 | 0 | 0 |
| 8 | max_grace_period | INTEGER | 1 | 0 | |
| 9 | notes | TEXT | 0 | 0 | |
| 10 | annual_fee | INTEGER | 0 | 0 | 0 |
即为正确。
截图示例:
二、如果你想换一种推送方式,这里推荐使用QQ邮箱推送,将QQ邮箱设置为置顶,提醒更加醒目。推送逻辑从 PushPlus 更改为您指定的 https://mail.guao.com/send 接口。
保留所有其他逻辑和代码结构不变,以下是修改后的 doScheduledPush 函数:
/**
* doScheduledPush.
*/
async function doScheduledPush(env) {
function escapeHtmlServer(str) {
if (str == null) return '';
return String(str).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":"'"}[m]));
}
function formatDateYMD(d) {
const dt = d instanceof Date ? d : new Date(d);
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`;
}
try {
const { results } = await env.DB.prepare('SELECT * FROM credit_cards').all();
const cards = results || [];
const today = new Date();
const dueSoon = [];
for (const c of cards) {
if (!c) continue;
if (typeof c.billing_day === 'undefined' || typeof c.payment_type === 'undefined' || typeof c.payment_value === 'undefined') continue;
const info = getCardDatesServer(c, today);
if (info && Number(info.daysUntilPayment) === 1) {
dueSoon.push({ card: c, info });
}
}
if (dueSoon.length === 0) {
console.log('[scheduled] No cards due tomorrow.');
return;
}
const listHtml = dueSoon.map(item => {
const c = item.card;
const dDate = item.info && item.info.nextPaymentDeadline ? new Date(item.info.nextPaymentDeadline) : null;
const dateStr = dDate ? formatDateYMD(dDate) : '';
// Fixed the 'tail' logic to avoid potential character issues
let tail = "";
if (c.last_4_digits) {
tail = " (尾号 " + escapeHtmlServer(c.last_4_digits) + ")";
}
return '<p style="margin:10px 0;font-size:15px;color:#334155;">· <b>' + escapeHtmlServer(c.bank_name) + '</b>' + tail + ': <span style="color:#e11d48;font-weight:bold;">' + dateStr + '</span> ·​</p>';
}).join('');
// Constructed with clean blocks to prevent double title perception
const contentHtml = `
<div style="font-family:sans-serif;padding:20px;color:#1e293b;max-width:600px;border:1px solid #eee;border-radius:8px;background-color:#ffffff;">
<p style="font-size:18px;margin-top:0;margin-bottom:15px;color:#475569;line-height:1.5;">以下账单即将到最后还款期限,请尽快还款:​</p>
${listHtml}
<p style="margin-top:20px;font-size:15px;color:#94a3b8;border-top:1px dashed #eee;padding-top:10px;line-height:1.5;">​​​提示:尽快还款,避免逾期。如已还款请忽略。
点击<a href="https://cards.guao.de/" style="color:#3b82f6;text-decoration:none;">https://cards.guao.de/</a>查看<br/></p>
</div>`.trim();
const payload = {
fromName: "信用卡助手",
to: "12345678@qq.com",
subject: "还款日到期提醒 - 仅剩余1天",
text: "您有信用卡账单即将到期,请查看详情。",
html: contentHtml
};
const response = await fetch("https://mail.guao.de/send", {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const respText = await response.text();
console.log('[scheduled] Push status:', response.status, respText);
} catch (err) {
console.error('[scheduled] Error:', err);
}
}修改点说明:
接口地址:将 pushplusApi 替换为 https://mail.guao.com/send 。
- 参数映射:
token保持不变,对应您的 API 密钥。- 增加了
to字段,对应接收邮箱(从env.RECEIVER_EMAIL获取)。 - 将原有的
title映射为subject(邮件主题)。 - 将
template: 'html'映射为type: 'html'。
- 其它要求:请记得在 Cloudflare Workers 自行配置 https://github.com/woshichenghaibo/ 以确保推送正常工作。
