fix: 完善全系统业务闭环(大神入驻联动用户角色、服务评价人工下架监管、友圈搭子上车审核与抽屉)

这个提交包含在:
xcbyy
2026-10-13 18:04:27 +08:00
父节点 c14a1a3474
当前提交 010edc130f
共有 9 个文件被更改,包括 520 次插入 和 12 次删除

查看文件

@@ -55,6 +55,7 @@ export default {
'menu.banner.navCategory': 'Category Navigation',
'menu.community': 'Community & Content',
'menu.community.postManage': 'Post & Comment Audit',
'menu.community.reviewManage': 'Review Management',
'menu.server.dashboard': 'Dashboard-Server',
'menu.server.workplace': 'Workplace-Server',
'menu.server.monitor': 'Monitor-Server',

查看文件

@@ -55,6 +55,7 @@ export default {
'menu.banner.navCategory': '金刚区导航品类',
'menu.community': '友圈与内容管理',
'menu.community.postManage': '动态与评论监管',
'menu.community.reviewManage': '服务评价与回复监管',
'menu.server.dashboard': '仪表盘-服务端',
'menu.server.workplace': '工作台-服务端',
'menu.server.monitor': '实时监控-服务端',

查看文件

@@ -22,6 +22,16 @@ const COMMUNITY: AppRouteRecordRaw = {
roles: ['*'],
},
},
{
path: 'review-manage',
name: 'ReviewManage',
component: () => import('@/views/community/review-manage/index.vue'),
meta: {
locale: 'menu.community.reviewManage',
requiresAuth: true,
roles: ['*'],
},
},
],
};

查看文件

@@ -153,7 +153,7 @@
<a-table-column title="发布时间" data-index="createdAt" :width="160" />
<a-table-column title="管理操作" :width="200" fixed="right">
<a-table-column title="管理操作" :width="280" fixed="right">
<template #cell="{ record }">
<a-space :size="8">
<!-- 置顶切换 -->
@@ -185,6 +185,16 @@
评论
</a-button>
<!-- 搭子上车申请 -->
<a-button
size="small"
type="text"
status="success"
@click="openAppliesDrawer(record)"
>
上车申请
</a-button>
<!-- 违规删除 -->
<a-popconfirm
content="确定彻底删除该条动态及其所有评论吗?此操作不可逆。"
@@ -246,6 +256,62 @@
该动态暂无任何评论互动
</div>
</a-drawer>
<!-- 搭子上车申请抽屉 (Drawer) -->
<a-drawer
v-model:visible="appliesDrawerVisible"
:width="540"
:title="`搭子上车申请 - ${currentPost ? currentPost.author : ''}`"
:footer="false"
>
<div v-if="loadingApplies" style="text-align: center; padding: 40px 0">
<a-spin />
</div>
<div v-else-if="appliesList.length > 0">
<a-list :bordered="false">
<a-list-item v-for="app in appliesList" :key="app.id">
<a-list-item-meta
:title="app.applicantName || app.applicant_name || '搭子玩家'"
:description="`申请时间: ${app.createdAt || app.created_at || '-'} | 用户ID: ${app.applicantId || app.applicant_id || '-'}`"
>
<template #avatar>
<a-avatar :size="36">
<img :src="app.applicantAvatar || app.applicant_avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120'" alt="avatar" />
</a-avatar>
</template>
</a-list-item-meta>
<template #actions>
<a-space>
<a-button
v-if="app.status === 'PENDING' || app.status === '待处理'"
size="mini"
type="primary"
status="success"
@click="handleApplyAction(app.id, 'ACCEPTED')"
>
同意上车
</a-button>
<a-button
v-if="app.status === 'PENDING' || app.status === '待处理'"
size="mini"
type="secondary"
status="danger"
@click="handleApplyAction(app.id, 'REJECTED')"
>
婉拒
</a-button>
<a-tag v-else :color="app.status === 'ACCEPTED' || app.status === '已同意' ? 'green' : 'gray'">
{{ app.status === 'ACCEPTED' ? '已同意' : (app.status === 'REJECTED' ? '已婉拒' : app.status) }}
</a-tag>
</a-space>
</template>
</a-list-item>
</a-list>
</div>
<div v-else style="text-align: center; padding: 60px 0; color: var(--color-text-3)">
该动态暂无任何搭子上车申请
</div>
</a-drawer>
</div>
</template>
@@ -266,6 +332,11 @@
const loadingComments = ref(false);
const commentsList = ref<any[]>([]);
// 搭子上车申请抽屉
const appliesDrawerVisible = ref(false);
const loadingApplies = ref(false);
const appliesList = ref<any[]>([]);
const fetchData = async () => {
loading.value = true;
try {
@@ -345,6 +416,32 @@
}
};
const openAppliesDrawer = async (record: any) => {
currentPost.value = record;
appliesDrawerVisible.value = true;
loadingApplies.value = true;
try {
const res: any = await axios.get(`/api/posts/${record.id}/applies`);
appliesList.value = res.data?.data || res.data || [];
} catch (err) {
appliesList.value = [];
} finally {
loadingApplies.value = false;
}
};
const handleApplyAction = async (applyId: string, status: string) => {
try {
const res: any = await axios.post(`/api/post-applies/${applyId}/handle`, { status });
Message.success(res.data?.message || (status === 'ACCEPTED' ? '已同意上车' : '已婉拒'));
if (currentPost.value) {
openAppliesDrawer(currentPost.value);
}
} catch (err: any) {
Message.error('处置上车申请失败');
}
};
onMounted(() => {
fetchData();
});

查看文件

@@ -0,0 +1,218 @@
<template>
<div class="container">
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item>内容与社区管理</a-breadcrumb-item>
<a-breadcrumb-item>服务评价与回复监管</a-breadcrumb-item>
</a-breadcrumb>
<a-card class="general-card" title="平台订单服务评价与大神回复人工监管中心">
<template #extra>
<a-space>
<a-button @click="fetchData">
<template #icon><icon-refresh /></template>
刷新评价
</a-button>
</a-space>
</template>
<a-alert type="info" style="margin-bottom: 16px">
评价合规底线:平台保护买家客观真实评价权益,同时严厉打击恶意差评敲诈、人身攻击辱骂、同行恶意抹黑或私下引流等违规言论;删除违规评价将自动重新核算大神的综合评分。
</a-alert>
<!-- 搜索筛选栏 -->
<a-row :gutter="16" style="margin-bottom: 16px">
<a-col :span="6">
<a-input
v-model="searchKeyword"
placeholder="搜索评价人、关联订单号或评价内容"
allow-clear
>
<template #prefix><icon-search /></template>
</a-input>
</a-col>
<a-col :span="5">
<a-select v-model="ratingFilter" placeholder="评分星级筛选" allow-clear>
<a-option :value="0">全部星级</a-option>
<a-option :value="5">5星好评</a-option>
<a-option :value="4">4星良好</a-option>
<a-option :value="3">3星中评</a-option>
<a-option :value="2">2星差评</a-option>
<a-option :value="1">1星极差</a-option>
</a-select>
</a-col>
</a-row>
<a-table
:data="filteredList"
:loading="loading"
:pagination="{ pageSize: 10, showTotal: true }"
row-key="id"
>
<template #columns>
<a-table-column title="评价工单ID" data-index="id" :width="140">
<template #cell="{ record }">
<span class="code-font">{{ record.id }}</span>
</template>
</a-table-column>
<a-table-column title="关联订单" data-index="orderId" :width="160">
<template #cell="{ record }">
<span class="code-font">{{ record.orderId }}</span>
</template>
</a-table-column>
<a-table-column title="评价用户" :width="160">
<template #cell="{ record }">
<a-space>
<a-avatar :size="32" shape="circle">
<img :src="record.userAvatar" alt="avatar" />
</a-avatar>
<div>
<div style="font-weight: 600">{{ record.userNickname }}</div>
<div style="font-size: 11px; color: var(--color-text-3)">UID: {{ record.userId }}</div>
</div>
</a-space>
</template>
</a-table-column>
<a-table-column title="被评大神ID" data-index="expertId" :width="120">
<template #cell="{ record }">
<span class="code-font">大神 #{{ record.expertId }}</span>
</template>
</a-table-column>
<a-table-column title="评分与印象标签" :width="200">
<template #cell="{ record }">
<div style="margin-bottom: 4px">
<a-rate :model-value="record.rating" readonly allow-half size="small" />
<span style="font-weight: 600; margin-left: 6px; color: #ff7d00">{{ record.rating }}分</span>
</div>
<a-space wrap :size="4">
<a-tag v-for="(tag, idx) in record.tags" :key="idx" size="small" color="arcoblue">
{{ tag }}
</a-tag>
</a-space>
</template>
</a-table-column>
<a-table-column title="买家评价陈述" :width="280">
<template #cell="{ record }">
<div class="content-box">{{ record.content || '无文字评价' }}</div>
</template>
</a-table-column>
<a-table-column title="大神回复内容" :width="220">
<template #cell="{ record }">
<div v-if="record.replyContent" class="reply-box">
<div style="font-weight: 600; font-size: 11px; color: #165dff">大神回复:</div>
<div>{{ record.replyContent }}</div>
</div>
<span v-else style="color: var(--color-text-4); font-size: 12px">暂未回复</span>
</template>
</a-table-column>
<a-table-column title="评价时间" data-index="createdAt" :width="160" />
<a-table-column title="操作" :width="120" fixed="right" align="center">
<template #cell="{ record }">
<a-popconfirm
content="确定下架并删除该条评价吗?删除后将自动重算大神信誉评级,此操作不可逆。"
type="warning"
@ok="handleDelete(record.id)"
>
<a-button type="text" status="danger" size="small">
违规删除
</a-button>
</a-popconfirm>
</template>
</a-table-column>
</template>
</a-table>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import axios from 'axios';
import { Message } from '@arco-design/web-vue';
const loading = ref(false);
const rawList = ref<any[]>([]);
const searchKeyword = ref('');
const ratingFilter = ref(0);
const fetchData = async () => {
loading.value = true;
try {
const res: any = await axios.get('/api/reviews');
if (res && res.data) {
rawList.value = Array.isArray(res.data) ? res.data : (res.data.data || []);
}
} catch (err: any) {
Message.error('获取评价列表失败');
} finally {
loading.value = false;
}
};
const filteredList = computed(() => {
return rawList.value.filter((item) => {
const matchRating = !ratingFilter.value || Math.floor(item.rating) === ratingFilter.value;
const kw = searchKeyword.value.trim().toLowerCase();
const matchKw =
!kw ||
String(item.id).toLowerCase().includes(kw) ||
String(item.orderId).toLowerCase().includes(kw) ||
String(item.userNickname || '').toLowerCase().includes(kw) ||
String(item.content || '').toLowerCase().includes(kw);
return matchRating && matchKw;
});
});
const handleDelete = async (id: string) => {
try {
const res: any = await axios.delete(`/api/reviews/${id}`);
Message.success(res.data?.message || '违规评价已下架删除');
fetchData();
} catch (err: any) {
Message.error(err.response?.data?.message || err.message || '删除失败');
}
};
onMounted(() => {
fetchData();
});
</script>
<style scoped lang="less">
.container {
padding: 16px 20px;
}
.breadcrumb {
margin-bottom: 16px;
}
.code-font {
font-family: monospace;
font-weight: 600;
}
.content-box {
font-size: 12px;
line-height: 1.5;
color: var(--color-text-1);
background: var(--color-fill-2);
padding: 6px 10px;
border-radius: 4px;
white-space: pre-wrap;
word-break: break-all;
}
.reply-box {
font-size: 12px;
line-height: 1.4;
color: var(--color-text-2);
background: var(--color-fill-3);
padding: 6px 8px;
border-radius: 4px;
border-left: 3px solid #165dff;
}
</style>