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:
@@ -136,20 +136,25 @@ import {
|
||||
Bell,
|
||||
Calendar,
|
||||
ChatDotRound,
|
||||
Clock,
|
||||
Connection,
|
||||
DataAnalysis,
|
||||
DataBoard,
|
||||
DocumentChecked,
|
||||
Expand,
|
||||
Fold,
|
||||
House,
|
||||
Location,
|
||||
LocationFilled,
|
||||
Lock,
|
||||
Memo,
|
||||
Opportunity,
|
||||
Picture,
|
||||
Reading,
|
||||
Search,
|
||||
Setting,
|
||||
StarFilled,
|
||||
Tickets,
|
||||
User,
|
||||
UserFilled,
|
||||
Warning,
|
||||
@@ -169,14 +174,20 @@ const navigation: NavigationItem[] = [
|
||||
{ label: '资料审核', path: '/review', permissions: ['review.view'], icon: DocumentChecked },
|
||||
{ label: '匹配管理', path: '/activities/interests', permissions: ['activity.review_registration'], icon: StarFilled },
|
||||
{ label: '公益红娘', path: '/referral', permissions: ['referral.manage'], icon: Connection },
|
||||
{ label: '红娘服务', path: '/matchmaker', permissions: ['matchmaker.service.view'], icon: Connection },
|
||||
{ label: '公益咨询', path: '/counseling', permissions: ['counseling.service.view'], icon: ChatDotRound },
|
||||
{ label: '活动管理', path: '/activities', permissions: ['activity.view'], icon: Calendar },
|
||||
{ label: '活动签到', path: '/activities/checkin', permissions: ['activity.checkin'], icon: Calendar },
|
||||
{ label: 'Banner管理', path: '/banner', permissions: ['banner.view'], icon: Picture },
|
||||
{ label: '内容管理', path: '/content', permissions: ['content.view'], icon: Reading },
|
||||
{ label: '善缘服务站', path: '/service-station', permissions: ['service_location.view'], icon: Location },
|
||||
{ label: '服务预约', path: '/service-appointment', permissions: ['service_appointment.view'], icon: House },
|
||||
{ label: '专项服务', path: '/service-program', permissions: ['service_program.manage'], icon: Tickets },
|
||||
{ label: '举报风控', path: '/risk/reports', permissions: ['risk.report.view'], icon: Warning },
|
||||
{ label: '申诉管理', path: '/appeals', permissions: ['risk.report.process'], icon: Memo },
|
||||
{ label: '数据统计', path: '/analytics', permissions: ['analytics.view'], icon: DataAnalysis },
|
||||
{ label: '组织人员', path: '/organizations', permissions: ['organization.manage'], icon: UserFilled },
|
||||
{ label: '操作日志', path: '/audit', permissions: ['user.view'], icon: Clock },
|
||||
{ label: '系统设置', path: '/operators', permissions: ['permission.manage'], icon: Setting },
|
||||
]
|
||||
|
||||
@@ -195,10 +206,17 @@ const currentTitle = computed(() => (route.meta?.title as string) || '页面')
|
||||
const operator = computed(() => authStore.operator)
|
||||
const permissions = computed(() => authStore.permissions || [])
|
||||
const currentRoleName = computed(() => {
|
||||
if (permissions.value.includes('permission.manage')) return '平台管理员'
|
||||
if (permissions.value.includes('counseling.service.view')) return '公益咨询人员'
|
||||
if (permissions.value.includes('service_appointment.view')) return '服务站运营人员'
|
||||
if (permissions.value.includes('review.view')) return '资料审核人员'
|
||||
// 按权限从高到低判定,覆盖七类后台角色(平台管理员 / 咨询管理员 / 内容编辑员 /
|
||||
// 风控运营 / 公益咨询 / 服务站运营 / 资料审核 / 公益红娘)。
|
||||
const perms = permissions.value
|
||||
if (perms.includes('permission.manage')) return '平台管理员'
|
||||
if (perms.includes('counseling.service.assign') || perms.includes('counseling.note.view')) return '咨询管理员'
|
||||
if (perms.includes('content.publish')) return '内容编辑员'
|
||||
if (perms.includes('risk.report.process')) return '风控运营人员'
|
||||
if (perms.includes('counseling.service.view')) return '公益咨询人员'
|
||||
if (perms.includes('service_appointment.view')) return '服务站运营人员'
|
||||
if (perms.includes('review.view')) return '资料审核人员'
|
||||
if (perms.includes('matchmaker.service.view')) return '公益红娘'
|
||||
return '运营人员'
|
||||
})
|
||||
const visibleNavigation = computed(() => navigation.filter((item) => item.permissions.every(hasPerm)))
|
||||
|
||||
Reference in New Issue
Block a user