feat: 删除JSP视图层,完善评价和通知系统,新增拼团模块
- 删除所有 JSP 页面(20个文件),前端完全迁移至 Vue 3 SPA - 完善评价系统:ReviewDialog 组件、用户评价历史页、评价状态检查API - 新增通知系统:Notification 实体/仓库/服务/控制器,NotificationCenter 接入真实API - 新增拼团模块:GroupBuying 全套后端和前端页面 - 修复 review check API 参数双重包装导致请求格式错误 - 修复通知 API 路径缺少 /api 前缀和响应格式处理 - MessageListenerService 集成 NotificationService 创建持久化通知 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
181
flash-sale-frontend/src/components/business/ReviewDialog.vue
Normal file
181
flash-sale-frontend/src/components/business/ReviewDialog.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<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 :src="item.productImage" :alt="item.productName" wrapper-class="w-16 h-16 rounded" img-class="w-16 h-16 object-cover 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" show-text :texts="['很差', '较差', '一般', '满意', '非常满意']" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1">评价内容</label>
|
||||
<el-input
|
||||
v-model="item.content"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="分享一下你的使用感受吧"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</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 :src="item.productImage" :alt="item.productName" wrapper-class="w-16 h-16 rounded" img-class="w-16 h-16 object-cover rounded" />
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<h4 class="font-semibold">{{ item.productName }}</h4>
|
||||
<el-tag type="success" size="small">已评价</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"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
:disabled="!canSubmit"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
提交评价
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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) 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()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user