feat(admin-web): matchmaker management view — D3 debt cleared
New module admin-web/src/modules/matchmaker/MatchmakerManagementView.vue (2 tabs): - Service request queue: assign operator / create recommendation - Recommendations list: pending_view / viewed / liked / passed / confirmed_mutual Route added: /matchmaker -> MatchmakerManagementView.vue (permissions: matchmaker.service.view) D3 debt cleared: admin-web matchmaker recommendation now has a real view. vue-tsc --noEmit -> 0 errors.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="matchmaker-management">
|
||||
<el-card>
|
||||
<template #header><div class="card-header"><span>公益红娘管理</span></div></template>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="服务请求队列" name="services">
|
||||
<el-table :data="services" stripe v-loading="loading">
|
||||
<el-table-column prop="userId" label="用户ID" width="120" />
|
||||
<el-table-column prop="serviceType" label="服务类型" width="160">
|
||||
<template #default="{ row }">{{ serviceTypeLabel(row.serviceType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="需求说明" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="110">
|
||||
<template #default="{ row }"><el-tag :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="提交时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status==='submitted'" size="small" type="primary" @click="openAssign(row)">分配</el-button>
|
||||
<el-button v-if="row.status==='assigned' || row.status==='processing'" size="small" @click="openRecommendation(row)">创建推荐</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="推荐列表" name="recommendations">
|
||||
<el-table :data="recommendations" stripe v-loading="loading">
|
||||
<el-table-column prop="userId" label="用户" width="110" />
|
||||
<el-table-column prop="targetUserId" label="推荐对象" width="110" />
|
||||
<el-table-column prop="reason" label="推荐理由" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="130">
|
||||
<template #default="{ row }"><el-tag :type="recStatusTag(row.status)">{{ recStatusLabel(row.status) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 分配弹窗 -->
|
||||
<el-dialog v-model="assignDialogVisible" title="分配红娘服务" width="400px">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="红娘"><el-select v-model="assignForm.operatorId" placeholder="选择红娘">
|
||||
<el-option label="王红娘" value="operator_wang" />
|
||||
<el-option label="陈红娘" value="operator_chen" />
|
||||
</el-select></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="assignDialogVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doAssign">确认分配</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 创建推荐弹窗 -->
|
||||
<el-dialog v-model="recDialogVisible" title="创建红娘推荐" width="480px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="推荐给用户"><el-input v-model="recForm.userId" disabled /></el-form-item>
|
||||
<el-form-item label="推荐对象ID"><el-input v-model="recForm.targetUserId" placeholder="输入被推荐对象的用户ID" /></el-form-item>
|
||||
<el-form-item label="推荐理由"><el-input v-model="recForm.reason" type="textarea" :rows="3" placeholder="说明为什么这个对象适合该用户" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="recDialogVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="doCreateRecommendation">发送推荐</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { get, post, put } from '@/api/client'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const activeTab = ref('services')
|
||||
const loading = ref(false)
|
||||
const services = ref<any[]>([])
|
||||
const recommendations = ref<any[]>([])
|
||||
const assignDialogVisible = ref(false)
|
||||
const recDialogVisible = ref(false)
|
||||
const currentServiceId = ref('')
|
||||
const assignForm = ref({ operatorId: '' })
|
||||
const recForm = ref({ userId: '', targetUserId: '', reason: '' })
|
||||
|
||||
const SERVICE_TYPE_LABEL: Record<string, string> = {
|
||||
recommendation: '推荐对象', referral_counseling: '转介咨询',
|
||||
referral_activity: '转介活动', referral_station: '转介服务站'
|
||||
}
|
||||
const STATUS_LABEL: Record<string, string> = {
|
||||
submitted: '待分配', assigned: '已分配', processing: '处理中', completed: '已完成', cancelled: '已取消'
|
||||
}
|
||||
const STATUS_TAG: Record<string, string> = {
|
||||
submitted: 'warning', assigned: 'primary', processing: '', completed: 'success', cancelled: 'danger'
|
||||
}
|
||||
const REC_STATUS_LABEL: Record<string, string> = {
|
||||
pending_view: '待用户查看', viewed: '已查看', liked: '已喜欢',
|
||||
passed: '已跳过', confirmed_mutual: '双向确认', rejected: '已拒绝', expired: '已过期'
|
||||
}
|
||||
const REC_STATUS_TAG: Record<string, string> = {
|
||||
pending_view: 'warning', viewed: 'info', liked: 'primary',
|
||||
passed: 'info', confirmed_mutual: 'success', rejected: 'danger', expired: 'info'
|
||||
}
|
||||
|
||||
function serviceTypeLabel(t: string) { return SERVICE_TYPE_LABEL[t] || t }
|
||||
function statusLabel(s: string) { return STATUS_LABEL[s] || s }
|
||||
function statusTag(s: string) { return STATUS_TAG[s] || 'info' }
|
||||
function recStatusLabel(s: string) { return REC_STATUS_LABEL[s] || s }
|
||||
function recStatusTag(s: string) { return REC_STATUS_TAG[s] || 'info' }
|
||||
function formatDateTime(d: string) { return d ? new Date(d).toLocaleString('zh-CN') : '-' }
|
||||
|
||||
async function loadServices() {
|
||||
loading.value = true
|
||||
try { const res: any = await get('/matchmaker/services/operator/queue'); services.value = res?.data || [] } finally { loading.value = false }
|
||||
}
|
||||
async function loadRecommendations() {
|
||||
loading.value = true
|
||||
try { const res: any = await get('/matchmaker/recommendations/operator'); recommendations.value = res?.data || [] } finally { loading.value = false }
|
||||
}
|
||||
async function refresh() {
|
||||
if (activeTab.value === 'services') await loadServices(); else await loadRecommendations()
|
||||
}
|
||||
|
||||
async function openAssign(row: any) { currentServiceId.value = row.id; assignForm.value.operatorId = ''; assignDialogVisible.value = true }
|
||||
async function doAssign() {
|
||||
try {
|
||||
await put(`/matchmaker/services/${currentServiceId.value}/assign`, { assignedOperatorId: assignForm.value.operatorId })
|
||||
ElMessage.success('分配成功'); assignDialogVisible.value = false; loadServices()
|
||||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||||
}
|
||||
|
||||
function openRecommendation(row: any) {
|
||||
currentServiceId.value = row.id
|
||||
recForm.value = { userId: row.userId, targetUserId: '', reason: '' }
|
||||
recDialogVisible.value = true
|
||||
}
|
||||
async function doCreateRecommendation() {
|
||||
if (!recForm.value.targetUserId || !recForm.value.reason) { ElMessage.warning('请填写推荐对象和理由'); return }
|
||||
try {
|
||||
await post(`/matchmaker/services/${currentServiceId.value}/recommendation`, {
|
||||
targetUserId: recForm.value.targetUserId, reason: recForm.value.reason
|
||||
})
|
||||
ElMessage.success('推荐已发送给用户'); recDialogVisible.value = false; loadRecommendations()
|
||||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||||
}
|
||||
|
||||
onMounted(refresh)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header { display: flex; align-items: center; justify-content: space-between; }
|
||||
</style>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
@@ -35,6 +35,8 @@ const routes: RouteRecordRaw[] = [
|
||||
{ path: 'service-program', name: 'ServiceProgram', component: () => import('@/modules/service-program/ServiceProgramView.vue'), meta: { title: '专项服务项目', permissions: ['service_program.manage'] } },
|
||||
{ path: 'counseling', name: 'Counseling', component: () => import('@/modules/counseling/CounselingView.vue'), meta: { title: '婚恋咨询', permissions: ['counseling.service.view'] } },
|
||||
{ path: 'referral', name: 'ReferralManagement', component: () => import('@/modules/referral/ReferralManagementView.vue'), meta: { title: '转介管理', permissions: ['referral.manage'] } },
|
||||
{ path: 'matchmaker', name: 'MatchmakerManagement', component: () => import('@/modules/matchmaker/MatchmakerManagementView.vue'), meta: { title: '红娘管理', permissions: ['matchmaker.service.view'] } },
|
||||
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user