feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具
落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<main class="yxg-page">
|
||||
<div class="yxg-hero">
|
||||
<BackButton />
|
||||
<PageTitle feature="reports" title="我的成长报告" sub="按类型筛选 · 愈心解码 / 星座 / 节律 / 匹配 / 意象" />
|
||||
</div>
|
||||
|
||||
<div class="yxg-sheet">
|
||||
<div class="yxg-chips">
|
||||
<button
|
||||
v-for="f in filters"
|
||||
:key="f.key"
|
||||
type="button"
|
||||
class="yxg-chip yxg-chip-solid"
|
||||
:class="{ on: filter === f.key }"
|
||||
@click="filter = f.key"
|
||||
>{{ f.label }}</button>
|
||||
</div>
|
||||
|
||||
<p v-if="loading" class="yxg-hint pad">加载中…</p>
|
||||
<p v-else-if="error" class="yxg-err pad">
|
||||
{{ error }}
|
||||
<button type="button" class="yxg-link" @click="load">重试</button>
|
||||
</p>
|
||||
<template v-else>
|
||||
<div v-if="!filtered.length" class="yxg-card empty">
|
||||
<p>还没有{{ filter === 'all' ? '' : '该类' }}成长报告。</p>
|
||||
<router-link class="yxg-btn" to="/explore">去探索</router-link>
|
||||
</div>
|
||||
<ul v-else class="yxg-list">
|
||||
<li v-for="r in filtered" :key="r.id">
|
||||
<router-link :to="`/reports/${r.id}`">
|
||||
<span class="yxg-body">
|
||||
<strong>{{ typeLabel(r.type) }}</strong>
|
||||
<span class="desc">{{ headlineOf(r) }}</span>
|
||||
<span class="meta">{{ formatDate(r.created_at) }}</span>
|
||||
</span>
|
||||
<span class="chev" aria-hidden="true">›</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { GrowthReport } from '@yuxingu/types'
|
||||
import { api } from '../api/client'
|
||||
import BackButton from '../components/BackButton.vue'
|
||||
import PageTitle from '../components/PageTitle.vue'
|
||||
|
||||
const items = ref<GrowthReport[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const filter = ref('all')
|
||||
|
||||
const filters = [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'portrait', label: '解码' },
|
||||
{ key: 'star', label: '星座' },
|
||||
{ key: 'synastry', label: '合盘' },
|
||||
{ key: 'rhythm', label: '节律' },
|
||||
{ key: 'relation', label: '匹配' },
|
||||
{ key: 'image_card', label: '意象' },
|
||||
]
|
||||
|
||||
const filtered = computed(() => {
|
||||
if (filter.value === 'all') return items.value
|
||||
return items.value.filter((r) => r.type === filter.value)
|
||||
})
|
||||
|
||||
function typeLabel(t: string) {
|
||||
const map: Record<string, string> = {
|
||||
portrait: '△ 愈心解码',
|
||||
relation: '♡ 人格匹配',
|
||||
synastry: '✧ 合盘',
|
||||
star: '✦ 星座',
|
||||
rhythm: '☯ 身心节律',
|
||||
image_card: '◈ 意象卡片',
|
||||
}
|
||||
return map[t] || '▣ 成长报告'
|
||||
}
|
||||
|
||||
function headlineOf(r: GrowthReport) {
|
||||
const s = (r.summary || {}) as Record<string, unknown>
|
||||
return String(s.headline || s.diff_one_liner || s.one_liner || '查看报告')
|
||||
}
|
||||
|
||||
function formatDate(v: string) {
|
||||
try {
|
||||
return new Date(v).toLocaleString('zh-CN')
|
||||
} catch {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await api.listReports()
|
||||
items.value = res.items || []
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pad{padding:0 16px}
|
||||
.empty .yxg-btn{margin-top:12px}
|
||||
</style>
|
||||
Reference in New Issue
Block a user