Files
FlashSaleSystem/community-fresh-group-buy-frontend/src/components/business/ReviewDialog.vue
2026-05-06 23:30:54 +08:00

198 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-dialog
:model-value="visible"
title="商品评价"
width="640px"
@update:model-value="$emit('update:visible', $event)"
>
<div v-if="checkLoading" class="text-center py-8">
<el-icon :size="32" class="animate-spin">
<Loading/>
</el-icon>
<p class="mt-2 text-gray-500">加载评价状态...</p>
</div>
<div v-else class="space-y-6">
<div v-if="reviewableItems.length === 0 && reviewedItems.length === 0" class="text-center py-8">
<el-empty description="暂无可评价商品"/>
</div>
<!-- 待评价商品 -->
<div v-for="item in reviewableItems" :key="item.productId" class="border rounded-lg p-4">
<div class="flex gap-4 mb-4">
<SafeImage :alt="item.productName" :src="item.productImage" img-class="w-16 h-16 object-cover rounded"
wrapper-class="w-16 h-16 rounded"/>
<div class="flex-1">
<h4 class="font-semibold">{{ item.productName }}</h4>
<div class="text-sm text-gray-500">¥{{ item.price }} × {{ item.quantity }}</div>
</div>
</div>
<div class="mb-3">
<label class="block text-sm text-gray-600 mb-1">评分</label>
<el-rate v-model="item.rating" :texts="['很差', '较差', '一般', '满意', '非常满意']" show-text/>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">评价内容</label>
<el-input
v-model="item.content"
:rows="3"
maxlength="500"
placeholder="分享一下你的使用感受吧"
show-word-limit
type="textarea"
/>
</div>
</div>
<!-- 已评价商品 -->
<div v-for="item in reviewedItems" :key="'reviewed-' + item.productId" class="border rounded-lg p-4 bg-gray-50">
<div class="flex gap-4">
<SafeImage :alt="item.productName" :src="item.productImage" img-class="w-16 h-16 object-cover rounded"
wrapper-class="w-16 h-16 rounded"/>
<div class="flex-1">
<div class="flex items-center justify-between mb-1">
<h4 class="font-semibold">{{ item.productName }}</h4>
<el-tag size="small" type="success">已评价</el-tag>
</div>
<el-rate :model-value="item.existingReview!.rating" disabled/>
<p class="text-sm text-gray-600 mt-1">{{ item.existingReview!.content }}</p>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="$emit('update:visible', false)">关闭</el-button>
<el-button
v-if="reviewableItems.length > 0"
:disabled="!canSubmit"
:loading="submitting"
type="primary"
@click="handleSubmit"
>
提交评价
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import {ref, computed, watch} from 'vue'
import {ElMessage} from 'element-plus'
import {reviewApi} from '@/api/modules/review'
import type {ReviewItem} from '@/api/modules/review'
import type {OrderItem} from '@/types/api'
import SafeImage from '@/components/common/SafeImage.vue'
interface ReviewableItem extends OrderItem {
rating: number
content: string
reviewed: boolean
existingReview?: ReviewItem
}
const props = defineProps<{
visible: boolean
orderId: number
orderItems: OrderItem[]
}>()
const emit = defineEmits<{
'update:visible': [value: boolean]
success: []
}>()
const checkLoading = ref(false)
const submitting = ref(false)
const items = ref<ReviewableItem[]>([])
const reviewableItems = computed(() => items.value.filter(i => !i.reviewed))
const reviewedItems = computed(() => items.value.filter(i => i.reviewed))
const canSubmit = computed(() => reviewableItems.value.some(i => i.content.trim()))
const loadReviewStatus = async () => {
if (!props.orderId || !props.orderItems.length) {
items.value = []
return
}
checkLoading.value = true
try {
const list: ReviewableItem[] = props.orderItems.map(item => ({
...item,
rating: 5,
content: '',
reviewed: false,
existingReview: undefined,
}))
const checks = await Promise.all(
list.map(item => reviewApi.checkReview(props.orderId, item.productId).catch(() => null))
)
checks.forEach((res, index) => {
if (res?.success && res.data.reviewed) {
list[index].reviewed = true
list[index].existingReview = res.data.review
}
})
items.value = list
} finally {
checkLoading.value = false
}
}
const handleSubmit = async () => {
const toSubmit = reviewableItems.value.filter(i => i.content.trim())
if (toSubmit.length === 0) {
ElMessage.warning('请至少填写一条评价内容')
return
}
submitting.value = true
let successCount = 0
try {
for (const item of toSubmit) {
try {
await reviewApi.create({
orderId: props.orderId,
productId: item.productId,
rating: item.rating,
content: item.content.trim(),
})
item.reviewed = true
item.existingReview = {rating: item.rating, content: item.content} as ReviewItem
successCount++
} catch (error: any) {
const respData = error?.response?.data
const msg = respData?.message || error?.message || '提交失败'
ElMessage.error(`${item.productName}: ${msg}`)
}
}
if (successCount > 0) {
ElMessage.success(`成功提交 ${successCount} 条评价`)
emit('success')
if (reviewableItems.value.length === 0) {
emit('update:visible', false)
}
}
} finally {
submitting.value = false
}
}
watch(() => props.visible, (val) => {
if (val) loadReviewStatus()
if (!val) items.value = []
})
watch(
() => [props.orderId, props.orderItems],
() => {
if (props.visible) loadReviewStatus()
},
{immediate: true}
)
</script>