Files
danyang-match/miniapp/pages/service-station/service-station.wxml
T
WorkBuddy 72b4e9b846 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.
2026-09-17 22:21:31 +08:00

40 lines
3.5 KiB
Plaintext

<view class="service-shell station-page app-page reference-page">
<ui-service-hero eyebrow="公益服务 · 就在身边" title="善缘服务站" subtitle="面对面提供可靠、温暖的本地婚恋服务" image-src="/assets/service/station-hero.jpg" tone="rose" />
<view class="service-content">
<ui-loading wx:if="{{loading}}" mode="skeleton" rows="4" />
<ui-error-state wx:elif="{{loadError}}" title="暂时无法加载服务站" description="请检查网络后重试" bind:retry="onRetry" />
<ui-empty wx:elif="{{!serviceLocation}}" illustration="default" title="所在区域暂无服务站" description="新的公益服务阵地正在筹备中" />
<block wx:else>
<view class="station-name-card"><image src="/assets/icons/home/service-station.png" mode="aspectFit" /><view><text class="station-name">{{serviceLocation.name}}</text><text class="station-short-name">{{serviceLocation.shortName || '善缘丹阳公益婚恋服务阵地'}}</text></view></view>
<view class="station-info-card">
<view class="station-info-row"><image src="/assets/icons/home/service-station.png" mode="aspectFit" /><view><text class="station-info-label">地址</text><text class="station-info-value">{{serviceLocation.address || '以预约确认为准'}}</text></view></view>
<view class="station-info-row" bindtap="call"><image src="/assets/icons/home/counseling.png" mode="aspectFit" /><view><text class="station-info-label">联系电话</text><text class="station-info-value">{{serviceLocation.contactPhoneDisplay || '以预约确认为准'}}</text></view><image class="station-arrow" src="/assets/icons/ui/chevron-right.png" mode="aspectFit" /></view>
<view class="station-info-row"><image src="/assets/icons/home/activity.png" mode="aspectFit" /><view><text class="station-info-label">开放时间</text><text class="station-info-value">{{serviceLocation.openingHoursText || '以平台通知为准'}}</text></view></view>
</view>
<view class="block" wx:if="{{announcements.length}}">
<text class="block-title">临时公告</text>
<view class="station-announcements">
<view
wx:for="{{announcements}}"
wx:key="id"
class="station-announcement"
data-id="{{item.id}}"
bindtap="onShowAnnouncement"
>
<view class="station-announcement-head"><text class="station-announcement-dot"></text><text class="station-announcement-title">{{item.title}}</text></view>
<text class="station-announcement-body">{{item.content || item.title}}</text>
</view>
</view>
</view>
<view class="block"><text class="block-title">服务项目</text><view class="station-services"><view wx:for="{{serviceLocation.services}}" wx:key="*this" class="station-service"><image src="/assets/icons/home/matchmaker.png" mode="aspectFit" /><text>{{item}}</text></view></view></view>
<view class="privacy-note"><image src="/assets/icons/home/anti-fraud.png" mode="aspectFit" /><text>服务站人员仅处理现场预约与指定服务,不会浏览无关用户资料。</text></view>
<view class="map-button" bindtap="openMap"><image src="/assets/icons/home/service-station.png" mode="aspectFit" /><text>地图定位</text></view>
</block>
</view>
<view wx:if="{{serviceLocation}}" class="fixed-action"><view class="service-actions"><view class="secondary-btn" bindtap="call">联系服务站</view><view class="primary-btn" bindtap="book">预约到站服务</view></view></view>
</view>