feat:撤销功能
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<xn-form-container title="编辑项目阶段" :width="700" v-model:open="open" :destroy-on-close="true" @close="onClose">
|
||||
<div class="stage-form">
|
||||
<div class="stage-form__toolbar">
|
||||
<a-space>
|
||||
<a-tooltip title="撤销上一步修改">
|
||||
<a-button :disabled="!canUndo" @click="undo">撤销</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="恢复撤销的修改">
|
||||
<a-button :disabled="!canRedo" @click="redo">恢复</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="stage-form__header">
|
||||
<div>阶段名称</div>
|
||||
<div>阶段序号</div>
|
||||
@@ -32,7 +42,7 @@
|
||||
import { createVNode } from 'vue'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { cloneDeep, isEqual } from 'lodash-es'
|
||||
// 抽屉状态
|
||||
const open = ref(false)
|
||||
const emit = defineEmits({ successful: null })
|
||||
@@ -42,10 +52,56 @@
|
||||
const visibleStages = computed(() => formData.value.filter((item) => !item.isDeleted))
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 撤销/恢复(类似 WPS / Ctrl+Z / Ctrl+Y)
|
||||
const undoStack = ref([])
|
||||
const redoStack = ref([])
|
||||
const isRestoring = ref(false)
|
||||
let historyTimer = null
|
||||
const MAX_HISTORY = 50
|
||||
|
||||
const snapshotOf = (list) => cloneDeep(Array.isArray(list) ? list : [])
|
||||
const initHistory = () => {
|
||||
undoStack.value = [snapshotOf(formData.value)]
|
||||
redoStack.value = []
|
||||
}
|
||||
const commitHistory = () => {
|
||||
if (isRestoring.value) return
|
||||
const next = snapshotOf(formData.value)
|
||||
const last = undoStack.value[undoStack.value.length - 1]
|
||||
if (last && isEqual(last, next)) return
|
||||
undoStack.value.push(next)
|
||||
if (undoStack.value.length > MAX_HISTORY) undoStack.value.shift()
|
||||
redoStack.value = []
|
||||
}
|
||||
const canUndo = computed(() => undoStack.value.length > 1)
|
||||
const canRedo = computed(() => redoStack.value.length > 0)
|
||||
const applySnapshot = (snap) => {
|
||||
isRestoring.value = true
|
||||
formData.value = snapshotOf(snap)
|
||||
nextTick(() => {
|
||||
isRestoring.value = false
|
||||
})
|
||||
}
|
||||
const undo = () => {
|
||||
if (!canUndo.value) return
|
||||
const current = snapshotOf(formData.value)
|
||||
const prev = undoStack.value[undoStack.value.length - 2]
|
||||
redoStack.value.push(current)
|
||||
undoStack.value.pop()
|
||||
applySnapshot(prev)
|
||||
}
|
||||
const redo = () => {
|
||||
if (!canRedo.value) return
|
||||
const current = snapshotOf(formData.value)
|
||||
const next = redoStack.value.pop()
|
||||
undoStack.value.push(current)
|
||||
applySnapshot(next)
|
||||
}
|
||||
|
||||
const createEmptyStage = () => ({
|
||||
stageId: '',
|
||||
stageName: '',
|
||||
projectId: projectId,
|
||||
projectId: projectId.value,
|
||||
stageSeq: visibleStages.value.length + 1,
|
||||
isDeleted: false,
|
||||
_key: `${Date.now()}-${Math.random()}`
|
||||
@@ -69,15 +125,19 @@
|
||||
if (!formData.value.length) {
|
||||
formData.value.push(createEmptyStage())
|
||||
}
|
||||
initHistory()
|
||||
}
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
formData.value = []
|
||||
open.value = false
|
||||
undoStack.value = []
|
||||
redoStack.value = []
|
||||
}
|
||||
|
||||
const addRow = () => {
|
||||
formData.value.push(createEmptyStage())
|
||||
commitHistory()
|
||||
}
|
||||
|
||||
const removeRow = (stage) => {
|
||||
@@ -85,8 +145,22 @@
|
||||
if (!visibleStages.value.length) {
|
||||
formData.value.push(createEmptyStage())
|
||||
}
|
||||
commitHistory()
|
||||
}
|
||||
|
||||
// 输入编辑用 watch 做快照(做个轻量防抖,避免每个字符都入栈)
|
||||
watch(
|
||||
formData,
|
||||
() => {
|
||||
if (isRestoring.value) return
|
||||
if (historyTimer) clearTimeout(historyTimer)
|
||||
historyTimer = setTimeout(() => {
|
||||
commitHistory()
|
||||
}, 300)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const submitStages = () => {
|
||||
submitLoading.value = true
|
||||
const submitList = formData.value.map(({ _key, ...rest }) => rest)
|
||||
@@ -128,6 +202,10 @@
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.stage-form__toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.stage-form__header,
|
||||
.stage-form__row {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user