Files
WorkBuddy 3c2b967274
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
release: freeze 2026-10-15 validated release baseline
2026-10-15 09:47:31 +08:00

350 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 红娘工作台主页
*
* 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' });
}
},
});
},
});