fix(ticket): invalidate reopened detail actions

This commit is contained in:
mlogclub
2026-05-02 20:32:42 +08:00
parent 567d9cb8e7
commit 1c65bec11b
@@ -76,15 +76,16 @@ export function TicketDetailDialog({
const [editOpen, setEditOpen] = useState(false) const [editOpen, setEditOpen] = useState(false)
const [editSaving, setEditSaving] = useState(false) const [editSaving, setEditSaving] = useState(false)
const loadSeqRef = useRef(0) const loadSeqRef = useRef(0)
const dialogSeqRef = useRef(0)
const currentTicketIdRef = useRef<number | null>(null) const currentTicketIdRef = useRef<number | null>(null)
currentTicketIdRef.current = open ? ticketId : null currentTicketIdRef.current = open ? ticketId : null
function isCurrentTicket(targetTicketId: number) { function isCurrentOperation(targetTicketId: number, dialogSeq: number) {
return currentTicketIdRef.current === targetTicketId return currentTicketIdRef.current === targetTicketId && dialogSeqRef.current === dialogSeq
} }
const loadDetail = useCallback(async (targetTicketId = ticketId) => { const loadDetail = useCallback(async (targetTicketId = ticketId, dialogSeq = dialogSeqRef.current) => {
if (currentTicketIdRef.current !== targetTicketId) { if (!targetTicketId || !isCurrentOperation(targetTicketId, dialogSeq)) {
return return
} }
const seq = loadSeqRef.current + 1 const seq = loadSeqRef.current + 1
@@ -97,12 +98,12 @@ export function TicketDetailDialog({
setLoading(true) setLoading(true)
try { try {
const data = await fetchTicketDetail(targetTicketId) const data = await fetchTicketDetail(targetTicketId)
if (loadSeqRef.current !== seq || currentTicketIdRef.current !== targetTicketId) { if (loadSeqRef.current !== seq || !isCurrentOperation(targetTicketId, dialogSeq)) {
return return
} }
setDetail(data) setDetail(data)
} catch (error) { } catch (error) {
if (loadSeqRef.current !== seq || currentTicketIdRef.current !== targetTicketId) { if (loadSeqRef.current !== seq || !isCurrentOperation(targetTicketId, dialogSeq)) {
return return
} }
toast.error(error instanceof Error ? error.message : "加载工单详情失败") toast.error(error instanceof Error ? error.message : "加载工单详情失败")
@@ -114,10 +115,9 @@ export function TicketDetailDialog({
}, [open, ticketId]) }, [open, ticketId])
useEffect(() => { useEffect(() => {
void loadDetail() dialogSeqRef.current += 1
}, [loadDetail]) setDetail(null)
setLoading(false)
useEffect(() => {
setStatusSaving(null) setStatusSaving(null)
setProgressSaving(false) setProgressSaving(false)
setEditSaving(false) setEditSaving(false)
@@ -126,30 +126,35 @@ export function TicketDetailDialog({
setProgressContent("") setProgressContent("")
}, [open, ticketId]) }, [open, ticketId])
useEffect(() => {
void loadDetail(ticketId, dialogSeqRef.current)
}, [loadDetail, ticketId])
async function handleStatusChange(status: TicketStatus) { async function handleStatusChange(status: TicketStatus) {
if (!detail || detail.ticket.status === status) { if (!detail || detail.ticket.status === status) {
return return
} }
const activeTicketId = detail.ticket.id const activeTicketId = detail.ticket.id
const activeDialogSeq = dialogSeqRef.current
setStatusSaving(status) setStatusSaving(status)
try { try {
await changeTicketStatus({ ticketId: activeTicketId, status }) await changeTicketStatus({ ticketId: activeTicketId, status })
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
toast.success("工单状态已更新") toast.success("工单状态已更新")
await loadDetail(activeTicketId) await loadDetail(activeTicketId, activeDialogSeq)
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
onChanged() onChanged()
} catch (error) { } catch (error) {
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
toast.error(error instanceof Error ? error.message : "更新工单状态失败") toast.error(error instanceof Error ? error.message : "更新工单状态失败")
} finally { } finally {
if (isCurrentTicket(activeTicketId)) { if (isCurrentOperation(activeTicketId, activeDialogSeq)) {
setStatusSaving(null) setStatusSaving(null)
} }
} }
@@ -160,6 +165,7 @@ export function TicketDetailDialog({
return return
} }
const activeTicketId = detail.ticket.id const activeTicketId = detail.ticket.id
const activeDialogSeq = dialogSeqRef.current
const content = progressContent.trim() const content = progressContent.trim()
if (!content) { if (!content) {
toast.error("请填写处理进展") toast.error("请填写处理进展")
@@ -171,23 +177,23 @@ export function TicketDetailDialog({
ticketId: activeTicketId, ticketId: activeTicketId,
content, content,
}) })
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
toast.success("处理进展已记录") toast.success("处理进展已记录")
setProgressContent("") setProgressContent("")
await loadDetail(activeTicketId) await loadDetail(activeTicketId, activeDialogSeq)
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
onChanged() onChanged()
} catch (error) { } catch (error) {
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
toast.error(error instanceof Error ? error.message : "记录处理进展失败") toast.error(error instanceof Error ? error.message : "记录处理进展失败")
} finally { } finally {
if (isCurrentTicket(activeTicketId)) { if (isCurrentOperation(activeTicketId, activeDialogSeq)) {
setProgressSaving(false) setProgressSaving(false)
} }
} }
@@ -195,11 +201,12 @@ export function TicketDetailDialog({
async function handleAssigned() { async function handleAssigned() {
const activeTicketId = ticket?.id const activeTicketId = ticket?.id
if (!activeTicketId || !isCurrentTicket(activeTicketId)) { const activeDialogSeq = dialogSeqRef.current
if (!activeTicketId || !isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
await loadDetail(activeTicketId) await loadDetail(activeTicketId, activeDialogSeq)
if (!isCurrentTicket(activeTicketId)) { if (!isCurrentOperation(activeTicketId, activeDialogSeq)) {
return return
} }
onChanged() onChanged()
@@ -210,26 +217,27 @@ export function TicketDetailDialog({
toast.error("请选择工单") toast.error("请选择工单")
return return
} }
const activeDialogSeq = dialogSeqRef.current
setEditSaving(true) setEditSaving(true)
try { try {
await updateTicket(payload) await updateTicket(payload)
if (!isCurrentTicket(payload.ticketId)) { if (!isCurrentOperation(payload.ticketId, activeDialogSeq)) {
return return
} }
toast.success("工单已更新") toast.success("工单已更新")
setEditOpen(false) setEditOpen(false)
await loadDetail(payload.ticketId) await loadDetail(payload.ticketId, activeDialogSeq)
if (!isCurrentTicket(payload.ticketId)) { if (!isCurrentOperation(payload.ticketId, activeDialogSeq)) {
return return
} }
onChanged() onChanged()
} catch (error) { } catch (error) {
if (!isCurrentTicket(payload.ticketId)) { if (!isCurrentOperation(payload.ticketId, activeDialogSeq)) {
return return
} }
toast.error(error instanceof Error ? error.message : "更新工单失败") toast.error(error instanceof Error ? error.message : "更新工单失败")
} finally { } finally {
if (isCurrentTicket(payload.ticketId)) { if (isCurrentOperation(payload.ticketId, activeDialogSeq)) {
setEditSaving(false) setEditSaving(false)
} }
} }