C#会员卡积分管理系统源码:SQLite+WinForms实现增删改查与积分事务
简介这份资源是一套基于C#与WinForms开发的会员卡积分管理系统包含完整源码与可直接运行的成品程序面向学习桌面应用开发、数据库编程或需要搭建会员积分管理方案的开发者与计算机专业学生。压缩包共49个文件约3.12MB涵盖cs源码、dll类库、exe可执行文件、resx资源、db数据库、config配置及sln解决方案等结构完整便于二次开发。系统以SQLite3存储会员信息、消费记录与积分数据借助ListView展示会员列表与积分明细并涉及多线程处理数据库交互与界面更新覆盖会员管理、积分累计兑换、消费记录追踪及权限控制等核心模块。已有1163人学习下载读者可参考其业务逻辑分层、数据库操作封装与皮肤美化实现快速理解C#桌面项目的组织方式与积分系统设计思路。1. 从一张 Excel 会员表说起这套 C# 源码到底能省掉多少重复劳动很多中小门店的会员管理起点都是一张 Excel 表卡号、姓名、电话、余额、积分五列数据看着清爽。等到要查“上个月消费满 500 的会员有哪些”、要给某张卡手动补 200 积分、要导出对账单时Excel 就开始翻车——公式一多就卡多人同时改就冲突时间一长版本满天飞。这套会员卡积分管理系统 C# 源码含成品解决的就是这个阶段的痛点把散落在表格里的会员数据收进一个带界面的桌面程序用 SQLite 做本地存储用 ListView 做数据展示开箱即用也能直接改。它适合三类人一是刚接手门店数字化、想先跑通一个能用的工具再谈采购的运营二是学 C# WinForms 想找一个完整 CRUD 项目练手的开发者三是需要给客户快速交付一个会员模块、不想从零搭框架的接单方。源码含成品的意思是编译好的可执行文件和工程文件都在你可以先跑起来看效果再决定要不要动代码。下面按“能跑起来 → 看懂结构 → 改得动 → 避得开坑”的顺序拆一遍。2. 环境准备与首次运行把成品跑起来再谈改代码2.1 运行环境与依赖确认这套源码基于 .NET Framework 的 WinForms 技术栈常见做法是配 Visual Studio 2019 或 2022 的社区版安装时勾选“.NET 桌面开发”工作负载。数据库用的是 SQLite不需要单独装服务端但工程里通常会引用System.Data.SQLite这个 NuGet 包或者直接带一个SQLite.Interop.dll。如果你拿到的是成品文件夹先确认目录里有没有这个 interop 文件它分 x86 和 x64 两个版本放错位置就是启动即崩的经典翻车现场。首次运行前建议先看App.config或bin\Debug下有没有member.db之类的数据库文件。没有的话程序一般会在首次启动时自动建库建表有的话直接双击 exe 就能看到主界面。这里有个细节如果程序启动后 ListView 是空的不一定是代码问题可能是数据库路径写成了相对路径而你的工作目录不对。常见做法是把连接字符串里的|DataDirectory|替换成绝对路径做一次排查。2.2 从成品到源码工程结构速览用 Visual Studio 打开.sln文件后解决方案资源管理器里通常能看到几个关键部分Forms文件夹放窗体Models或Entity放实体类DAL或Helper放数据库操作Program.cs是入口。会员卡积分管理系统的核心窗体一般有三个主窗体负责导航和 ListView 展示会员编辑窗体负责新增和修改积分调整窗体负责加减分和记录流水。先别急着改代码做一件事在解决方案里右键“生成”看能不能零错误通过。如果报缺少引用多半是 NuGet 包没还原右键解决方案选“还原 NuGet 包”即可。生成成功后按 F5 调试运行用成品里自带的测试数据点一遍新增、查询、修改、删除把整个交互流程走通。这一步的目的是建立“正常状态”的基线后面改出问题才知道是哪一步引入的。2.3 数据库初始化与连接字符串SQLite 的优势是零配置但连接字符串写错一样连不上。下面是一段常见的初始化代码负责在程序启动时检查数据库文件是否存在不存在就建表// 数据库初始化检查文件并建表 string dbPath Path.Combine(Application.StartupPath, member.db); string connStr $Data Source{dbPath};Version3;; if (!File.Exists(dbPath)) { using (var conn new SQLiteConnection(connStr)) { conn.Open(); string sql CREATE TABLE Member ( CardNo TEXT PRIMARY KEY, -- 卡号主键 Name TEXT NOT NULL, -- 姓名 Phone TEXT, -- 电话 Balance REAL DEFAULT 0, -- 余额 Points INTEGER DEFAULT 0, -- 积分 CreateTime TEXT -- 开卡时间 ); using (var cmd new SQLiteCommand(sql, conn)) { cmd.ExecuteNonQuery(); } } }逻辑说明Application.StartupPath保证数据库跟着 exe 走避免工作目录变化导致找不到文件。CardNo设为主键意味着卡号不能重复这是会员系统的基本约束。Balance用 REAL、Points用 INTEGER对应金额和整数积分。参数方面如果你想让数据库放到固定目录把dbPath改成绝对路径即可如果要多门店共用SQLite 不适合并发写得换服务端数据库这是选型边界。3. ListView 绑定会员数据从查询到刷新的完整链路3.1 查询语句与 DataTable 填充ListView 本身不存数据它只是展示层。常见做法是先用SQLiteDataAdapter把查询结果填进DataTable再逐行绑到 ListView 的 Items 里。这样做的原因是 DataTable 自带列结构绑定逻辑清晰后续要导出 Excel 也能直接复用。下面这段代码演示按姓名模糊查询并填充// 按姓名模糊查询填充 DataTable string keyword txtSearch.Text.Trim(); string sql SELECT CardNo, Name, Phone, Balance, Points FROM Member; if (!string.IsNullOrEmpty(keyword)) { sql WHERE Name LIKE kw OR CardNo LIKE kw; } using (var conn new SQLiteConnection(connStr)) using (var adapter new SQLiteDataAdapter(sql, conn)) { if (!string.IsNullOrEmpty(keyword)) { // 参数化查询防止 SQL 注入 adapter.SelectCommand.Parameters.AddWithValue(kw, % keyword %); } DataTable dt new DataTable(); adapter.Fill(dt); BindListView(dt); // 绑定到 ListView }逻辑说明参数化查询用kw占位而不是字符串拼接这是防止 SQL 注入的基本功。LIKE两边的%表示任意字符所以输入“张”能匹配“张三”“张伟”。参数方面AddWithValue会自动推断类型但如果你遇到日期或 decimal 精度问题建议显式指定SQLiteType。查询结果不直接操作 ListView而是先落 DataTable是为了让数据层和界面层解耦后面换控件也不用重写查询。3.2 ListView 列头与行绑定的细节绑定的时候ListView 要先设View View.Details再添加列头最后清空旧行、逐行添加。这里有个容易忽略的点ListViewItem的第一个参数对应第一列后续用SubItems.Add追加。如果列顺序和 SQL 查询字段顺序不一致显示就会错位。下面是一个绑定方法的骨架// 把 DataTable 绑定到 ListView private void BindListView(DataTable dt) { lvMembers.BeginUpdate(); // 暂停重绘提升大批量绑定性能 lvMembers.Items.Clear(); foreach (DataRow row in dt.Rows) { var item new ListViewItem(row[CardNo].ToString()); item.SubItems.Add(row[Name].ToString()); item.SubItems.Add(row[Phone].ToString()); item.SubItems.Add(Convert.ToDecimal(row[Balance]).ToString(F2)); item.SubItems.Add(row[Points].ToString()); item.Tag row[CardNo].ToString(); // 把卡号存进 Tag方便后续操作 lvMembers.Items.Add(item); } lvMembers.EndUpdate(); }逻辑说明BeginUpdate和EndUpdate成对出现避免每加一行就重绘一次数据量上千时差别很明显。Tag存卡号是个实用技巧双击某行要编辑时直接从Tag取主键不用去解析单元格文本。参数方面ToString(F2)保证金额显示两位小数如果数据库里存的是浮点数显示前统一格式化能避免“19.999999”这种尴尬。3.3 增删改后的刷新策略新增或修改会员后ListView 必须刷新才能看到最新数据。最省事的做法是重新执行一次查询但要注意如果当前有搜索关键词刷新时应该保留关键词否则用户会感觉“我刚搜完一保存列表就全了”。常见做法是把查询逻辑抽成一个LoadMembers()方法新增、修改、删除成功后都调它内部自动读取当前搜索框内容。这样代码只有一份行为一致不会出现某个入口忘了刷新。另外删除操作建议加二次确认并且用CardNo作为条件不要用 ListView 的行索引。行索引会随排序和刷新变化拿它去删数据迟早删错。这是很多新手项目里最典型的血泪经验。4. 积分与余额的业务逻辑别让浮点数坑了账4.1 积分增减的事务处理积分调整看起来只是UPDATE Member SET Points Points delta但实际业务里往往还要写一条流水记录方便对账。这两步必须在一个事务里要么都成功要么都回滚否则会出现“积分加了但流水没记”的黑匣子状态。下面是一个事务示例// 积分调整更新余额并写流水事务保证一致性 using (var conn new SQLiteConnection(connStr)) { conn.Open(); using (var trans conn.BeginTransaction()) { try { string updateSql UPDATE Member SET Points Points delta WHERE CardNo cardNo; using (var cmd new SQLiteCommand(updateSql, conn, trans)) { cmd.Parameters.AddWithValue(delta, delta); cmd.Parameters.AddWithValue(cardNo, cardNo); cmd.ExecuteNonQuery(); } string logSql INSERT INTO PointLog (CardNo, Delta, Remark, LogTime) VALUES (cardNo, delta, remark, logTime); using (var cmd new SQLiteCommand(logSql, conn, trans)) { cmd.Parameters.AddWithValue(cardNo, cardNo); cmd.Parameters.AddWithValue(delta, delta); cmd.Parameters.AddWithValue(remark, remark); cmd.Parameters.AddWithValue(logTime, DateTime.Now.ToString(s)); cmd.ExecuteNonQuery(); } trans.Commit(); } catch { trans.Rollback(); throw; } } }逻辑说明BeginTransaction之后的命令都要带上trans参数否则不在同一个事务里。DateTime.Now.ToString(s)输出可排序的 ISO 格式比默认格式更适合存库。参数方面delta可以是正数也可以是负数对应加分和扣分界面层负责校验扣分不能超过现有积分。如果业务允许透支那就在校验里放开但流水必须记清楚。4.2 余额计算为什么建议用 decimalSQLite 没有原生的 decimal 类型但 C# 侧读取时如果直接转double做金额累加会出现精度误差。常见做法是数据库里用 INTEGER 存“分”显示时除以 100或者用 TEXT 存 decimal 字符串读取时decimal.Parse。前者更适合做统计后者改动小。下面演示按“分”存储的读写// 写入元转分避免浮点误差 int cents (int)Math.Round(decimal.Parse(txtBalance.Text) * 100); cmd.Parameters.AddWithValue(balance, cents); // 读取分转元格式化显示 decimal balance Convert.ToInt32(row[Balance]) / 100m; item.SubItems.Add(balance.ToString(F2));逻辑说明Math.Round配合decimal.Parse能避免19.99 * 100 1998.9999这类问题。参数方面如果历史数据已经用 REAL 存了元迁移时要做一次全量转换转换脚本里同样用 decimal 运算不要用 double。这是财务相关模块的基本纪律省这一步后面对账对到怀疑人生。4.3 卡号生成与唯一性校验会员卡号如果是手工输入重复是迟早的事。常见做法是开卡时自动生成规则可以是“前缀 日期 自增序号”比如M20250101001。生成后插入前再查一次是否存在存在就序号加一重试。虽然主键约束能兜底但让用户看到“主键冲突”的报错不如自己处理好。参数方面前缀和日期格式建议做成配置项不同门店可能有不同规则。5. 避坑与排查这套源码最容易翻车的五个地方5.1 启动报“找不到 SQLite.Interop.dll”现象双击 exe 或 F5 调试时弹窗提示无法加载 SQLite 原生库。原因System.Data.SQLite依赖 x86/x64 的 interop 文件而工程的目标平台是 Any CPU运行时可能选错位数。解决在解决方案里把目标平台明确设为 x64 或 x86并确认bin目录下对应子文件夹里有SQLite.Interop.dll。如果用的是 NuGet 包重新安装System.Data.SQLite.Core通常会补齐。5.2 ListView 数据重复叠加现象每次查询后列表越来越长旧数据没清掉。原因绑定前忘了Items.Clear()或者刷新逻辑里又追加了一遍。解决在BindListView开头统一清空并且确保所有刷新入口都走同一个方法。如果用了DataSource绑定方式注意DataTable本身也要重新查询不能只刷新控件。5.3 中文查询不到结果现象输入中文姓名搜索列表为空但数据库里确实有这条记录。原因SQLite 默认的LIKE对中文大小写不敏感但如果你在连接字符串里加了UTF8相关参数不当或者数据库文件编码不对就可能匹配失败。解决确认数据库以 UTF-8 存储查询时不要额外做编码转换。另一个常见原因是参数值前后带了空格Trim()一下再传。5.4 修改会员后余额显示没变现象编辑窗体保存成功但主界面 ListView 里的余额还是旧值。原因保存后没有触发主界面刷新或者刷新时用了缓存数据。解决编辑窗体关闭时通过事件或返回值通知主窗体重新查询。如果用的是模态对话框ShowDialog返回DialogResult.OK后再调LoadMembers()这是最稳的做法。5.5 数据库文件被占用无法复制现象想备份member.db提示文件正在使用。原因SQLite 连接没有及时释放或者程序还在运行。解决确保所有SQLiteConnection都用using包裹用完即关。备份时先退出程序或者用 SQLite 的在线备份 API。如果只是临时查看复制一份到别的目录再打开不要直接动原文件。6. 二次开发与验证把成品改成自己门店的形状6.1 加一个“消费打折”字段的完整流程假设你要给会员加一个折扣率字段用于收银时自动打折。步骤是先在Member表加一列Discount REAL DEFAULT 1.0然后在实体类加属性在编辑窗体加输入框在 ListView 加一列最后在查询和更新语句里带上这个字段。每一步都要对应改漏一处就会出现“保存了但查不到”的玄学问题。下面是对应的 SQL 和绑定片段-- 给已有数据库加字段默认不打折 ALTER TABLE Member ADD COLUMN Discount REAL DEFAULT 1.0;// 读取时格式化折扣1.0 显示为“无折扣” decimal discount Convert.ToDecimal(row[Discount]); item.SubItems.Add(discount 1.0m ? 无折扣 : (discount * 10).ToString(F1) 折);逻辑说明ALTER TABLE对已有数据补默认值不会影响历史记录。显示时把 0.9 转成“9折”更符合门店习惯。参数方面折扣率建议限制在 0.1 到 1.0 之间界面层做校验数据库层可以加 CHECK 约束但 SQLite 对已有表加约束比较麻烦通常靠代码保证。6.2 用查询验证数据一致性改完代码后别只点界面跑几条 SQL 验证一下。比如检查积分流水总和是否等于会员当前积分-- 验证每个会员的流水总和应等于当前积分 SELECT m.CardNo, m.Points, IFNULL(SUM(l.Delta), 0) AS LogSum FROM Member m LEFT JOIN PointLog l ON m.CardNo l.CardNo GROUP BY m.CardNo HAVING m.Points IFNULL(SUM(l.Delta), 0);如果这条查询返回了行说明有会员的积分和流水对不上要么是历史数据问题要么是某次调整没走事务。常见做法是把这条检查做成一个“对账”按钮定期跑一次。参数方面IFNULL处理没有流水的会员避免 NULL 导致比较失败。6.3 发布前的检查清单在把成品交付给门店之前我一般会强制走一遍这几项换一台没装过 Visual Studio 的电脑只装 .NET Framework 运行时看能不能跑把数据库清空从开卡到消费到积分兑换走一遍完整流程断网运行确认没有隐藏的网络依赖最后把member.db备份一份再模拟一次误删恢复。这套流程不复杂但能挡掉大部分“在我电脑上好好的”问题。从那以后我每次改完这类桌面项目都会先清空数据跑一遍全流程再拿真实数据做一次对账查询确认没有黑匣子状态才交付。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

UDS诊断入门:ECU统一诊断服务的核心原理与工程实践

UDS诊断入门:ECU统一诊断服务的核心原理与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 3:44:55 阅读更多 →
iOS企业级应用开发实践源码解析:架构分层与工程化落地

iOS企业级应用开发实践源码解析:架构分层与工程化落地

简介:这份源码包面向具备一定编程基础、希望向企业级iOS开发进阶的开发者,内容取自《iOS应用开发实战》部分章节,覆盖从环境搭建到架构设计的完整知识链路。包内共2000个文件,以229个.h头文件、227个.m实现文件、220个.o编译产物及…

2026/10/13 3:44:54 阅读更多 →
RouterOS WebFig汉化补丁:v7.9–v7.13内核级本地化方案

RouterOS WebFig汉化补丁:v7.9–v7.13内核级本地化方案

简介:本资源是专为中文网络管理员与RouterOS初学者设计的Web界面汉化补丁包,解决英文WebFig界面带来的理解与操作障碍,显著提升中文用户在企业级路由配置中的效率与体验。压缩包共4个文件(257KB),含1个核心…

2026/10/13 3:43:54 阅读更多 →

最新新闻

SQL UNIQUE约束详解:原理、数据库差异与防重复设计实践

SQL UNIQUE约束详解:原理、数据库差异与防重复设计实践

1. UNIQUE约束到底是什么,为什么你需要它做数据库设计这些年,我见过太多因为“忘记加唯一约束”而崩溃的业务系统。最典型的场景:用户注册接口被脚本刷了一遍,username重复了;订单表里同一笔订单被回调程序insert了两次…

2026/10/13 7:55:19 阅读更多 →
Agent-Skills:智能体原子化能力的设计与工程实践

Agent-Skills:智能体原子化能力的设计与工程实践

1. “agent-skills”不是新词,而是智能体能力体系的命名共识 最近在多个技术社区、开源项目文档和内部架构评审会上,“agent-skills”这个组合词出现频率陡增——它既不指代某个具体开源库,也不是某家公司的私有协议,而是一类设计…

2026/10/13 7:55:19 阅读更多 →
Flink DataStream与Table API转换实战:Schema推导、时间属性与常见避坑指南

Flink DataStream与Table API转换实战:Schema推导、时间属性与常见避坑指南

做实时数据处理这几年,Flink的DataStream和Table API来回切换是我用得最频繁的操作之一。刚接触那会儿,总觉得这两套API是两套独立的体系,流上处理业务逻辑用DataStream,做统计查询用Table,中间隔着一道说不清的墙。后…

2026/10/13 7:55:19 阅读更多 →
LiteLLM:大模型API统一网关与智能路由实战指南

LiteLLM:大模型API统一网关与智能路由实战指南

1. LiteLLM不是另一个大模型,而是一把“万能适配器”最近在几个技术群和开源社区里,频繁看到有人发问:“LiteLLM到底是个啥?是又一个新模型吗?”“它和Ollama、LM Studio有什么区别?”——这种误解非常典型…

2026/10/13 7:55:19 阅读更多 →
LiteLLM:大模型API统一接入与智能路由实战指南

LiteLLM:大模型API统一接入与智能路由实战指南

1. 为什么“litellm”突然在开发者圈里被高频提及——它不是新模型,而是新范式最近两周,在多个技术社区、内部分享会和开源项目讨论区里,“litellm”这个词出现的频率明显升高。它不像Llama、Qwen或Claude那样代表一个具体的大语言模型&#…

2026/10/13 7:55:19 阅读更多 →
开源代码审查实战:流程设计、工具配置与团队落地方法

开源代码审查实战:流程设计、工具配置与团队落地方法

代码审查这事儿,我过去几年一直想好好聊一聊。初看上去,open-code-review 这个项目标题没什么特别,好像就是把自己团队做代码评审的过程、工具和规范整理出来,开源给社区用。但如果真这么理解,那就有点浪费了。代码审查…

2026/10/13 7:54:17 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →