chore: seal Design Vision v1 and monorepo scaffold
Archive the differentiated YuXinGu product docs, AI engineering system, design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity over early innovation (see .ai/product/STRATEGY.md). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="app-shell">
|
||||
<router-view />
|
||||
<TabBar v-if="showTab" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import TabBar from './components/TabBar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const showTab = computed(() => route.meta.tab !== false)
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createClient, createBrowserAdapters } from '@yuxingu/sdk'
|
||||
|
||||
/** Shared API client for user-h5 (proxied to Go in dev). */
|
||||
export const api = createClient({
|
||||
baseURL: '',
|
||||
adapters: createBrowserAdapters(),
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<nav class="tabbar" aria-label="主导航">
|
||||
<router-link
|
||||
v-for="item in items"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="item"
|
||||
:class="{ ask: item.ask, on: isActive(item.to) }"
|
||||
>
|
||||
<span class="ni">{{ item.icon }}</span>
|
||||
{{ item.label }}
|
||||
</router-link>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const items = [
|
||||
{ to: '/', icon: '⌂', label: '首页' },
|
||||
{ to: '/explore', icon: '◎', label: '探索' },
|
||||
{ to: '/ask', icon: '问', label: '问', ask: true },
|
||||
{ to: '/companion', icon: '♡', label: '陪伴' },
|
||||
{ to: '/mine', icon: '◍', label: '我的' },
|
||||
]
|
||||
|
||||
function isActive(to: string): boolean {
|
||||
if (to === '/') return route.path === '/'
|
||||
return route.path.startsWith(to)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabbar{
|
||||
position:fixed;bottom:0;left:50%;transform:translateX(-50%);
|
||||
width:100%;max-width:var(--yxg-max-w);background:#fff;
|
||||
display:flex;justify-content:space-around;
|
||||
padding:6px 0 calc(10px + env(safe-area-inset-bottom,0));
|
||||
border-top:1px solid #f0f0f0;z-index:100;
|
||||
}
|
||||
.item{
|
||||
flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;
|
||||
font-size:10px;color:var(--yxg-sub);padding:2px 0;
|
||||
}
|
||||
.item.on{color:var(--yxg-pri);font-weight:600}
|
||||
.ni{font-size:16px;line-height:1.2}
|
||||
.item.ask .ni{
|
||||
width:36px;height:36px;border-radius:50%;background:var(--yxg-pri);color:#fff;
|
||||
display:flex;align-items:center;justify-content:center;font-size:14px;margin-top:-10px;
|
||||
}
|
||||
.item.ask.on{color:var(--yxg-pri)}
|
||||
</style>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import '@yuxingu/ui/tokens.css'
|
||||
import './styles/app.css'
|
||||
|
||||
createApp(App).use(createPinia()).use(router).mount('#app')
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<main class="page">
|
||||
<h1>问</h1>
|
||||
<p class="sub">愈心 AI · 基于档案的解读(占位)</p>
|
||||
<div class="card">后续接入档案上下文问答;与「真人顾问」双轨同屏。</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page{padding:24px 16px}
|
||||
h1{font-size:22px}
|
||||
.sub{color:var(--yxg-sub);font-size:13px;margin:6px 0 16px}
|
||||
.card{background:#fff;border-radius:16px;padding:18px;font-size:14px;color:#666;line-height:1.6}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<main class="page">
|
||||
<h1>陪伴</h1>
|
||||
<p class="sub">节气 · 每日心情(占位)</p>
|
||||
<div class="card">今日节气内容将由 API `/api/v1/solar-terms/today` 下发。</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page{padding:24px 16px}
|
||||
h1{font-size:22px}
|
||||
.sub{color:var(--yxg-sub);font-size:13px;margin:6px 0 16px}
|
||||
.card{background:#fff;border-radius:16px;padding:18px;font-size:14px;color:#666;line-height:1.6}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<main class="page">
|
||||
<button class="back" type="button" @click="$router.back()">← 返回</button>
|
||||
<h1>愈心解码</h1>
|
||||
<p class="sub" v-if="y">生日:{{ y }}-{{ m }}-{{ d }}</p>
|
||||
<div class="card">
|
||||
简版报告将由 Go API 生成。当前为页面骨架;完整「结论免费 / 原因付费」逻辑见 PRD。
|
||||
</div>
|
||||
<p class="disc">
|
||||
本内容为文化测评与健康生活方式参考,不构成医疗建议。
|
||||
</p>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const y = computed(() => String(route.query.y || ''))
|
||||
const m = computed(() => String(route.query.m || ''))
|
||||
const d = computed(() => String(route.query.d || ''))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page{padding:16px}
|
||||
.back{border:none;background:#fff;border-radius:10px;padding:8px 12px;margin-bottom:12px}
|
||||
h1{font-size:22px}
|
||||
.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}
|
||||
.disc{font-size:11px;color:#bbb;margin-top:16px;line-height:1.5}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<main class="page">
|
||||
<h1>探索</h1>
|
||||
<p class="sub">量表 · 数字心理 · 星镜(逐步迁入)</p>
|
||||
<ul class="list">
|
||||
<li>性格测评库(对接 /api/v1/scales)</li>
|
||||
<li>数字心理学</li>
|
||||
<li>星镜</li>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page{padding:24px 16px}
|
||||
h1{font-size:22px}
|
||||
.sub{color:var(--yxg-sub);font-size:13px;margin:6px 0 16px}
|
||||
.list{background:#fff;border-radius:16px;padding:8px 16px;line-height:2.2;font-size:14px;color:#555}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<main class="home">
|
||||
<header class="brand">
|
||||
<div class="word">愈<span>心</span>谷</div>
|
||||
<p class="tag">愈见自己 · 遇见更好</p>
|
||||
</header>
|
||||
|
||||
<section class="decode-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="goDecode">生成</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 tests" :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>
|
||||
|
||||
<section class="block">
|
||||
<div class="head"><h3>服务状态</h3></div>
|
||||
<p class="api">API:{{ apiStatus }}</p>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { validateBirth } from '@yuxingu/utils'
|
||||
import { api } from '../api/client'
|
||||
|
||||
const router = useRouter()
|
||||
const year = ref('')
|
||||
const month = ref('')
|
||||
const day = ref('')
|
||||
const err = ref('')
|
||||
const apiStatus = ref('检测中…')
|
||||
|
||||
const tests = [
|
||||
{ to: '/decode', icon: '△', label: '愈心解码', bg: '#FFF3D6', fg: '#C8923A' },
|
||||
{ to: '/explore', icon: '◆', label: '性格测评', bg: '#E3EEFF', fg: '#4A90E2' },
|
||||
{ to: '/ask', icon: '问', label: '愈心问答', bg: '#FFE4E4', fg: '#E54D42' },
|
||||
{ to: '/companion', icon: '☯', label: '节气陪伴', bg: '#E0F6E4', fg: '#5CB85C' },
|
||||
]
|
||||
|
||||
function goDecode() {
|
||||
const y = Number(year.value)
|
||||
const m = Number(month.value)
|
||||
const d = Number(day.value)
|
||||
const msg = validateBirth(y, m, d)
|
||||
if (msg) {
|
||||
err.value = msg
|
||||
return
|
||||
}
|
||||
err.value = ''
|
||||
router.push({ path: '/decode', 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}
|
||||
.decode-card{
|
||||
background:#fff;border-radius:20px;padding:18px 14px;text-align:center;
|
||||
box-shadow:0 8px 28px rgba(229,77,66,.12);
|
||||
}
|
||||
.decode-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;
|
||||
}
|
||||
.row button{
|
||||
padding:10px 16px;border:none;border-radius:22px;color:#fff;font-weight:600;
|
||||
background:linear-gradient(135deg,#ff7a6e,var(--yxg-pri));
|
||||
}
|
||||
.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(4,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;
|
||||
}
|
||||
.api{font-size:13px;color:var(--yxg-sub)}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<main class="page">
|
||||
<h1>我的</h1>
|
||||
<p class="sub">档案 · 会员 · 订单</p>
|
||||
<div class="card">登录与会员状态将在商业化阶段接入。</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page{padding:24px 16px}
|
||||
h1{font-size:22px}
|
||||
.sub{color:var(--yxg-sub);font-size:13px;margin:6px 0 16px}
|
||||
.card{background:#fff;border-radius:16px;padding:18px;font-size:14px;color:#666;line-height:1.6}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'home', component: () => import('../pages/HomePage.vue'), meta: { tab: true } },
|
||||
{ path: '/explore', name: 'explore', component: () => import('../pages/ExplorePage.vue'), meta: { tab: true } },
|
||||
{ path: '/ask', name: 'ask', component: () => import('../pages/AskPage.vue'), meta: { tab: true } },
|
||||
{ path: '/companion', name: 'companion', component: () => import('../pages/CompanionPage.vue'), meta: { tab: true } },
|
||||
{ path: '/mine', name: 'mine', component: () => import('../pages/MinePage.vue'), meta: { tab: true } },
|
||||
{ path: '/decode', name: 'decode', component: () => import('../pages/DecodePage.vue'), meta: { tab: false } },
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,12 @@
|
||||
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
||||
html,body,#app{min-height:100%}
|
||||
body{
|
||||
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
|
||||
background:linear-gradient(180deg,var(--yxg-bg-start) 0%,var(--yxg-bg-end) 28%,#fff9f7 100%);
|
||||
color:var(--yxg-text);
|
||||
}
|
||||
.app-shell{
|
||||
max-width:var(--yxg-max-w);margin:0 auto;min-height:100vh;
|
||||
padding-bottom:calc(var(--yxg-nav-h) + env(safe-area-inset-bottom,0px) + 12px);
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<object, object, unknown>
|
||||
export default component
|
||||
}
|
||||
Reference in New Issue
Block a user