feat: 完善双端演示注册与红娘服务闭环

This commit is contained in:
WorkBuddy
2026-10-21 23:41:47 +08:00
parent afee993626
commit 39a18a50ee
63 changed files with 1298 additions and 217 deletions
@@ -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; }
+6
View File
@@ -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' })
},
+1
View File
@@ -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;
+23 -5
View File
@@ -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() {