feat(ECR-041): OpsCMS Banner 薄写面(Write-Wave 首刀)

Admin POST/PUT + admin.cms.write/审计;C 端 GET /home/banners;首页投影回退静态 homeFeeds;migration 000051;不碰 FeedSlot/支付/UGC。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-13 19:55:27 +08:00
co-authored by Cursor
parent ad45d17dff
commit 9707b72808
35 changed files with 1203 additions and 29 deletions
+13 -1
View File
@@ -6,6 +6,8 @@ import {
homeFeeds,
homeGridRow1,
homeGridRow2,
mapHomeBannersToFeeds,
type HomeFeed,
type HomeTool,
} from '../lib/homeCatalog'
import type { HomeToolIconName } from '../components/HomeToolIcon.vue'
@@ -62,6 +64,7 @@ export function useHomePage() {
gridRow1: [...homeGridRow1] as HomeTool[],
gridRow2: [...homeGridRow2] as HomeTool[],
})
const feeds = ref<HomeFeed[]>([...homeFeeds])
onMounted(() => {
void api
@@ -89,6 +92,15 @@ export function useHomePage() {
.catch(() => {
/* keep static fallback */
})
void api
.getHomeBanners('home')
.then((res) => {
const mapped = mapHomeBannersToFeeds(res.items || [])
if (mapped.length) feeds.value = mapped
})
.catch(() => {
/* keep static homeFeeds */
})
})
function goPlus(kind: 'inviteFill' | 'add' | 'synastry') {
@@ -115,7 +127,7 @@ export function useHomePage() {
tips,
tipsLoading,
...toRefs(grid),
feeds: homeFeeds,
feeds,
goPlus,
trackGrid,
}
+11 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { homeFeeds, homeGridRow1, homeGridRow2 } from '../lib/homeCatalog'
import { homeFeeds, homeGridRow1, homeGridRow2, mapHomeBannersToFeeds } from '../lib/homeCatalog'
describe('homeCatalog', () => {
it('keeps 12-grid rows', () => {
@@ -14,4 +14,14 @@ describe('homeCatalog', () => {
expect(f.title.length).toBeGreaterThan(0)
}
})
it('maps banners to feeds and drops external links', () => {
const mapped = mapHomeBannersToFeeds([
{ title: '会员', link_path: '/membership', sort_order: 2 },
{ title: '坏链', link_path: 'https://x.example', sort_order: 1 },
])
expect(mapped).toHaveLength(1)
expect(mapped[0].to).toBe('/membership')
expect(mapped[0].title).toBe('会员')
})
})
+26
View File
@@ -90,3 +90,29 @@ export const homeFeeds: HomeFeed[] = [
tone: 'fc-e',
},
]
const FEED_TONES = ['fc-e', 'fc-a', 'fc-b', 'fc-c', 'fc-a', 'fc-e'] as const
/** Project active home banners into feed cards (ECR-041). */
export function mapHomeBannersToFeeds(
items: Array<{ title: string; link_path?: string | null; sort_order?: number }>,
): HomeFeed[] {
const tones = FEED_TONES
const out: HomeFeed[] = []
const sorted = [...items].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
for (let i = 0; i < sorted.length; i++) {
const it = sorted[i]
const to = (it.link_path || '').trim()
if (!to.startsWith('/') || to.includes('://')) continue
out.push({
to,
icon: 'portrait',
title: it.title,
meta: '运营推荐',
stat: '推荐',
tone: tones[i % tones.length],
tag: '荐',
})
}
return out
}