Files
digital-psychology/apps/user-h5/src/pages/RelationPage.vue
T
jackyu66gitandCursor 14b836f53b feat: add RelationInsight API and wire H5 relation/profile pages
Second P1 growth engine: compare two profiles, gate full tips behind
deep-access mock payment, and list personal archives on /profile.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 16:28:10 +08:00

146 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<main class="page">
<button class="back" type="button" @click="$router.back()"> 返回</button>
<h1>关系理解</h1>
<p class="sub">添加重要的人了解双方差异与相处建议</p>
<div class="card">
<label>TA 的称呼</label>
<input v-model="otherName" placeholder="例如:伴侣" />
<label>TA 的生日</label>
<div class="row">
<input v-model.number="oy" type="number" placeholder="年" />
<input v-model.number="om" type="number" placeholder="月" />
<input v-model.number="od" type="number" placeholder="日" />
</div>
<p class="hint">将使用你最近一份的档案进行对比若没有会先引导去首页创建个人画像</p>
<button type="button" :disabled="loading" @click="run">生成关系理解</button>
<p v-if="error" class="err">{{ error }}</p>
</div>
<template v-if="report">
<div class="card">
<p><strong></strong>{{ meStyle }}</p>
<p><strong>TA</strong>{{ otherStyle }}</p>
<p class="line">{{ diff }}</p>
<div class="tags">
<span v-for="k in meKeys" :key="'a'+k">·{{ k }}</span>
<span v-for="k in otherKeys" :key="'b'+k">TA·{{ k }}</span>
</div>
</div>
<div v-if="report.has_deep_access && detail" class="card">
<h2>相处建议</h2>
<ul>
<li v-for="(t, i) in tips" :key="i">{{ t }}</li>
</ul>
</div>
<div v-else class="card lock">
<p>完整相处建议可在深度版或成长会员中查看</p>
<button type="button" :disabled="paying" @click="buyDeep">查看深度版模拟支付</button>
</div>
</template>
</main>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { GrowthReport, Profile } from '@yuxingu/types'
import { api } from '../api/client'
const otherName = ref('TA')
const oy = ref<number | null>(1992)
const om = ref<number | null>(6)
const od = ref<number | null>(8)
const loading = ref(false)
const paying = ref(false)
const error = ref('')
const report = ref<GrowthReport | null>(null)
const summary = computed(() => (report.value?.summary || {}) as Record<string, unknown>)
const detail = computed(() => (report.value?.detail || null) as Record<string, unknown> | null)
const meStyle = computed(() => String(summary.value.me_style || ''))
const otherStyle = computed(() => String(summary.value.other_style || ''))
const diff = computed(() => String(summary.value.diff_one_liner || ''))
const meKeys = computed(() => (Array.isArray(summary.value.me_keywords) ? summary.value.me_keywords as string[] : []))
const otherKeys = computed(() => (Array.isArray(summary.value.other_keywords) ? summary.value.other_keywords as string[] : []))
const tips = computed(() => {
const d = detail.value
if (!d) return [] as string[]
const a = Array.isArray(d.communication) ? d.communication as string[] : []
const b = Array.isArray(d.interaction) ? d.interaction as string[] : []
const c = Array.isArray(d.maintenance) ? d.maintenance as string[] : []
return [...a, ...b, ...c]
})
async function ensureSelf(): Promise<Profile> {
const { items } = await api.listProfiles()
const self = (items || []).find((p) => p.relation === 'self')
if (self) return self
throw new Error('请先在首页完成性格探索,创建「我」的个人档案')
}
async function run() {
loading.value = true
error.value = ''
report.value = null
try {
if (!oy.value || !om.value || !od.value) {
throw new Error('请填写 TA 的完整生日')
}
const self = await ensureSelf()
const birth = `${oy.value}-${String(om.value).padStart(2, '0')}-${String(od.value).padStart(2, '0')}`
const other = await api.createProfile({
relation: 'other',
birth_date: birth,
display_name: otherName.value || 'TA',
relation_type: 'partner',
})
const out = await api.createRelationInsight(self.id, other.id)
report.value = out.report
} catch (e) {
error.value = e instanceof Error ? e.message : '生成失败'
} finally {
loading.value = false
}
}
async function buyDeep() {
if (!report.value) return
paying.value = true
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)
} catch (e) {
error.value = e instanceof Error ? e.message : '支付失败'
} finally {
paying.value = false
}
}
</script>
<style scoped>
.page{padding:16px}
.back{border:none;background:#fff;border-radius:10px;padding:8px 12px;margin-bottom:12px}
h1{font-size:22px}
h2{font-size:16px;margin-bottom:8px}
.sub{color:var(--yxg-sub);margin:8px 0 14px;font-size:13px}
.card{background:#fff;border-radius:16px;padding:18px;font-size:14px;line-height:1.7;color:#555;margin-bottom:12px}
label{display:block;font-size:12px;color:#999;margin:8px 0 4px}
input{width:100%;box-sizing:border-box;padding:10px;border:1.5px solid #eee;border-radius:12px;margin-bottom:4px}
.row{display:flex;gap:8px}
.row input{flex:1}
.hint{font-size:12px;color:#aaa;margin:8px 0}
button{
margin-top:10px;padding:10px 16px;border:none;border-radius:22px;color:#fff;font-weight:600;
background:linear-gradient(135deg,#ff7a6e,var(--yxg-pri));
}
button:disabled{opacity:.6}
.err{color:var(--yxg-pri);font-size:13px;margin-top:8px}
.line{margin-top:8px;color:#333}
.tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.tags span{background:#fff3d6;color:#c8923a;padding:4px 10px;border-radius:999px;font-size:12px}
ul{padding-left:18px;margin:0}
li{margin:6px 0}
</style>