feat: 物流+评论
This commit is contained in:
parent
580cd25221
commit
f1f3b01081
@ -6,7 +6,15 @@ const order = {
|
||||
getOrderDetail: ['/order/detail'], // 获取订单详情
|
||||
packOrder: ['/order/toShipping'], // 打包
|
||||
unpackOrder: ['/order/unpack'], // 取消打包
|
||||
finishDeliver: ['/order/delivered'] // 妥投
|
||||
finishDeliver: ['/order/delivered'], // 妥投
|
||||
getLogisticsInfo: ['/logistics/query'], // 获取物流信息
|
||||
|
||||
/**
|
||||
* 用户评论
|
||||
*/
|
||||
getUserReviews: ['/comment/list'], // 获取用户评论
|
||||
agreeComment: ['/comment/agree'], // 同意
|
||||
rejectComment: ['/comment/reject'] // 拒绝
|
||||
}
|
||||
|
||||
export default order
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
<!-- 物流弹窗 -->
|
||||
<logistics-dialog
|
||||
v-model="logisticsDialogVisible"
|
||||
:order-id="currentOrderId"
|
||||
:track-number="curTrackNumber"
|
||||
@close="handleCloseLogisticsDialog"
|
||||
/>
|
||||
<!-- 打包 -->
|
||||
@ -412,11 +412,11 @@ const onButtonClick = (interfaceUri: string, packageData: PackageItem) => {
|
||||
}
|
||||
// 物流弹窗相关
|
||||
const logisticsDialogVisible = ref(false)
|
||||
const currentOrderId = ref<number>(NaN)
|
||||
const curTrackNumber = ref<number>(NaN)
|
||||
|
||||
// 查看物流
|
||||
const handleViewLogistics = (packageData: PackageItem) => {
|
||||
currentOrderId.value = packageData.trackNumber
|
||||
curTrackNumber.value = packageData.trackNumber
|
||||
logisticsDialogVisible.value = true
|
||||
}
|
||||
|
||||
@ -431,7 +431,7 @@ const handlePack = (orderLines: OrderItem[]) => {
|
||||
// 关闭物流弹窗
|
||||
const handleCloseLogisticsDialog = () => {
|
||||
logisticsDialogVisible.value = false
|
||||
currentOrderId.value = NaN
|
||||
curTrackNumber.value = NaN
|
||||
}
|
||||
|
||||
const orderStatusMap = {
|
||||
|
||||
486
src/views/order/list/logistics-dialog.vue
Normal file
486
src/views/order/list/logistics-dialog.vue
Normal file
@ -0,0 +1,486 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="物流跟踪"
|
||||
width="800px"
|
||||
:before-close="handleClose"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="logistics-dialog" v-loading="loading">
|
||||
<!-- 状态时间线 -->
|
||||
<div class="status-timeline">
|
||||
<div
|
||||
v-for="(stage, index) in statusStages"
|
||||
:key="stage.key"
|
||||
class="timeline-item"
|
||||
:class="{ active: stage.active, completed: stage.completed }"
|
||||
>
|
||||
<div class="timeline-icon">
|
||||
<el-icon v-if="stage.icon" :size="24">
|
||||
<component :is="stage.icon" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="timeline-label">{{ stage.label }}</div>
|
||||
<div v-if="index < statusStages.length - 1" class="timeline-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 物流跟踪记录 -->
|
||||
<div class="tracking-list">
|
||||
<div
|
||||
v-for="(item, index) in trackingRecords"
|
||||
:key="index"
|
||||
class="tracking-item"
|
||||
:class="{ 'is-first': index === 0 }"
|
||||
>
|
||||
<div class="tracking-time">{{ item.time }}</div>
|
||||
<div class="tracking-content">
|
||||
<div class="tracking-status">{{ item.status }}</div>
|
||||
<div class="tracking-message">{{ item.context }}</div>
|
||||
</div>
|
||||
<div v-if="index < trackingRecords.length - 1" class="tracking-line"></div>
|
||||
</div>
|
||||
<div v-if="!loading && trackingRecords.length === 0" class="empty-tracking">
|
||||
<el-empty description="暂无物流信息" :image-size="80" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部信息区域 -->
|
||||
<div class="bottom-info">
|
||||
<!-- 物流信息 -->
|
||||
<div class="logistics-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">第三方物流:</span>
|
||||
<span class="info-value">{{ logisticsInfo.logisticsCompany || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">发货人:</span>
|
||||
<span class="info-value"
|
||||
>{{ logisticsInfo.deliveryMan || '-' }}:{{ logisticsInfo.deliveryPhone || '' }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">派件人:</span>
|
||||
<span class="info-value"
|
||||
>{{ logisticsInfo.pickupMan || '-' }}:{{ logisticsInfo.pickupPhone || '' }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 包裹信息图标 -->
|
||||
<!-- <div
|
||||
v-if="packageInfo && (packageInfo.dimensions || packageInfo.num)"
|
||||
class="package-icon-wrapper"
|
||||
>
|
||||
<div class="package-icon">
|
||||
<img src="" alt="package" />
|
||||
</div>
|
||||
<div class="package-text">
|
||||
<template v-if="packageInfo.dimensions && packageInfo.num">
|
||||
{{ formatPackageInfo(packageInfo.dimensions, packageInfo.num) }}
|
||||
</template>
|
||||
<template v-else-if="packageInfo.dimensions">
|
||||
{{ packageInfo.dimensions }}
|
||||
</template>
|
||||
<template v-else-if="packageInfo.num"> x {{ packageInfo.num }} </template>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Box, Goods, User } from '@element-plus/icons-vue'
|
||||
|
||||
interface LogisticsInfo {
|
||||
logisticsCompany?: string
|
||||
pickupMan?: string
|
||||
pickupPhone?: string
|
||||
deliveryMan?: string
|
||||
deliveryPhone?: string
|
||||
}
|
||||
|
||||
interface TrackingRecord {
|
||||
time: string
|
||||
status: string
|
||||
context: string
|
||||
}
|
||||
/*
|
||||
interface PackageInfo {
|
||||
dimensions?: string
|
||||
num?: string
|
||||
} */
|
||||
|
||||
interface StatusStage {
|
||||
key: string
|
||||
label: string
|
||||
icon?: any
|
||||
active: boolean
|
||||
completed: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
trackNumber: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
// 内部状态
|
||||
const loading = ref(false)
|
||||
// 物流信息
|
||||
const logisticsInfo = ref<LogisticsInfo>({})
|
||||
const trackingRecords = ref<TrackingRecord[]>([])
|
||||
/* const packageInfo = ref<PackageInfo>({})
|
||||
const currentStatus = ref<'' | 'pickup' | 'shipping' | 'delivered'>('') */
|
||||
|
||||
// 状态阶段
|
||||
const statusStages = computed<StatusStage[]>(() => {
|
||||
return [
|
||||
{
|
||||
key: 'pickup',
|
||||
label: '提货',
|
||||
icon: Box,
|
||||
active: false,
|
||||
completed: true
|
||||
/* active: currentStatus.value === 'pickup',
|
||||
completed: currentStatus.value === 'shipping' || currentStatus.value === 'delivered' */
|
||||
},
|
||||
{
|
||||
key: 'shipping',
|
||||
label: '运输中',
|
||||
icon: Goods,
|
||||
active: false,
|
||||
completed: true
|
||||
/* active: currentStatus.value === 'shipping',
|
||||
completed: currentStatus.value === 'delivered' */
|
||||
},
|
||||
{
|
||||
key: 'delivered',
|
||||
label: '交付',
|
||||
icon: User,
|
||||
active: false,
|
||||
completed: true
|
||||
/* active: currentStatus.value === 'delivered',
|
||||
completed: false */
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 获取物流信息
|
||||
const fetchLogisticsData = async (trackNumber: number) => {
|
||||
loading.value = true
|
||||
const res = await api.order.getLogisticsInfo.post!<any>({ trackNumber }).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
const data = res.data
|
||||
logisticsInfo.value = {
|
||||
logisticsCompany: data.logisticsCompany,
|
||||
pickupPhone: data.courierInfo.deliveryManPhone,
|
||||
pickupMan: data.courierInfo.pickupManName,
|
||||
deliveryPhone: data.courierInfo.pickupManPhone,
|
||||
deliveryMan: data.courierInfo.deliveryManName
|
||||
}
|
||||
trackingRecords.value = data.data
|
||||
/* packageInfo.value = { dimensions: '0.01 x 2', num: '2' }
|
||||
currentStatus.value = 'shipping' */
|
||||
}
|
||||
|
||||
// 监听弹窗打开和 trackNumber 变化
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
fetchLogisticsData(props.trackNumber)
|
||||
} else if (!visible) {
|
||||
logisticsInfo.value = {
|
||||
logisticsCompany: '',
|
||||
pickupPhone: '',
|
||||
pickupMan: '',
|
||||
deliveryPhone: '',
|
||||
deliveryMan: ''
|
||||
}
|
||||
trackingRecords.value = []
|
||||
/* packageInfo.value = {}
|
||||
currentStatus.value = 'pickup' */
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.logistics-dialog {
|
||||
padding: 8px 0;
|
||||
|
||||
.status-timeline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 16px;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
// content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
height: 2px;
|
||||
background: #ebeef5;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
.timeline-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: #f5f7fa;
|
||||
border: 2px solid #dcdfe6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.timeline-label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.timeline-icon {
|
||||
background: #e6f4ff;
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.timeline-label {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.timeline-icon {
|
||||
background: #f0f9ff;
|
||||
border-color: #67c23a;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.timeline-label {
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: -50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #ebeef5;
|
||||
z-index: -1;
|
||||
|
||||
.timeline-item.active ~ &,
|
||||
.timeline-item.completed ~ & {
|
||||
background: #67c23a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tracking-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 24px 0 16px;
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
|
||||
// 自定义滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f5f7fa;
|
||||
border-radius: 3px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c0c4cc;
|
||||
border-radius: 3px;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
// Firefox 滚动条样式
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #c0c4cc #f5f7fa;
|
||||
|
||||
.tracking-item {
|
||||
position: relative;
|
||||
padding-left: 150px;
|
||||
padding-bottom: 24px;
|
||||
min-height: 60px;
|
||||
|
||||
&.is-first {
|
||||
.tracking-time {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tracking-status {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.tracking-time {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.tracking-content {
|
||||
.tracking-status {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tracking-message {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.tracking-line {
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 20px;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: #ebeef5;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.tracking-line {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 130px;
|
||||
top: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #dcdfe6;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
|
||||
&.is-first::before {
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-tracking {
|
||||
padding: 40px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 16px 0 16px 40px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
margin-top: 16px;
|
||||
|
||||
.logistics-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
gap: 4px;
|
||||
|
||||
.info-label {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #303133;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.package-icon-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 60px;
|
||||
|
||||
.package-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.package-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -221,8 +221,8 @@ const handleSubmit = async () => {
|
||||
}
|
||||
await api.order.packOrder.post!(submitData)
|
||||
ElMessage.success('打包成功')
|
||||
tableData.value = tableData.value.filter((item) => !selectedRows.value.includes(item))
|
||||
handleClose(false)
|
||||
selectedRows.value = []
|
||||
} catch (error) {
|
||||
ElMessage.error('打包失败')
|
||||
} finally {
|
||||
|
||||
614
src/views/order/user-reviews/index.vue
Normal file
614
src/views/order/user-reviews/index.vue
Normal file
@ -0,0 +1,614 @@
|
||||
<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>
|
||||
Loading…
x
Reference in New Issue
Block a user