落地埋点 ingest/数据看板、首页宫格 CMS 与测评上下架;含账号引导、问答流式与免责声明去重,以及 review P1 审计同事务修复。 Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.0 KiB
TypeScript
116 lines
3.0 KiB
TypeScript
import { computed, onMounted, reactive, ref, toRefs } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { api } from '../api/client'
|
|
import { AnalyticsEvent, track } from '../lib/analytics'
|
|
import {
|
|
homeFeeds,
|
|
homeGridRow1,
|
|
homeGridRow2,
|
|
homeSearchHints,
|
|
type HomeTool,
|
|
} from '../lib/homeCatalog'
|
|
import type { HomeToolIconName } from '../components/HomeToolIcon.vue'
|
|
import { useHomeMood } from './useHomeMood'
|
|
|
|
const ICONS = new Set([
|
|
'mbti', 'star', 'portrait', 'rhythm', 'synastry', 'astro',
|
|
'companion', 'ask', 'cards', 'reports', 'growth', 'relation',
|
|
])
|
|
|
|
function mapTools(
|
|
items: Array<{
|
|
row_index: number
|
|
sort_order: number
|
|
path: string
|
|
icon: string
|
|
label: string
|
|
badge?: string | null
|
|
badge_tone?: string | null
|
|
}>,
|
|
): { row1: HomeTool[]; row2: HomeTool[] } {
|
|
const toTool = (it: (typeof items)[0]): HomeTool | null => {
|
|
if (!ICONS.has(it.icon)) return null
|
|
const t: HomeTool = {
|
|
to: it.path,
|
|
icon: it.icon as HomeToolIconName,
|
|
label: it.label,
|
|
}
|
|
if (it.badge) t.badge = it.badge
|
|
if (it.badge_tone === 'hot' || it.badge_tone === 'new') t.badgeTone = it.badge_tone
|
|
return t
|
|
}
|
|
const sorted = [...items].sort(
|
|
(a, b) => a.row_index - b.row_index || a.sort_order - b.sort_order,
|
|
)
|
|
const row1: HomeTool[] = []
|
|
const row2: HomeTool[] = []
|
|
for (const it of sorted) {
|
|
const t = toTool(it)
|
|
if (!t) continue
|
|
if (it.row_index === 1) row1.push(t)
|
|
else if (it.row_index === 2) row2.push(t)
|
|
}
|
|
return { row1, row2 }
|
|
}
|
|
|
|
export function useHomePage() {
|
|
const router = useRouter()
|
|
const profileLabel = ref('自己')
|
|
const plusOpen = ref(false)
|
|
const mood = useHomeMood()
|
|
const grid = reactive({
|
|
gridRow1: [...homeGridRow1] as HomeTool[],
|
|
gridRow2: [...homeGridRow2] as HomeTool[],
|
|
})
|
|
|
|
onMounted(() => {
|
|
void api
|
|
.getHomeTools()
|
|
.then((res) => {
|
|
const mapped = mapTools(res.items || [])
|
|
if (mapped.row1.length || mapped.row2.length) {
|
|
grid.gridRow1 = mapped.row1
|
|
grid.gridRow2 = mapped.row2
|
|
}
|
|
})
|
|
.catch(() => {
|
|
/* keep static fallback */
|
|
})
|
|
})
|
|
|
|
const searchHint = computed(() => {
|
|
const i = new Date().getHours() % homeSearchHints.length
|
|
return homeSearchHints[i]
|
|
})
|
|
|
|
function goPlus(kind: 'inviteFill' | 'add' | 'synastry') {
|
|
plusOpen.value = false
|
|
track(AnalyticsEvent.HomeCtaPortrait, { source: 'home_plus', kind })
|
|
if (kind === 'inviteFill') {
|
|
router.push({ path: '/profile', query: { inviteFill: '1' } })
|
|
return
|
|
}
|
|
if (kind === 'add') {
|
|
router.push({ path: '/profile', query: { add: '1' } })
|
|
return
|
|
}
|
|
router.push({ path: '/synastry', query: { invite: '1' } })
|
|
}
|
|
|
|
function trackGrid(label: string) {
|
|
track(AnalyticsEvent.HomeCtaPortrait, { source: 'home_grid', label })
|
|
}
|
|
|
|
return {
|
|
router,
|
|
profileLabel,
|
|
plusOpen,
|
|
mood,
|
|
searchHint,
|
|
...toRefs(grid),
|
|
feeds: homeFeeds,
|
|
goPlus,
|
|
trackGrid,
|
|
}
|
|
}
|