Files
digital-psychology/apps/user-h5/src/composables/useSynastryPage.ts
T
jackyu66gitandCursor 13860bf1ef
ci / h5 (push) Canceled after 0s
ci / api (push) Canceled after 0s
ci / ess-docs (push) Canceled after 0s
feat(ECR-011): 昵称、首页贴士与档案 Self 唯一/合盘交叉校验
每账号仅一条 self(migration 40902);合盘只选 TA;账号昵称可改;首页穿衣/颜色/养生贴士。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 22:05:00 +08:00

220 lines
6.3 KiB
TypeScript

import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { GrowthReport, Profile } from '@yuxingu/types'
import {
asPlanets,
ascLonFromChart,
birthLabel,
buildRelationSharePayload,
chartBlock,
chartPlanetsFromSummary,
chartTip as chartTipFromCharts,
labelList,
mainTabs,
overlayEntriesFrom,
profileInitial,
relationTypes,
resolveActiveChartKey,
sectionsFromDetail,
type SynastryMainTab,
} from '../lib/synastryChart'
import { ensureAccount } from '../lib/authSession'
import { createSynastryPageActions } from './synastryPageActions'
export type MainTab = SynastryMainTab
export { relationTypes, mainTabs, birthLabel, profileInitial }
export function useSynastryPage() {
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const adding = ref(false)
const paying = ref(false)
const inviting = ref(false)
const nearbyLoading = ref(false)
const error = ref('')
const nearbyHint = ref('')
const invitePath = ref('')
const inviteHighlight = ref(false)
const profiles = ref<Profile[]>([])
const nearby = ref<{ profile: Profile; distance_km: number }[]>([])
const profileA = ref('')
const profileB = ref('')
const asOf = ref(new Date().toISOString().slice(0, 10))
const report = ref<GrowthReport | null>(null)
const shareOpen = ref(false)
const ty = ref('')
const tm = ref('')
const td = ref('')
const tName = ref('TA')
const mainTab = ref<MainTab>('compare')
const subTab = ref<'natal' | 'prog'>('natal')
const marksWho = ref<'me' | 'other'>('me')
const relationType = ref('伴侣')
const showClassicSelects = ref(false)
const showQuickAdd = ref(false)
const needsSubTab = computed(() => ['composite', 'davison', 'marks'].includes(mainTab.value))
const pickableProfiles = computed(() =>
profiles.value.filter((p) => p.relation === 'other' && p.id !== profileA.value),
)
const selfProfiles = computed(() => profiles.value.filter((p) => p.relation === 'self'))
const profileAName = computed(() => {
const p = profiles.value.find((x) => x.id === profileA.value)
return p?.display_name || ''
})
const selfInitial = computed(() => (profileAName.value || '我').slice(0, 1))
const summary = computed(() => (report.value?.summary || {}) as Record<string, unknown>)
const detail = computed(() => (report.value?.detail || null) as Record<string, unknown> | null)
const charts = computed(() => (summary.value.charts || {}) as Record<string, unknown>)
const headline = computed(() => String(summary.value.headline || ''))
const oneLiner = computed(() => String(summary.value.one_liner || ''))
const love = computed(() => Number(summary.value.love_index || 0))
const friend = computed(() => Number(summary.value.friend_index || 0))
const marriage = computed(() => Number(summary.value.marriage_index || 0))
const loveNote = computed(() => String(summary.value.love_note || ''))
const asOfLabel = computed(() => String(summary.value.as_of || asOf.value))
const planetsA = computed(() => chartPlanetsFromSummary(summary.value, 'chart_a'))
const planetsB = computed(() => chartPlanetsFromSummary(summary.value, 'chart_b'))
const ascA = computed(() => ascLonFromChart(summary.value.chart_a))
const ascB = computed(() => ascLonFromChart(summary.value.chart_b))
const aspectPreview = computed(() => labelList(summary.value.aspects_preview))
function chartTip(key: string): string {
return chartTipFromCharts(charts.value, key)
}
const activeChartKey = computed(() =>
resolveActiveChartKey(mainTab.value, subTab.value, marksWho.value),
)
const activePlanets = computed(() => asPlanets(chartBlock(charts.value, activeChartKey.value)?.planets))
const activeAsc = computed(() => {
const lon = chartBlock(charts.value, activeChartKey.value)?.asc_lon
return typeof lon === 'number' ? lon : null
})
const activeAspectPreview = computed(() =>
labelList(chartBlock(charts.value, activeChartKey.value)?.aspects_preview),
)
const activeChartTip = computed(() => chartTip(activeChartKey.value))
const overlayTip = computed(() => String(chartBlock(charts.value, 'overlay')?.tip || ''))
const overlayEntries = computed(() => overlayEntriesFrom(charts.value))
const fullAspects = computed(() => labelList(detail.value?.aspects))
const sections = computed(() => sectionsFromDetail(detail.value))
const sharePayload = computed(() =>
buildRelationSharePayload(
report.value,
summary.value,
headline.value,
love.value,
friend.value,
marriage.value,
),
)
const { loadProfiles, addTemp, generate, createInvite, loadNearby, buyDeep, reset } =
createSynastryPageActions({
loading,
adding,
paying,
inviting,
nearbyLoading,
error,
nearbyHint,
invitePath,
profiles,
nearby,
profileA,
profileB,
asOf,
report,
ty,
tm,
td,
tName,
mainTab,
showQuickAdd,
})
onMounted(async () => {
if (!(await ensureAccount(router, route.fullPath))) return
await loadProfiles()
if (route.query.invite === '1') {
inviteHighlight.value = true
if (profileA.value) {
await createInvite()
} else {
error.value = '请先完成自己的档案,再邀请好友合盘'
}
void router.replace({ path: '/synastry', query: {} })
}
})
return {
loading,
adding,
paying,
inviting,
nearbyLoading,
error,
nearbyHint,
invitePath,
inviteHighlight,
profiles,
nearby,
profileA,
profileB,
asOf,
report,
shareOpen,
ty,
tm,
td,
tName,
mainTab,
subTab,
marksWho,
relationType,
showClassicSelects,
showQuickAdd,
needsSubTab,
pickableProfiles,
selfProfiles,
profileAName,
selfInitial,
profileInitial,
headline,
oneLiner,
love,
friend,
marriage,
loveNote,
asOfLabel,
planetsA,
planetsB,
ascA,
ascB,
aspectPreview,
chartTip,
activePlanets,
activeAsc,
activeAspectPreview,
activeChartTip,
overlayTip,
overlayEntries,
fullAspects,
sections,
sharePayload,
detail,
birthLabel,
addTemp,
generate,
createInvite,
loadNearby,
buyDeep,
reset,
}
}