docs: 添加 PostgREST 使用情况分析和删除确认功能文档
1. PostgREST 使用情况分析文档 - PostgREST使用情况-后端API替代建议.md: 完整的迁移建议和优先级分析 - PostgREST实际使用清单.md: 当前使用的 PostgREST 接口清单 - PostgREST未使用函数清单.md: 已封装但未使用的函数列表 - PostgREST请求模块清单.md: 所有请求模块的使用情况 2. 删除操作延迟确认功能实施文档 - 功能设计和实现细节 - 使用示例和最佳实践 - 技术实现说明 这些文档用于: - 追踪 PostgREST 到 FastAPI 的迁移进度 - 指导后续的接口迁移工作 - 记录 UI 改进的实施细节 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
# 删除操作延迟确认功能实施文档
|
||||
|
||||
> **实施时间**: 2025-11-25
|
||||
> **功能描述**: 为所有删除操作添加4秒延迟确认功能,防止误删除操作
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [功能概述](#功能概述)
|
||||
- [技术实现](#技术实现)
|
||||
- [已更新的文件](#已更新的文件)
|
||||
- [使用示例](#使用示例)
|
||||
- [测试验证](#测试验证)
|
||||
|
||||
---
|
||||
|
||||
## 功能概述
|
||||
|
||||
### 需求背景
|
||||
|
||||
为了防止用户误操作导致数据被删除,所有删除操作都需要:
|
||||
1. 显示确认弹窗提示
|
||||
2. 确认按钮在4秒倒计时结束后才能点击
|
||||
3. 倒计时期间按钮显示剩余秒数
|
||||
|
||||
### 核心功能
|
||||
|
||||
- **延迟确认**: 确认按钮在4秒倒计时后才可点击
|
||||
- **倒计时显示**: 按钮文本显示 "删除 (4s)" → "删除 (3s)" → ... → "删除"
|
||||
- **视觉反馈**: 倒计时期间按钮呈半透明状态且鼠标样式为禁用
|
||||
- **统一体验**: 所有删除操作使用相同的确认流程
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. MessageModal 组件增强
|
||||
|
||||
**文件**: `app/components/ui/MessageModal.tsx`
|
||||
|
||||
#### 新增 Props
|
||||
|
||||
```typescript
|
||||
interface MessageModalProps {
|
||||
// ... 现有属性
|
||||
// 确认按钮延迟时间(秒)- 用于危险操作(如删除)
|
||||
confirmDelay?: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### 状态管理
|
||||
|
||||
```typescript
|
||||
const [remainingSeconds, setRemainingSeconds] = useState(confirmDelay);
|
||||
```
|
||||
|
||||
#### 倒计时逻辑
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
if (isOpen && confirmDelay > 0) {
|
||||
setRemainingSeconds(confirmDelay);
|
||||
const timer = setInterval(() => {
|
||||
setRemainingSeconds((prev) => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [isOpen, confirmDelay]);
|
||||
```
|
||||
|
||||
#### 按钮渲染
|
||||
|
||||
```typescript
|
||||
<button
|
||||
className="message-modal-button primary"
|
||||
onClick={handleConfirm}
|
||||
disabled={remainingSeconds > 0}
|
||||
style={{
|
||||
opacity: remainingSeconds > 0 ? 0.5 : 1,
|
||||
cursor: remainingSeconds > 0 ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{remainingSeconds > 0 ? `${confirmText} (${remainingSeconds}s)` : confirmText}
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已更新的文件
|
||||
|
||||
### 1. 文档管理模块 (documents.list.tsx)
|
||||
|
||||
**文件路径**: `app/routes/documents.list.tsx`
|
||||
|
||||
#### 更新位置 1: 单个文档删除 (Line 580-596)
|
||||
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: `确定要删除文档"${name}"吗?`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: () => {
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 更新位置 2: 批量删除文档 (Line 617-642)
|
||||
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认批量删除",
|
||||
message: `确认删除选中的 ${selectedRowKeys.length} 个文档?`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: () => {
|
||||
// 批量删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 评查点管理模块 (rules.list.tsx)
|
||||
|
||||
**文件路径**: `app/routes/rules.list.tsx`
|
||||
|
||||
#### 更新位置 1: 单个评查点删除 (Line 526-542)
|
||||
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: `确认删除评查点【${rule.name}】吗?`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: () => {
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 更新位置 2: 批量删除评查点 (Line 603-634)
|
||||
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认批量删除",
|
||||
message: `确定要删除选中的 ${selectedIds.length} 个评查点吗?此操作不可恢复。`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: async () => {
|
||||
// 批量删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 评查点分组管理模块 (rule-groups._index.tsx)
|
||||
|
||||
**文件路径**: `app/routes/rule-groups._index.tsx`
|
||||
|
||||
#### 导入更新
|
||||
|
||||
```typescript
|
||||
// 原代码
|
||||
import { toastService } from "~/components/ui";
|
||||
|
||||
// 新代码
|
||||
import { toastService, messageService } from "~/components/ui";
|
||||
```
|
||||
|
||||
#### 更新位置 1: 单个分组删除 (Line 233-275)
|
||||
|
||||
**原代码** (使用 `confirm()`):
|
||||
```typescript
|
||||
if (confirm("确定要删除该分组吗?此操作将同时删除该分组下的所有评查点,且不可恢复。")) {
|
||||
// 删除逻辑
|
||||
}
|
||||
```
|
||||
|
||||
**新代码** (使用 `messageService`):
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: "确定要删除该分组吗?此操作将同时删除该分组下的所有评查点,且不可恢复。",
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: async () => {
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 更新位置 2: 批量删除分组 (Line 307-328)
|
||||
|
||||
**原代码** (使用 `confirm()`):
|
||||
```typescript
|
||||
if (!confirm(`确定要删除选中的 ${selectedIds.length} 个分组吗?此操作不可恢复。`)) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
**新代码** (使用 `messageService`):
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认批量删除",
|
||||
message: `确定要删除选中的 ${selectedIds.length} 个分组吗?此操作不可恢复。`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: async () => {
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 提示词模板管理模块 (prompts._index.tsx)
|
||||
|
||||
**文件路径**: `app/routes/prompts._index.tsx`
|
||||
|
||||
#### 导入更新
|
||||
|
||||
```typescript
|
||||
// 原代码
|
||||
import { toastService } from "~/components/ui";
|
||||
|
||||
// 新代码
|
||||
import { toastService, messageService } from "~/components/ui";
|
||||
```
|
||||
|
||||
#### 更新位置: 删除模板 (Line 220-234)
|
||||
|
||||
**原代码** (使用 `confirm()`):
|
||||
```typescript
|
||||
if (confirm('确定要删除该模板吗?删除后无法恢复。')) {
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('intent', 'delete');
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
}
|
||||
```
|
||||
|
||||
**新代码** (使用 `messageService`):
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: "确定要删除该模板吗?删除后无法恢复。",
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: () => {
|
||||
const formData = new FormData();
|
||||
formData.append('id', id);
|
||||
formData.append('intent', 'delete');
|
||||
fetcher.submit(formData, { method: 'post' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 入口模块管理模块 (entry-modules._index.tsx)
|
||||
|
||||
**文件路径**: `app/routes/entry-modules._index.tsx`
|
||||
|
||||
#### 导入更新
|
||||
|
||||
```typescript
|
||||
// 原代码
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
|
||||
// 新代码
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
```
|
||||
|
||||
#### 更新位置: 删除入口模块 (Line 215-250)
|
||||
|
||||
**原代码** (使用 `confirm()`):
|
||||
```typescript
|
||||
if (confirm('确定要删除该入口模块吗?此操作不可撤销。')) {
|
||||
setIsDeleting(true);
|
||||
// 删除逻辑
|
||||
}
|
||||
```
|
||||
|
||||
**新代码** (使用 `messageService`):
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: "确定要删除该入口模块吗?此操作不可撤销。",
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: async () => {
|
||||
setIsDeleting(true);
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 文档类型管理模块 (document-types._index.tsx)
|
||||
|
||||
**文件路径**: `app/routes/document-types._index.tsx`
|
||||
|
||||
#### 导入更新
|
||||
|
||||
```typescript
|
||||
// 原代码
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
|
||||
// 新代码
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
```
|
||||
|
||||
#### 更新位置: 删除文档类型 (Line 204-239)
|
||||
|
||||
**原代码** (使用 `confirm()` 和 `alert()`):
|
||||
```typescript
|
||||
if (confirm('确定要删除该文档类型吗?此操作不会影响关联的评查点分组,但可能会影响使用该类型的文档评查。')) {
|
||||
setIsDeleting(true);
|
||||
// 删除逻辑
|
||||
if (result.success) {
|
||||
alert('删除成功!');
|
||||
} else {
|
||||
alert(`删除失败: ${result.error || '未知错误'}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**新代码** (使用 `messageService` 和 `toastService`):
|
||||
```typescript
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: "确定要删除该文档类型吗?此操作不会影响关联的评查点分组,但可能会影响使用该类型的文档评查。",
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // ✅ 新增
|
||||
onConfirm: async () => {
|
||||
setIsDeleting(true);
|
||||
// 删除逻辑
|
||||
if (result.success) {
|
||||
toastService.success('删除成功!');
|
||||
} else {
|
||||
toastService.error(`删除失败: ${result.error || '未知错误'}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```typescript
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
|
||||
const handleDelete = (id: string, name: string) => {
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: `确定要删除"${name}"吗?`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // 4秒延迟
|
||||
onConfirm: () => {
|
||||
// 执行删除操作
|
||||
deleteItem(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 批量删除示例
|
||||
|
||||
```typescript
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedIds.length === 0) {
|
||||
toastService.error('请至少选择一项');
|
||||
return;
|
||||
}
|
||||
|
||||
messageService.show({
|
||||
title: "确认批量删除",
|
||||
message: `确定要删除选中的 ${selectedIds.length} 项吗?此操作不可恢复。`,
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4,
|
||||
onConfirm: async () => {
|
||||
// 执行批量删除
|
||||
await batchDeleteItems(selectedIds);
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试清单
|
||||
|
||||
#### 1. 单个删除操作测试
|
||||
|
||||
- [ ] 文档删除 (`/documents`)
|
||||
- [ ] 评查点删除 (`/rules`)
|
||||
- [ ] 评查点分组删除 (`/rule-groups`)
|
||||
- [ ] 提示词模板删除 (`/prompts`)
|
||||
- [ ] 入口模块删除 (`/entry-modules`)
|
||||
- [ ] 文档类型删除 (`/document-types`)
|
||||
|
||||
#### 2. 批量删除操作测试
|
||||
|
||||
- [ ] 批量删除文档
|
||||
- [ ] 批量删除评查点
|
||||
- [ ] 批量删除评查点分组
|
||||
|
||||
### 测试要点
|
||||
|
||||
1. **倒计时功能**:
|
||||
- ✅ 弹窗打开后,确认按钮显示 "删除 (4s)"
|
||||
- ✅ 每秒递减: "删除 (3s)" → "删除 (2s)" → "删除 (1s)" → "删除"
|
||||
- ✅ 倒计时期间按钮不可点击
|
||||
|
||||
2. **视觉反馈**:
|
||||
- ✅ 倒计时期间按钮半透明 (opacity: 0.5)
|
||||
- ✅ 鼠标悬停显示禁用光标 (cursor: not-allowed)
|
||||
- ✅ 倒计时结束后按钮恢复正常样式
|
||||
|
||||
3. **交互行为**:
|
||||
- ✅ 点击取消按钮可以立即关闭弹窗
|
||||
- ✅ 点击遮罩层可以立即关闭弹窗
|
||||
- ✅ 按 ESC 键可以立即关闭弹窗
|
||||
- ✅ 倒计时结束后点击确认执行删除操作
|
||||
|
||||
4. **边界情况**:
|
||||
- ✅ 快速打开/关闭弹窗不会导致计时器泄漏
|
||||
- ✅ 多次打开弹窗,倒计时每次都重新开始
|
||||
|
||||
### 预期行为
|
||||
|
||||
**正常流程**:
|
||||
```
|
||||
用户点击删除 → 弹窗显示 → 确认按钮显示 "删除 (4s)"
|
||||
→ 倒计时 3秒 → 倒计时 2秒 → 倒计时 1秒 → "删除" 可点击
|
||||
→ 用户点击确认 → 执行删除 → 显示成功提示
|
||||
```
|
||||
|
||||
**取消流程**:
|
||||
```
|
||||
用户点击删除 → 弹窗显示 → 倒计时进行中
|
||||
→ 用户点击取消/遮罩层/ESC键 → 弹窗关闭 → 不执行删除
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 统计总结
|
||||
|
||||
### 更新统计
|
||||
|
||||
| 模块 | 文件名 | 删除操作数 | 导入变更 | 替换 confirm() |
|
||||
|------|--------|------------|----------|----------------|
|
||||
| 文档管理 | documents.list.tsx | 2 | - | - |
|
||||
| 评查点管理 | rules.list.tsx | 2 | - | - |
|
||||
| 评查点分组 | rule-groups._index.tsx | 2 | ✅ | ✅ |
|
||||
| 提示词模板 | prompts._index.tsx | 1 | ✅ | ✅ |
|
||||
| 入口模块 | entry-modules._index.tsx | 1 | ✅ | ✅ |
|
||||
| 文档类型 | document-types._index.tsx | 1 | ✅ | ✅ |
|
||||
| **总计** | **6 个文件** | **9 个操作** | **4 个文件** | **4 个文件** |
|
||||
|
||||
### 代码改动
|
||||
|
||||
- **新增代码**: MessageModal 组件增强 (~50 行)
|
||||
- **修改代码**: 9 个删除操作函数 (~200 行)
|
||||
- **导入变更**: 4 个文件添加 messageService 导入
|
||||
- **替换操作**: 4 个文件从 `confirm()` 迁移到 `messageService.show()`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 后续维护
|
||||
|
||||
### 新增删除操作开发规范
|
||||
|
||||
当开发人员需要添加新的删除功能时,请遵循以下规范:
|
||||
|
||||
```typescript
|
||||
// ✅ 正确做法
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
messageService.show({
|
||||
title: "确认删除",
|
||||
message: "确定要删除该项吗?",
|
||||
type: "warning",
|
||||
confirmText: "删除",
|
||||
cancelText: "取消",
|
||||
confirmDelay: 4, // 必须添加
|
||||
onConfirm: () => {
|
||||
// 删除逻辑
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ❌ 错误做法
|
||||
const handleDelete = (id: string) => {
|
||||
if (confirm("确定要删除吗?")) { // 不要使用原生 confirm
|
||||
// 删除逻辑
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Code Review 检查点
|
||||
|
||||
在代码审查时,请确认:
|
||||
1. ✅ 所有删除操作都使用 `messageService.show()`
|
||||
2. ✅ 所有删除确认都包含 `confirmDelay: 4`
|
||||
3. ✅ 没有使用原生 `confirm()` 或 `alert()`
|
||||
4. ✅ 导入了正确的 `messageService`
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系支持
|
||||
|
||||
如遇到问题,请联系开发团队。
|
||||
|
||||
**文档维护人**: Claude Code
|
||||
**最后更新**: 2025-11-25
|
||||
Reference in New Issue
Block a user