feat: 完善双端演示注册与红娘服务闭环
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
"pages/activity/activity",
|
||||
"pages/mine/mine",
|
||||
"pages/login/login",
|
||||
"pages/demo-register/demo-register",
|
||||
"pages/matchmaker-register/matchmaker-register",
|
||||
"pages/bind-phone/bind-phone",
|
||||
"pages/verification/verification",
|
||||
"pages/profile-basic/profile-basic",
|
||||
|
||||
@@ -18,6 +18,7 @@ exports.TAB = {
|
||||
// ============ 启动与身份类(规范 §4.1) ============
|
||||
exports.AUTH = {
|
||||
LOGIN: '/pages/login/login',
|
||||
DEMO_REGISTER: '/pages/demo-register/demo-register',
|
||||
BIND_PHONE: '/pages/bind-phone/bind-phone',
|
||||
VERIFICATION: '/pages/verification/verification',
|
||||
};
|
||||
@@ -112,6 +113,7 @@ exports.SERVICE = {
|
||||
// ============ 工作人员/红娘端(规范 §4.7) ============
|
||||
exports.OPERATOR = {
|
||||
LOGIN: '/pages/matchmaker-login/matchmaker-login',
|
||||
REGISTER: '/pages/matchmaker-register/matchmaker-register',
|
||||
WORKBENCH: '/pages/matchmaker-workbench/matchmaker-workbench',
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
"pages/activity/activity",
|
||||
"pages/mine/mine",
|
||||
"pages/login/login",
|
||||
"pages/demo-register/demo-register",
|
||||
"pages/matchmaker-register/matchmaker-register",
|
||||
"pages/bind-phone/bind-phone",
|
||||
"pages/verification/verification",
|
||||
"pages/profile-basic/profile-basic",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const { authStore } = require('../../stores');
|
||||
|
||||
Page({
|
||||
data: { code: 'demo_user_01', displayName: '演示用户', phone: '', loading: false, error: '' },
|
||||
onCodeInput(e) { this.setData({ code: e.detail.value, error: '' }); },
|
||||
onNameInput(e) { this.setData({ displayName: e.detail.value, error: '' }); },
|
||||
onPhoneInput(e) { this.setData({ phone: e.detail.value, error: '' }); },
|
||||
async submit() {
|
||||
if (this.data.loading) return;
|
||||
const code = this.data.code.trim();
|
||||
const displayName = this.data.displayName.trim();
|
||||
const phone = this.data.phone.trim();
|
||||
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(code) || !displayName || (phone && !/^1[3-9]\d{9}$/.test(phone))) {
|
||||
this.setData({ error: '请填写有效的演示编号、昵称和手机号' });
|
||||
return;
|
||||
}
|
||||
this.setData({ loading: true, error: '' });
|
||||
try {
|
||||
await authStore.loginWithDemo(`demo_openid_${code}`, displayName, phone);
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
} catch (err) {
|
||||
this.setData({ error: err.message || '注册失败,请重试' });
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
async loginExisting() {
|
||||
if (this.data.loading) return;
|
||||
const code = this.data.code.trim();
|
||||
if (!/^[a-zA-Z0-9_-]{1,100}$/.test(code)) {
|
||||
this.setData({ error: '请输入有效的演示编号' });
|
||||
return;
|
||||
}
|
||||
this.setData({ loading: true, error: '' });
|
||||
try {
|
||||
await authStore.loginWithDemo(`demo_openid_${code}`);
|
||||
wx.reLaunch({ url: '/pages/index/index' });
|
||||
} catch (err) {
|
||||
this.setData({ error: err.message || '登录失败,请重试' });
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{"navigationBarTitleText":"演示用户注册","usingComponents":{}}
|
||||
@@ -0,0 +1,11 @@
|
||||
<view class="register-page">
|
||||
<view class="register-title">演示用户注册</view>
|
||||
<view class="register-form">
|
||||
<view class="field"><text>账号编号</text><input value="{{code}}" bindinput="onCodeInput" placeholder="字母、数字或下划线" /></view>
|
||||
<view class="field"><text>昵称</text><input value="{{displayName}}" bindinput="onNameInput" maxlength="40" placeholder="演示昵称" /></view>
|
||||
<view class="field"><text>手机号(可选)</text><input value="{{phone}}" bindinput="onPhoneInput" type="number" maxlength="11" placeholder="演示手机号" /></view>
|
||||
<view class="error" wx:if="{{error}}">{{error}}</view>
|
||||
<button class="primary" loading="{{loading}}" disabled="{{loading}}" bindtap="submit">注册并登录</button>
|
||||
<button disabled="{{loading}}" bindtap="loginExisting">登录已有演示账号</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,8 @@
|
||||
.register-page { min-height: 100vh; background: var(--color-bg-subtle); padding: 48rpx 32rpx; box-sizing: border-box; }
|
||||
.register-title { font-size: 38rpx; font-weight: 700; color: var(--color-text-strong); margin-bottom: 32rpx; }
|
||||
.register-form { background: var(--color-bg-card); padding: 28rpx; border-radius: 12rpx; }
|
||||
.field { margin-bottom: 24rpx; }
|
||||
.field text { display: block; font-size: 26rpx; color: var(--color-text-secondary); margin-bottom: 10rpx; }
|
||||
.field input { border-bottom: 1rpx solid var(--color-divider); height: 72rpx; font-size: 30rpx; }
|
||||
.error { color: var(--color-danger-strong); font-size: 26rpx; margin-bottom: 16rpx; }
|
||||
.primary { background: var(--color-primary-solid); color: #fff; }
|
||||
@@ -5,6 +5,7 @@
|
||||
import { authStore, appStore } from '../../stores'
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { ROUTE } from '../../constants'
|
||||
import { IS_DEV } from '../../env'
|
||||
|
||||
interface LoginData {
|
||||
loggingIn: boolean
|
||||
@@ -19,6 +20,7 @@ Page({
|
||||
error: '',
|
||||
platformName: getApp().globalData.platformName,
|
||||
slogan: getApp().globalData.slogan,
|
||||
demoEnabled: IS_DEV,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -77,6 +79,10 @@ Page({
|
||||
wx.navigateTo({ url: ROUTE.OPERATOR.LOGIN })
|
||||
},
|
||||
|
||||
onDemoRegister() {
|
||||
wx.navigateTo({ url: ROUTE.AUTH.DEMO_REGISTER })
|
||||
},
|
||||
|
||||
onViewTerms() {
|
||||
wx.navigateTo({ url: '/pages/help-center/help-center?tab=terms' })
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<view class="login-content">
|
||||
<button bindtap="onOperatorEntry" disabled="{{loggingIn}}">红娘工作人员入口</button>
|
||||
<button wx:if="{{demoEnabled}}" bindtap="onDemoRegister" disabled="{{loggingIn}}">注册演示用户</button>
|
||||
<button
|
||||
class="login-btn {{loggingIn ? 'loading' : ''}}"
|
||||
bindtap="onLoginTap"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
const { appStore } = require('../../stores');
|
||||
const { API_BASE_URL } = require('../../env');
|
||||
const { IS_DEV } = require('../../env');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -13,6 +14,7 @@ Page({
|
||||
loading: false,
|
||||
error: '',
|
||||
platformName: getApp().globalData.platformName,
|
||||
demoEnabled: IS_DEV,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -51,10 +53,11 @@ Page({
|
||||
});
|
||||
|
||||
if (res.success) {
|
||||
const { accessToken, name } = res.data;
|
||||
const { accessToken, name, operatorId } = res.data;
|
||||
// 存储token
|
||||
wx.setStorageSync('operatorToken', accessToken);
|
||||
wx.setStorageSync('operatorName', name);
|
||||
wx.setStorageSync('operatorId', operatorId);
|
||||
|
||||
// 跳转到红娘工作台
|
||||
wx.reLaunch({ url: '/pages/matchmaker-workbench/matchmaker-workbench' });
|
||||
@@ -75,4 +78,8 @@ Page({
|
||||
showCancel: false,
|
||||
});
|
||||
},
|
||||
|
||||
onDemoRegister() {
|
||||
wx.navigateTo({ url: '/pages/matchmaker-register/matchmaker-register' });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<button class="login-btn" loading="{{loading}}" bindtap="onLogin">
|
||||
登 录
|
||||
</button>
|
||||
<button wx:if="{{demoEnabled}}" bindtap="onDemoRegister">注册演示红娘</button>
|
||||
|
||||
<view class="footer-link" bindtap="onForgotPassword">
|
||||
<text>忘记密码?</text>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
const { authService } = require('../../services');
|
||||
|
||||
Page({
|
||||
data: { name: '演示红娘', username: 'demo_matchmaker_02', password: '', loading: false, error: '' },
|
||||
onNameInput(e) { this.setData({ name: e.detail.value, error: '' }); },
|
||||
onUsernameInput(e) { this.setData({ username: e.detail.value, error: '' }); },
|
||||
onPasswordInput(e) { this.setData({ password: e.detail.value, error: '' }); },
|
||||
async submit() {
|
||||
if (this.data.loading) return;
|
||||
const name = this.data.name.trim();
|
||||
const username = this.data.username.trim();
|
||||
const password = this.data.password;
|
||||
if (!name || !/^[a-zA-Z][a-zA-Z0-9_]{3,29}$/.test(username) || password.length < 8) {
|
||||
this.setData({ error: '请输入姓名、4 至 30 位用户名和至少 8 位密码' });
|
||||
return;
|
||||
}
|
||||
this.setData({ loading: true, error: '' });
|
||||
try {
|
||||
await authService.operatorDemoRegister({ name, username, password });
|
||||
const result = await authService.operatorLogin({ username, password });
|
||||
wx.setStorageSync('operatorToken', result.accessToken);
|
||||
wx.setStorageSync('operatorName', result.name);
|
||||
wx.setStorageSync('operatorId', result.operatorId);
|
||||
wx.reLaunch({ url: '/pages/matchmaker-workbench/matchmaker-workbench' });
|
||||
} catch (err) {
|
||||
this.setData({ error: err.message || '注册失败,请重试' });
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{"navigationBarTitleText":"注册演示红娘","usingComponents":{}}
|
||||
@@ -0,0 +1,10 @@
|
||||
<view class="register-page">
|
||||
<view class="register-title">注册演示红娘</view>
|
||||
<view class="register-form">
|
||||
<view class="field"><text>姓名</text><input value="{{name}}" bindinput="onNameInput" placeholder="红娘姓名" /></view>
|
||||
<view class="field"><text>用户名</text><input value="{{username}}" bindinput="onUsernameInput" placeholder="4 至 30 位字母、数字或下划线" /></view>
|
||||
<view class="field"><text>密码</text><input type="password" value="{{password}}" bindinput="onPasswordInput" placeholder="至少 8 位" /></view>
|
||||
<view class="error" wx:if="{{error}}">{{error}}</view>
|
||||
<button class="primary" loading="{{loading}}" disabled="{{loading}}" bindtap="submit">注册并进入工作台</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,8 @@
|
||||
.register-page { min-height: 100vh; background: var(--color-bg-subtle); padding: 48rpx 32rpx; box-sizing: border-box; }
|
||||
.register-title { font-size: 38rpx; font-weight: 700; color: var(--color-text-strong); margin-bottom: 32rpx; }
|
||||
.register-form { background: var(--color-bg-card); padding: 28rpx; border-radius: 12rpx; }
|
||||
.field { margin-bottom: 24rpx; }
|
||||
.field text { display: block; font-size: 26rpx; color: var(--color-text-secondary); margin-bottom: 10rpx; }
|
||||
.field input { border-bottom: 1rpx solid var(--color-divider); height: 72rpx; font-size: 30rpx; }
|
||||
.error { color: var(--color-danger-strong); font-size: 26rpx; margin-bottom: 16rpx; }
|
||||
.primary { background: var(--color-primary-solid); color: #fff; }
|
||||
@@ -4,10 +4,13 @@ Page({
|
||||
data: {
|
||||
serviceId: '',
|
||||
detail: null,
|
||||
source: 'assignment',
|
||||
statusLabel: '',
|
||||
followups: [],
|
||||
followupTypes: ['followup', '需求了解', '候选建议', '活动协助', '其他'],
|
||||
loading: true,
|
||||
submitting: false,
|
||||
changingStatus: false,
|
||||
form: { followupType: 'followup', content: '', nextFollowupAt: '' },
|
||||
},
|
||||
|
||||
@@ -18,7 +21,7 @@ Page({
|
||||
wx.navigateBack();
|
||||
return;
|
||||
}
|
||||
this.setData({ serviceId });
|
||||
this.setData({ serviceId, source: options.source === 'management' ? 'management' : 'assignment' });
|
||||
this.loadDetail();
|
||||
},
|
||||
|
||||
@@ -34,8 +37,10 @@ Page({
|
||||
async loadDetail() {
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const detail = await api.get(`/operator/matchmaker/services/${this.data.serviceId}`, {}, this.tokenHeaders());
|
||||
this.setData({ detail: detail || null, followups: (detail && detail.followups) || [] });
|
||||
const path = this.data.source === 'management' ? '/operator/matchmaker/management/services/' : '/operator/matchmaker/services/';
|
||||
const detail = await api.get(`${path}${encodeURIComponent(this.data.serviceId)}`, {}, this.tokenHeaders());
|
||||
const labels = { submitted: '待处理', assigned: '已接单', in_service: '服务中', waiting_user: '待用户确认', completed: '已完成' };
|
||||
this.setData({ detail: detail || null, statusLabel: labels[detail && detail.status] || (detail && detail.status) || '', followups: (detail && detail.followups) || [] });
|
||||
} catch (error) {
|
||||
if (error && (error.code === 'FORBIDDEN' || error.code === 'NOT_FOUND' || error.code === 'DATA_SCOPE_DENIED')) {
|
||||
wx.showModal({ title: '服务权限已变更', content: '该服务对象已结束或不再属于当前服务范围。', showCancel: false,
|
||||
@@ -48,6 +53,21 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
async changeStatus(e) {
|
||||
const action = e.currentTarget.dataset.action;
|
||||
if (this.data.changingStatus || this.data.source === 'management') return;
|
||||
this.setData({ changingStatus: true });
|
||||
try {
|
||||
await api.post(`/operator/matchmaker/services/${encodeURIComponent(this.data.serviceId)}/${action}`, {}, this.tokenHeaders());
|
||||
wx.showToast({ title: action === 'start' ? '服务已开始' : '服务已完成', icon: 'success' });
|
||||
await this.loadDetail();
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.message || '操作失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ changingStatus: false });
|
||||
}
|
||||
},
|
||||
|
||||
onTypeChange(e) { this.setData({ 'form.followupType': this.data.followupTypes[Number(e.detail.value)] || 'followup' }); },
|
||||
onContentInput(e) { this.setData({ 'form.content': e.detail.value }); },
|
||||
onNextFollowupInput(e) { this.setData({ 'form.nextFollowupAt': e.detail.value }); },
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<text>地区:{{detail.userProfile.regionName || '未设置'}}</text>
|
||||
<text>婚姻状态:{{detail.userProfile.maritalStatus || '未设置'}}</text>
|
||||
</view>
|
||||
<view class="status">服务状态:{{detail.status}}</view>
|
||||
<view class="status">服务状态:{{statusLabel}}</view>
|
||||
<view wx:if="{{detail.description}}" class="description">服务需求:{{detail.description}}</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card" wx:if="{{source === 'assignment' && detail.status !== 'completed'}}">
|
||||
<view class="title">记录服务</view>
|
||||
<picker mode="selector" range="{{followupTypes}}" value="0" bindchange="onTypeChange">
|
||||
<view class="picker">服务类型:{{form.followupType}}</view>
|
||||
@@ -25,6 +25,13 @@
|
||||
<button type="primary" loading="{{submitting}}" disabled="{{submitting}}" bindtap="submitFollowup">保存服务记录</button>
|
||||
</view>
|
||||
|
||||
<view class="card" wx:if="{{source === 'assignment' && detail.status === 'assigned'}}">
|
||||
<button type="primary" loading="{{changingStatus}}" disabled="{{changingStatus}}" bindtap="changeStatus" data-action="start">开始服务</button>
|
||||
</view>
|
||||
<view class="card" wx:if="{{source === 'assignment' && (detail.status === 'in_service' || detail.status === 'waiting_user')}}">
|
||||
<button loading="{{changingStatus}}" disabled="{{changingStatus}}" bindtap="changeStatus" data-action="complete">完成服务</button>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="title">服务历史</view>
|
||||
<view wx:if="{{followups.length}}">
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
* 展示待处理服务、工作台数据概览
|
||||
*/
|
||||
const api = require('../../utils/api');
|
||||
const { authStore } = require('../../stores');
|
||||
const { matchmakerService } = require('../../services');
|
||||
const { IS_DEV } = require('../../env');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -20,6 +23,13 @@ Page({
|
||||
todayCount: 0,
|
||||
completedCount: 0,
|
||||
myAssignments: [],
|
||||
serviceTabs: [{ value: 'pending', label: '待处理' }, { value: 'in_progress', label: '进行中' }, { value: 'completed', label: '已完成' }],
|
||||
activeServiceTab: 'pending',
|
||||
services: [],
|
||||
visibleServices: [],
|
||||
demoEnabled: IS_DEV,
|
||||
creatingDemoUser: false,
|
||||
assigningServiceId: '',
|
||||
assignmentCount: 0,
|
||||
loading: true,
|
||||
currentDate: '',
|
||||
@@ -92,7 +102,7 @@ Page({
|
||||
}
|
||||
|
||||
if (assignmentRes) {
|
||||
const assignments = assignmentRes || [];
|
||||
const assignments = Array.isArray(assignmentRes) ? assignmentRes : (assignmentRes.list || []);
|
||||
// 联系授权申请目标 = 本人生效中的分配对象(B1:只能对分配对象发起)
|
||||
const targets = assignments.map((a) => ({
|
||||
userId: a.userId,
|
||||
@@ -105,6 +115,7 @@ Page({
|
||||
contactTargetIndex: 0,
|
||||
});
|
||||
this.loadContactAuthorizations();
|
||||
await this.loadServices(token, assignments);
|
||||
}
|
||||
if (referralRes) {
|
||||
this.setData({
|
||||
@@ -132,8 +143,83 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
async loadServices(token, assignments) {
|
||||
const headers = { Authorization: `Bearer ${token}` };
|
||||
const assigned = await Promise.allSettled(assignments.filter((item) => item.serviceId).map((item) =>
|
||||
api.get(`/operator/matchmaker/services/${encodeURIComponent(item.serviceId)}`, {}, headers)));
|
||||
const [pending, management] = await Promise.allSettled([
|
||||
api.get('/operator/matchmaker/pending', {}, headers),
|
||||
api.get('/operator/matchmaker/management/services', {}, headers),
|
||||
]);
|
||||
const services = new Map();
|
||||
assigned.forEach((result) => {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
const item = result.value;
|
||||
services.set(item.serviceId || item.id, { ...item, userDisplayName: item.userProfile && item.userProfile.displayName, source: 'assignment' });
|
||||
}
|
||||
});
|
||||
if (management.status === 'fulfilled') (management.value || []).forEach((item) =>
|
||||
services.set(item.serviceId || item.id, { ...item, source: item.status === 'submitted' || item.status === 'completed' ? 'management' : 'assignment' }));
|
||||
if (pending.status === 'fulfilled') (pending.value || []).forEach((item) =>
|
||||
services.set(item.serviceId || item.id, { ...item, source: 'management' }));
|
||||
this.setData({ services: [...services.values()].map((item) => ({
|
||||
...item,
|
||||
statusLabel: this.serviceStatusLabel(item.status),
|
||||
serviceTypeLabel: item.serviceType === 'recommend_partner' ? '推荐对象' : (item.serviceType || '公益红娘服务'),
|
||||
})) });
|
||||
this.selectServiceTab({ currentTarget: { dataset: { tab: this.data.activeServiceTab } } });
|
||||
},
|
||||
|
||||
selectServiceTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab;
|
||||
const statuses = tab === 'pending' ? ['submitted', 'pending']
|
||||
: tab === 'in_progress' ? ['assigned', 'in_service', 'in_progress', 'waiting_user'] : ['completed'];
|
||||
this.setData({ activeServiceTab: tab, visibleServices: this.data.services.filter((item) => statuses.includes(item.status)) });
|
||||
},
|
||||
|
||||
serviceStatusLabel(status) {
|
||||
return { submitted: '待处理', assigned: '已接单', in_service: '服务中', waiting_user: '待用户确认', completed: '已完成' }[status] || status;
|
||||
},
|
||||
|
||||
async createDemoUser() {
|
||||
if (this.data.creatingDemoUser) return;
|
||||
this.setData({ creatingDemoUser: true });
|
||||
try {
|
||||
const code = `demo_openid_workbench_${Date.now()}`;
|
||||
await authStore.loginWithDemo(code, '演示服务用户');
|
||||
await matchmakerService.applyService({ serviceType: 'recommend_partner', description: '红娘演示服务申请' });
|
||||
wx.showToast({ title: '演示申请已创建', icon: 'success' });
|
||||
this.setData({ activeServiceTab: 'pending' });
|
||||
await this.loadDashboard();
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.message || '注册演示用户失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ creatingDemoUser: false });
|
||||
}
|
||||
},
|
||||
|
||||
async assignToSelf(e) {
|
||||
const serviceId = e.currentTarget.dataset.id;
|
||||
const operatorId = wx.getStorageSync('operatorId');
|
||||
if (!serviceId || !operatorId || this.data.assigningServiceId) return;
|
||||
this.setData({ assigningServiceId: serviceId });
|
||||
try {
|
||||
const token = wx.getStorageSync('operatorToken');
|
||||
await api.post(`/operator/matchmaker/services/${encodeURIComponent(serviceId)}/assign`,
|
||||
{ operatorId, reason: '演示分配' }, { Authorization: `Bearer ${token}` });
|
||||
wx.showToast({ title: '已接单', icon: 'success' });
|
||||
this.setData({ activeServiceTab: 'in_progress' });
|
||||
await this.loadDashboard();
|
||||
} catch (err) {
|
||||
wx.showToast({ title: err.message || '接单失败', icon: 'none' });
|
||||
} finally {
|
||||
this.setData({ assigningServiceId: '' });
|
||||
}
|
||||
},
|
||||
|
||||
onViewService(e) {
|
||||
const serviceId = e.currentTarget.dataset.id;
|
||||
const source = e.currentTarget.dataset.source || 'assignment';
|
||||
if (!serviceId) {
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
const item = this.data.myAssignments[index] || {};
|
||||
@@ -144,7 +230,7 @@ Page({
|
||||
});
|
||||
return;
|
||||
}
|
||||
wx.navigateTo({ url: `/pages/matchmaker-service-detail/matchmaker-service-detail?id=${serviceId}` });
|
||||
wx.navigateTo({ url: `/pages/matchmaker-service-detail/matchmaker-service-detail?id=${serviceId}&source=${source}` });
|
||||
},
|
||||
|
||||
openReferral() {
|
||||
|
||||
@@ -44,42 +44,45 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的服务对象分配 -->
|
||||
<!-- 统一服务工作台 -->
|
||||
<view class="section section-compact" wx:if="{{!loading}}">
|
||||
<view class="section-header">
|
||||
<view class="section-title-wrap">
|
||||
<text class="section-dot"></text>
|
||||
<text class="section-title">我的服务对象分配</text>
|
||||
<text class="section-title">服务工作台</text>
|
||||
</view>
|
||||
<text class="section-count" wx:if="{{assignmentCount > 0}}">共{{assignmentCount}}人</text>
|
||||
<button wx:if="{{demoEnabled}}" class="detail-btn" size="mini" loading="{{creatingDemoUser}}" bindtap="createDemoUser">注册演示用户</button>
|
||||
</view>
|
||||
<view class="service-tabs">
|
||||
<view wx:for="{{serviceTabs}}" wx:key="value" class="service-tab {{activeServiceTab === item.value ? 'active' : ''}}" data-tab="{{item.value}}" bindtap="selectServiceTab">{{item.label}}</view>
|
||||
</view>
|
||||
|
||||
<view class="service-list" wx:if="{{myAssignments.length > 0}}">
|
||||
<view class="service-list" wx:if="{{visibleServices.length > 0}}">
|
||||
<view
|
||||
wx:for="{{myAssignments}}"
|
||||
wx:key="id"
|
||||
wx:for="{{visibleServices}}"
|
||||
wx:key="serviceId"
|
||||
class="service-card"
|
||||
data-id="{{item.serviceId}}"
|
||||
data-source="{{item.source}}"
|
||||
data-index="{{index}}"
|
||||
bindtap="onViewService"
|
||||
>
|
||||
<view class="service-left">
|
||||
<view class="service-header">
|
||||
<view class="service-tag">{{assignmentScopeLabel(item.assignmentScope)}}</view>
|
||||
<view class="service-tag">{{assignmentStatusLabel(item.status)}}</view>
|
||||
<view class="service-time">{{formatTime(item.assignedAt)}}</view>
|
||||
<view class="service-tag">{{item.statusLabel}}</view>
|
||||
<view class="service-time">{{item.serviceTypeLabel}}</view>
|
||||
</view>
|
||||
<view class="service-name">{{item.userDisplayName || '未设置姓名'}} <text wx:if="{{item.userDisplayNumber}}">{{item.userDisplayNumber}}</text></view>
|
||||
<view class="service-desc">{{item.userAge ? item.userAge + '岁 · ' : ''}}{{item.userRegionName || '地区未设置'}}</view>
|
||||
<view class="service-name">{{item.userDisplayName || item.userDisplayNumber || '服务对象'}}</view>
|
||||
<view class="service-desc">{{item.description || item.latestFollowupContent || '暂无服务进度'}}</view>
|
||||
</view>
|
||||
<button class="detail-btn" size="mini" catchtap="onViewService" data-id="{{item.serviceId}}" data-index="{{index}}">查看详情</button>
|
||||
<button wx:if="{{item.status === 'submitted' && demoEnabled}}" class="detail-btn" size="mini" catchtap="assignToSelf" data-id="{{item.serviceId}}" loading="{{assigningServiceId === item.serviceId}}">接单</button>
|
||||
<button wx:else class="detail-btn" size="mini" catchtap="onViewService" data-id="{{item.serviceId}}" data-source="{{item.source}}">查看详情</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state empty-compact" wx:else>
|
||||
<view class="empty-illustration">📭</view>
|
||||
<view class="empty-text">暂无服务对象分配</view>
|
||||
<view class="empty-hint">分配给你的服务对象将在这里展示</view>
|
||||
<view class="empty-text">暂无该状态服务</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -157,6 +157,9 @@
|
||||
}
|
||||
|
||||
/* 服务列表 */
|
||||
.service-tabs { display: flex; border-bottom: 1rpx solid var(--color-divider); margin-bottom: 20rpx; }
|
||||
.service-tab { flex: 1; padding: 18rpx 0; text-align: center; color: var(--color-text-tertiary); font-size: 26rpx; }
|
||||
.service-tab.active { color: var(--color-primary-strong); border-bottom: 4rpx solid currentColor; font-weight: 600; }
|
||||
.service-list {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
|
||||
@@ -16,6 +16,18 @@ Page({
|
||||
this.loadNotifications();
|
||||
},
|
||||
|
||||
getTypeText(type) {
|
||||
return { matchmaker: '红娘服务', appointment: '预约提醒', counseling: '咨询服务', premarital: '婚前辅导', service_location: '服务站服务', match: '匹配提醒', activity: '活动提醒', account: '账号状态', review: '审核通知', local_eligibility: '资格认证', risk: '安全提醒', system: '系统通知' }[type] || '系统通知';
|
||||
},
|
||||
|
||||
openNotification(item) {
|
||||
const type = item && item.type;
|
||||
const url = ['matchmaker', 'appointment', 'counseling', 'premarital', 'service_location'].includes(type)
|
||||
? '/pages/service-history/service-history'
|
||||
: ['match', 'activity'].includes(type) ? '/pages/fate-center/fate-center' : '/pages/settings/settings';
|
||||
wx.navigateTo({ url });
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadUnreadCount();
|
||||
},
|
||||
@@ -30,11 +42,12 @@ Page({
|
||||
{ Authorization: `Bearer ${token}` }
|
||||
);
|
||||
if (res) {
|
||||
const raw = Array.isArray(res) ? res : (res.notifications || res.items || res.records || []);
|
||||
const isNewPage = this.data.page === 1;
|
||||
this.setData({
|
||||
notifications: isNewPage ? res.notifications : [...this.data.notifications, ...res.notifications],
|
||||
unreadCount: res.unreadCount,
|
||||
hasMore: res.notifications.length >= 20,
|
||||
notifications: isNewPage ? raw.map(this.normalizeNotification) : [...this.data.notifications, ...raw.map(this.normalizeNotification)],
|
||||
unreadCount: res.unreadCount || res.count || 0,
|
||||
hasMore: raw.length >= 20,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
@@ -43,11 +56,15 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
normalizeNotification(item) {
|
||||
return { ...item, notificationId: item.notificationId || item.id, read: item.read === true || item.readStatus === 'read' };
|
||||
},
|
||||
|
||||
async loadUnreadCount() {
|
||||
try {
|
||||
const token = wx.getStorageSync('accessToken');
|
||||
const res = await api.get('/notifications/unread-count', {}, { Authorization: `Bearer ${token}` });
|
||||
this.setData({ unreadCount: res?.unreadCount || 0 });
|
||||
const res = await api.get('/notifications/unread', {}, { Authorization: `Bearer ${token}` });
|
||||
this.setData({ unreadCount: res?.unreadCount ?? res?.count ?? 0 });
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
@@ -63,6 +80,7 @@ Page({
|
||||
unreadCount: Math.max(0, this.data.unreadCount - 1)
|
||||
});
|
||||
} catch (err) { /* ignore */ }
|
||||
this.openNotification(this.data.notifications.find((n) => n.notificationId === notificationId));
|
||||
},
|
||||
|
||||
async onMarkAllRead() {
|
||||
|
||||
@@ -19,6 +19,22 @@ function wechatLogin(payload) {
|
||||
return api.post('/auth/wechat/login', payload, undefined, payload.code);
|
||||
}
|
||||
|
||||
function demoLogin(payload) {
|
||||
return api.post('/auth/demo/login', payload, undefined, payload.code);
|
||||
}
|
||||
|
||||
function demoRegister(payload) {
|
||||
return api.post('/auth/demo/register', payload, undefined, payload.code);
|
||||
}
|
||||
|
||||
function operatorDemoRegister(payload) {
|
||||
return api.post('/auth/operator/demo/register', payload);
|
||||
}
|
||||
|
||||
function operatorLogin(payload) {
|
||||
return api.post('/auth/operator/login', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号(API_INVENTORY 行 9)
|
||||
* @param {{ phoneCode: string }} payload wx.getPhoneNumber 拿到的 encryptedData/iv 或 phoneCode
|
||||
@@ -51,6 +67,10 @@ function logout() {
|
||||
|
||||
module.exports = {
|
||||
wechatLogin,
|
||||
demoLogin,
|
||||
demoRegister,
|
||||
operatorDemoRegister,
|
||||
operatorLogin,
|
||||
bindPhone,
|
||||
refresh,
|
||||
logout,
|
||||
|
||||
@@ -89,6 +89,23 @@ class AuthStore extends BaseStore {
|
||||
}
|
||||
}
|
||||
|
||||
async loginWithDemo(code, displayName, phone) {
|
||||
this.setState({ authStatus: 'logging_in', lastError: null });
|
||||
try {
|
||||
const result = displayName
|
||||
? await authService.demoRegister({ code, displayName, phone })
|
||||
: await authService.demoLogin({ code });
|
||||
api.saveToken(result.accessToken, result.refreshToken);
|
||||
const snapshot = { isLoggedIn: true, userId: result.userId, phoneBound: !result.isNewUser };
|
||||
this.setState({ ...snapshot, authStatus: 'idle' });
|
||||
this.persist(snapshot, STORAGE_KEYS.IS_LOGGED_IN);
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.setState({ authStatus: 'idle', lastError: err });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
* @param {{ phoneCode?: string, encryptedData?: string, iv?: string }} payload
|
||||
|
||||
Reference in New Issue
Block a user