fix(review): enforce pending-review submission and remove auto reply

This commit is contained in:
zetaloop
2026-02-22 15:18:38 +08:00
parent 385dac2d49
commit ca95165e1b
2 changed files with 48 additions and 31 deletions
+21 -1
View File
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea" import { Textarea } from "@/components/ui/textarea"
import { notifyInfo, notifySuccess } from "@/lib/toast"
import { useAuthStore } from "@/store/auth" import { useAuthStore } from "@/store/auth"
import { useOrderStore } from "@/store/orders" import { useOrderStore } from "@/store/orders"
import { useReviewStore } from "@/store/reviews" import { useReviewStore } from "@/store/reviews"
@@ -37,6 +38,18 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }>
const hasSubmitted = Boolean(userId && reviews.some((review) => review.fromUserId === userId)) const hasSubmitted = Boolean(userId && reviews.some((review) => review.fromUserId === userId))
const isRevealed = reviews.length >= 2 && reviews.every((review) => !review.sealed) const isRevealed = reviews.length >= 2 && reviews.every((review) => !review.sealed)
if (order.status !== "pending_review") {
return (
<div className="container mx-auto py-8 px-4 max-w-lg text-center space-y-4">
<h2 className="text-xl font-bold"></h2>
<p className="text-sm text-muted-foreground"></p>
<Link href={`/order/${id}`} className="text-sm text-primary hover:underline">
</Link>
</div>
)
}
if (hasSubmitted && !isRevealed) { if (hasSubmitted && !isRevealed) {
return ( return (
<div className="container mx-auto py-8 px-4 max-w-lg text-center space-y-4"> <div className="container mx-auto py-8 px-4 max-w-lg text-center space-y-4">
@@ -126,12 +139,19 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }>
disabled={rating === 0 || !userId} disabled={rating === 0 || !userId}
onClick={() => { onClick={() => {
if (!userId) return if (!userId) return
submitReview({ const decision = submitReview({
orderId: id, orderId: id,
fromUserId: userId, fromUserId: userId,
rating, rating,
content, content,
}) })
if (decision.ok) {
notifySuccess("评价已提交")
return
}
notifyInfo(decision.message ?? "评价提交失败")
}} }}
> >
+27 -30
View File
@@ -1,5 +1,7 @@
import { create } from "zustand" import { create } from "zustand"
import { generateId } from "@/lib/id" import { generateId } from "@/lib/id"
import { allow, deny } from "@/lib/policy/assert"
import type { PolicyDecision } from "@/lib/policy/decision"
import { mockReviews, mockUsers } from "@/lib/mock" import { mockReviews, mockUsers } from "@/lib/mock"
import type { Review } from "@/lib/types" import type { Review } from "@/lib/types"
import { useOrderStore } from "@/store/orders" import { useOrderStore } from "@/store/orders"
@@ -13,13 +15,11 @@ interface SubmitReviewInput {
interface ReviewState { interface ReviewState {
reviews: Review[] reviews: Review[]
submitReview: (input: SubmitReviewInput) => void submitReview: (input: SubmitReviewInput) => PolicyDecision
getReviewsByOrder: (orderId: string) => Review[] getReviewsByOrder: (orderId: string) => Review[]
hasUserReviewed: (orderId: string, userId: string) => boolean hasUserReviewed: (orderId: string, userId: string) => boolean
} }
const autoReplyTimers = new Map<string, ReturnType<typeof setTimeout>>()
function resolveUser(userId: string) { function resolveUser(userId: string) {
return mockUsers.find((user) => user.id === userId) return mockUsers.find((user) => user.id === userId)
} }
@@ -53,11 +53,32 @@ export const useReviewStore = create<ReviewState>((set, get) => ({
hasUserReviewed: (orderId, userId) => hasUserReviewed: (orderId, userId) =>
get().reviews.some((review) => review.orderId === orderId && review.fromUserId === userId), get().reviews.some((review) => review.orderId === orderId && review.fromUserId === userId),
submitReview: (input) => { submitReview: (input) => {
if (!input.fromUserId) {
return deny("AUTH_REQUIRED", "请先登录")
}
if (!Number.isFinite(input.rating) || input.rating < 1 || input.rating > 5) {
return deny("VALIDATION_FAILED", "评分范围应为 1-5")
}
const order = useOrderStore.getState().orders.find((item) => item.id === input.orderId)
if (!order) {
return deny("NOT_FOUND", "订单不存在")
}
if (order.status !== "pending_review") {
return deny("INVALID_STATUS", "仅待评价订单可提交评价")
}
const relation = resolveOrderUser(input.orderId, input.fromUserId) const relation = resolveOrderUser(input.orderId, input.fromUserId)
if (!relation) return if (!relation) {
return deny("NOT_PARTICIPANT", "仅订单参与方可评价")
}
const exists = get().hasUserReviewed(input.orderId, input.fromUserId) const exists = get().hasUserReviewed(input.orderId, input.fromUserId)
if (exists) return if (exists) {
return deny("ALREADY_DONE", "该订单已提交过评价")
}
const fromUser = resolveUser(input.fromUserId) const fromUser = resolveUser(input.fromUserId)
const createdAt = new Date().toISOString() const createdAt = new Date().toISOString()
@@ -79,37 +100,13 @@ export const useReviewStore = create<ReviewState>((set, get) => ({
const orderReviews = get().getReviewsByOrder(input.orderId) const orderReviews = get().getReviewsByOrder(input.orderId)
if (orderReviews.length >= 2) { if (orderReviews.length >= 2) {
const timer = autoReplyTimers.get(input.orderId)
if (timer) {
clearTimeout(timer)
autoReplyTimers.delete(input.orderId)
}
set((state) => ({ set((state) => ({
reviews: state.reviews.map((item) => reviews: state.reviews.map((item) =>
item.orderId === input.orderId ? { ...item, sealed: false } : item, item.orderId === input.orderId ? { ...item, sealed: false } : item,
), ),
})) }))
useOrderStore.getState().updateOrderStatus(input.orderId, "completed")
return
} }
if (autoReplyTimers.has(input.orderId)) return return allow()
const timer = setTimeout(() => {
autoReplyTimers.delete(input.orderId)
if (get().hasUserReviewed(input.orderId, relation.toUserId)) {
return
}
get().submitReview({
orderId: input.orderId,
fromUserId: relation.toUserId,
rating: 5,
content: `收到 ${relation.toUserName} 的评价,感谢本次对局。`,
})
}, 3000)
autoReplyTimers.set(input.orderId, timer)
}, },
})) }))