/** * 红娘工作台主页 * * ZC-MM-002(A1 / DEC-MATCHMAKER-002 锁定决策): * - 普通红娘禁止认领(原 POST /services/{id}/claim 端点已删除),本页不再有任何接单逻辑; * - 列表数据源从 GET /operator/matchmaker/pending(现仅分配权持有者可调,普通红娘 403) * 改为 GET /operator/matchmaker/assignments/my(仅返回本人生效中的服务对象分配)。 * 响应为 assignment 字段(userId/assignmentReason/status/assignedAt/serviceId 等), * 无服务类型/诉求描述字段,故按实际字段精简展示,不前端拼空值。 * * 展示待处理服务、工作台数据概览 */ const api = require('../../utils/api'); Page({ data: { operatorName: '', pendingCount: 0, inProgressCount: 0, todayCount: 0, completedCount: 0, myAssignments: [], assignmentCount: 0, loading: true, currentDate: '', // ZC-MM-003 / B1:受控联系授权(申请入口 + 本人授权列表/撤销;purpose 必填) contactTargets: [], contactTargetIndex: 0, contactChannelLabels: ['电话联系', '线下见面', '完整联系方式'], contactChannels: ['phone', 'offline_meeting', 'full_contact'], contactChannelIndex: 0, contactPurpose: '', contactAuths: [], contactAuthCount: 0, submittingAuth: false, referralTasks: [], recommendations: [], referralDialogVisible: false, referralTargetIndex: 0, referralTargetLabels: [], referralTypeIndex: 0, referralTypes: ['服务站', '公益活动'], referralTypeCodes: ['service_station', 'activity'], referralTargetId: '', referralNote: '', submittingReferral: false, }, onLoad() { wx.setNavigationBarTitle({ title: '红娘工作台' }); // 获取操作员信息 const operatorName = wx.getStorageSync('operatorName') || '红娘'; this.setData({ operatorName }); // 设置当前日期 const now = new Date(); const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`; this.setData({ currentDate: dateStr }); this.loadDashboard(); }, onShow() { // 每次显示刷新数据 this.loadDashboard(); }, async loadDashboard() { this.setData({ loading: true }); try { const token = wx.getStorageSync('operatorToken'); if (!token) { wx.redirectTo({ url: '/pages/matchmaker-login/matchmaker-login' }); return; } // 加载工作台数据 + 本人服务对象分配(ZC-MM-002:不再调用 /pending,普通红娘无该权限) const [dashRes, assignmentRes, referralRes, recommendationRes] = await Promise.all([ api.get('/operator/matchmaker/dashboard', {}, { Authorization: `Bearer ${token}` }), api.get('/operator/matchmaker/assignments/my', {}, { Authorization: `Bearer ${token}` }), api.get('/referrals/operator/history', {}, { Authorization: `Bearer ${token}` }), api.get('/operator/matchmaker/recommendations', {}, { Authorization: `Bearer ${token}` }), ]); if (dashRes) { this.setData({ pendingCount: dashRes.pendingCount || 0, inProgressCount: dashRes.inProgressCount || 0, todayCount: dashRes.todayCount || 0, completedCount: dashRes.completedCount || 0, }); } if (assignmentRes) { const assignments = assignmentRes || []; // 联系授权申请目标 = 本人生效中的分配对象(B1:只能对分配对象发起) const targets = assignments.map((a) => ({ userId: a.userId, label: (a.userDisplayNumber || '待实名编号') + '(' + (a.userDisplayName || '服务对象') + ')', })); this.setData({ myAssignments: assignments, assignmentCount: assignments.length, contactTargets: targets, contactTargetIndex: 0, }); this.loadContactAuthorizations(); } if (referralRes) { this.setData({ referralTasks: (referralRes || []).map((item) => ({ ...item, typeLabel: item.type === 'activity' ? '公益活动' : '服务站', targetLabel: item.type === 'activity' ? '公益活动转介' : this.serviceTypeLabel(item.serviceType), statusLabel: item.userConfirmedAt ? '用户已确认' : '待用户确认', timeText: this.formatTime(item.createdAt || item.registeredAt), })), }); } if (recommendationRes) { this.setData({ recommendations: (recommendationRes || []).map((item) => ({ ...item, statusLabel: this.recommendationStatusLabel(item.status), })) }); } } catch (err) { console.error('加载工作台数据失败:', err); } finally { this.setData({ loading: false }); } }, onViewService(e) { const serviceId = e.currentTarget.dataset.id; if (!serviceId) { const index = Number(e.currentTarget.dataset.index); const item = this.data.myAssignments[index] || {}; wx.showModal({ title: item.userDisplayNumber || '服务对象', content: `${item.userDisplayName || '未设置姓名'}${item.userAge ? `,${item.userAge}岁` : ''}${item.userRegionName ? `,${item.userRegionName}` : ''}\n当前分配未关联服务单,请联系组长补建服务记录。`, showCancel: false, }); return; } wx.navigateTo({ url: `/pages/matchmaker-service-detail/matchmaker-service-detail?id=${serviceId}` }); }, openReferral() { if (!this.data.myAssignments.length) { wx.showToast({ title: '暂无可转介的服务对象', icon: 'none' }); return; } this.setData({ referralDialogVisible: true, referralTargetIndex: 0, referralTargetLabels: this.data.myAssignments.map((item) => `${item.userDisplayNumber || '待实名编号'}(${item.userDisplayName || '服务对象'})`), referralTypeIndex: 0, referralTargetId: '', referralNote: '', }); }, closeReferral() { this.setData({ referralDialogVisible: false }); }, noop() {}, onReferralTargetChange(e) { this.setData({ referralTargetIndex: Number(e.detail.value) }); }, onReferralTypeChange(e) { this.setData({ referralTypeIndex: Number(e.detail.value) }); }, onReferralTargetIdInput(e) { this.setData({ referralTargetId: e.detail.value }); }, onReferralNoteInput(e) { this.setData({ referralNote: e.detail.value }); }, async submitReferral() { if (this.data.submittingReferral) return; const assignment = this.data.myAssignments[this.data.referralTargetIndex]; const targetId = (this.data.referralTargetId || '').trim(); if (!assignment || !assignment.userId) { wx.showToast({ title: '请选择服务对象', icon: 'none' }); return; } if (!targetId) { wx.showToast({ title: '请填写活动或服务站编号', icon: 'none' }); return; } this.setData({ submittingReferral: true }); try { const token = wx.getStorageSync('operatorToken'); const type = this.data.referralTypeCodes[this.data.referralTypeIndex]; const path = type === 'activity' ? '/referrals/activity' : '/referrals/service-station'; const payload = type === 'activity' ? { userId: assignment.userId, activityId: targetId } : { userId: assignment.userId, locationId: targetId, serviceType: 'counselor_consultation', note: this.data.referralNote.trim() }; await api.post(path, payload, { Authorization: `Bearer ${token}` }, api.idempotencyKey(`matchmaker-referral:${assignment.userId}:${targetId}`)); wx.showToast({ title: '转介已提交,等待用户确认', icon: 'none' }); this.setData({ referralDialogVisible: false }); await this.loadDashboard(); } catch (error) { wx.showToast({ title: (error && error.message) || '转介提交失败', icon: 'none' }); } finally { this.setData({ submittingReferral: false }); } }, // ==================== ZC-MM-003 / B1:受控联系授权 ==================== /** 本人授权列表(GET /operator/matchmaker/contact-authorizations/my) */ async loadContactAuthorizations() { try { const token = wx.getStorageSync('operatorToken'); const res = await api.get('/operator/matchmaker/contact-authorizations/my', {}, { Authorization: `Bearer ${token}` }); if (res) { const items = ((res.list || [])).map((item) => Object.assign({}, item, { channelLabel: this.contactChannelLabel(item.channel), statusLabel: this.contactStatusLabel(item.status), expireText: item.expiresAt ? this.formatTime(item.expiresAt) : '', revocable: item.status === 'active' || item.status === 'pending_approval' || item.status === 'pending_consent', })); this.setData({ contactAuths: items, contactAuthCount: items.length }); } } catch (err) { console.error('加载联系授权失败:', err); } }, contactChannelLabel(code) { const labels = { phone: '电话联系', offline_meeting: '线下见面', full_contact: '完整联系方式' }; return labels[code] || code || ''; }, contactStatusLabel(code) { const labels = { pending_approval: '待审批', pending_consent: '待用户确认', active: '生效中', rejected: '用户已拒绝', expired: '已过期', revoked: '已撤销', }; return labels[code] || code || ''; }, serviceTypeLabel(code) { const labels = { counselor_consultation: '婚恋咨询', premarital_counseling: '婚前辅导', legal_consultation: '法律咨询', }; return labels[code] || '服务站服务'; }, recommendationStatusLabel(code) { const labels = { pending_view: '待用户查看', viewed: '用户已查看', liked: '用户已喜欢', passed: '用户已跳过', confirmed_mutual: '已双向确认', expired: '已过期' }; return labels[code] || code || '未知状态'; }, onContactTargetChange(e) { this.setData({ contactTargetIndex: Number(e.detail.value) }); }, onContactChannelChange(e) { this.setData({ contactChannelIndex: Number(e.detail.value) }); }, onContactPurposeInput(e) { this.setData({ contactPurpose: e.detail.value }); }, /** 发起授权申请(purpose 必填;B1:用户随后单独明示同意,本页不产生任何用户授权) */ async submitContactRequest() { if (this.data.submittingAuth) return; const target = this.data.contactTargets[this.data.contactTargetIndex]; if (!target) { wx.showToast({ title: '暂无可申请的服务对象', icon: 'none' }); return; } const purpose = (this.data.contactPurpose || '').trim(); if (!purpose) { wx.showToast({ title: '授权目的必填', icon: 'none' }); return; } const channel = this.data.contactChannels[this.data.contactChannelIndex]; this.setData({ submittingAuth: true }); try { const token = wx.getStorageSync('operatorToken'); const res = await api.post('/operator/matchmaker/contact-authorizations', { userId: target.userId, channel: channel, purpose: purpose, }, { Authorization: `Bearer ${token}` }); if (res) { wx.showToast({ title: '申请已提交,等待审批', icon: 'none' }); this.setData({ contactPurpose: '' }); this.loadContactAuthorizations(); } } catch (err) { wx.showToast({ title: err.message || '申请失败', icon: 'none' }); } finally { this.setData({ submittingAuth: false }); } }, /** 撤销本人授权(reason 必填) */ revokeContactAuth(e) { const id = e.currentTarget.dataset.id; wx.showModal({ title: '撤销联系授权', editable: true, placeholderText: '请输入撤销理由(必填)', success: async (res) => { if (!res.confirm) return; const reason = (res.content || '').trim(); if (!reason) { wx.showToast({ title: '撤销理由必填', icon: 'none' }); return; } try { const token = wx.getStorageSync('operatorToken'); const resp = await api.post(`/operator/matchmaker/contact-authorizations/${id}/revoke`, { reason: reason }, { Authorization: `Bearer ${token}` }); if (resp) { wx.showToast({ title: '已撤销', icon: 'none' }); this.loadContactAuthorizations(); } } catch (err) { wx.showToast({ title: '撤销失败', icon: 'none' }); } }, }); }, // 分配状态 → 中文(避免枚举码直接上屏,见 profileLabels 铁律) assignmentStatusLabel(code) { const labels = { active: '生效中', ended: '已结束', revoked: '已撤销', transferred: '已转分配', expired: '已过期', }; return labels[code] || code || ''; }, // 分配范围 → 中文 assignmentScopeLabel(code) { const labels = { primary: '主责', collaborating: '协同' }; return labels[code] || code || ''; }, formatTime(timeStr) { if (!timeStr) return ''; const date = new Date(timeStr); const now = new Date(); const diff = now - date; const days = Math.floor(diff / 86400000); if (days === 0) return '今天'; if (days === 1) return '昨天'; if (days < 7) return days + '天前'; const m = date.getMonth() + 1; const d = date.getDate(); return m + '月' + d + '日'; }, onLogout() { wx.showModal({ title: '退出登录', content: '确定要退出登录吗?', success: (res) => { if (res.confirm) { wx.removeStorageSync('operatorToken'); wx.removeStorageSync('operatorName'); wx.reLaunch({ url: '/pages/matchmaker-login/matchmaker-login' }); } }, }); }, });