Files
yuxingu-Miniprogram-dev/pages/yxg/growth-plan.vue
T
jackyu66gitandCursor b09d82df8d feat: 小程序统一走 Go 后台,咨询与登录切到 /api/v1
去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 00:26:28 +08:00

111 lines
3.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<yxg-page title="成长计划">
<view class="card">
<text class="lbl">新计划标题</text>
<input class="inp" v-model="title" maxlength="40" placeholder="例如:每晚 11 点前放下手机" />
<text class="lbl">焦点可选</text>
<input class="inp" v-model="focus" maxlength="40" placeholder="例如:保护睡眠与情绪" />
<view class="btn" :class="{ off: saving || !title.trim() }" @click="create"><text>创建计划</text></view>
<text v-if="err" class="err">{{ err }}</text>
</view>
<text v-if="loading" class="hint">加载中</text>
<view v-for="p in plans" :key="p.id" class="card">
<text class="h1">{{ p.title }}</text>
<text v-if="p.focus" class="meta">{{ p.focus }}</text>
<view class="btn ghost" @click="checkin(p.id)">
<text>{{ checking === p.id ? '记录中…' : '今日打卡' }}</text>
</view>
<view v-for="c in (checkins[p.id] || [])" :key="c.id" class="ck">
<text>{{ c.day }}{{ c.note ? ' · ' + c.note : '' }}</text>
</view>
</view>
<text v-if="!loading && !plans.length" class="hint">还没有计划先创建一个小目标吧</text>
</yxg-page>
</template>
<script setup>
import YxgPage from '@/components/yxg/yxg-page.vue'
import { yxgApi } from '@/util/yxgApi.js'
import { ensureAccount } from '@/util/yxgAuth.js'
const plans = ref([])
const checkins = ref({})
const title = ref('')
const focus = ref('')
const loading = ref(true)
const saving = ref(false)
const checking = ref('')
const err = ref('')
async function load() {
loading.value = true
if (!(await ensureAccount('/growth-plan'))) {
loading.value = false
return
}
try {
const res = await yxgApi.listGrowthPlans()
plans.value = res.items || []
const next = {}
for (const p of plans.value) {
const ck = await yxgApi.listGrowthCheckins(p.id)
next[p.id] = ck.items || []
}
checkins.value = next
} catch (e) {
err.value = e instanceof Error ? e.message : '加载失败'
} finally {
loading.value = false
}
}
async function create() {
if (saving.value || !title.value.trim()) return
saving.value = true
err.value = ''
try {
await yxgApi.createGrowthPlan({ title: title.value.trim(), focus: focus.value.trim() || undefined })
title.value = ''
focus.value = ''
await load()
} catch (e) {
err.value = e instanceof Error ? e.message : '创建失败'
} finally {
saving.value = false
}
}
async function checkin(id) {
if (checking.value) return
checking.value = id
try {
await yxgApi.createGrowthCheckin(id, {})
await load()
} catch (e) {
uni.showToast({ title: e.message || '打卡失败', icon: 'none' })
} finally {
checking.value = ''
}
}
onShow(load)
</script>
<style>
page { background-color: #ffd1c7; }
</style>
<style scoped>
.card { margin: 12px 16px; padding: 16px; background: #fff; border-radius: 16px; }
.lbl { display: block; font-size: 12px; color: #999; margin: 8px 0 6px; }
.inp { width: 100%; height: 42px; background: #fdfaf8; border-radius: 12px; padding: 0 12px; box-sizing: border-box; }
.h1 { display: block; font-size: 16px; font-weight: 700; color: #333; }
.meta, .hint, .err, .ck { display: block; margin-top: 6px; font-size: 13px; color: #666; }
.err { color: #e54d42; }
.btn {
margin-top: 12px; 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; }
.btn.off { opacity: 0.45; }
</style>