2025-11-20 17:10:28 +08:00

615 lines
16 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>
<div class="user-reviews-page">
<!-- 筛选栏 -->
<el-card class="filter-card" shadow="never">
<el-form :inline="true" class="filter-form">
<el-form-item label="商品id">
<el-input
v-model="filters.productId"
placeholder="请输入商品ID"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="订单号">
<el-input
v-model="filters.tradeOrderId"
placeholder="请输入订单ID"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="买家名称">
<el-input
v-model="filters.buyerName"
placeholder="请输入买家名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="filters.dateRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button type="danger" @click="handleReset" style="margin-left: 8px">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 评论列表 -->
<el-card class="order-table-card" shadow="never">
<!-- INSERT_YOUR_CODE -->
<el-tabs
class="bg-white"
v-model="filters.status"
@tab-change="handleTabChange"
style="margin-bottom: 18px"
>
<el-tab-pane label="全部" name=""></el-tab-pane>
<el-tab-pane label="同意" name="approval_pass"></el-tab-pane>
<el-tab-pane label="拒绝" name="approval_not_pass"></el-tab-pane>
</el-tabs>
<div class="order-table-header">
<div class="col goods">商品</div>
<div class="col total">评分</div>
<!-- <div class="col status">状态</div> -->
<div class="col actions">操作</div>
</div>
<div class="order-list">
<div v-for="review in reviews" :key="review.id" class="order-item">
<div class="order-top">
<div class="top-left">
<el-checkbox v-model="review.checked" />
<span class="platform">订单{{ review.tradeOrderId }}</span>
<el-divider direction="vertical" />
<div class="platform-name">购de着</div>
<!-- <el-link :underline="false" type="primary">{{ order.summary }}</el-link> -->
</div>
<div class="top-right">
<span class="seller">{{ review.buyerName }}</span>
<el-divider direction="vertical" />
<span class="order-meta">创建时间{{ review.createTime }}</span>
</div>
</div>
<div class="order-content">
<!-- 左侧评论详情 -->
<div class="review-left p-4 relative">
<div class="main-review">
<p class="mb-2">主评{{ review.productComment }}</p>
<el-image
class="mr-2"
v-for="img in review.vvCommentDetailEntities"
:key="img.id"
style="width: 80px; height: 80px"
:src="img.commentUrl"
:preview-src-list="review.vvCommentDetailEntities.map((it) => it.commentUrl)"
fit="cover"
/>
</div>
<div class="review-date text-xs">{{ review.modifyTime }}</div>
<div class="review-actions absolute bottom-2 right-2">
<span v-if="review.reason">拒绝原因:{{ review.reason }}</span>
<!-- <el-button link type="primary" size="small" @click="handleViewReplies(review)">
<el-icon><ChatDotRound /></el-icon>
{{ review.replyCount }} 回复
</el-button>
<el-button link type="danger" size="small" @click="handleReport(review)">
<el-icon><WarningFilled /></el-icon>
举报
</el-button> -->
</div>
</div>
<!-- 中间:评分区域 -->
<div class="review-middle p-3">
<div class="rating-item">
<span class="rating-label">总体评分:</span>
<el-rate
v-model="review.overallRating"
disabled
show-score
size="small"
text-color="#ff9900"
score-template="{value}"
/>
</div>
<div class="rating-item">
<span class="rating-label">商品分:</span>
<el-rate
v-model="review.descMatch"
disabled
show-score
size="small"
text-color="#ff9900"
score-template="{value}"
/>
</div>
<div class="rating-item">
<span class="rating-label">卖家服务评分:</span>
<el-rate
v-model="review.sellerService"
disabled
show-score
size="small"
text-color="#ff9900"
score-template="{value}"
/>
</div>
<div class="rating-item">
<span class="rating-label">物流评分:</span>
<el-rate
v-model="review.logisticsService"
disabled
show-score
size="small"
text-color="#ff9900"
score-template="{value}"
/>
</div>
</div>
<!-- 右侧:商品信息和操作 -->
<div class="review-right">
<div class="product-section p-3">
<el-image class="product-image" :src="review.productImage" fit="cover">
<template #error>
<div class="image-fallback">无图</div>
</template>
</el-image>
<div class="product-info">
<div class="product-tag">{{ review.productTitle }}</div>
<div class="product-name">{{ review.skuInfo }}</div>
</div>
</div>
</div>
<div class="flex flex-col items-center justify-center">
<el-button
v-for="(action, i) in review.commentActionList"
:key="action.interfaceUri"
:type="i === 0 ? 'primary' : ''"
class="!ml-0 !mt-2"
@click="onButtonClick(action.interfaceUri, review)"
>{{ action.desc }}</el-button
>
</div>
</div>
</div>
</div>
<div class="pagination">
<el-pagination
background
layout="prev, pager, next, jumper"
:page-size="20"
size="small"
:total="pagination.total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</el-card>
<el-dialog v-model="dialogVisible" title="拒绝原因" width="500px">
<el-form :model="form" label-width="0">
<el-form-item label=" ">
<el-input v-model="form.reason" type="textarea" :rows="4" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onRejectComment(form.reason)">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Search, ChatDotRound, WarningFilled, Refresh } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
// import order from '@/api/order'
// import request from '@/utils/request'
const dialogVisible = ref(false)
const form = reactive({
reason: '',
id: NaN
})
// 筛选条件
const filters = reactive({
productId: '',
tradeOrderId: '',
buyerName: '',
dateRange: [],
status: ''
})
// 评论数据
interface Review {
id: number
checked: boolean
tradeOrderId: string
buyerName: string
createTime: string
productComment: string
modifyTime: string
descMatch: number
sellerService: number
logisticsService: number
productImage: string
productTitle: string
skuInfo: string
reason: string
vvCommentDetailEntities: { id: string; commentUrl: string }[]
commentActionList: { desc: string; interfaceUri: string }[]
}
const reviews = ref<Review[]>([])
const loading = ref(false)
// 分页
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 搜索
const handleSearch = async () => {
pagination.currentPage = 1
await fetchReviews()
}
// 获取评论列表
const fetchReviews = async () => {
loading.value = true
const res = await api.order.getUserReviews.post!<{ rows: Review[]; total: number }>({
currentPage: pagination.currentPage,
pageSize: pagination.pageSize,
...filters,
minCreateTimestamp: filters.dateRange?.[0],
maxCreateTimestamp: filters.dateRange?.[1]
})
reviews.value = res.data.rows.map((item) => {
let skuInfo = ''
try {
skuInfo = JSON.parse(item.skuInfo)
.map((it: any) => `${it.propertyName}:${it.propertyValue}`)
.join(',')
} catch (error) {
skuInfo = item.skuInfo
}
return {
...item,
skuInfo,
createTime: new Date(item.createTime).toLocaleString(),
modifyTime: new Date(item.modifyTime).toLocaleString()
}
})
pagination.total = res.data.total
loading.value = false
}
const onButtonClick = (interfaceUri: string, review: Review) => {
if (interfaceUri === '/mm/comment/agree') {
onAgreeComment(review)
} else if (interfaceUri === '/app/comment/reject') {
form.reason = ''
form.id = review.id
dialogVisible.value = true
}
}
// 同意
const onAgreeComment = (review: Review) => {
api.order.agreeComment.post!({
id: review.id
}).then(() => {
ElMessage.success('同意成功')
fetchReviews()
})
}
// 拒绝
const onRejectComment = (review: Review) => {
api.order.rejectComment.post!(form).then(() => {
ElMessage.success('拒绝成功')
dialogVisible.value = false
fetchReviews()
})
}
const handleTabChange = () => {
fetchReviews()
}
// 分页大小改变
const handleSizeChange = (size: number) => {
pagination.pageSize = size
pagination.currentPage = 1
fetchReviews()
}
// 当前页改变
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
fetchReviews()
}
// 重置
const handleReset = () => {
filters.productId = ''
filters.tradeOrderId = ''
filters.buyerName = ''
filters.dateRange = []
fetchReviews()
}
// 组件挂载时获取数据
onMounted(() => {
fetchReviews()
})
</script>
<style scoped lang="scss">
.user-reviews-page {
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
.filter-card {
margin-bottom: 20px;
.filter-hint {
font-size: 14px;
color: #909399;
margin-bottom: 16px;
}
.filter-form {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
padding: 20px 0;
}
}
.user-reviews-page {
.review-card {
.review-content {
flex-direction: column;
.review-middle {
border-left: none;
border-right: none;
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
padding: 16px 0;
}
}
}
}
.order-table-card {
padding: 0 0 16px;
.order-table-header {
display: grid;
grid-template-columns: minmax(400px, 1fr) 240px 300px 150px;
padding: 12px 24px;
background: #f9fafc;
border-bottom: 1px solid #ebeef5;
font-size: 13px;
color: #909399;
.col:first-child {
padding-left: 44px;
}
}
.order-list {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 0;
}
.order-item {
border: 1px solid #ebeef5;
border-radius: 6px;
background: #fff;
overflow-x: scroll;
.order-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px;
background: #fafafa;
border-bottom: 1px solid #f0f2f5;
font-size: 13px;
color: #606266;
.top-left,
.top-right {
display: flex;
align-items: center;
gap: 8px;
}
.platform {
font-weight: 600;
color: #303133;
}
.seller {
color: #303133;
}
.order-meta {
color: #909399;
}
}
.order-content {
display: grid;
grid-template-columns: minmax(400px, 1fr) 240px 300px 150px;
gap: 12px;
padding: 0;
font-size: 13px;
color: #606266;
.goods {
display: flex;
flex-direction: column;
.goods-item + .goods-item {
border-top: 1px solid #f0f2f5;
}
.goods-item {
display: flex;
gap: 12px;
border-right: 1px solid #f0f2f5;
border-radius: 6px;
padding: 12px;
align-items: center;
.goods-image {
width: 60px;
height: 60px;
border-radius: 6px;
overflow: hidden;
}
.image-fallback {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: #f2f3f5;
color: #909399;
font-size: 12px;
}
.goods-info {
flex: 1;
.title {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 6px;
}
.sub {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #909399;
}
}
.goods-price {
font-weight: 600;
color: #303133;
}
}
}
.total {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
.amount {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.remark {
font-size: 12px;
color: #909399;
}
}
.delivery {
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
.delivery-type {
font-weight: 600;
color: #303133;
}
.delivery-warehouse {
font-size: 12px;
color: #909399;
}
}
.status {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
.status-label {
font-weight: 600;
color: #303133;
}
.status-detail {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #909399;
}
}
.actions {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
gap: 8px;
}
}
}
.pagination {
display: flex;
justify-content: flex-end;
padding: 0 24px;
margin-top: 8px;
}
}
</style>