去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。 Co-authored-by: Cursor <cursoragent@cursor.com>
129 lines
4.5 KiB
Vue
129 lines
4.5 KiB
Vue
<template>
|
||
<yxg-page title="愈心解码">
|
||
<text v-if="loading" class="hint">生成中…</text>
|
||
<text v-else-if="error" class="err">{{ error }}</text>
|
||
<view v-else-if="needBirth" class="card">
|
||
<text class="h1">一个生日,读懂性格与身心节奏</text>
|
||
<text class="desc">填写公历生日,生成你的愈心解码。</text>
|
||
<view class="date-row">
|
||
<input class="inp" type="number" v-model="year" placeholder="年" />
|
||
<input class="inp" type="number" v-model="month" placeholder="月" />
|
||
<input class="inp" type="number" v-model="day" placeholder="日" />
|
||
</view>
|
||
<text v-if="formError" class="err">{{ formError }}</text>
|
||
<view class="btn" @click="start"><text>开始解码</text></view>
|
||
</view>
|
||
<view v-else-if="report" class="card">
|
||
<text class="h1">{{ headline }}</text>
|
||
<text class="lead">{{ oneLiner }}</text>
|
||
<view class="tags">
|
||
<text v-for="k in keywords" :key="k" class="tag">{{ k }}</text>
|
||
</view>
|
||
<text v-if="bodyText" class="body">{{ bodyText }}</text>
|
||
<view class="navs">
|
||
<view class="btn ghost" @click="needBirth = true"><text>重新生成</text></view>
|
||
<view class="btn" @click="yxgGo(`/reports/${report.id}`)"><text>完整报告</text></view>
|
||
</view>
|
||
</view>
|
||
</yxg-page>
|
||
</template>
|
||
|
||
<script setup>
|
||
import YxgPage from '@/components/yxg/yxg-page.vue'
|
||
import { yxgApi } from '@/util/yxgApi.js'
|
||
import { ensureAccount, ensureSelfProfile, loadSelfLatest } from '@/util/yxgAuth.js'
|
||
import { yxgGo } from '@/util/yxgNav.js'
|
||
|
||
const loading = ref(false)
|
||
const needBirth = ref(true)
|
||
const error = ref('')
|
||
const formError = ref('')
|
||
const report = ref(null)
|
||
const year = ref('')
|
||
const month = ref('')
|
||
const day = ref('')
|
||
|
||
const summary = computed(() => report.value?.summary || {})
|
||
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 : []))
|
||
const bodyText = computed(() => String(report.value?.detail?.narrative || report.value?.detail?.body_text || summary.value.overview || ''))
|
||
|
||
async function generate(y, m, d) {
|
||
loading.value = true
|
||
error.value = ''
|
||
needBirth.value = false
|
||
try {
|
||
const birth = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
|
||
const profile = await ensureSelfProfile({ birth_date: birth, display_name: '我' })
|
||
report.value = await yxgApi.createPortrait(profile.id)
|
||
} catch (e) {
|
||
error.value = e instanceof Error ? e.message : '生成失败'
|
||
needBirth.value = true
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function start() {
|
||
const y = Number(year.value), m = Number(month.value), d = Number(day.value)
|
||
if (!y || !m || !d) {
|
||
formError.value = '请填写完整生日'
|
||
return
|
||
}
|
||
generate(y, m, d)
|
||
}
|
||
|
||
async function load() {
|
||
if (!(await ensureAccount('/portrait'))) return
|
||
loading.value = true
|
||
try {
|
||
const latest = await loadSelfLatest('portrait')
|
||
if (latest.report) {
|
||
report.value = latest.report
|
||
needBirth.value = false
|
||
} else {
|
||
needBirth.value = true
|
||
if (latest.profile?.birth_date) {
|
||
const [y, m, d] = String(latest.profile.birth_date).split('-')
|
||
year.value = y
|
||
month.value = m
|
||
day.value = d
|
||
}
|
||
}
|
||
} catch (e) {
|
||
error.value = e instanceof Error ? e.message : '加载失败'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onLoad((q) => {
|
||
if (q.y) year.value = q.y
|
||
if (q.m) month.value = q.m
|
||
if (q.d) day.value = q.d
|
||
load()
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
page { background-color: #ffd1c7; }
|
||
</style>
|
||
<style scoped>
|
||
.hint, .err { display: block; padding: 12px 16px; font-size: 13px; color: #999; }
|
||
.err { color: #e54d42; }
|
||
.card { margin: 12px 16px; padding: 18px 16px; background: #fff; border-radius: 18px; }
|
||
.h1 { display: block; font-size: 20px; font-weight: 700; color: #333; }
|
||
.desc, .lead, .body { display: block; margin-top: 10px; font-size: 14px; color: #555; line-height: 1.65; }
|
||
.date-row { display: flex; gap: 8px; margin: 14px 0; }
|
||
.inp { flex: 1; height: 42px; background: #fdfaf8; border-radius: 12px; padding: 0 10px; text-align: center; }
|
||
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||
.tag { padding: 3px 8px; border-radius: 8px; background: #ffe4e4; color: #e54d42; font-size: 11px; }
|
||
.navs { display: flex; gap: 10px; margin-top: 16px; }
|
||
.btn {
|
||
flex: 1; height: 42px; border-radius: 999px; background: #e54d42; color: #fff;
|
||
display: flex; align-items: center; justify-content: center; font-weight: 700;
|
||
}
|
||
.btn.ghost { background: #f7f2ef; color: #8a4a3a; }
|
||
</style>
|