feat(dispute): support bilateral evidence, timeline, and one-time appeal
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import { create } from "zustand"
|
||||
import { generateId } from "@/lib/id"
|
||||
import { mockDisputes } from "@/lib/mock"
|
||||
import type { Dispute } from "@/lib/types"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
|
||||
type DisputeTimelineType = "created" | "response" | "reviewing" | "resolved" | "appealed"
|
||||
|
||||
interface DisputeTimelineItem {
|
||||
id: string
|
||||
type: DisputeTimelineType
|
||||
content: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface DisputeRecord extends Dispute {
|
||||
respondentReason?: string
|
||||
respondentEvidence: string[]
|
||||
appealReason?: string
|
||||
appealedAt?: string
|
||||
timeline: DisputeTimelineItem[]
|
||||
}
|
||||
|
||||
interface SubmitDisputeInput {
|
||||
orderId: string
|
||||
initiatorId: string
|
||||
initiatorName: string
|
||||
reason: string
|
||||
evidence: string[]
|
||||
}
|
||||
|
||||
interface DisputeState {
|
||||
disputes: DisputeRecord[]
|
||||
getDisputeByOrderId: (orderId: string) => DisputeRecord | undefined
|
||||
submitDispute: (input: SubmitDisputeInput) => DisputeRecord
|
||||
submitResponse: (disputeId: string, reason: string, evidence: string[]) => void
|
||||
submitAppeal: (disputeId: string, reason: string) => void
|
||||
}
|
||||
|
||||
const progressTimers = new Map<string, ReturnType<typeof setTimeout>[]>()
|
||||
|
||||
function clearProgressTimers(disputeId: string) {
|
||||
const timers = progressTimers.get(disputeId)
|
||||
if (!timers) return
|
||||
timers.forEach((timer) => {
|
||||
clearTimeout(timer)
|
||||
})
|
||||
progressTimers.delete(disputeId)
|
||||
}
|
||||
|
||||
function asRecord(dispute: Dispute): DisputeRecord {
|
||||
const timeline: DisputeTimelineItem[] = [
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "created",
|
||||
content: `${dispute.initiatorName} 提交争议`,
|
||||
createdAt: dispute.createdAt,
|
||||
},
|
||||
]
|
||||
|
||||
if (dispute.status === "reviewing") {
|
||||
timeline.push({
|
||||
id: generateId("timeline"),
|
||||
type: "reviewing",
|
||||
content: "平台已受理并进入审核",
|
||||
createdAt: dispute.createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
if (dispute.status === "resolved") {
|
||||
timeline.push({
|
||||
id: generateId("timeline"),
|
||||
type: "resolved",
|
||||
content: "平台已给出仲裁结果",
|
||||
createdAt: dispute.createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...dispute,
|
||||
respondentEvidence: [],
|
||||
timeline,
|
||||
}
|
||||
}
|
||||
|
||||
export const useDisputeStore = create<DisputeState>((set, get) => {
|
||||
const scheduleProgress = (disputeId: string) => {
|
||||
clearProgressTimers(disputeId)
|
||||
|
||||
const toReviewing = setTimeout(() => {
|
||||
set((state) => ({
|
||||
disputes: state.disputes.map((dispute) => {
|
||||
if (dispute.id !== disputeId) return dispute
|
||||
if (dispute.status !== "open" && dispute.status !== "appealed") return dispute
|
||||
|
||||
return {
|
||||
...dispute,
|
||||
status: "reviewing",
|
||||
timeline: [
|
||||
...dispute.timeline,
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "reviewing",
|
||||
content: "平台已受理并进入审核",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}, 5000)
|
||||
|
||||
const toResolved = setTimeout(() => {
|
||||
set((state) => ({
|
||||
disputes: state.disputes.map((dispute) => {
|
||||
if (dispute.id !== disputeId) return dispute
|
||||
if (dispute.status !== "reviewing" && dispute.status !== "appealed") return dispute
|
||||
|
||||
return {
|
||||
...dispute,
|
||||
status: "resolved",
|
||||
result: dispute.result ?? "partial_refund",
|
||||
timeline: [
|
||||
...dispute.timeline,
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "resolved",
|
||||
content: "平台已给出仲裁结果",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
}))
|
||||
clearProgressTimers(disputeId)
|
||||
}, 10000)
|
||||
|
||||
progressTimers.set(disputeId, [toReviewing, toResolved])
|
||||
}
|
||||
|
||||
return {
|
||||
disputes: mockDisputes.map(asRecord),
|
||||
getDisputeByOrderId: (orderId) => get().disputes.find((dispute) => dispute.orderId === orderId),
|
||||
submitDispute: (input) => {
|
||||
const createdAt = new Date().toISOString()
|
||||
const dispute: DisputeRecord = {
|
||||
id: generateId("disp"),
|
||||
orderId: input.orderId,
|
||||
initiatorId: input.initiatorId,
|
||||
initiatorName: input.initiatorName,
|
||||
reason: input.reason.trim(),
|
||||
evidence: input.evidence,
|
||||
status: "open",
|
||||
createdAt,
|
||||
respondentEvidence: [],
|
||||
timeline: [
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "created",
|
||||
content: `${input.initiatorName} 提交争议`,
|
||||
createdAt,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
set((state) => ({ disputes: [dispute, ...state.disputes] }))
|
||||
useOrderStore.getState().updateOrderStatus(input.orderId, "disputed")
|
||||
scheduleProgress(dispute.id)
|
||||
return dispute
|
||||
},
|
||||
submitResponse: (disputeId, reason, evidence) => {
|
||||
if (!reason.trim()) return
|
||||
set((state) => ({
|
||||
disputes: state.disputes.map((dispute) => {
|
||||
if (dispute.id !== disputeId || dispute.respondentReason) return dispute
|
||||
|
||||
return {
|
||||
...dispute,
|
||||
respondentReason: reason.trim(),
|
||||
respondentEvidence: evidence,
|
||||
timeline: [
|
||||
...dispute.timeline,
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "response",
|
||||
content: "对方已提交回应材料",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
}))
|
||||
},
|
||||
submitAppeal: (disputeId, reason) => {
|
||||
if (!reason.trim()) return
|
||||
set((state) => ({
|
||||
disputes: state.disputes.map((dispute) => {
|
||||
if (dispute.id !== disputeId || dispute.appealedAt || dispute.status !== "resolved") {
|
||||
return dispute
|
||||
}
|
||||
|
||||
return {
|
||||
...dispute,
|
||||
status: "appealed",
|
||||
appealReason: reason.trim(),
|
||||
appealedAt: new Date().toISOString(),
|
||||
timeline: [
|
||||
...dispute.timeline,
|
||||
{
|
||||
id: generateId("timeline"),
|
||||
type: "appealed",
|
||||
content: "已提交申诉,平台将复核",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}),
|
||||
}))
|
||||
scheduleProgress(disputeId)
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user