chore: establish V1.1 recovery baseline
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 黑名单管理页(重构:用 matchStore + ui-nav-bar + DangerActionDialog)
|
||||
*
|
||||
* 列表展示已拉黑用户(脱敏字段),支持解除拉黑。
|
||||
*
|
||||
* 不变量(AGENTS.md):
|
||||
* - 第 4 条:Block 优先级最高——解除拉黑后对方可重新与你互动;
|
||||
* 解除需 DangerActionDialog 二次确认。
|
||||
* - 第 8 条:敏感数据默认拒绝——列表仅展示后端返回的脱敏字段(昵称/拉黑时间/原因),
|
||||
* 不展示手机号/身份证等敏感信息。
|
||||
* - 第 9 条:PostgreSQL 是 Source of Truth——列表状态以 matchStore 同步服务端返回为准。
|
||||
*/
|
||||
const { matchStore, appStore } = require('../../stores');
|
||||
const { DANGER_ACTION } = require('../../constants');
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
Page({
|
||||
data: {
|
||||
blockedList: [],
|
||||
blockedHasMore: true,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
actionLoadingUserId: '',
|
||||
|
||||
// DangerActionDialog
|
||||
showDangerDialog: false,
|
||||
dangerAction: DANGER_ACTION.UNBLOCK,
|
||||
dangerTargetName: '',
|
||||
pendingUnblockUserId: '',
|
||||
|
||||
statusBarHeight: 20,
|
||||
navBarHeight: 44,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
try {
|
||||
const info = (wx.getWindowInfo && wx.getWindowInfo()) || wx.getSystemInfoSync();
|
||||
this.setData({
|
||||
statusBarHeight: info.statusBarHeight || 20,
|
||||
navBarHeight: info.platform === 'android' ? 48 : 44,
|
||||
});
|
||||
} catch (e) { /* 使用默认值 */ }
|
||||
|
||||
this._unsub = matchStore.bindToPage(this, {
|
||||
blockedList: 'blockedList',
|
||||
blockedHasMore: 'blockedHasMore',
|
||||
});
|
||||
|
||||
this.fetchList(true);
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this._unsub) this._unsub();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.fetchList(true);
|
||||
},
|
||||
|
||||
async fetchList(first) {
|
||||
if (this.data.loading) return;
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
await matchStore.fetchBlockedList({ pageSize: PAGE_SIZE });
|
||||
} catch (err) {
|
||||
appStore.toastError(err);
|
||||
} finally {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.fetchList(true).finally(() => wx.stopPullDownRefresh());
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.loadingMore || !this.data.blockedHasMore) return;
|
||||
this.setData({ loadingMore: true });
|
||||
matchStore.loadMoreBlockedList()
|
||||
.catch((err) => appStore.toastError(err))
|
||||
.finally(() => this.setData({ loadingMore: false }));
|
||||
},
|
||||
|
||||
// ============ 解除拉黑 ============
|
||||
|
||||
onUnblock(e) {
|
||||
const userId = e.currentTarget.dataset.userId;
|
||||
const userName = e.currentTarget.dataset.userName || '对方';
|
||||
if (!userId || this.data.actionLoadingUserId) return;
|
||||
this.setData({
|
||||
showDangerDialog: true,
|
||||
dangerTargetName: userName,
|
||||
pendingUnblockUserId: userId,
|
||||
});
|
||||
},
|
||||
|
||||
async onDangerConfirm() {
|
||||
const userId = this.data.pendingUnblockUserId;
|
||||
this.setData({ showDangerDialog: false, pendingUnblockUserId: '' });
|
||||
if (!userId) return;
|
||||
this.setData({ actionLoadingUserId: userId });
|
||||
try {
|
||||
await matchStore.unblock(userId);
|
||||
// 从本地列表移除(Store 的 unblock 不自动移除,需页面处理)
|
||||
matchStore.setState({
|
||||
blockedList: matchStore.state.blockedList.filter((u) => u.userId !== userId),
|
||||
});
|
||||
appStore.toastSuccess('已解除拉黑');
|
||||
} catch (err) {
|
||||
appStore.toastError(err);
|
||||
} finally {
|
||||
this.setData({ actionLoadingUserId: '' });
|
||||
}
|
||||
},
|
||||
|
||||
onDangerCancel() {
|
||||
this.setData({ showDangerDialog: false, pendingUnblockUserId: '' });
|
||||
},
|
||||
|
||||
onBack() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "黑名单管理",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundColor": "#FFF8F7",
|
||||
"backgroundTextStyle": "dark",
|
||||
"usingComponents": {
|
||||
"ui-nav-bar": "/components/nav-bar/index",
|
||||
"ui-loading": "/components/loading/index",
|
||||
"ui-empty": "/components/empty/index",
|
||||
"ui-avatar": "/components/avatar/index",
|
||||
"ui-danger-action-dialog": "/components/danger-action-dialog/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<!-- 黑名单管理页(重构:matchStore + ui-nav-bar + DangerActionDialog) -->
|
||||
<view class="page">
|
||||
<ui-nav-bar
|
||||
title="黑名单管理"
|
||||
show-back="{{true}}"
|
||||
status-bar-height="{{statusBarHeight}}"
|
||||
nav-bar-height="{{navBarHeight}}"
|
||||
bind:back="onBack"
|
||||
/>
|
||||
|
||||
<view class="body" style="padding-top: {{statusBarHeight + navBarHeight}}px;">
|
||||
<ui-loading wx:if="{{loading}}" mode="skeleton" rows="4" />
|
||||
|
||||
<block wx:elif="{{blockedList.length > 0}}">
|
||||
<view class="list">
|
||||
<view class="item" wx:for="{{blockedList}}" wx:key="userId">
|
||||
<ui-avatar
|
||||
src="{{item.avatarUrl}}"
|
||||
size="lg"
|
||||
default-text="{{item.userName}}"
|
||||
/>
|
||||
<view class="info">
|
||||
<text class="name">{{item.userName || '用户'}}</text>
|
||||
<text class="meta">拉黑于 {{item.blockedAtText || '未知时间'}}</text>
|
||||
<text class="reason" wx:if="{{item.reason}}">原因:{{item.reason}}</text>
|
||||
</view>
|
||||
<button
|
||||
class="btn-unblock {{actionLoadingUserId === item.userId ? 'disabled' : ''}}"
|
||||
catchtap="onUnblock"
|
||||
data-user-id="{{item.userId}}"
|
||||
data-user-name="{{item.userName}}"
|
||||
disabled="{{actionLoadingUserId === item.userId}}"
|
||||
>解除拉黑</button>
|
||||
</view>
|
||||
</view>
|
||||
<ui-loading wx:if="{{loadingMore}}" mode="skeleton" rows="2" />
|
||||
</block>
|
||||
|
||||
<ui-empty
|
||||
wx:else
|
||||
illustration="default"
|
||||
title="黑名单是空的"
|
||||
description="你还没有拉黑过任何人。拉黑后对方将无法与你互动。"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 解除拉黑二次确认 -->
|
||||
<ui-danger-action-dialog
|
||||
visible="{{showDangerDialog}}"
|
||||
action="{{dangerAction}}"
|
||||
target-name="{{dangerTargetName}}"
|
||||
bind:confirm="onDangerConfirm"
|
||||
bind:cancel="onDangerCancel"
|
||||
/>
|
||||
</view>
|
||||
@@ -0,0 +1,72 @@
|
||||
/* 黑名单管理页 — Design Tokens */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #FFF8F7;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.reason {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #B0B0B0;
|
||||
}
|
||||
|
||||
.btn-unblock {
|
||||
flex-shrink: 0;
|
||||
height: 64rpx;
|
||||
line-height: 60rpx;
|
||||
padding: 0 28rpx;
|
||||
border-radius: 32rpx;
|
||||
font-size: 24rpx;
|
||||
background: #fff;
|
||||
color: #E96B6B;
|
||||
border: 2rpx solid #E96B6B;
|
||||
}
|
||||
|
||||
.btn-unblock::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-unblock.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
Reference in New Issue
Block a user