fix(ops-backend/service-station): close Phase 19 KEEP-VERIFY — 25 defects (unreachable announcements + station write privilege escalation; banner 500 + soft-delete leak; content publish dead-end; program approval dead-end/draft leak/zero counts/missing withdraw; zero-coverage test schema) ; add V042 announcement chain + 34 guard tests; full regression 602 green
Backend - Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint. - Fix privilege escalation: create/update were guarded only by the class-level service_location.view, making V015's service_location.manage a never-used dead permission -> read-only roles could edit stations. Permissions moved to method level. - Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest (it was NOT scanned, which is why the escalation stayed invisible). - Add create/update validation (address NOT NULL + status CHECK only blew up on PG). - Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live). - Content: operator list/detail split (admin «publish» was a dead end because it reused the published-only public API); category deactivate/reactivate; article body on edit. - Service Program: operator member list now includes pending (approval reachable); active-only public visibility (draft leak); memberCount/pendingCount aggregation; withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash). - MeController: add the missing GET /me/service-history route. Schema / guards - schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and content_categories (+ uk_content_category_code, chk_category_status); move both into REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42. - V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029. - New: ServiceLocationAnnouncementPhase19IntegrationTest (24), ServiceLocationControllerPermissionContractTest (5), ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <-> backend whitelist <-> admin labels). Frontend - admin-web: ServiceStationView gains announcement management (statuses, create/edit, publish/archive with confirm, expiry); write buttons gated by service_location.manage. - miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer talks to utils/api directly; announcement failure degrades to [] instead of erroring. - ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to the service layer. Verification - Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no collateral failures; restored, NEG-VAL residue = 0. - Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420). - admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0. - ui-tests 14/14 PASS.
This commit is contained in:
@@ -1 +1,49 @@
|
||||
Page({data:{},report(){wx.navigateTo({url:'/pages/report/report?type=content'})},related(){wx.navigateTo({url:'/pages/counseling-intro/counseling-intro'})}})
|
||||
/**
|
||||
* U54 内容详情
|
||||
*
|
||||
* Phase 19:此前正文与标题**写死在 wxml**(任何文章都显示同一篇演示内容)。
|
||||
* 现接入 GET /content/articles/{id},按 id 展示真实正文。
|
||||
*
|
||||
* 边界:用户端内容只做列表与详情,不做 Feed / 评论 / 点赞。
|
||||
*/
|
||||
const { contentService } = require('../../services');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: true,
|
||||
error: '',
|
||||
article: null,
|
||||
paragraphs: [],
|
||||
},
|
||||
onLoad(options) {
|
||||
this.articleId = (options && options.id) || '';
|
||||
this.load();
|
||||
},
|
||||
async load() {
|
||||
if (!this.articleId) {
|
||||
this.setData({ loading: false, error: '未指定内容' });
|
||||
return;
|
||||
}
|
||||
this.setData({ loading: true, error: '' });
|
||||
try {
|
||||
const article = await contentService.getArticleDetail(this.articleId);
|
||||
if (!article) {
|
||||
this.setData({ loading: false, error: '内容不存在或已下架' });
|
||||
return;
|
||||
}
|
||||
const paragraphs = String(article.content || '')
|
||||
.split(/\n+/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
this.setData({ article, paragraphs, loading: false });
|
||||
} catch (error) {
|
||||
this.setData({ loading: false, error: '加载失败,请稍后重试' });
|
||||
}
|
||||
},
|
||||
report() {
|
||||
wx.navigateTo({ url: '/pages/report/report?type=content' });
|
||||
},
|
||||
related() {
|
||||
wx.navigateTo({ url: '/pages/counseling-intro/counseling-intro' });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
<view class="service-page"><view class="hero"><text class="eyebrow">沟通成长 · 阅读约 5 分钟</text><text class="hero-title">第一次见面,怎样自然地开始交流</text><text class="hero-desc">善缘丹阳婚恋课堂</text></view><view class="article-content"><text class="lead">把注意力放在认识一个真实的人,而不是完成一场“面试”。</text><text>初次见面时,可以从当下的环境、共同参与的活动或轻松的生活话题开始。相比连续发问,认真倾听并回应对方刚刚说过的内容,更容易建立平等、自然的交流。</text><text style="display:block;margin-top:28rpx">不必急着判断结果,也不需要过度展示自己。尊重彼此的节奏,在涉及联系方式、住址和财务信息时保留必要边界。</text><view class="notice">本文为公益科普内容,不能替代专业咨询或法律意见。</view></view><view class="content"><view class="block"><text class="block-title">需要进一步帮助?</text><view class="card menu-row" bindtap="related"><view class="feature-icon">咨</view><view class="feature-copy"><text class="feature-title">了解婚恋咨询</text><text class="feature-desc">由咨询人员提供关系与情绪支持</text></view><text class="arrow">›</text></view></view></view></view>
|
||||
<view class="service-page"><view wx:if="{{loading}}" class="content"><view class="card empty-panel"><view class="empty-symbol">…</view><text class="empty-title">正在加载</text></view></view><view wx:elif="{{error}}" class="content"><view class="card empty-panel"><view class="empty-symbol">!</view><text class="empty-title">{{error}}</text></view></view><block wx:else><view class="hero"><text class="eyebrow">善缘丹阳婚恋课堂</text><text class="hero-title">{{article.title}}</text><text class="hero-desc">{{article.summary}}</text></view><view class="article-content"><text wx:if="{{article.summary}}" class="lead">{{article.summary}}</text><text wx:for="{{paragraphs}}" wx:key="*this" style="display:block;margin-top:28rpx">{{item}}</text><view class="notice">本文为公益科普内容,不能替代专业咨询或法律意见。</view></view></block><view class="content"><view class="block"><text class="block-title">需要进一步帮助?</text><view class="card menu-row" bindtap="related"><view class="feature-icon">咨</view><view class="feature-copy"><text class="feature-title">了解婚恋咨询</text><text class="feature-desc">由咨询人员提供关系与情绪支持</text></view><text class="arrow">›</text></view><view class="card menu-row" bindtap="report"><view class="feature-icon">报</view><view class="feature-copy"><text class="feature-title">举报不实内容</text><text class="feature-desc">发现虚假或违规信息可提交举报</text></view><text class="arrow">›</text></view></view></view></view>
|
||||
|
||||
Reference in New Issue
Block a user