CI/CD Pipeline / UI Contracts (source-structure gates) (push) Canceled after 0s
CI/CD Pipeline / E2E (live stack) (push) Canceled after 0s
CI/CD Pipeline / Backend Build & Test (push) Canceled after 0s
CI/CD Pipeline / Admin Web Build (push) Canceled after 0s
CI/CD Pipeline / Mini Program Compile Check (push) Canceled after 0s
CI/CD Pipeline / Migration Validation (push) Canceled after 0s
CI/CD Pipeline / Test Shell Guard (Phase 23 Gate) (push) Canceled after 0s
529 lines
28 KiB
Vue
529 lines
28 KiB
Vue
<template>
|
||
<div class="matchmaker-management">
|
||
<el-card>
|
||
<template #header><div class="card-header"><span>公益红娘管理</span></div></template>
|
||
<el-tabs v-model="activeTab" @tab-change="refresh">
|
||
<el-tab-pane v-if="canAssign" label="服务进展与跟进" name="management">
|
||
<el-table :data="managedServices" stripe v-loading="loading">
|
||
<el-table-column prop="userDisplayNumber" label="用户编号" min-width="150" />
|
||
<el-table-column prop="operatorDisplayId" label="红娘编号" min-width="150" />
|
||
<el-table-column label="服务状态" width="120"><template #default="{ row }">{{ statusLabel(row.status) }}</template></el-table-column>
|
||
<el-table-column prop="latestFollowupContent" label="最新跟进" min-width="230" show-overflow-tooltip />
|
||
<el-table-column label="操作" width="110"><template #default="{ row }">
|
||
<el-button link type="primary" @click="openServiceDetail(row.serviceId, true)">查看记录</el-button>
|
||
</template></el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<!-- ZC-MM-002:服务请求队列仅分配权持有者可见(matchmaker.assign 或 matchmaker.team.assign) -->
|
||
<el-tab-pane v-if="canAssign" label="服务请求队列" name="services">
|
||
<el-table :data="services" stripe v-loading="loading">
|
||
<el-table-column prop="userDisplayNumber" label="用户编号" width="190">
|
||
<template #default="{ row }">{{ row.userDisplayNumber || '实名后生成' }}</template>
|
||
</el-table-column>
|
||
<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==='in_service'" size="small" @click="openRecommendation(row)">创建推荐</el-button>
|
||
<el-button v-if="row.status!=='submitted'" size="small" @click="openServiceDetail(row.serviceId || row.id)">查看服务</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="userDisplayNumber" label="用户" width="110" />
|
||
<el-table-column prop="targetUserDisplayNumber" 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>
|
||
|
||
<!-- ZC-MM-002 新增:服务对象分配(本人名下生效中的分配 + 转分配/撤销/结束) -->
|
||
<el-tab-pane label="服务对象分配" name="assignments">
|
||
<el-table :data="assignments" stripe v-loading="assignmentsLoading">
|
||
<el-table-column prop="userDisplayNumber" label="用户编号" width="190">
|
||
<template #default="{ row }">{{ row.userDisplayNumber || '实名后生成' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="assignmentScope" label="范围" width="90">
|
||
<template #default="{ row }">{{ assignmentScopeLabel(row.assignmentScope) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="assignmentReason" label="分配理由" show-overflow-tooltip />
|
||
<el-table-column prop="status" label="状态" width="90">
|
||
<template #default="{ row }"><el-tag :type="assignmentStatusTag(row.status)">{{ assignmentStatusLabel(row.status) }}</el-tag></template>
|
||
</el-table-column>
|
||
<el-table-column prop="assignedAt" label="分配时间" width="170">
|
||
<template #default="{ row }">{{ formatDateTime(row.assignedAt) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="expiresAt" label="有效期至" width="170">
|
||
<template #default="{ row }">{{ row.expiresAt ? formatDateTime(row.expiresAt) : '长期' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220">
|
||
<template #default="{ row }">
|
||
<el-button v-if="row.serviceId" size="small" @click="openServiceDetail(row.serviceId)">查看服务</el-button>
|
||
<el-button size="small" type="primary" @click="openTransfer(row)">转分配</el-button>
|
||
<el-button size="small" @click="openTerminate(row, 'revoke')">撤销</el-button>
|
||
<el-button size="small" @click="openTerminate(row, 'end')">结束</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
<!-- ZC-MM-003 / B1 立项:受控联系授权(审批岗队列 + 本人授权/撤销) -->
|
||
<el-tab-pane label="联系授权审批" name="contactAuths">
|
||
<el-table :data="contactAuths" stripe v-loading="contactAuthsLoading">
|
||
<el-table-column prop="operatorDisplayId" label="红娘编号" width="120" />
|
||
<el-table-column prop="userDisplayNumber" label="用户编号" width="120" />
|
||
<el-table-column prop="channel" label="渠道" width="110">
|
||
<template #default="{ row }">{{ contactChannelLabel(row.channel) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="purpose" label="授权目的" show-overflow-tooltip />
|
||
<el-table-column prop="status" label="状态" width="110">
|
||
<template #default="{ row }"><el-tag :type="contactStatusTag(row.status)">{{ contactStatusLabel(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 prop="expiresAt" label="窗口/有效期至" width="170">
|
||
<template #default="{ row }">{{ formatDateTime(row.expiresAt) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220">
|
||
<template #default="{ row }">
|
||
<template v-if="row.status === 'pending_approval' && canApprove">
|
||
<el-button size="small" type="primary" @click="doContactApprove(row)">批准</el-button>
|
||
<el-button size="small" type="danger" @click="doContactReject(row)">拒绝</el-button>
|
||
</template>
|
||
<el-button v-if="['active','pending_approval','pending_consent'].includes(row.status)"
|
||
size="small" @click="doContactRevoke(row)">撤销</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</el-card>
|
||
|
||
<!-- 分配弹窗(ZC-MM-002:去硬编码红娘名单,改为加载可分配红娘接口;理由必填) -->
|
||
<el-dialog v-model="assignDialogVisible" title="分配红娘服务" width="440px">
|
||
<el-form label-width="90px">
|
||
<el-form-item label="红娘">
|
||
<el-select v-model="assignForm.matchmakerOperatorId" placeholder="选择红娘" style="width: 100%">
|
||
<el-option v-for="op in assignableMatchmakers" :key="op.id" :label="op.name" :value="op.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="分配理由" required>
|
||
<el-input v-model="assignForm.reason" type="textarea" :rows="2" placeholder="说明分配依据(必填)" />
|
||
</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>
|
||
|
||
<!-- 转分配弹窗(ZC-MM-002:服务对象分配) -->
|
||
<el-dialog v-model="transferDialogVisible" title="转分配服务对象" width="440px">
|
||
<el-form label-width="90px">
|
||
<el-form-item label="新红娘">
|
||
<el-select v-model="transferForm.newMatchmakerOperatorId" placeholder="选择接手红娘" style="width: 100%">
|
||
<el-option v-for="op in assignableMatchmakers" :key="op.id" :label="op.name" :value="op.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="转分配理由" required>
|
||
<el-input v-model="transferForm.reason" type="textarea" :rows="2" placeholder="说明转分配依据(必填)" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="transferDialogVisible=false">取消</el-button>
|
||
<el-button type="primary" @click="doTransfer">确认转分配</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 撤销/结束弹窗(ZC-MM-002:服务对象分配) -->
|
||
<el-dialog v-model="terminateDialogVisible" :title="terminateAction==='revoke' ? '撤销服务对象分配' : '结束服务对象分配'" width="440px">
|
||
<el-form label-width="90px">
|
||
<el-form-item :label="terminateAction==='revoke' ? '撤销理由' : '结束理由'" required>
|
||
<el-input v-model="terminateForm.reason" type="textarea" :rows="2" placeholder="说明理由(必填)" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="terminateDialogVisible=false">取消</el-button>
|
||
<el-button type="danger" @click="doTerminate">确认</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.userDisplayNumber" disabled /></el-form-item>
|
||
<el-form-item label="推荐对象编号"><el-input v-model="recForm.targetUserDisplayNumber" placeholder="输入13位用户编号" /></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>
|
||
|
||
<el-dialog v-model="serviceDetailVisible" title="服务详情" width="620px">
|
||
<el-descriptions v-if="serviceDetail" :column="2" border>
|
||
<el-descriptions-item label="服务编号">{{ serviceDetail.serviceDisplayId }}</el-descriptions-item>
|
||
<el-descriptions-item label="状态">{{ statusLabel(serviceDetail.status) }}</el-descriptions-item>
|
||
<el-descriptions-item label="服务类型">{{ serviceTypeLabel(serviceDetail.serviceType) }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ formatDateTime(serviceDetail.createdAt) }}</el-descriptions-item>
|
||
<el-descriptions-item label="服务需求" :span="2">{{ serviceDetail.description || '未填写' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
<el-divider content-position="left">服务历史</el-divider>
|
||
<el-empty v-if="!serviceDetail?.followups?.length" description="暂无服务记录" />
|
||
<el-timeline v-else>
|
||
<el-timeline-item v-for="item in serviceDetail.followups" :key="item.id" :timestamp="formatDateTime(item.createdAt)">
|
||
{{ item.content }}
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
<template v-if="serviceDetail && !managementDetail">
|
||
<el-divider />
|
||
<el-button type="primary" @click="openFollowup">添加跟进记录</el-button>
|
||
<el-button
|
||
v-if="['completed', 'cancelled'].indexOf(serviceDetail.status) < 0"
|
||
type="success"
|
||
plain
|
||
@click="completeCurrentService"
|
||
>完成服务</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="followupDialogVisible" title="添加跟进记录" width="520px">
|
||
<el-form label-width="90px">
|
||
<el-form-item label="跟进方式">
|
||
<el-select v-model="followupForm.followupType" style="width: 100%">
|
||
<el-option label="电话沟通" value="phone_call" />
|
||
<el-option label="微信沟通" value="wechat" />
|
||
<el-option label="面对面" value="face_to_face" />
|
||
<el-option label="其他" value="other" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="跟进内容" required>
|
||
<el-input v-model="followupForm.content" type="textarea" :rows="4" placeholder="记录本次服务进展" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="followupDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitFollowup">保存记录</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onActivated } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { get, post } from '@/api/client'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { useAuthStore } from '@/stores/auth'
|
||
|
||
const authStore = useAuthStore()
|
||
const route = useRoute()
|
||
const managementDetail = ref(false)
|
||
const managedServices = ref<any[]>([])
|
||
// ZC-MM-002:分配权 = matchmaker.assign(平台授权)或 matchmaker.team.assign(红娘组长)
|
||
const canAssign = computed(() => {
|
||
const perms: string[] = authStore.permissions || []
|
||
return perms.includes('matchmaker.assign') || perms.includes('matchmaker.team.assign')
|
||
})
|
||
|
||
const activeTab = ref(canAssign.value ? 'services' : 'assignments')
|
||
const loading = ref(false)
|
||
const services = ref<any[]>([])
|
||
const recommendations = ref<any[]>([])
|
||
const assignments = ref<any[]>([])
|
||
const assignmentsLoading = ref(false)
|
||
const assignableMatchmakers = ref<any[]>([])
|
||
const assignDialogVisible = ref(false)
|
||
const transferDialogVisible = ref(false)
|
||
const terminateDialogVisible = ref(false)
|
||
const recDialogVisible = ref(false)
|
||
const currentServiceId = ref('')
|
||
const currentUserId = ref('')
|
||
const currentAssignmentId = ref('')
|
||
const terminateAction = ref<'revoke' | 'end'>('revoke')
|
||
const assignForm = ref({ matchmakerOperatorId: '', reason: '' })
|
||
const transferForm = ref({ newMatchmakerOperatorId: '', reason: '' })
|
||
const terminateForm = ref({ reason: '' })
|
||
const recForm = ref({ userDisplayNumber: '', targetUserDisplayNumber: '', reason: '' })
|
||
const serviceDetailVisible = ref(false)
|
||
const serviceDetail = ref<any>(null)
|
||
const followupDialogVisible = ref(false)
|
||
const followupForm = ref({ followupType: 'phone_call', content: '' })
|
||
|
||
const SERVICE_TYPE_LABEL: Record<string, string> = {
|
||
recommendation: '推荐对象', referral_counseling: '转介咨询',
|
||
referral_activity: '转介活动', referral_station: '转介服务站'
|
||
}
|
||
const STATUS_LABEL: Record<string, string> = {
|
||
submitted: '待分配', assigned: '已分配', in_service: '服务中', waiting_user: '等待用户',
|
||
processing: '处理中', completed: '已完成', cancelled: '已取消'
|
||
}
|
||
const STATUS_TAG: Record<string, string> = {
|
||
submitted: 'warning', assigned: 'primary', in_service: 'primary', waiting_user: 'warning',
|
||
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'
|
||
}
|
||
const ASSIGNMENT_STATUS_LABEL: Record<string, string> = {
|
||
active: '生效中', ended: '已结束', revoked: '已撤销', transferred: '已转分配', expired: '已过期'
|
||
}
|
||
const ASSIGNMENT_STATUS_TAG: Record<string, string> = {
|
||
active: 'success', ended: 'info', revoked: 'danger', transferred: 'info', expired: 'info'
|
||
}
|
||
|
||
// ===== ZC-MM-003 / B1:受控联系授权(枚举码不上屏,中文映射) =====
|
||
const CONTACT_CHANNEL_LABEL: Record<string, string> = {
|
||
phone: '电话联系', offline_meeting: '线下见面', full_contact: '完整联系方式'
|
||
}
|
||
const CONTACT_STATUS_LABEL: Record<string, string> = {
|
||
pending_approval: '待审批', pending_consent: '待用户确认', active: '生效中',
|
||
rejected: '已拒绝', expired: '已过期', revoked: '已撤销'
|
||
}
|
||
const CONTACT_STATUS_TAG: Record<string, string> = {
|
||
pending_approval: 'warning', pending_consent: 'warning', active: 'success',
|
||
rejected: 'danger', expired: 'info', revoked: '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 assignmentStatusLabel(s: string) { return ASSIGNMENT_STATUS_LABEL[s] || s }
|
||
function assignmentStatusTag(s: string) { return ASSIGNMENT_STATUS_TAG[s] || 'info' }
|
||
function assignmentScopeLabel(s: string) { return s === 'primary' ? '主责' : '协同' }
|
||
function formatDateTime(d: string) { return d ? new Date(d).toLocaleString('zh-CN') : '-' }
|
||
function contactChannelLabel(s: string) { return CONTACT_CHANNEL_LABEL[s] || s }
|
||
function contactStatusLabel(s: string) { return CONTACT_STATUS_LABEL[s] || s }
|
||
function contactStatusTag(s: string) { return CONTACT_STATUS_TAG[s] || 'info' }
|
||
|
||
async function openServiceDetail(serviceId: string, management = false) {
|
||
managementDetail.value = management
|
||
if (!serviceId) return
|
||
try {
|
||
const res: any = await get(`/operator/matchmaker/${management ? 'management/' : ''}services/${serviceId}`)
|
||
serviceDetail.value = res?.data || null
|
||
serviceDetailVisible.value = true
|
||
} catch { serviceDetail.value = null }
|
||
}
|
||
|
||
function openFollowup() {
|
||
followupForm.value = { followupType: 'phone_call', content: '' }
|
||
followupDialogVisible.value = true
|
||
}
|
||
|
||
async function submitFollowup() {
|
||
if (!followupForm.value.content.trim() || !serviceDetail.value?.serviceId) {
|
||
ElMessage.warning('请填写跟进内容')
|
||
return
|
||
}
|
||
try {
|
||
await post(`/operator/matchmaker/services/${serviceDetail.value.serviceId}/followup`, followupForm.value)
|
||
ElMessage.success('跟进记录已保存')
|
||
followupDialogVisible.value = false
|
||
await openServiceDetail(serviceDetail.value.serviceId)
|
||
} catch (e: any) {
|
||
ElMessage.error(e?.response?.data?.message || '保存跟进记录失败')
|
||
}
|
||
}
|
||
|
||
async function completeCurrentService() {
|
||
if (!serviceDetail.value?.serviceId) return
|
||
try {
|
||
await ElMessageBox.confirm('确认将该红娘服务标记为已完成吗?', '完成服务', { type: 'warning' })
|
||
await post(`/operator/matchmaker/services/${serviceDetail.value.serviceId}/complete`, {})
|
||
ElMessage.success('服务已完成')
|
||
await openServiceDetail(serviceDetail.value.serviceId)
|
||
await loadAssignments()
|
||
} catch (e: any) {
|
||
if (e !== 'cancel' && e !== 'close') ElMessage.error(e?.response?.data?.message || '完成服务失败')
|
||
}
|
||
}
|
||
|
||
// ===== ZC-MM-003 / B1:受控联系授权 =====
|
||
// 审批岗客户端可见性提示(真实判定在 Service 层动态执行:principal 权限 ∈ approver-authorities 配置)
|
||
const canApprove = computed(() => {
|
||
const perms: string[] = authStore.permissions || []
|
||
return perms.includes('matchmaker.assign') || perms.includes('matchmaker.team.assign')
|
||
})
|
||
const contactAuths = ref<any[]>([])
|
||
const contactAuthsLoading = ref(false)
|
||
|
||
async function loadContactAuths() {
|
||
contactAuthsLoading.value = true
|
||
try {
|
||
const endpoint = canApprove.value
|
||
? '/operator/matchmaker/contact-authorizations/pending'
|
||
: '/operator/matchmaker/contact-authorizations/my'
|
||
const res: any = await get(endpoint)
|
||
contactAuths.value = res?.data?.list || []
|
||
} finally { contactAuthsLoading.value = false }
|
||
}
|
||
async function doContactApprove(row: any) {
|
||
try {
|
||
await post(`/operator/matchmaker/contact-authorizations/${row.id}/approve`, {})
|
||
ElMessage.success('已批准,等待用户单独确认'); loadContactAuths()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
async function doContactReject(row: any) {
|
||
try {
|
||
await post(`/operator/matchmaker/contact-authorizations/${row.id}/reject`, {})
|
||
ElMessage.success('已拒绝'); loadContactAuths()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
async function doContactRevoke(row: any) {
|
||
try {
|
||
const { value } = await ElMessageBox.prompt('撤销理由(必填)', '撤销联系授权', {
|
||
confirmButtonText: '确认撤销', cancelButtonText: '取消',
|
||
inputValidator: (v: string) => (v && v.trim() ? true : '撤销理由必填'),
|
||
})
|
||
await post(`/operator/matchmaker/contact-authorizations/${row.id}/revoke`, { reason: value.trim() })
|
||
ElMessage.success('已撤销'); loadContactAuths()
|
||
} catch { /* 用户取消 */ }
|
||
}
|
||
|
||
async function loadServices() {
|
||
loading.value = true
|
||
// Phase 13 契约修复:原 `/matchmaker/services/operator/queue` 后端不存在(404)。
|
||
// 后端实际为 GET /operator/matchmaker/pending(待分配队列,ZC-MM-002 后仅分配权持有者可调)。
|
||
try { const res: any = await get('/operator/matchmaker/pending'); services.value = res?.data || [] } finally { loading.value = false }
|
||
}
|
||
async function loadRecommendations() {
|
||
loading.value = true
|
||
// Phase 13 契约修复:原 `/matchmaker/recommendations/operator` 不存在(404)。
|
||
// 后端新增 GET /operator/matchmaker/recommendations(限本人名下服务的推荐)。
|
||
try { const res: any = await get('/operator/matchmaker/recommendations'); recommendations.value = res?.data || [] } finally { loading.value = false }
|
||
}
|
||
async function loadAssignments() {
|
||
assignmentsLoading.value = true
|
||
// ZC-MM-002:本人名下生效中的服务对象分配
|
||
try { const res: any = await get('/operator/matchmaker/assignments/my'); assignments.value = res?.data || [] } finally { assignmentsLoading.value = false }
|
||
}
|
||
async function loadAssignableMatchmakers() {
|
||
// ZC-MM-002:去硬编码红娘名单,改为后端可分配红娘接口(组长按组织过滤、平台授权人员全部)
|
||
try { const res: any = await get('/operator/matchmaker/assignments/assignable-matchmakers'); assignableMatchmakers.value = res?.data || [] } catch { assignableMatchmakers.value = [] }
|
||
}
|
||
async function refresh() {
|
||
if (activeTab.value === 'management') {
|
||
loading.value = true
|
||
try { const res: any = await get('/operator/matchmaker/management/services'); managedServices.value = res?.data || [] }
|
||
finally { loading.value = false }
|
||
}
|
||
else if (activeTab.value === 'services') await loadServices()
|
||
else if (activeTab.value === 'assignments') await loadAssignments()
|
||
else if (activeTab.value === 'contactAuths') await loadContactAuths()
|
||
else await loadRecommendations()
|
||
}
|
||
|
||
async function openAssign(row: any) {
|
||
// /pending 返回字段为 serviceId(Phase 13 契约),兜底 row.id
|
||
currentServiceId.value = row.serviceId || row.id
|
||
currentUserId.value = row.userId
|
||
assignForm.value = { matchmakerOperatorId: '', reason: '' }
|
||
await loadAssignableMatchmakers()
|
||
assignDialogVisible.value = true
|
||
}
|
||
async function doAssign() {
|
||
if (!assignForm.value.matchmakerOperatorId) { ElMessage.warning('请选择红娘'); return }
|
||
if (!assignForm.value.reason || !assignForm.value.reason.trim()) { ElMessage.warning('分配理由必填'); return }
|
||
// ZC-MM-002:分配走统一分配接口(创建 assignment + 服务单联动置 assigned,同事务)
|
||
try {
|
||
await post('/operator/matchmaker/assignments', {
|
||
userId: currentUserId.value,
|
||
matchmakerOperatorId: assignForm.value.matchmakerOperatorId,
|
||
serviceId: currentServiceId.value,
|
||
reason: assignForm.value.reason.trim()
|
||
})
|
||
ElMessage.success('分配成功'); assignDialogVisible.value = false; loadServices()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
|
||
async function openTransfer(row: any) {
|
||
currentAssignmentId.value = row.id
|
||
transferForm.value = { newMatchmakerOperatorId: '', reason: '' }
|
||
await loadAssignableMatchmakers()
|
||
transferDialogVisible.value = true
|
||
}
|
||
async function doTransfer() {
|
||
if (!transferForm.value.newMatchmakerOperatorId) { ElMessage.warning('请选择接手红娘'); return }
|
||
if (!transferForm.value.reason || !transferForm.value.reason.trim()) { ElMessage.warning('转分配理由必填'); return }
|
||
try {
|
||
await post(`/operator/matchmaker/assignments/${currentAssignmentId.value}/transfer`, {
|
||
newMatchmakerOperatorId: transferForm.value.newMatchmakerOperatorId,
|
||
reason: transferForm.value.reason.trim()
|
||
})
|
||
ElMessage.success('转分配成功'); transferDialogVisible.value = false; loadAssignments()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
|
||
function openTerminate(row: any, action: 'revoke' | 'end') {
|
||
currentAssignmentId.value = row.id
|
||
terminateAction.value = action
|
||
terminateForm.value = { reason: '' }
|
||
terminateDialogVisible.value = true
|
||
}
|
||
async function doTerminate() {
|
||
if (!terminateForm.value.reason || !terminateForm.value.reason.trim()) {
|
||
ElMessage.warning(terminateAction.value === 'revoke' ? '撤销理由必填' : '结束理由必填'); return
|
||
}
|
||
try {
|
||
await post(`/operator/matchmaker/assignments/${currentAssignmentId.value}/${terminateAction.value}`, {
|
||
reason: terminateForm.value.reason.trim()
|
||
})
|
||
ElMessage.success('操作成功'); terminateDialogVisible.value = false; loadAssignments()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
|
||
function openRecommendation(row: any) {
|
||
currentServiceId.value = row.id
|
||
recForm.value = { userDisplayNumber: row.userDisplayNumber || '实名后生成', targetUserDisplayNumber: '', reason: '' }
|
||
recDialogVisible.value = true
|
||
}
|
||
async function doCreateRecommendation() {
|
||
if (!recForm.value.targetUserDisplayNumber || !recForm.value.reason) { ElMessage.warning('请填写推荐对象和理由'); return }
|
||
// Phase 13 契约修复:原 POST /matchmaker/services/{id}/recommendation → 后端为 /operator/matchmaker/services/{id}/recommend
|
||
try {
|
||
await post(`/operator/matchmaker/services/${currentServiceId.value}/recommend`, {
|
||
targetUserId: recForm.value.targetUserDisplayNumber, reason: recForm.value.reason
|
||
})
|
||
ElMessage.success('推荐已发送给用户'); recDialogVisible.value = false; loadRecommendations()
|
||
} catch (e: any) { ElMessage.error(e?.response?.data?.message || '操作失败') }
|
||
}
|
||
|
||
onActivated(async () => {
|
||
if (canAssign.value && typeof route.query.serviceId === 'string') {
|
||
activeTab.value = 'management'
|
||
await openServiceDetail(route.query.serviceId, true)
|
||
}
|
||
await refresh()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.card-header { display: flex; align-items: center; justify-content: space-between; }
|
||
</style>
|