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:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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('会员')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user