198 lines
5.9 KiB
Vue
198 lines
5.9 KiB
Vue
<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>
|