Files
danyang-match/miniapp/pages/contact-consent/contact-consent.wxss
T
WorkBuddy d3735e3afa feat(matchmaker,contactauth): 受控联系授权机制立项落地(OQ-MATCHMAKER-003 / DEC-MATCHMAKER-003 B1 例外通道)
后端:
- V056 matchmaker_contact_authorizations 表:channel CHECK(phone/offline_meeting/full_contact)、六态状态机(pending_approval→pending_consent→active→expired/revoked, →rejected)、部分唯一索引 uq_mm_contact_auth_active 防同红娘-同用户-同渠道多 ACTIVE、双态惰性收敛(active 过期 + pending_consent 超同意窗口)
- ContactAuthorizationConfig 五 canonical key(matchmaker.contact.*):enabled 代码缺省 false fail-safe(yml 显式 true)/approver-authorities 双岗并列/consent-deadline-hours 72/default-validity-days 7/retention-days 365,全部 DEFAULT+CONFIG 待产品+法务终审
- ContactAuthorizationPolicy.assertContactAllowed(404 化 deny 先审计后 404) + canContact 徽标查询
- ContactAuthorizationService:request(须有效分配+purpose 必填)/approve(权限码动态判定∈配置+组长组织范围)/consent(用户本人,仅 pending_consent)/decline/revoke(reason 必填)/disclose(唯一披露出口:enabled fail-safe→assertActiveAssignment→assertContactAllowed→仅返回 phone/disclosedAt/authorizationId 三键,每次落 SensitiveAccessLog user_phone_disclosure,phone 不落应用日志)
- 双控制器:运营端 /operator/matchmaker/contact-authorizations + C 端 /contact-authorizations(UserPrincipal 归属校验,独立端点无预同意参数),不碰 SecurityConfig

守卫翻转(机械锁强制复审机制按设计生效):
- MatchmakerContactGuardTest 由「锁不存在」翻转为「锁新契约」五条:回显白名单类收敛/端点前缀收敛(受治理三包)/到期语义/consent 独立性三端扫描(后端+miniapp+admin-web,修复原只扫后端的预同意参数逃逸缺口)/ReferralService 白名单与无 @Scheduled 保留
- run-backend-contracts CURATED 18→20

前端:
- miniapp 新页 pages/contact-consent(目的/渠道/窗口明示+独立同意拒绝按钮无预勾选,app.json 第 64 页) + 红娘工作台申请/列表/撤销
- admin-web「联系授权审批」tab(审批岗队列+本人授权撤销,枚举码中文映射)

验证:mvn verify 895/895(3 skip)、UI 契约 24/24(64 页)、后端契约门禁 228/CURATED 20、小程序真编译 87 WXML 0 失败;负向注入 5(工程师)+2(QA 不同落点)判红→还原复绿;QA 抓 disclose 键数收敛断言缺口已补并回归;QA fresh eyes PASS 0 P0/P1
2026-10-11 13:08:02 +08:00

22 lines
1.3 KiB
Plaintext

/* 联系授权确认页样式 */
.page { padding: 24rpx; background: #f7f8fa; min-height: 100vh; box-sizing: border-box; }
.intro { background: #eef4ff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; }
.intro-title { font-size: 32rpx; font-weight: 600; color: #1f2d3d; margin-bottom: 12rpx; }
.intro-text { font-size: 26rpx; color: #5e6d82; line-height: 1.6; }
.section { margin-bottom: 28rpx; }
.section-title { font-size: 30rpx; font-weight: 600; color: #1f2d3d; margin-bottom: 16rpx; }
.card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; }
.card.slim { padding: 20rpx; }
.row { display: flex; margin-bottom: 12rpx; }
.label { width: 150rpx; color: #909399; font-size: 26rpx; flex-shrink: 0; }
.value { color: #303133; font-size: 26rpx; flex: 1; word-break: break-all; }
.value.strong { font-weight: 600; color: #1f2d3d; }
.actions { display: flex; justify-content: flex-end; gap: 20rpx; margin-top: 16rpx; }
.btn { font-size: 28rpx; border-radius: 12rpx; padding: 0 40rpx; line-height: 72rpx; margin: 0; }
.btn.decline { background: #f4f4f5; color: #606266; }
.btn.consent { background: #2f7df6; color: #fff; }
.empty { text-align: center; padding: 60rpx 0; }
.empty-text { color: #909399; font-size: 26rpx; }
.loading-wrap { text-align: center; padding: 40rpx 0; }
.loading-text { color: #909399; font-size: 26rpx; }