refactor(ECR-005): 再拆 Ask/Decode/Portrait 与 composable,补强 OpenAPI
ci / h5 (push) Canceled after 0s
ci / api (push) Canceled after 0s
ci / ess-docs (push) Canceled after 0s

压低贴线 SFC/composable;报告/档案/量表/意象卡 path 挂 Envelope $ref;CI ESS 门禁跟到 ECR-005。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 22:38:32 +08:00
co-authored by Cursor
parent 7034c92de7
commit 00fc7f82b9
31 changed files with 2017 additions and 1251 deletions
+19 -123
View File
@@ -56,7 +56,6 @@
<p class="yxg-disc">自我探索与生活方式参考不是占卜或算命体质内容非医疗建议</p>
</div>
<!-- Sticky bottom CTAs -->
<div v-if="report" class="sticky-bar">
<router-link class="sticky-btn" to="/ask">去问答</router-link>
<router-link class="sticky-btn primary" to="/relation">去做匹配</router-link>
@@ -68,135 +67,32 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { GrowthReport } from '@yuxingu/types'
import { validateBirth } from '@yuxingu/utils'
import { api } from '../api/client'
import BackButton from '../components/BackButton.vue'
import BirthDateInputs from '../components/BirthDateInputs.vue'
import DecodePanel from '../components/DecodePanel.vue'
import HomeToolIcon from '../components/HomeToolIcon.vue'
import PageShell from '../components/PageShell.vue'
import ShareSheet from '../components/ShareSheet.vue'
import { AnalyticsEvent, track } from '../lib/analytics'
import type { PortraitSharePayload } from '../lib/shareLink'
import { usePortraitPage } from '../composables/usePortraitPage'
const route = useRoute()
const loading = ref(false)
const needBirth = ref(true)
const error = ref('')
const formError = ref('')
const report = ref<GrowthReport | null>(null)
const paying = ref(false)
const shareOpen = ref(false)
const year = ref(String(route.query.y || ''))
const month = ref(String(route.query.m || ''))
const day = ref(String(route.query.d || ''))
const summary = computed(() => (report.value?.summary || {}) as Record<string, unknown>)
const detail = computed(() => (report.value?.detail || null) as Record<string, unknown> | null)
const headline = computed(() => String(summary.value.headline || ''))
const oneLiner = computed(() => String(summary.value.one_liner || ''))
const keywords = computed(() => (Array.isArray(summary.value.keywords) ? (summary.value.keywords as string[]) : []))
const sharePayload = computed<PortraitSharePayload | null>(() => {
if (!report.value) return null
return {
type: 'portrait',
title: headline.value || String(summary.value.person_title || '愈心解码'),
line: oneLiner.value,
keywords: keywords.value,
}
})
async function generate(y: number, m: number, d: number) {
loading.value = true
error.value = ''
formError.value = ''
needBirth.value = false
try {
const birth = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
const profile = await api.createProfile({ relation: 'self', birth_date: birth, display_name: '我' })
report.value = await api.createPortrait(profile.id)
track(AnalyticsEvent.PortraitCompleted, { source: 'form' })
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
needBirth.value = true
} finally {
loading.value = false
}
}
async function start() {
const y = Number(year.value)
const m = Number(month.value)
const d = Number(day.value)
const msg = validateBirth(y, m, d)
if (msg) {
formError.value = msg
return
}
await generate(y, m, d)
}
async function load() {
const reportId = String(route.query.report_id || '')
if (reportId) {
loading.value = true
needBirth.value = false
error.value = ''
try {
report.value = await api.getReport(reportId)
track(AnalyticsEvent.PortraitCompleted, { source: 'report_id' })
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
needBirth.value = true
} finally {
loading.value = false
}
return
}
const y = Number(route.query.y)
const m = Number(route.query.m)
const d = Number(route.query.d)
if (y && m && d && !validateBirth(y, m, d)) {
year.value = String(y)
month.value = String(m)
day.value = String(d)
await generate(y, m, d)
return
}
needBirth.value = true
loading.value = false
}
function retry() {
if (report.value) {
load()
return
}
needBirth.value = true
error.value = ''
}
async function buyDeep() {
if (!report.value) return
paying.value = true
error.value = ''
track(AnalyticsEvent.DeepAccessClicked, { surface: 'portrait' })
try {
const { order_id } = await api.createOrder({ kind: 'deep_access', report_id: report.value.id })
await api.payMock(order_id)
report.value = await api.getReport(report.value.id)
track(AnalyticsEvent.PurchaseCompleted, { kind: 'deep_access' })
} catch (e) {
error.value = e instanceof Error ? e.message : '支付失败'
} finally {
paying.value = false
}
}
onMounted(load)
const {
loading,
needBirth,
error,
formError,
report,
paying,
shareOpen,
year,
month,
day,
summary,
detail,
sharePayload,
start,
retry,
buyDeep,
} = usePortraitPage()
</script>
<style scoped>