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:
@@ -1,65 +1,174 @@
|
||||
<template>
|
||||
<!-- 测测式首页:暖色顶区 → 建档卡 → chips → 白 sheet(金刚区 / feed / 横滑) -->
|
||||
<main class="home">
|
||||
<header class="brand">
|
||||
<div class="word">愈<span>心</span>谷</div>
|
||||
<p class="tag">认识自己 · 理解他人</p>
|
||||
</header>
|
||||
<span class="glow g1" aria-hidden="true" />
|
||||
<span class="glow g2" aria-hidden="true" />
|
||||
|
||||
<section class="portrait-card">
|
||||
<h2>性格探索</h2>
|
||||
<p class="sub">填写生日,生成你的成长画像</p>
|
||||
<div class="row">
|
||||
<input v-model="year" type="number" placeholder="1990" />
|
||||
<span>年</span>
|
||||
<input v-model="month" type="number" placeholder="1" />
|
||||
<span>月</span>
|
||||
<input v-model="day" type="number" placeholder="1" />
|
||||
<span>日</span>
|
||||
<button type="button" @click="goPortrait">开始</button>
|
||||
<header class="top">
|
||||
<router-link to="/profile" class="avatar" aria-label="个人档案">◍</router-link>
|
||||
<div class="brand">
|
||||
<BrandLogo size="header" class="home-logo" />
|
||||
</div>
|
||||
<router-link to="/reports" class="side-link" aria-label="我的报告">报告</router-link>
|
||||
</header>
|
||||
<p class="tagline">愈见自己 · 遇见更好</p>
|
||||
|
||||
<section class="decode">
|
||||
<div class="di-title">愈心解码</div>
|
||||
<div class="di-sub">一个生日,读懂性格与节奏</div>
|
||||
<div class="di-inputs">
|
||||
<BirthDateInputs v-model:year="year" v-model:month="month" v-model:day="day" />
|
||||
<button type="button" @click="goPortrait">生成</button>
|
||||
</div>
|
||||
<p v-if="err" class="err">{{ err }}</p>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<div class="head"><h3>快捷入口</h3><router-link to="/explore">全部</router-link></div>
|
||||
<div class="grid">
|
||||
<router-link v-for="t in entries" :key="t.to" :to="t.to" class="cell">
|
||||
<div class="icon" :style="{ background: t.bg, color: t.fg }">{{ t.icon }}</div>
|
||||
<span>{{ t.label }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
<nav class="chips" aria-label="快捷分类">
|
||||
<a
|
||||
v-for="c in chips"
|
||||
:key="c.key"
|
||||
href="#"
|
||||
class="chip"
|
||||
:class="{ on: chipOn === c.key }"
|
||||
@click.prevent="onChip(c)"
|
||||
>{{ c.label }}</a>
|
||||
</nav>
|
||||
|
||||
<section class="block">
|
||||
<div class="head"><h3>服务状态</h3></div>
|
||||
<p class="api">API:{{ apiStatus }}</p>
|
||||
</section>
|
||||
<div class="sheet">
|
||||
<section class="section" id="grid">
|
||||
<div class="sec-head">
|
||||
<span class="title">认识自己</span>
|
||||
<router-link class="more" to="/explore">全部</router-link>
|
||||
</div>
|
||||
<div class="test-grid">
|
||||
<router-link v-for="t in entries" :key="t.to" :to="t.to" class="test-item">
|
||||
<div class="icon" :class="'icon-' + t.tone" aria-hidden="true">{{ t.icon }}</div>
|
||||
<div class="label">{{ t.label }}</div>
|
||||
<span v-if="t.badge" class="badge">{{ t.badge }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="sec-head">
|
||||
<span class="title">热门推荐</span>
|
||||
<router-link class="more" to="/explore">更多</router-link>
|
||||
</div>
|
||||
<div class="feed">
|
||||
<router-link v-for="f in feeds" :key="f.to" :to="f.to" class="feed-card" :class="f.tone">
|
||||
<span v-if="f.tag" class="feed-tag">{{ f.tag }}</span>
|
||||
<div class="feed-cover" aria-hidden="true">{{ f.icon }}</div>
|
||||
<div class="feed-body">
|
||||
<div class="name">{{ f.title }}</div>
|
||||
<div class="meta">{{ f.meta }}</div>
|
||||
<div class="stat">{{ f.stat }}</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="sec-head"><span class="title">精选工具</span></div>
|
||||
<div class="scroll-row">
|
||||
<router-link v-for="m in minis" :key="m.to" :to="m.to" class="mini-card" :class="m.tone">
|
||||
<div class="mi" aria-hidden="true">{{ m.icon }}</div>
|
||||
<div class="mt">{{ m.title }}</div>
|
||||
<div class="md">{{ m.desc }}</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { validateBirth } from '@yuxingu/utils'
|
||||
import { api } from '../api/client'
|
||||
import BirthDateInputs from '../components/BirthDateInputs.vue'
|
||||
import BrandLogo from '../components/BrandLogo.vue'
|
||||
import { AnalyticsEvent, track } from '../lib/analytics'
|
||||
import type { IconTone } from '../lib/featureIcons'
|
||||
|
||||
const router = useRouter()
|
||||
const year = ref('')
|
||||
const month = ref('')
|
||||
const day = ref('')
|
||||
const err = ref('')
|
||||
const apiStatus = ref('检测中…')
|
||||
const chipOn = ref('decode')
|
||||
|
||||
const entries = [
|
||||
{ to: '/portrait', icon: '△', label: '性格探索', bg: '#FFF3D6', fg: '#C8923A' },
|
||||
{ to: '/explore', icon: '◆', label: '人格测评', bg: '#E3EEFF', fg: '#4A90E2' },
|
||||
{ to: '/relation', icon: '◇', label: '关系理解', bg: '#FFE8D6', fg: '#E8985A' },
|
||||
{ to: '/ask', icon: '问', label: 'AI问答', bg: '#FFE4E4', fg: '#E54D42' },
|
||||
{ to: '/companion', icon: '☯', label: '节气陪伴', bg: '#E0F6E4', fg: '#5CB85C' },
|
||||
{ to: '/profile', icon: '◍', label: '个人档案', bg: '#F0F4F8', fg: '#5A6A7A' },
|
||||
{ to: '/membership', icon: '◇', label: '成长会员', bg: '#F5F0FF', fg: '#8B5CF6' },
|
||||
const chips = [
|
||||
{ key: 'decode', label: '愈心解码', to: '/portrait' },
|
||||
{ key: 'star', label: '星座', to: '/star' },
|
||||
{ key: 'synastry', label: '合盘', to: '/synastry' },
|
||||
{ key: 'match', label: '人格匹配', to: '/relation' },
|
||||
{ key: 'ask', label: 'AI 问答', to: '/ask' },
|
||||
{ key: 'more', label: '更多测评', to: '/explore/tests' },
|
||||
]
|
||||
|
||||
/** 金刚区:四核心优先;星座为单入口(太阳/月亮/上升在结果页内分维) */
|
||||
const entries: { to: string; icon: string; label: string; tone: IconTone; badge?: string }[] = [
|
||||
{ to: '/portrait', icon: '△', label: '愈心解码', tone: 'gold', badge: '热' },
|
||||
{ to: '/star', icon: '✦', label: '星座', tone: 'night', badge: 'AI' },
|
||||
{ to: '/synastry', icon: '✧', label: '合盘', tone: 'night', badge: '新' },
|
||||
{ to: '/relation', icon: '♡', label: '人格匹配', tone: 'rose', badge: '热' },
|
||||
{ to: '/ask', icon: '◎', label: 'AI问答', tone: 'gold' },
|
||||
{ to: '/rhythm', icon: '☯', label: '身心节律', tone: 'green' },
|
||||
{ to: '/cards', icon: '◈', label: '意象卡片', tone: 'teal' },
|
||||
{ to: '/companion', icon: '♡', label: '节气陪伴', tone: 'pink' },
|
||||
{ to: '/growth-plan', icon: '▽', label: '成长计划', tone: 'teal' },
|
||||
{ to: '/membership', icon: '⑨', label: '成长会员', tone: 'purple' },
|
||||
{ to: '/reports', icon: '☰', label: '我的报告', tone: 'mute' },
|
||||
{ to: '/profile', icon: '◍', label: '个人档案', tone: 'orange' },
|
||||
{ to: '/explore/tests', icon: '⋯', label: '更多测评', tone: 'mute' },
|
||||
]
|
||||
|
||||
const feeds = [
|
||||
{
|
||||
to: '/portrait',
|
||||
icon: '△',
|
||||
title: '愈心解码',
|
||||
meta: '一个生日,读懂性格与身心节奏',
|
||||
stat: '核心入口',
|
||||
tone: 'fc-e',
|
||||
tag: '热',
|
||||
},
|
||||
{
|
||||
to: '/star',
|
||||
icon: '✦',
|
||||
title: '星座排盘',
|
||||
meta: '圆形本命盘 · 相位 · 日周月年与一生运势',
|
||||
stat: '本周热门',
|
||||
tone: 'fc-b',
|
||||
tag: '新',
|
||||
},
|
||||
{
|
||||
to: '/synastry',
|
||||
icon: '✧',
|
||||
title: '合盘',
|
||||
meta: '比较盘 · 恋爱 / 友情 / 婚姻指数',
|
||||
stat: '了解彼此',
|
||||
tone: 'fc-c',
|
||||
tag: '新',
|
||||
},
|
||||
]
|
||||
|
||||
const minis = [
|
||||
{ to: '/portrait', icon: '△', title: '愈心解码', desc: '生日生成性格解码', tone: 'mc-sand' },
|
||||
{ to: '/star', icon: '✦', title: '星座', desc: '排盘与运势', tone: 'mc-sky' },
|
||||
{ to: '/synastry', icon: '✧', title: '合盘', desc: '五主盘 · 推运 · 三指数', tone: 'mc-coral' },
|
||||
{ to: '/ask', icon: '◎', title: 'AI 问答', desc: '结合档案聊聊卡住的事', tone: 'mc-mint' },
|
||||
]
|
||||
|
||||
function onChip(c: (typeof chips)[number]) {
|
||||
chipOn.value = c.key
|
||||
if (c.to.startsWith('#')) {
|
||||
document.querySelector(c.to)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
return
|
||||
}
|
||||
router.push(c.to)
|
||||
}
|
||||
|
||||
function goPortrait() {
|
||||
const y = Number(year.value)
|
||||
const m = Number(month.value)
|
||||
@@ -70,49 +179,186 @@ function goPortrait() {
|
||||
return
|
||||
}
|
||||
err.value = ''
|
||||
track(AnalyticsEvent.HomeCtaPortrait)
|
||||
router.push({ path: '/portrait', query: { y: String(y), m: String(m), d: String(d) } })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await api.healthz()
|
||||
apiStatus.value = data.status === 'ok' ? '已连接' : '异常'
|
||||
} catch {
|
||||
apiStatus.value = '未连接(请启动 apps/api)'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home{padding:18px 16px 8px}
|
||||
.brand{text-align:center;margin-bottom:14px}
|
||||
.word{font-size:28px;font-weight:700;letter-spacing:.18em}
|
||||
.word span{color:var(--yxg-pri)}
|
||||
.tag{font-size:12px;color:rgba(0,0,0,.38);margin-top:6px;letter-spacing:.12em}
|
||||
.portrait-card{
|
||||
background:#fff;border-radius:20px;padding:18px 14px;text-align:center;
|
||||
box-shadow:0 8px 28px rgba(229,77,66,.12);
|
||||
.home{
|
||||
position:relative;overflow-x:hidden;padding-bottom:8px;
|
||||
background:
|
||||
radial-gradient(120% 80% at 80% -10%, rgba(255,180,160,.55) 0%, transparent 55%),
|
||||
radial-gradient(90% 60% at 10% 8%, rgba(255,210,190,.7) 0%, transparent 50%),
|
||||
linear-gradient(180deg, #ffd6cc 0%, #ffe8e0 28%, #fff6f2 55%, #fff9f7 100%);
|
||||
margin-top:-4px;
|
||||
}
|
||||
.portrait-card h2{font-size:17px;color:var(--yxg-gold);letter-spacing:.1em}
|
||||
.sub{font-size:12px;color:#aaa;margin:4px 0 14px}
|
||||
.row{display:flex;gap:6px;align-items:center;justify-content:center;flex-wrap:wrap}
|
||||
.row input{
|
||||
width:56px;padding:10px 4px;border:1.5px solid #eee;border-radius:12px;
|
||||
text-align:center;font-size:15px;outline:none;
|
||||
.glow{
|
||||
position:absolute;pointer-events:none;border-radius:50%;filter:blur(2px);z-index:0;
|
||||
}
|
||||
.row button{
|
||||
padding:10px 16px;border:none;border-radius:22px;color:#fff;font-weight:600;
|
||||
.glow.g1{width:140px;height:140px;top:40px;right:-30px;background:rgba(255,255,255,.45)}
|
||||
.glow.g2{width:90px;height:90px;top:100px;left:-20px;background:rgba(255,200,180,.4)}
|
||||
|
||||
/* 顶栏:左档案 · 中品牌 · 右报告(测测式工具条,非营销大标题) */
|
||||
.top{
|
||||
display:grid;grid-template-columns:48px 1fr 48px;align-items:center;
|
||||
padding:10px 14px 0;position:relative;z-index:2;
|
||||
}
|
||||
.avatar,.side-link{
|
||||
width:36px;height:36px;border-radius:12px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
background:rgba(255,255,255,.72);border:1px solid rgba(255,255,255,.9);
|
||||
font-size:12px;color:#666;backdrop-filter:blur(6px);
|
||||
}
|
||||
.avatar{font-size:16px}
|
||||
.side-link{font-size:11px;font-weight:600;letter-spacing:.02em}
|
||||
.brand{
|
||||
display:flex;align-items:center;justify-content:center;min-height:36px;
|
||||
}
|
||||
.brand :deep(img.brand-logo){
|
||||
width:min(160px,54vw);height:auto;max-height:36px;
|
||||
object-fit:contain;
|
||||
}
|
||||
.tagline{
|
||||
text-align:center;margin:6px 0 0;font-size:12px;
|
||||
color:rgba(0,0,0,.38);letter-spacing:.12em;position:relative;z-index:2;
|
||||
}
|
||||
|
||||
/* 建档主卡 */
|
||||
.decode{
|
||||
margin:14px 16px 0;position:relative;z-index:2;
|
||||
padding:18px 16px 16px;background:#fff;border-radius:20px;
|
||||
box-shadow:0 8px 28px rgba(229,77,66,.12);text-align:center;
|
||||
animation:bdayIn .5s ease both;
|
||||
}
|
||||
.di-title{
|
||||
font-family:var(--font-display);
|
||||
font-size:17px;font-weight:700;color:#c8923a;letter-spacing:.1em;
|
||||
}
|
||||
.di-sub{font-size:12px;color:#aaa;margin:4px 0 14px}
|
||||
.di-inputs{display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}
|
||||
.di-inputs button{
|
||||
padding:11px 18px;border:none;border-radius:22px;color:#fff;font-size:14px;font-weight:600;
|
||||
background:linear-gradient(135deg,#ff7a6e,var(--yxg-pri));
|
||||
box-shadow:0 6px 16px rgba(229,77,66,.28);
|
||||
}
|
||||
.err{color:var(--yxg-pri);font-size:12px;margin-top:8px}
|
||||
.block{margin-top:22px;background:#fff;border-radius:18px;padding:16px;box-shadow:0 2px 10px rgba(0,0,0,.04)}
|
||||
.head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
||||
.head h3{font-size:16px}
|
||||
.head a{font-size:12px;color:#bbb}
|
||||
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px 8px}
|
||||
.cell{display:flex;flex-direction:column;align-items:center;gap:8px;font-size:11px;color:#555}
|
||||
.icon{
|
||||
width:52px;height:52px;border-radius:18px;display:flex;align-items:center;justify-content:center;font-size:20px;
|
||||
@keyframes bdayIn{
|
||||
from{opacity:0;transform:translateY(8px)}
|
||||
to{opacity:1;transform:translateY(0)}
|
||||
}
|
||||
|
||||
/* chips:sheet 外、暖色底上 */
|
||||
.chips{
|
||||
display:flex;gap:8px;padding:14px 16px 4px;
|
||||
overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;
|
||||
position:relative;z-index:2;
|
||||
}
|
||||
.chips::-webkit-scrollbar{display:none}
|
||||
.chip{
|
||||
flex-shrink:0;padding:7px 14px;border-radius:999px;
|
||||
background:rgba(255,255,255,.72);border:1px solid rgba(255,255,255,.9);
|
||||
font-size:12px;color:#666;text-decoration:none;
|
||||
backdrop-filter:blur(6px);
|
||||
}
|
||||
.chip.on{background:#fff;color:var(--yxg-pri);font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
||||
|
||||
/* 白 sheet */
|
||||
.sheet{
|
||||
margin-top:14px;background:#fff;
|
||||
border-radius:22px 22px 0 0;
|
||||
padding:8px 0 28px;
|
||||
box-shadow:0 -4px 24px rgba(180,100,80,.06);
|
||||
position:relative;z-index:3;
|
||||
animation:sheetUp .55s cubic-bezier(.22,.8,.28,1) both;
|
||||
}
|
||||
@keyframes sheetUp{
|
||||
from{opacity:0;transform:translateY(24px)}
|
||||
to{opacity:1;transform:translateY(0)}
|
||||
}
|
||||
.section{padding:0 16px;margin-top:20px}
|
||||
.sec-head{
|
||||
display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;
|
||||
}
|
||||
.sec-head .title{font-size:17px;font-weight:700;color:#222;letter-spacing:.02em}
|
||||
.sec-head .more{font-size:12px;color:#bbb;text-decoration:none}
|
||||
.sec-head .more::after{content:" ›"}
|
||||
|
||||
.test-grid{
|
||||
display:grid;grid-template-columns:repeat(4,1fr);gap:14px 8px;padding:4px 2px 2px;
|
||||
}
|
||||
.test-item{
|
||||
display:flex;flex-direction:column;align-items:center;gap:8px;
|
||||
color:#333;position:relative;
|
||||
}
|
||||
.test-item:active{transform:scale(.92)}
|
||||
.test-item .icon{
|
||||
width:54px;height:54px;border-radius:18px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:22px;box-shadow:inset 0 -2px 0 rgba(0,0,0,.03);
|
||||
}
|
||||
.test-item .label{
|
||||
font-size:11px;color:#555;text-align:center;line-height:1.25;
|
||||
max-width:68px;word-break:keep-all;
|
||||
}
|
||||
.badge{
|
||||
position:absolute;top:-4px;right:6px;
|
||||
font-size:9px;background:linear-gradient(135deg,#ff7a6e,var(--yxg-pri));
|
||||
color:#fff;padding:1px 5px;border-radius:6px;line-height:1.4;
|
||||
box-shadow:0 2px 6px rgba(229,77,66,.3);
|
||||
}
|
||||
|
||||
.feed{display:flex;flex-direction:column;gap:12px}
|
||||
.feed-card{
|
||||
display:flex;gap:14px;align-items:stretch;
|
||||
padding:12px;background:#fafafa;border-radius:16px;
|
||||
color:#333;position:relative;overflow:hidden;
|
||||
}
|
||||
.feed-card:active{transform:scale(.985);background:#f5f5f5}
|
||||
.feed-cover{
|
||||
width:72px;height:72px;border-radius:14px;flex-shrink:0;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:28px;position:relative;overflow:hidden;
|
||||
}
|
||||
.feed-cover::after{
|
||||
content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(160deg,rgba(255,255,255,.35),transparent 50%);
|
||||
}
|
||||
.feed-body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;padding:2px 0}
|
||||
.feed-body .name{font-size:15px;font-weight:650;color:#222;line-height:1.35}
|
||||
.feed-body .meta{font-size:12px;color:#999;margin-top:4px;line-height:1.4}
|
||||
.feed-body .stat{font-size:11px;color:#c4c4c4;margin-top:6px}
|
||||
.feed-tag{
|
||||
position:absolute;top:10px;right:10px;
|
||||
font-size:10px;color:#fff;background:var(--yxg-pri);
|
||||
padding:2px 7px;border-radius:8px;
|
||||
}
|
||||
.fc-a .feed-cover{background:linear-gradient(145deg,#ffd0c8,#ffb0a4);color:#c23b32}
|
||||
.fc-b .feed-cover{background:linear-gradient(145deg,#e4d8f8,#c9b6f0);color:#5b4a8a}
|
||||
.fc-c .feed-cover{background:linear-gradient(145deg,#d6e8ff,#b0d0f5);color:#3a6fb0}
|
||||
.fc-e .feed-cover{background:linear-gradient(145deg,#ffe6c8,#f5c98a);color:#b07a28}
|
||||
|
||||
.scroll-row{
|
||||
display:flex;gap:10px;overflow-x:auto;padding:2px 0 4px;
|
||||
-webkit-overflow-scrolling:touch;scrollbar-width:none;
|
||||
}
|
||||
.scroll-row::-webkit-scrollbar{display:none}
|
||||
.mini-card{
|
||||
flex:0 0 148px;padding:14px 14px 16px;border-radius:16px;color:#333;
|
||||
}
|
||||
.mini-card:active{transform:scale(.97)}
|
||||
.mini-card .mi{font-size:22px;margin-bottom:8px}
|
||||
.mini-card .mt{font-size:14px;font-weight:650}
|
||||
.mini-card .md{font-size:11px;color:rgba(0,0,0,.4);margin-top:3px;line-height:1.35}
|
||||
.mc-coral{background:linear-gradient(160deg,#ffe8e2,#ffd4ca)}
|
||||
.mc-mint{background:linear-gradient(160deg,#e4f6ea,#d0edd8)}
|
||||
.mc-sky{background:linear-gradient(160deg,#e6f0ff,#d4e4ff)}
|
||||
.mc-sand{background:linear-gradient(160deg,#fff3e0,#ffe4c2)}
|
||||
|
||||
@media (max-width:380px){
|
||||
.word{font-size:18px}
|
||||
.test-item .icon{width:48px;height:48px;font-size:20px;border-radius:16px}
|
||||
.test-item .label{font-size:10px}
|
||||
.feed-cover{width:64px;height:64px}
|
||||
}
|
||||
.api{font-size:13px;color:var(--yxg-sub)}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user