Files
jiapuapp/pages/records/r11-merit-records.vue
T
2026-07-21 07:53:08 +08:00

267 lines
7.2 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-11用途功德记录贡献汇总与同页新增 -->
<template>
<view class="merit-page" :class="stateClasses">
<ModulePageBackground module="records" />
<view class="page-header">
<PageHeader title="功德记录" action="新增" @action="createMerit" />
</view>
<view v-if="meritState === 'loading'" class="page-loading">
<AppLoading
text="正在整理功德记录"
description="请稍候,正在读取家人对家族事务的支持。"
/>
</view>
<view v-else class="page-content">
<view v-if="meritState === 'ready'" class="merit-summary">
<text>共同贡献</text>
<text>{{ totalContribution }} </text>
<text>每一次时间物资与心力的付出都值得被记住</text>
</view>
<template v-if="meritState === 'ready' && meritRecords.length">
<view v-for="merit in meritRecords" :key="merit.id" class="merit-card">
<text>{{ merit.category }}</text>
<text>{{ merit.title }}</text>
<text>{{ merit.contributor }} · {{ merit.date }}</text>
<text>{{ merit.description }}</text>
</view>
<AppButton block label="新增功德记录" @click="createMerit" />
</template>
<view v-else class="state-card">
<text>
{{ meritState === "error" ? "功德记录暂不可用" : "还没有功德记录" }}
</text>
<text>
{{
meritState === "error"
? "请稍后重新查看,已有记录不会受到影响。"
: "记录第一份对家族事务的时间、物资或心力支持。"
}}
</text>
<AppButton
:type="meritState === 'error' ? 'secondary' : 'primary'"
block
:label="meritState === 'error' ? '重新查看' : '新增记录'"
@click="
meritState === 'error' ? (meritState = 'ready') : createMerit()
"
/>
</view>
</view>
<AppDialog
:visible="dialogVisible"
eyebrow="功德记录"
title="记下一份家族贡献"
confirm-text="保存记录"
cancel-text="取消"
show-cancel
@confirm="saveMerit"
@cancel="dialogVisible = false"
>
<view class="dialog-form">
<input v-model="meritForm.title" placeholder="贡献事项" />
<input v-model="meritForm.contributor" placeholder="贡献人" />
<input v-model="meritForm.date" placeholder="日期" />
<textarea
v-model="meritForm.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 meritRecords = ref([
{
id: 1,
category: "共同修缮",
title: "修缮祠堂",
contributor: "汤氏家人共同参与",
date: "2024 年春",
description: "协助整理院落、修补门窗并登记旧物。",
},
{
id: 2,
category: "奖学助学",
title: "支持后辈勤学",
contributor: "家族教育小组",
date: "2024 年夏",
description: "为家族中努力求学的孩子提供书籍与经验分享。",
},
]);
const totalContribution = computed(() => meritRecords.value.length);
const meritState = ref("loading");
const dialogVisible = ref(false);
const toastVisible = ref(false);
const formError = ref("");
const meritForm = reactive({
title: "",
contributor: "",
date: "",
description: "",
category: "家族贡献",
});
let timer = null;
const stateClasses = computed(() => ({
"merit-state--loading": meritState.value === "loading",
"merit-state--empty": meritState.value === "empty",
"merit-state--error": meritState.value === "error",
}));
onLoad((q) => {
meritState.value = ["loading", "empty", "error"].includes(q.state)
? q.state
: "ready";
});
const createMerit = () => {
Object.assign(meritForm, {
title: "",
contributor: "",
date: "",
description: "",
category: "家族贡献",
});
formError.value = "";
dialogVisible.value = true;
};
const saveMerit = () => {
if (!meritForm.title.trim() || !meritForm.contributor.trim()) {
formError.value = "请填写贡献事项和贡献人";
return;
}
meritRecords.value.unshift({ id: Date.now(), ...meritForm });
meritState.value = "ready";
dialogVisible.value = false;
toastVisible.value = true;
timer = setTimeout(() => (toastVisible.value = false), 1800);
};
onUnmounted(() => {
if (timer) clearTimeout(timer);
});
</script>
<style scoped lang="scss">
.merit-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;
}
.merit-summary,
.merit-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;
}
.merit-summary {
text-align: center;
}
.merit-summary > text,
.merit-card > text,
.state-card > text {
display: block;
}
.merit-summary > text:first-child {
color: $brand-red;
font-size: 21rpx;
}
.merit-summary > text:nth-child(2) {
margin-top: 5rpx;
color: $ink;
font-size: 38rpx;
font-weight: 700;
}
.merit-summary > text:last-child {
margin-top: 9rpx;
color: $ink-muted;
font-size: 22rpx;
line-height: 1.5;
}
.merit-card > text:first-child {
color: $brand-red;
font-size: 20rpx;
}
.merit-card > text:nth-child(2) {
margin-top: 6rpx;
color: $ink;
font-size: 31rpx;
font-weight: 700;
}
.merit-card > text:nth-child(3) {
margin-top: 8rpx;
color: $ink-muted;
font-size: 21rpx;
}
.merit-card > text:last-child {
margin-top: 9rpx;
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: 14rpx 0;
}
.dialog-form input,
.dialog-form textarea {
width: 100%;
min-height: 62rpx;
margin-top: 7rpx;
padding: 11rpx 18rpx;
box-sizing: border-box;
color: $ink;
font-size: 22rpx;
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
center/100% 100% no-repeat;
}
.dialog-form text {
display: block;
margin-top: 7rpx;
color: $brand-red;
font-size: 20rpx;
}
</style>