feat:增加文件写入安全约束

This commit is contained in:
冯普
2026-05-15 10:09:39 +08:00
parent 26ad1d1a7c
commit 7afa40e39f
3 changed files with 98 additions and 14 deletions
+9 -1
View File
@@ -14,4 +14,12 @@
- 新增页面时必须同步更新 `index.html``SITE_MAP`
## 用户纠正记录
(暂无记录)
### 2026-05-15 | 批量操作覆盖已完成修改(严重)
- **事故**:在 11d3464 提交中,批量处理多个页面时,对 arc-employee/arc-unit/arc-algo.html 的已完成修改被后续写入操作意外覆盖,导致用户工作丢失。
- **根因**:在同一会话中处理大量文件时,重新写入文件未保护先前已完成的修改。
- **强制规则**
1. 对文件执行写入/编辑操作前,必须确认不会覆盖该文件中已完成的其他修改
2. 批量处理多文件时,每个文件的修改必须基于当前最新内容(先读后改),禁止使用缓存的旧版本内容写入
3. 提交前必须用 `git diff --staged` 检查所有待提交文件的变更是否符合预期,确保没有意外回退
4. 若一个文件在同一会话中被多次修改,最终提交前必须完整审查该文件的累计变更
+11 -1
View File
@@ -1,4 +1,4 @@
<!-- Last updated: 2026-05-14 | Commit: 3e29c5e -->
<!-- Last updated: 2026-05-15 | Commit: 26ad1d1 -->
# 项目约束
@@ -31,6 +31,16 @@
- 新增原型页面时,必须同步在 `index.html``SITE_MAP` 中添加对应条目
- PRD 文档生成后需要在导航页对应的一级菜单卡片左下角加上 PRD 文档的跳转逻辑
## 文件写入安全(严重事故教训 2026-05-15
> **绝对禁止**:批量操作时覆盖已完成的文件修改。
- 对文件执行写入/编辑前,**必须先读取该文件当前最新内容**,禁止使用内存中缓存的旧版本写入
- 批量处理多文件时,每个文件独立「读取→修改→写入」,不得跨文件复用旧内容
- 同一会话中若一个文件被多次修改,后续修改必须基于前次修改后的最新状态
- **提交前必须执行 `git diff --staged` 审查**,确认所有变更符合预期、无意外回退
- 若发现 diff 中出现非预期的删除或回退,必须立即中止提交并向用户报告
## 记忆体系(会话启动必读)
> **强制规则**:每次新会话启动时,必须先读取本项目的记忆体系索引,再按需加载关键记忆文件,然后才能开始处理用户请求。长会话中若上下文被压缩,同样需要重新加载。
@@ -44,12 +44,40 @@
.page-content { flex: 1; overflow-y: auto; background: #f5f5f5; }
.page-content::-webkit-scrollbar { display: none; }
/* 日期栏 */
.date-bar {
background: #fff; padding: 12px 16px 14px; text-align: center;
/* 筛选栏 */
.filter-bar {
background: #fff; padding: 10px 16px; display: flex;
align-items: center; justify-content: space-between;
border-bottom: 0.5px solid #f0f0f0;
}
.date-text { font-size: 15px; font-weight: 600; color: #1a1a1a; }
.canteen-selector {
display: flex; align-items: center; gap: 4px; cursor: pointer;
font-size: 15px; font-weight: 600; color: #1a1a1a; position: relative;
}
.canteen-selector .arrow { font-size: 10px; color: #999; transition: transform 0.2s; }
.canteen-selector.open .arrow { transform: rotate(180deg); }
.canteen-dropdown {
position: absolute; top: 100%; left: 0; margin-top: 8px;
background: #fff; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.12);
min-width: 140px; z-index: 100; overflow: hidden; display: none;
}
.canteen-dropdown.show { display: block; }
.canteen-option {
padding: 12px 16px; font-size: 14px; color: #333; cursor: pointer;
border-bottom: 0.5px solid #f5f5f5;
}
.canteen-option:last-child { border-bottom: none; }
.canteen-option:active { background: #f5f5f5; }
.canteen-option.selected { color: #1677ff; font-weight: 600; }
.date-picker {
display: flex; align-items: center; gap: 6px; cursor: pointer;
}
.date-picker input {
border: none; background: none; font-size: 14px; color: #1a1a1a;
font-weight: 500; width: 120px; text-align: right; cursor: pointer;
font-family: inherit;
}
.date-picker .cal-icon { font-size: 16px; color: #1677ff; }
/* 餐次 Tab */
.meal-tabs {
@@ -138,14 +166,26 @@
<div class="nav-bar">
<a class="nav-back" href="eat-recipe.html"></a>
<div class="nav-title">今日食谱</div>
<div class="nav-action">📅</div>
</div>
<!-- 内容区 -->
<div class="page-content">
<!-- 日期 -->
<div class="date-bar">
<div class="date-text" id="dateText"></div>
<!-- 筛选栏:食堂 + 日期 -->
<div class="filter-bar">
<div class="canteen-selector" id="canteenSelector">
<span class="canteen-name" id="canteenName">一号食堂</span>
<span class="arrow"></span>
<div class="canteen-dropdown" id="canteenDropdown">
<div class="canteen-option selected" data-val="一号食堂">一号食堂</div>
<div class="canteen-option" data-val="二号食堂">二号食堂</div>
<div class="canteen-option" data-val="三号食堂">三号食堂</div>
<div class="canteen-option" data-val="清真食堂">清真食堂</div>
</div>
</div>
<div class="date-picker">
<input type="date" id="dateInput">
<span class="cal-icon">📅</span>
</div>
</div>
<!-- 餐次切换 -->
@@ -368,11 +408,37 @@
</div>
<script>
/* 动态设置今日日期 */
/* 日期控件默认当天 */
var now = new Date();
var weekDays = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
var dateStr = now.getFullYear() + '年' + (now.getMonth()+1) + '' + now.getDate() + '' + weekDays[now.getDay()];
document.getElementById('dateText').textContent = dateStr;
var y = now.getFullYear(), m = String(now.getMonth()+1).padStart(2,'0'), d = String(now.getDate()).padStart(2,'0');
document.getElementById('dateInput').value = y + '-' + m + '-' + d;
/* 食堂选择器 */
var selector = document.getElementById('canteenSelector');
var dropdown = document.getElementById('canteenDropdown');
var nameEl = document.getElementById('canteenName');
selector.addEventListener('click', function(e) {
e.stopPropagation();
selector.classList.toggle('open');
dropdown.classList.toggle('show');
});
document.querySelectorAll('.canteen-option').forEach(function(opt) {
opt.addEventListener('click', function(e) {
e.stopPropagation();
document.querySelectorAll('.canteen-option').forEach(function(o){ o.classList.remove('selected'); });
opt.classList.add('selected');
nameEl.textContent = opt.dataset.val;
selector.classList.remove('open');
dropdown.classList.remove('show');
});
});
document.addEventListener('click', function() {
selector.classList.remove('open');
dropdown.classList.remove('show');
});
/* 餐次 Tab 切换 */
var tabs = document.querySelectorAll('.meal-tab');