Files
jiapuapp/pages/records/r08-growth-journal.vue
T
2026-07-21 07:53:08 +08:00

254 lines
6.9 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.
<!-- 页面编号R-08用途人物成长日志时间轴与同页新增 -->
<template>
<view class="timeline-page" :class="stateClasses">
<ModulePageBackground module="records" />
<view class="page-header">
<PageHeader title="成长日志" action="记录" @action="recordGrowth" />
</view>
<view v-if="timelineState === 'loading'" class="page-loading">
<AppLoading
text="正在读取成长日志"
:description="`请稍候,正在整理${personName}的成长记录。`"
/>
</view>
<view v-else class="page-content">
<view class="person-lead">
<text>{{ personName }}</text>
<text>成长中的每一个瞬间</text>
</view>
<template v-if="timelineState === 'ready' && growthRecords.length">
<view
v-for="(record, index) in growthRecords"
:key="record.id"
class="timeline-card"
>
<text> {{ growthRecords.length - index }} </text>
<text>{{ record.title }}</text>
<text>{{ record.date }}</text>
<text>{{ record.description }}</text>
</view>
<AppButton block label="记录成长" @click="recordGrowth" />
</template>
<view v-else class="state-card">
<text>
{{
timelineState === "error" ? "成长日志暂不可用" : "还没有成长记录"
}}
</text>
<text>
{{
timelineState === "error"
? "请稍后重新查看,已有记录不会受到影响。"
: "从第一次微笑、入园或毕业开始记录。"
}}
</text>
<AppButton
:type="timelineState === 'error' ? 'secondary' : 'primary'"
block
:label="timelineState === 'error' ? '重新查看' : '记录成长'"
@click="
timelineState === 'error'
? (timelineState = 'ready')
: recordGrowth()
"
/>
</view>
</view>
<AppDialog
:visible="dialogVisible"
eyebrow="成长日志"
title="记录一个成长瞬间"
confirm-text="保存记录"
cancel-text="取消"
show-cancel
@confirm="saveGrowth"
@cancel="dialogVisible = false"
>
<view class="dialog-form">
<input v-model="growthForm.title" placeholder="事件名称" />
<input v-model="growthForm.date" placeholder="日期" />
<textarea
v-model="growthForm.description"
auto-height
placeholder="写下当时的故事"
/>
<text v-if="formError">{{ formError }}</text>
</view>
</AppDialog>
<AppToast :visible="toastVisible" message="成长记录已保存" />
</view>
</template>
<script setup>
import { computed, onUnmounted, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppDialog from "@/components/AppDialog.vue";
import AppLoading from "@/components/AppLoading.vue";
import AppToast from "@/components/AppToast.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
const personName = ref("汤小满");
const growthRecords = ref([
{
id: 1,
title: "第一次叫爸爸",
date: "2024 年 3 月",
description: "家人共同听见了这声清晰的呼唤。",
},
{
id: 2,
title: "入园第一天",
date: "2024 年 9 月",
description: "背着小书包,勇敢地向家人挥手。",
},
]);
const timelineState = ref("loading");
const dialogVisible = ref(false);
const toastVisible = ref(false);
const formError = ref("");
const growthForm = reactive({ title: "", date: "", description: "" });
let timer = null;
const stateClasses = computed(() => ({
"timeline-state--loading": timelineState.value === "loading",
"timeline-state--empty": timelineState.value === "empty",
"timeline-state--error": timelineState.value === "error",
}));
onLoad((q) => {
personName.value = String(q.personName || "汤小满");
timelineState.value = ["loading", "empty", "error"].includes(q.state)
? q.state
: "ready";
});
const recordGrowth = () => {
Object.assign(growthForm, { title: "", date: "", description: "" });
formError.value = "";
dialogVisible.value = true;
};
const saveGrowth = () => {
if (!growthForm.title.trim() || !growthForm.date.trim()) {
formError.value = "请填写事件名称和日期";
return;
}
growthRecords.value.unshift({ id: Date.now(), ...growthForm });
timelineState.value = "ready";
dialogVisible.value = false;
toastVisible.value = true;
timer = setTimeout(() => (toastVisible.value = false), 1800);
};
onUnmounted(() => {
if (timer) clearTimeout(timer);
});
</script>
<style scoped lang="scss">
.timeline-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.page-header,
.page-loading,
.page-content {
z-index: 1;
}
.page-loading {
min-height: calc(100vh - 100rpx);
}
.page-content {
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 18rpx 24rpx 72rpx;
}
.person-lead {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 8rpx 18rpx;
min-height: 62rpx;
align-items: center;
padding: 0 20rpx;
color: $ink-muted;
font-size: 22rpx;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png")
center/100% 100% no-repeat;
}
.person-lead text:first-child {
color: $brand-red;
font-weight: 700;
}
.timeline-card,
.state-card {
box-sizing: border-box;
padding: 34rpx 46rpx;
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
center/100% 100% no-repeat;
}
.timeline-card > text,
.state-card > text {
display: block;
}
.timeline-card > text:first-child {
color: $brand-red;
font-size: 20rpx;
}
.timeline-card > text:nth-child(2) {
margin-top: 6rpx;
color: $ink;
font-size: 31rpx;
font-weight: 700;
}
.timeline-card > text:nth-child(3) {
margin-top: 8rpx;
color: $ink-muted;
font-size: 21rpx;
}
.timeline-card > text:last-child {
margin-top: 10rpx;
color: $ink;
font-size: 23rpx;
line-height: 1.55;
}
.state-card {
min-height: 340rpx;
padding-top: 78rpx;
text-align: center;
}
.state-card > text:first-child {
color: $ink;
font-size: 35rpx;
font-weight: 700;
}
.state-card > text:nth-child(2) {
margin-top: 18rpx;
color: $ink-muted;
font-size: 24rpx;
line-height: 1.65;
}
.state-card .app-button {
margin-top: 28rpx;
}
.dialog-form {
width: 100%;
margin: 18rpx 0;
}
.dialog-form input,
.dialog-form textarea {
width: 100%;
min-height: 70rpx;
margin-top: 10rpx;
padding: 14rpx 20rpx;
box-sizing: border-box;
color: $ink;
font-size: 23rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
}
.dialog-form text {
display: block;
margin-top: 8rpx;
color: $brand-red;
font-size: 20rpx;
}
</style>