Files
momentry_studio/src/composables/useUndoRedo.ts
Momentry Studio f3d75ebce8 feat: Library 批量注册 + PeopleView 修复 + 国际化
Library:
- 添加 checkbox 和鼠标框选功能
- Unregistered 卡片添加快速注册按钮
- 状态标签移至左下方,人脸图标移至左侧中间
- Deep Scan 改为 unregister -> register -> process
- 完整国际化(日语、繁体中文、英语)

PeopleView:
- 修复卡片不显示问题(使用 get_file_traces API)
- 移除对 identity_id 字段的依赖

其他:
- 添加日语语言包
- 添加 useDragSelect composable
- 新增文档:Face Bbox Null Bug Report
2026-10-01 00:42:13 +08:00

255 lines
7.5 KiB
TypeScript

import { reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { apiCall } from '@/api'
import { invalidatePeople, ensurePeople } from '@/store'
interface HistoryCounts {
patchUndo: number
patchRedo: number
bindUndo: number
bindRedo: number
}
interface MergeHistoryItem {
mergeId: string
sourceUuid: string
targetUuid: string
sourceName: string
targetName: string
undone: boolean
createdAt: string
}
interface ActionHistoryItem {
type: 'patch' | 'bind' | 'merge' | 'delete'
label: string
uuid: string
timestamp: string
canUndo: boolean
canRedo: boolean
}
export function useUndoRedo() {
const { t } = useI18n()
const counts = reactive<Record<string, HistoryCounts>>({})
const mergeHistory = reactive<MergeHistoryItem[]>([])
const recentActions = reactive<ActionHistoryItem[]>([])
async function refreshCounts(uuid: string) {
try {
const [patchHist, bindHist] = await Promise.all([
apiCall('identity_history', { uuid, page: 1, pageSize: 1 }),
apiCall('identity_bind_history', { uuid, page: 1, pageSize: 1 }),
])
if (!counts[uuid]) counts[uuid] = { patchUndo: 0, patchRedo: 0, bindUndo: 0, bindRedo: 0 }
counts[uuid].patchUndo = patchHist?.undo_stack_count ?? patchHist?.undoStackCount ?? 0
counts[uuid].patchRedo = patchHist?.redo_stack_count ?? patchHist?.redoStackCount ?? 0
counts[uuid].bindUndo = bindHist?.undo_stack_count ?? bindHist?.undoStackCount ?? 0
counts[uuid].bindRedo = bindHist?.redo_stack_count ?? bindHist?.redoStackCount ?? 0
} catch (e) {
console.error('Failed to refresh undo counts:', e)
}
}
async function refreshMergeHistory(uuid?: string) {
try {
const params: Record<string, any> = { pageSize: 10 }
if (uuid) params.sourceUuid = uuid
const data = await apiCall('merge_history', params)
const items = data?.history || data?.merges || data || []
mergeHistory.splice(0, mergeHistory.length)
for (const m of Array.isArray(items) ? items : []) {
mergeHistory.push({
mergeId: m.merge_id ?? m.mergeId ?? '',
sourceUuid: m.source_uuid ?? m.sourceUuid ?? '',
targetUuid: m.target_uuid ?? m.targetUuid ?? '',
sourceName: m.source_name ?? m.sourceName ?? '',
targetName: m.target_name ?? m.targetName ?? '',
undone: m.is_undone ?? m.isUndone ?? false,
createdAt: m.created_at ?? m.createdAt ?? '',
})
}
} catch (e) {
console.error('Failed to refresh merge history:', e)
}
}
async function refreshActionHistory(uuid: string) {
try {
const [patchHist, bindHist] = await Promise.all([
apiCall('identity_history', { uuid, page: 1, pageSize: 10 }),
apiCall('identity_bind_history', { uuid, page: 1, pageSize: 10 }),
])
const actions: ActionHistoryItem[] = []
const patchItems = patchHist?.history || patchHist?.records || []
for (const r of Array.isArray(patchItems) ? patchItems : []) {
const op = r.operation ?? r.op ?? ''
const label = op === 'delete' ? t('people.action.delete_person') : op === 'update' ? t('people.action.update_person') : op
actions.push({
type: op === 'delete' ? 'delete' : 'patch',
label,
uuid,
timestamp: r.created_at ?? r.createdAt ?? r.timestamp ?? '',
canUndo: !r.is_undone && !r.isUndone,
canRedo: !!(r.is_undone || r.isUndone),
})
}
const bindItems = bindHist?.history || bindHist?.records || []
for (const r of Array.isArray(bindItems) ? bindItems : []) {
const op = r.operation ?? r.op ?? ''
const label = op === 'bind' ? t('people.action.bind_face') : op === 'unbind' ? t('people.action.unbind_face') : op === 'bind_trace' ? t('people.action.bind_trace') : op
actions.push({
type: 'bind',
label,
uuid,
timestamp: r.created_at ?? r.createdAt ?? r.timestamp ?? '',
canUndo: !(r.is_undone ?? r.isUndone),
canRedo: !!(r.is_undone ?? r.isUndone),
})
}
actions.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
recentActions.splice(0, recentActions.length, ...actions.slice(0, 20))
} catch (e) {
console.error('Failed to refresh action history:', e)
}
}
async function undo(uuid: string, steps = 1) {
try {
await apiCall('identity_undo', { uuid, steps })
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Undo failed:', e)
throw e
}
}
async function redo(uuid: string, steps = 1) {
try {
await apiCall('identity_redo', { uuid, steps })
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Redo failed:', e)
throw e
}
}
async function bindUndo(uuid: string, steps = 1) {
try {
await apiCall('identity_bind_undo', { uuid, steps })
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Bind undo failed:', e)
throw e
}
}
async function bindRedo(uuid: string, steps = 1) {
try {
await apiCall('identity_bind_redo', { uuid, steps })
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Bind redo failed:', e)
throw e
}
}
async function mergeUndo(mergeId: string) {
try {
await apiCall('merge_undo', { mergeId })
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Merge undo failed:', e)
throw e
}
}
async function mergeRedo(mergeId: string) {
try {
await apiCall('merge_redo', { mergeId })
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Merge redo failed:', e)
throw e
}
}
function canUndo(uuid: string): boolean {
const c = counts[uuid]
if (!c) return false
return c.patchUndo > 0 || c.bindUndo > 0
}
function canRedo(uuid: string): boolean {
const c = counts[uuid]
if (!c) return false
return c.patchRedo > 0 || c.bindRedo > 0
}
async function undoAction(uuid: string, type: 'patch' | 'bind') {
try {
if (type === 'bind') {
await apiCall('identity_bind_undo', { uuid, steps: 1 })
} else {
await apiCall('identity_undo', { uuid, steps: 1 })
}
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Undo action failed:', e)
throw e
}
}
async function redoAction(uuid: string, type: 'patch' | 'bind') {
try {
if (type === 'bind') {
await apiCall('identity_bind_redo', { uuid, steps: 1 })
} else {
await apiCall('identity_redo', { uuid, steps: 1 })
}
await refreshCounts(uuid)
await refreshActionHistory(uuid)
invalidatePeople()
await ensurePeople()
} catch (e) {
console.error('Redo action failed:', e)
throw e
}
}
return {
counts,
mergeHistory,
recentActions,
refreshCounts,
refreshMergeHistory,
refreshActionHistory,
undo,
redo,
bindUndo,
bindRedo,
mergeUndo,
mergeRedo,
canUndo,
canRedo,
undoAction,
redoAction,
}
}