Compare commits

...

2 Commits

Author SHA1 Message Date
aiyimickey d62dcd2f82 docs: 更新 CLAUDE.md — 新增 useDirtyTracker Hook 文档 2026-07-13 17:20:42 +08:00
aiyimickey 47661c31f1 feat: 新增 useDirtyTracker Hook — 脏状态追踪
支持数组/表单'修改后才显示保存按钮'模式:
- isDirty(key, currentItem): 判断指定key是否相对上次保存有变更
- markClean(key, item): 标记为已保存,隐藏保存按钮
- removeItem(key): 清理已删除项的追踪记录
2026-07-13 16:41:10 +08:00
3 changed files with 51 additions and 0 deletions

View File

@ -42,6 +42,9 @@ shared/
├── Table.tsx # 表格styled theadtbody 由调用方提供)
├── Toast.tsx # 通知条(右下角固定定位)
├── Pagination.tsx # 分页(中文标签:"上一页"/"下一页"
├── hooks/ # React Hooks
│ ├── useDirtyTracker.ts # 脏状态追踪("修改后才显示保存按钮"模式)
│ └── index.ts # Barrel 导出
└── login-page.tsx # 统一登录页SSO + LDAP 切换,不在 barrel 中导出)
```
@ -350,6 +353,7 @@ import LoginPage from '@shared/ui/login-page' // 单独导入,不在 barrel
| Table | `headers: string[]`, `children: ReactNode` | 样式化 theadtbody 由调用方提供 |
| Toast | `message`, `type: 'success'|'error'|'info'`, `onClose` | 右下角固定通知条 |
| Pagination | `page`, `totalPages`, `onPageChange` | 中文标签totalPages <= 1 时隐藏 |
| useDirtyTracker | `isDirty(key, item): boolean`, `markClean(key, item): void`, `removeItem(key): void` | 脏状态追踪 Hook"修改后才显示保存按钮"模式 |
| LoginPage | `siteName: string`, `title?: string` | 完整登录页,支持 SSO 和 LDAP 切换 |
---

1
ui/hooks/index.ts Normal file
View File

@ -0,0 +1 @@
export { useDirtyTracker } from './useDirtyTracker'

View File

@ -0,0 +1,46 @@
'use client'
import { useState, useRef } from 'react'
/**
* Hook "修改后才显示保存按钮"
*
* @example
* const { isDirty, markClean, removeItem } = useDirtyTracker()
* {items.map(item => (
* <div key={item.id}>
* <input onChange={...} />
* {isDirty(item.id, item) && <button onClick={save}></button>}
* </div>
* ))}
* // 保存成功后: markClean(id, savedItem)
* // 删除时: removeItem(id)
*/
export function useDirtyTracker() {
const savedMap = useRef<Map<string, string>>(new Map())
/**
* key markClean
* markClean key dirty
*/
const isDirty = (key: string, current: unknown): boolean => {
const saved = savedMap.current.get(key)
if (saved === undefined) return true // 从未保存过 → dirty
return saved !== JSON.stringify(current)
}
/**
* key "已保存" dirty
*/
const markClean = (key: string, item: unknown): void => {
savedMap.current.set(key, JSON.stringify(item))
}
/**
* key
*/
const removeItem = (key: string): void => {
savedMap.current.delete(key)
}
return { isDirty, markClean, removeItem }
}