2026-09-09 22:05:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 红娘工作台主页
|
2026-10-09 22:46:32 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 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 等),
|
|
|
|
|
|
* 无服务类型/诉求描述字段,故按实际字段精简展示,不前端拼空值。
|
|
|
|
|
|
*
|
2026-09-09 22:05:00 +08:00
|
|
|
|
* 展示待处理服务、工作台数据概览
|
|
|
|
|
|
*/
|
|
|
|
|
|
const api = require('../../utils/api');
|
|
|
|
|
|
|
|
|
|
|
|
Page({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
operatorName: '',
|
|
|
|
|
|
pendingCount: 0,
|
|
|
|
|
|
inProgressCount: 0,
|
|
|
|
|
|
todayCount: 0,
|
|
|
|
|
|
completedCount: 0,
|
2026-10-09 22:46:32 +08:00
|
|
|
|
myAssignments: [],
|
|
|
|
|
|
assignmentCount: 0,
|
2026-09-09 22:05:00 +08:00
|
|
|
|
loading: true,
|
|
|
|
|
|
currentDate: '',
|
2026-10-11 13:08:02 +08:00
|
|
|
|
// ZC-MM-003 / B1:受控联系授权(申请入口 + 本人授权列表/撤销;purpose 必填)
|
|
|
|
|
|
contactTargets: [],
|
|
|
|
|
|
contactTargetIndex: 0,
|
|
|
|
|
|
contactChannelLabels: ['电话联系', '线下见面', '完整联系方式'],
|
|
|
|
|
|
contactChannels: ['phone', 'offline_meeting', 'full_contact'],
|
|
|
|
|
|
contactChannelIndex: 0,
|
|
|
|
|
|
contactPurpose: '',
|
|
|
|
|
|
contactAuths: [],
|
|
|
|
|
|
contactAuthCount: 0,
|
|
|
|
|
|
submittingAuth: false,
|
2026-10-15 09:47:31 +08:00
|
|
|
|
referralTasks: [],
|
|
|
|
|
|
recommendations: [],
|
|
|
|
|
|
referralDialogVisible: false,
|
|
|
|
|
|
referralTargetIndex: 0,
|
|
|
|
|
|
referralTargetLabels: [],
|
|
|
|
|
|
referralTypeIndex: 0,
|
|
|
|
|
|
referralTypes: ['服务站', '公益活动'],
|
|
|
|
|
|
referralTypeCodes: ['service_station', 'activity'],
|
|
|
|
|
|
referralTargetId: '',
|
|
|
|
|
|
referralNote: '',
|
|
|
|
|
|
submittingReferral: false,
|
2026-09-09 22:05:00 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onLoad() {
|
|
|
|
|
|
wx.setNavigationBarTitle({ title: '红娘工作台' });
|
|
|
|
|
|
// 获取操作员信息
|
|
|
|
|
|
const operatorName = wx.getStorageSync('operatorName') || '红娘';
|
|
|
|
|
|
this.setData({ operatorName });
|
2026-10-09 22:46:32 +08:00
|
|
|
|
|
2026-09-09 22:05:00 +08:00
|
|
|
|
// 设置当前日期
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
|
|
|
|
|
|
this.setData({ currentDate: dateStr });
|
2026-10-09 22:46:32 +08:00
|
|
|
|
|
2026-09-09 22:05:00 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-09 22:46:32 +08:00
|
|
|
|
// 加载工作台数据 + 本人服务对象分配(ZC-MM-002:不再调用 /pending,普通红娘无该权限)
|
2026-10-15 09:47:31 +08:00
|
|
|
|
const [dashRes, assignmentRes, referralRes, recommendationRes] = await Promise.all([
|
2026-09-09 22:05:00 +08:00
|
|
|
|
api.get('/operator/matchmaker/dashboard', {}, { Authorization: `Bearer ${token}` }),
|
2026-10-09 22:46:32 +08:00
|
|
|
|
api.get('/operator/matchmaker/assignments/my', {}, { Authorization: `Bearer ${token}` }),
|
2026-10-15 09:47:31 +08:00
|
|
|
|
api.get('/referrals/operator/history', {}, { Authorization: `Bearer ${token}` }),
|
|
|
|
|
|
api.get('/operator/matchmaker/recommendations', {}, { Authorization: `Bearer ${token}` }),
|
2026-09-09 22:05:00 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
2026-10-15 09:47:31 +08:00
|
|
|
|
if (dashRes) {
|
2026-09-09 22:05:00 +08:00
|
|
|
|
this.setData({
|
2026-10-15 09:47:31 +08:00
|
|
|
|
pendingCount: dashRes.pendingCount || 0,
|
|
|
|
|
|
inProgressCount: dashRes.inProgressCount || 0,
|
|
|
|
|
|
todayCount: dashRes.todayCount || 0,
|
|
|
|
|
|
completedCount: dashRes.completedCount || 0,
|
2026-09-09 22:05:00 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-15 09:47:31 +08:00
|
|
|
|
if (assignmentRes) {
|
|
|
|
|
|
const assignments = assignmentRes || [];
|
2026-10-11 13:08:02 +08:00
|
|
|
|
// 联系授权申请目标 = 本人生效中的分配对象(B1:只能对分配对象发起)
|
|
|
|
|
|
const targets = assignments.map((a) => ({
|
|
|
|
|
|
userId: a.userId,
|
2026-10-15 09:47:31 +08:00
|
|
|
|
label: (a.userDisplayNumber || '待实名编号') + '(' + (a.userDisplayName || '服务对象') + ')',
|
2026-10-11 13:08:02 +08:00
|
|
|
|
}));
|
2026-09-09 22:05:00 +08:00
|
|
|
|
this.setData({
|
2026-10-09 22:46:32 +08:00
|
|
|
|
myAssignments: assignments,
|
|
|
|
|
|
assignmentCount: assignments.length,
|
2026-10-11 13:08:02 +08:00
|
|
|
|
contactTargets: targets,
|
|
|
|
|
|
contactTargetIndex: 0,
|
2026-09-09 22:05:00 +08:00
|
|
|
|
});
|
2026-10-11 13:08:02 +08:00
|
|
|
|
this.loadContactAuthorizations();
|
2026-09-09 22:05:00 +08:00
|
|
|
|
}
|
2026-10-15 09:47:31 +08:00
|
|
|
|
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),
|
|
|
|
|
|
})) });
|
|
|
|
|
|
}
|
2026-09-09 22:05:00 +08:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('加载工作台数据失败:', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.setData({ loading: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onViewService(e) {
|
|
|
|
|
|
const serviceId = e.currentTarget.dataset.id;
|
2026-10-09 22:46:32 +08:00
|
|
|
|
if (!serviceId) {
|
2026-10-15 09:47:31 +08:00
|
|
|
|
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,
|
|
|
|
|
|
});
|
2026-10-09 22:46:32 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-09-09 22:05:00 +08:00
|
|
|
|
wx.navigateTo({ url: `/pages/matchmaker-service-detail/matchmaker-service-detail?id=${serviceId}` });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-10-15 09:47:31 +08:00
|
|
|
|
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 });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-10-11 13:08:02 +08:00
|
|
|
|
// ==================== 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}` });
|
2026-10-15 09:47:31 +08:00
|
|
|
|
if (res) {
|
|
|
|
|
|
const items = ((res.list || [])).map((item) => Object.assign({}, item, {
|
2026-10-11 13:08:02 +08:00
|
|
|
|
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 || '';
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-10-15 09:47:31 +08:00
|
|
|
|
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 || '未知状态';
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-10-11 13:08:02 +08:00
|
|
|
|
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}` });
|
2026-10-15 09:47:31 +08:00
|
|
|
|
if (res) {
|
2026-10-11 13:08:02 +08:00
|
|
|
|
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}` });
|
2026-10-15 09:47:31 +08:00
|
|
|
|
if (resp) {
|
2026-10-11 13:08:02 +08:00
|
|
|
|
wx.showToast({ title: '已撤销', icon: 'none' });
|
|
|
|
|
|
this.loadContactAuthorizations();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
wx.showToast({ title: '撤销失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-10-09 22:46:32 +08:00
|
|
|
|
// 分配状态 → 中文(避免枚举码直接上屏,见 profileLabels 铁律)
|
|
|
|
|
|
assignmentStatusLabel(code) {
|
|
|
|
|
|
const labels = {
|
|
|
|
|
|
active: '生效中',
|
|
|
|
|
|
ended: '已结束',
|
|
|
|
|
|
revoked: '已撤销',
|
|
|
|
|
|
transferred: '已转分配',
|
|
|
|
|
|
expired: '已过期',
|
|
|
|
|
|
};
|
|
|
|
|
|
return labels[code] || code || '';
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 分配范围 → 中文
|
|
|
|
|
|
assignmentScopeLabel(code) {
|
|
|
|
|
|
const labels = { primary: '主责', collaborating: '协同' };
|
|
|
|
|
|
return labels[code] || code || '';
|
2026-09-09 22:05:00 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
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' });
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|