feat: 按测测功能重构 H5 首页与各子页,并修正顶栏「+」添加档案菜单
对齐测测交互:首页「+」支持邀请填档案/添加档案/邀请合盘;各 Tab 与工具页按同一视觉与功能标准落地;本地对照截图目录显式忽略。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,43 +1,50 @@
|
||||
<template>
|
||||
<main class="yxg-page">
|
||||
<div class="yxg-hero">
|
||||
<PageShell>
|
||||
<template #hero>
|
||||
<BackButton />
|
||||
<PageTitle feature="growth" title="成长计划" sub="小目标 · 每日打卡(生活成长,非运势)" />
|
||||
</div>
|
||||
<div class="head">
|
||||
<HomeToolIcon name="growth" :size="48" />
|
||||
<div>
|
||||
<h1 class="title">成长计划</h1>
|
||||
<p class="sub">小目标 · 每日打卡</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="yxg-sheet">
|
||||
<div class="yxg-card">
|
||||
<label class="yxg-label">新计划标题</label>
|
||||
<input class="yxg-input" v-model="title" maxlength="40" placeholder="例如:每晚 11 点前放下手机" />
|
||||
<label class="yxg-label">焦点(可选)</label>
|
||||
<input class="yxg-input" v-model="focus" maxlength="80" placeholder="例如:保护睡眠与情绪" />
|
||||
<button class="yxg-btn" type="button" :disabled="saving || !title.trim()" @click="create">创建计划</button>
|
||||
<p v-if="err" class="yxg-err">{{ err }}</p>
|
||||
<div class="body">
|
||||
<div class="create">
|
||||
<label class="lbl">新计划标题</label>
|
||||
<input class="inp" v-model="title" maxlength="40" placeholder="例如:每晚 11 点前放下手机" />
|
||||
<label class="lbl">焦点(可选)</label>
|
||||
<input class="inp" v-model="focus" maxlength="80" placeholder="例如:保护睡眠与情绪" />
|
||||
<button class="cta" type="button" :disabled="saving || !title.trim()" @click="create">创建计划</button>
|
||||
<p v-if="err" class="err">{{ err }}</p>
|
||||
</div>
|
||||
|
||||
<p v-if="loading" class="yxg-hint pad">加载中…</p>
|
||||
<ul v-else class="yxg-list plans">
|
||||
<li v-for="p in plans" :key="p.id" class="plan">
|
||||
<strong>▽ {{ p.title }}</strong>
|
||||
<p v-if="loading" class="hint">加载中…</p>
|
||||
<div v-else class="plans">
|
||||
<div v-for="p in plans" :key="p.id" class="plan">
|
||||
<strong>{{ p.title }}</strong>
|
||||
<p v-if="p.focus" class="focus">{{ p.focus }}</p>
|
||||
<button type="button" class="yxg-btn yxg-btn-ghost" :disabled="checking === p.id" @click="checkin(p.id)">
|
||||
<button type="button" class="check" :disabled="checking === p.id" @click="checkin(p.id)">
|
||||
{{ checking === p.id ? '记录中…' : '今日打卡' }}
|
||||
</button>
|
||||
<ul v-if="checkins[p.id]?.length" class="cks">
|
||||
<li v-for="c in checkins[p.id]" :key="c.id">{{ c.day }}{{ c.note ? ' · ' + c.note : '' }}</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-if="!plans.length" class="empty">还没有计划,先创建一个小目标吧。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p v-if="!plans.length" class="hint">还没有计划,先创建一个小目标吧。</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</PageShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { api } from '../api/client'
|
||||
import BackButton from '../components/BackButton.vue'
|
||||
import PageTitle from '../components/PageTitle.vue'
|
||||
import HomeToolIcon from '../components/HomeToolIcon.vue'
|
||||
import PageShell from '../components/PageShell.vue'
|
||||
|
||||
const plans = ref<{ id: string; title: string; focus: string }[]>([])
|
||||
const checkins = ref<Record<string, { id: string; day: string; note?: string }[]>>({})
|
||||
@@ -83,7 +90,7 @@ async function checkin(id: string) {
|
||||
checking.value = id
|
||||
err.value = ''
|
||||
try {
|
||||
await api.growthCheckin(id, {})
|
||||
await api.createGrowthCheckin(id, {})
|
||||
const ck = await api.listGrowthCheckins(id)
|
||||
checkins.value[id] = ck.items || []
|
||||
} catch (e) {
|
||||
@@ -97,14 +104,89 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.yxg-card{margin-top:8px}
|
||||
.yxg-card .yxg-label:first-child{margin-top:0}
|
||||
.yxg-btn{margin-top:12px}
|
||||
.pad{padding:0 16px}
|
||||
.plans{list-style:none}
|
||||
.plan{display:block}
|
||||
.plan strong{font-size:15px;color:#222}
|
||||
.focus{font-size:13px;color:#888;margin:6px 0}
|
||||
.cks{margin-top:8px;padding-left:16px;font-size:12px;color:#999}
|
||||
.empty{color:#999;font-size:13px;padding:8px 4px}
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.sub {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.38);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.body { padding: 8px 16px 0; }
|
||||
.create {
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: var(--radius-xl);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.lbl {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin: 8px 0 6px;
|
||||
}
|
||||
.lbl:first-child { margin-top: 0; }
|
||||
.inp {
|
||||
width: 100%;
|
||||
padding: 11px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1.5px solid #eee;
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
}
|
||||
.cta {
|
||||
margin-top: 14px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
|
||||
box-shadow: 0 6px 16px rgba(229, 77, 66, 0.28);
|
||||
}
|
||||
.cta:disabled { opacity: 0.45; }
|
||||
.plans { display: flex; flex-direction: column; gap: 10px; }
|
||||
.plan {
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
border: 1px solid #f5f0ed;
|
||||
}
|
||||
.plan strong { font-size: 15px; color: #222; }
|
||||
.focus { font-size: 12px; color: #888; margin: 6px 0 10px; }
|
||||
.check {
|
||||
padding: 8px 14px;
|
||||
border-radius: 18px;
|
||||
border: 1.5px solid #f5c4bc;
|
||||
background: #fff;
|
||||
color: var(--color-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.check:disabled { opacity: 0.5; }
|
||||
.cks {
|
||||
list-style: none;
|
||||
margin: 10px 0 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.cks li { padding: 4px 0; }
|
||||
.hint { font-size: 13px; color: #999; text-align: center; padding: 16px; }
|
||||
.err { color: var(--color-primary); font-size: 12px; margin-top: 8px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user