615 lines
16 KiB
Vue
615 lines
16 KiB
Vue
<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>
|