修改完成
This commit is contained in:
@@ -14,10 +14,6 @@
|
||||
<view class="people-content">
|
||||
<template v-if="peopleState === 'ready'">
|
||||
<view class="people-search">
|
||||
<image
|
||||
src="/static/assets/modules/records/transparent/r01-search-input-frame.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<input
|
||||
v-model="keywordInput"
|
||||
confirm-type="search"
|
||||
@@ -41,11 +37,6 @@
|
||||
class="person-card"
|
||||
@click="openPerson(person)"
|
||||
>
|
||||
<image
|
||||
class="person-card__skin"
|
||||
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view class="person-card__copy">
|
||||
<text class="person-card__name">{{ person.name }}</text>
|
||||
<text class="person-card__meta"
|
||||
@@ -63,11 +54,7 @@
|
||||
</view>
|
||||
|
||||
<view v-else class="people-result-empty">
|
||||
<image
|
||||
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view
|
||||
<view class="people-state-card__copy"
|
||||
><text>没有找到相关人物</text
|
||||
><text>请更换姓名、身份或世代关键词后再试。</text></view
|
||||
>
|
||||
@@ -81,11 +68,7 @@
|
||||
</template>
|
||||
|
||||
<view v-else class="people-state-card">
|
||||
<image
|
||||
src="/static/assets/modules/records/transparent/r01-person-name-card.png"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<view>
|
||||
<view class="people-state-card__copy">
|
||||
<text>{{
|
||||
peopleState === "empty" ? "还没有人物记录" : "人物录暂不可用"
|
||||
}}</text>
|
||||
@@ -171,12 +154,7 @@ const openPerson = (person) =>
|
||||
url: `/pages/records/r02-person-detail?personId=${person.id}`,
|
||||
});
|
||||
const showCreateNotice = () => {
|
||||
toastVisible.value = true;
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => {
|
||||
toastVisible.value = false;
|
||||
toastTimer = null;
|
||||
}, 1800);
|
||||
uni.navigateTo({ url: "/pages/records/r02-person-detail?mode=create" });
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -186,37 +164,28 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.people-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
flex-direction: column;
|
||||
background: $paper;
|
||||
}
|
||||
.people-page__header,
|
||||
.people-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
}
|
||||
.people-content {
|
||||
flex: 1;
|
||||
padding: 22rpx 24rpx 100rpx;
|
||||
}
|
||||
.people-search {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 126rpx;
|
||||
width: 100%;
|
||||
height: 82rpx;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
}
|
||||
.people-search > image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
background: url("/static/assets/modules/records/transparent/r01-search-input-frame.png") center / 100% 100% no-repeat;
|
||||
}
|
||||
.people-search input {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
@@ -230,7 +199,6 @@ onUnmounted(() => {
|
||||
color: #8a7965;
|
||||
}
|
||||
.people-search__action {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 126rpx;
|
||||
@@ -246,7 +214,6 @@ onUnmounted(() => {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.person-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: clamp(92px, 190rpx, 108px);
|
||||
@@ -254,20 +221,12 @@ onUnmounted(() => {
|
||||
margin-top: 12px;
|
||||
padding: 16% 10%;
|
||||
box-sizing: border-box;
|
||||
background: url("/static/assets/modules/records/transparent/r01-person-name-card.png") center / 100% 100% no-repeat;
|
||||
}
|
||||
.person-card:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.person-card__skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.person-card__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
@@ -294,27 +253,12 @@ onUnmounted(() => {
|
||||
}
|
||||
.people-result-empty,
|
||||
.people-state-card {
|
||||
position: relative;
|
||||
margin-top: 38rpx;
|
||||
background: url("/static/assets/modules/records/transparent/r01-person-name-card.png") top center / 100% 220rpx no-repeat;
|
||||
text-align: center;
|
||||
}
|
||||
.people-result-empty > image,
|
||||
.people-state-card > image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
min-height: 118px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.people-result-empty > view,
|
||||
.people-state-card > view {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.people-state-card__copy {
|
||||
display: flex;
|
||||
height: 220rpx;
|
||||
min-height: 118px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<view class="person-detail-page" :class="`person-detail-state--${personState}`">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="person-detail-header"><PageHeader :title="personState === 'edit' ? '编辑人物' : '人物详情'" /></view>
|
||||
<view class="person-detail-header"><PageHeader :title="personState === 'edit' ? (isCreateMode ? '新建人物' : '编辑人物') : '人物详情'" /></view>
|
||||
|
||||
<view v-if="personState === 'loading'" class="person-detail-loading">
|
||||
<AppLoading text="正在读取人物档案" description="请稍候,正在整理人物资料。" />
|
||||
@@ -11,7 +11,6 @@
|
||||
<view v-else class="person-detail-content">
|
||||
<template v-if="['detail', 'edit', 'privacy'].includes(personState)">
|
||||
<view class="person-identity-card">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/r01-person-name-card.png" mode="scaleToFill" />
|
||||
<view class="person-identity-card__copy">
|
||||
<text class="person-identity-card__name">{{ person.name }}</text>
|
||||
<text class="person-identity-card__meta">{{ person.role }} · 第 {{ person.generation }} 世</text>
|
||||
@@ -22,23 +21,24 @@
|
||||
|
||||
<template v-if="personState === 'detail'">
|
||||
<view v-for="item in detailSections" :key="item.title" class="person-archive-card">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/module-content-frame.png" mode="scaleToFill" />
|
||||
<view><text>{{ item.title }}</text><text>{{ item.copy }}</text></view>
|
||||
</view>
|
||||
<view class="person-related-actions">
|
||||
<AppButton type="secondary" block label="成长日志" @click="toGrowthJournal" />
|
||||
<AppButton type="secondary" block label="人生大事" @click="toLifeEvents" />
|
||||
</view>
|
||||
<view class="person-edit-action" @click="enterEdit"><AppButton block label="编辑人物" /></view>
|
||||
</template>
|
||||
|
||||
<template v-else-if="personState === 'edit'">
|
||||
<view v-for="field in shortFields" :key="field.key" class="person-field">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/module-field-frame.png" mode="scaleToFill" />
|
||||
<text class="person-field__label">{{ field.label }}</text>
|
||||
<input v-model="draft[field.key]" :type="field.key === 'generation' ? 'number' : 'text'" :placeholder="`请输入${field.label}`" />
|
||||
<text v-if="errors[field.key]" class="person-field-error">{{ errors[field.key] }}</text>
|
||||
</view>
|
||||
<view v-for="field in longFields" :key="field.key" class="person-long-field">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/module-content-frame.png" mode="scaleToFill" />
|
||||
<text class="person-long-field__label">{{ field.label }}</text>
|
||||
<textarea v-model="draft[field.key]" :placeholder="`请输入${field.label}`" />
|
||||
<textarea v-model="draft[field.key]" auto-height :placeholder="`请输入${field.label}`" />
|
||||
</view>
|
||||
<view class="person-edit-actions">
|
||||
<view class="person-save-action" @click="savePerson"><AppButton block label="保存人物" /></view>
|
||||
@@ -47,13 +47,11 @@
|
||||
</template>
|
||||
|
||||
<view v-else-if="personState === 'privacy'" class="person-state-card">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/module-content-frame.png" mode="scaleToFill" />
|
||||
<view><text>部分资料未公开</text><text>人物小传与家族印记受隐私设置保护,当前只展示公开身份。</text></view>
|
||||
<view class="person-state-action" @click="returnToPeople"><AppButton block label="返回人物录" /></view>
|
||||
</view>
|
||||
|
||||
<view v-else class="person-state-card">
|
||||
<image class="person-card-skin" src="/static/assets/modules/records/transparent/module-content-frame.png" mode="scaleToFill" />
|
||||
<view>
|
||||
<text>{{ personState === 'expired' ? '人物档案已失效' : '人物档案暂不可用' }}</text>
|
||||
<text>{{ personState === 'expired' ? '这份人物资料已无法查看,请返回人物录选择其他档案。' : '请稍后重新查看,已有资料不会受到影响。' }}</text>
|
||||
@@ -85,6 +83,7 @@ const person = reactive({ ...people[0] });
|
||||
const draft = reactive({ name: "", role: "", generation: "", biography: "", legacy: "" });
|
||||
const errors = reactive({ name: "", role: "", generation: "" });
|
||||
const personState = ref("loading");
|
||||
const isCreateMode = ref(false);
|
||||
const toastVisible = ref(false);
|
||||
let toastTimer = null;
|
||||
const shortFields = [{ key: "name", label: "姓名" }, { key: "role", label: "身份" }, { key: "generation", label: "世代" }];
|
||||
@@ -100,7 +99,8 @@ const savePerson = () => {
|
||||
if (!String(draft.role).trim()) errors.role = "请填写身份";
|
||||
if (!String(draft.generation).trim()) errors.generation = "请填写世代";
|
||||
if (errors.name || errors.role || errors.generation) return;
|
||||
Object.assign(person, { ...draft, name: draft.name.trim(), role: draft.role.trim(), generation: String(draft.generation).trim() });
|
||||
Object.assign(person, { id: person.id || "new", ...draft, name: draft.name.trim(), role: draft.role.trim(), generation: String(draft.generation).trim() });
|
||||
isCreateMode.value = false;
|
||||
personState.value = "detail";
|
||||
toastVisible.value = true;
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
@@ -108,7 +108,29 @@ const savePerson = () => {
|
||||
};
|
||||
const returnToPeople = () => uni.reLaunch({ url: "/pages/records/r01-people-list" });
|
||||
const restoreDetail = () => { personState.value = "detail"; };
|
||||
const toGrowthJournal = () =>
|
||||
uni.navigateTo({
|
||||
url: `/pages/records/r08-growth-journal?personId=${person.id}`,
|
||||
});
|
||||
const toLifeEvents = () =>
|
||||
uni.navigateTo({
|
||||
url: `/pages/records/r09-life-events?personId=${person.id}`,
|
||||
});
|
||||
onLoad((query) => {
|
||||
isCreateMode.value = query.mode === "create";
|
||||
if (isCreateMode.value) {
|
||||
Object.assign(person, {
|
||||
id: "",
|
||||
name: "",
|
||||
role: "",
|
||||
generation: "",
|
||||
biography: "",
|
||||
legacy: "",
|
||||
});
|
||||
copyToDraft();
|
||||
personState.value = "edit";
|
||||
return;
|
||||
}
|
||||
const selected = people.find((item) => item.id === String(query.personId || ""));
|
||||
if (selected) Object.assign(person, selected);
|
||||
copyToDraft();
|
||||
@@ -119,33 +141,31 @@ onUnmounted(() => { if (toastTimer) clearTimeout(toastTimer); });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.person-detail-page { min-height: 100vh; overflow-x: hidden; background: $paper; }
|
||||
.person-detail-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
|
||||
.person-detail-header,.person-detail-loading,.person-detail-content { z-index: 1; }
|
||||
.person-detail-loading { min-height: calc(100vh - 100rpx); }
|
||||
.person-detail-content { padding: 18rpx 24rpx 72rpx; }
|
||||
.person-identity-card,.person-archive-card,.person-field,.person-long-field,.person-state-card { position: relative; }
|
||||
.person-card-skin { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.person-identity-card { display: flex; min-height: 190rpx; padding: 30rpx 10%; box-sizing: border-box; }
|
||||
.person-identity-card__copy { position: relative; z-index: 1; display: flex; flex: 1; flex-direction: column; justify-content: center; }
|
||||
.person-identity-card { display: flex; min-height: 190rpx; padding: 30rpx 10%; box-sizing: border-box; background: url("/static/assets/modules/records/transparent/r01-person-name-card.png") center / 100% 100% no-repeat; }
|
||||
.person-identity-card__copy { display: flex; flex: 1; flex-direction: column; justify-content: center; }
|
||||
.person-identity-card__copy text { display: block; }
|
||||
.person-identity-card__name { color: $ink; font-family: STKaiti,KaiTi,serif; font-size: 38rpx; font-weight: 700; }
|
||||
.person-identity-card__meta { margin-top: 8rpx; color: $ink-muted; font-size: 25rpx; font-weight: 600; }
|
||||
.person-identity-card__hint { margin-top: 8rpx; color: #806a51; font-size: 21rpx; }
|
||||
.person-archive-card { min-height: 154rpx; margin-top: 14rpx; padding: 32rpx 42rpx; box-sizing: border-box; }
|
||||
.person-archive-card > view { position: relative; z-index: 1; }
|
||||
.person-archive-card,.person-long-field,.person-state-card { background: url("/static/assets/modules/records/transparent/module-content-frame.png") center / 100% 100% no-repeat; }
|
||||
.person-archive-card text { display: block; }
|
||||
.person-archive-card text:first-child,.person-long-field__label { color: $brand-red; font-size: 24rpx; font-weight: 700; }
|
||||
.person-archive-card text:last-child { margin-top: 11rpx; color: $ink; font-size: 24rpx; line-height: 1.55; }
|
||||
.person-edit-action { margin-top: 20rpx; }
|
||||
.person-field { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8rpx 24rpx; min-height: 92rpx; margin-top: 12rpx; padding: 18rpx 28rpx; box-sizing: border-box; }
|
||||
.person-field__label { position: relative; z-index: 1; color: $ink; font-size: 23rpx; font-weight: 700; }
|
||||
.person-field input { position: relative; z-index: 1; width: 100%; min-width: 0; height: 56rpx; color: $ink; font-size: 23rpx; text-align: right; }
|
||||
.person-field-error { position: relative; z-index: 1; grid-column: 1 / -1; display: block; color: $brand-red; font-size: 21rpx; text-align: right; }
|
||||
.person-related-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14rpx; margin-top: 18rpx; }
|
||||
.person-field { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8rpx 24rpx; min-height: 92rpx; margin-top: 12rpx; padding: 18rpx 28rpx; box-sizing: border-box; background: url("/static/assets/modules/records/transparent/module-field-frame.png") center / 100% 100% no-repeat; }
|
||||
.person-field__label { color: $ink; font-size: 23rpx; font-weight: 700; }
|
||||
.person-field input { width: 100%; min-width: 0; min-height: 56rpx; color: $ink; font-size: 23rpx; text-align: right; }
|
||||
.person-field-error { grid-column: 1 / -1; display: block; color: $brand-red; font-size: 21rpx; text-align: right; }
|
||||
.person-long-field { display: flex; flex-direction: column; min-height: 210rpx; margin-top: 14rpx; padding: 28rpx 38rpx; box-sizing: border-box; }
|
||||
.person-long-field__label,.person-long-field textarea { position: relative; z-index: 1; }
|
||||
.person-long-field textarea { width: 100%; height: 112rpx; min-height: 112rpx; margin-top: 14rpx; color: $ink; font-size: 23rpx; line-height: 1.5; }
|
||||
.person-long-field textarea { width: 100%; min-height: 112rpx; margin-top: 14rpx; color: $ink; font-size: 23rpx; line-height: 1.5; }
|
||||
.person-edit-actions { display: flex; flex-direction: column; gap: 14rpx; margin-top: 20rpx; }
|
||||
.person-state-card { display: flex; flex-direction: column; min-height: 300rpx; margin-top: 26rpx; padding: 72rpx 54rpx 42rpx; box-sizing: border-box; text-align: center; }
|
||||
.person-state-card > view { position: relative; z-index: 1; }
|
||||
.person-state-card text { display: block; }
|
||||
.person-state-card text:first-child { color: $ink; font-family: STKaiti,KaiTi,serif; font-size: 34rpx; font-weight: 700; }
|
||||
.person-state-card text:last-child { margin-top: 16rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.6; }
|
||||
|
||||
@@ -1,5 +1,193 @@
|
||||
<!-- 页面编号:R-03;用途:贺礼列表。 -->
|
||||
<template><ModulePage page-id="r03" /></template>
|
||||
<!-- 页面编号:R-03;用途:贺礼簿列表、空态、失败与新增入口。 -->
|
||||
<template>
|
||||
<view class="gift-page" :class="stateClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader title="贺礼簿" action="新增" @action="createGift" />
|
||||
</view>
|
||||
<view v-if="giftState === 'loading'" class="page-loading">
|
||||
<AppLoading
|
||||
text="正在整理贺礼簿"
|
||||
description="请稍候,正在读取家人的礼仪往来。"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="page-content">
|
||||
<template v-if="giftState === 'ready' && giftBooks.length">
|
||||
<view
|
||||
v-for="gift in giftBooks"
|
||||
:key="gift.id"
|
||||
class="record-card"
|
||||
role="button"
|
||||
:aria-label="`查看${gift.title}`"
|
||||
@click="openGiftBook(gift)"
|
||||
>
|
||||
<text class="record-card__tag">{{ gift.occasion }}</text>
|
||||
<text class="record-card__title">{{ gift.title }}</text>
|
||||
<text class="record-card__copy">
|
||||
{{ gift.from }} · {{ gift.date }}
|
||||
</text>
|
||||
<text class="record-card__hint">查看并编辑贺礼</text>
|
||||
</view>
|
||||
<AppButton block label="新增贺礼" @click="createGift" />
|
||||
</template>
|
||||
<view v-else class="state-card">
|
||||
<text>
|
||||
{{ giftState === "error" ? "贺礼簿暂不可用" : "还没有贺礼记录" }}
|
||||
</text>
|
||||
<text>
|
||||
{{
|
||||
giftState === "error"
|
||||
? "请稍后重新查看,已有记录不会受到影响。"
|
||||
: "从第一份家人之间的心意开始记录。"
|
||||
}}
|
||||
</text>
|
||||
<AppButton
|
||||
:type="giftState === 'error' ? 'secondary' : 'primary'"
|
||||
block
|
||||
:label="giftState === 'error' ? '重新查看' : '新增贺礼'"
|
||||
@click="giftState === 'error' ? restoreGifts() : createGift()"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
const baseGifts = [
|
||||
{
|
||||
id: "301",
|
||||
title: "新春贺礼",
|
||||
occasion: "春节",
|
||||
from: "汤文正一家",
|
||||
date: "2024 年 2 月 10 日",
|
||||
},
|
||||
{
|
||||
id: "302",
|
||||
title: "寿宴礼单",
|
||||
occasion: "寿辰",
|
||||
from: "汤淑华",
|
||||
date: "2024 年 4 月 18 日",
|
||||
},
|
||||
{
|
||||
id: "303",
|
||||
title: "添丁祝福",
|
||||
occasion: "新生",
|
||||
from: "汤文清一家",
|
||||
date: "2024 年 6 月 2 日",
|
||||
},
|
||||
];
|
||||
const giftBooks = ref([...baseGifts]);
|
||||
const giftState = ref("loading");
|
||||
const stateClasses = computed(() => ({
|
||||
"gift-state--loading": giftState.value === "loading",
|
||||
"gift-state--empty": giftState.value === "empty",
|
||||
"gift-state--error": giftState.value === "error",
|
||||
}));
|
||||
onLoad((query) => {
|
||||
const count = Math.max(
|
||||
1,
|
||||
Math.min(Number(query.count) || baseGifts.length, 50),
|
||||
);
|
||||
giftBooks.value = Array.from({ length: count }, (_, i) => ({
|
||||
...baseGifts[i % baseGifts.length],
|
||||
id: String(301 + i),
|
||||
title:
|
||||
count > 3 ? `${baseGifts[i % 3].title}(${i + 1})` : baseGifts[i].title,
|
||||
}));
|
||||
giftState.value = ["loading", "empty", "error"].includes(query.state)
|
||||
? query.state
|
||||
: "ready";
|
||||
});
|
||||
const openGiftBook = (gift) =>
|
||||
uni.navigateTo({
|
||||
url: `/pages/records/r04-gift-editor?mode=view&giftId=${gift.id}`,
|
||||
});
|
||||
const createGift = () =>
|
||||
uni.navigateTo({ url: "/pages/records/r04-gift-editor?mode=create" });
|
||||
const restoreGifts = () => {
|
||||
giftState.value = "ready";
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.gift-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;
|
||||
}
|
||||
.record-card,
|
||||
.state-card {
|
||||
box-sizing: border-box;
|
||||
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.record-card {
|
||||
min-height: 190rpx;
|
||||
padding: 34rpx 46rpx;
|
||||
}
|
||||
.record-card > text,
|
||||
.state-card > text {
|
||||
display: block;
|
||||
}
|
||||
.record-card__tag {
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.record-card__title {
|
||||
margin-top: 6rpx;
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.record-card__copy {
|
||||
margin-top: 9rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.record-card__hint {
|
||||
margin-top: 10rpx;
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding: 78rpx 52rpx 50rpx;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,260 @@
|
||||
<!-- 页面编号:R-04;用途:贺礼新增、详情与删除确认。 -->
|
||||
<template><ModulePage page-id="r04" /></template>
|
||||
<!-- 页面编号:R-04;用途:贺礼查看、新增、编辑、保存与删除确认。 -->
|
||||
<template>
|
||||
<view class="gift-editor-page" :class="editorClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader
|
||||
:title="
|
||||
mode === 'create'
|
||||
? '新增贺礼'
|
||||
: mode === 'view'
|
||||
? '贺礼详情'
|
||||
: '编辑贺礼'
|
||||
"
|
||||
:action="mode === 'view' ? '编辑' : ''"
|
||||
@action="mode = 'edit'"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="editorState === 'loading'" class="page-loading">
|
||||
<AppLoading
|
||||
text="正在读取贺礼"
|
||||
description="请稍候,正在整理这份礼仪记录。"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="page-content">
|
||||
<view v-if="editorState === 'success'" class="state-card">
|
||||
<text>贺礼已保存</text>
|
||||
<text>这份心意已加入家族贺礼簿。</text>
|
||||
<AppButton block label="返回贺礼簿" @click="backToGifts" />
|
||||
</view>
|
||||
<view v-else-if="mode === 'view'" class="detail-card">
|
||||
<text>{{ giftForm.title }}</text>
|
||||
<view v-for="item in detailRows" :key="item.label">
|
||||
<text>{{ item.label }}</text>
|
||||
<text>{{ item.value }}</text>
|
||||
</view>
|
||||
<AppButton block label="编辑贺礼" @click="mode = 'edit'" />
|
||||
<AppButton
|
||||
type="secondary"
|
||||
block
|
||||
label="删除记录"
|
||||
@click="confirmDelete"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="form-card">
|
||||
<text>
|
||||
{{ mode === "create" ? "记录一份家人心意" : "修改贺礼信息" }}
|
||||
</text>
|
||||
<view v-for="field in fields" :key="field.key" class="field-row">
|
||||
<text>{{ field.label }}</text>
|
||||
<input
|
||||
v-model="giftForm[field.key]"
|
||||
:placeholder="`请输入${field.label}`"
|
||||
/>
|
||||
<text v-if="giftErrors[field.key]">{{ giftErrors[field.key] }}</text>
|
||||
</view>
|
||||
<text v-if="editorState === 'error'" class="save-error">
|
||||
保存失败,请检查内容后重试。
|
||||
</text>
|
||||
<AppButton
|
||||
block
|
||||
:disabled="editorState === 'saving'"
|
||||
:label="editorState === 'saving' ? '正在保存' : '保存贺礼'"
|
||||
@click="saveGift"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="mode === 'edit'"
|
||||
type="secondary"
|
||||
block
|
||||
label="删除记录"
|
||||
@click="confirmDelete"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="deleteVisible"
|
||||
eyebrow="删除确认"
|
||||
title="删除这份贺礼?"
|
||||
message="删除后将返回贺礼簿,本地演示记录不会继续显示。"
|
||||
confirm-text="确认删除"
|
||||
cancel-text="保留记录"
|
||||
show-cancel
|
||||
@confirm="deleteGift"
|
||||
@cancel="deleteVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
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 ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
const records = [
|
||||
{
|
||||
id: "301",
|
||||
title: "新春贺礼",
|
||||
from: "汤文正一家",
|
||||
date: "2024-02-10",
|
||||
note: "新春团拜时赠予长辈的心意",
|
||||
},
|
||||
{
|
||||
id: "302",
|
||||
title: "寿宴礼单",
|
||||
from: "汤淑华",
|
||||
date: "2024-04-18",
|
||||
note: "汤老先生八十寿辰",
|
||||
},
|
||||
];
|
||||
const giftId = ref("");
|
||||
const mode = ref("create");
|
||||
const editorState = ref("ready");
|
||||
const deleteVisible = ref(false);
|
||||
const forceSaveFailure = ref(false);
|
||||
const giftForm = reactive({ title: "", from: "", date: "", note: "" });
|
||||
const giftErrors = reactive({ title: "", from: "", date: "" });
|
||||
const fields = [
|
||||
{ key: "title", label: "贺礼名称" },
|
||||
{ key: "from", label: "赠送人" },
|
||||
{ key: "date", label: "日期" },
|
||||
{ key: "note", label: "备注" },
|
||||
];
|
||||
let saveTimer = null;
|
||||
const editorClasses = computed(() => ({
|
||||
"gift-editor-state--saving": editorState.value === "saving",
|
||||
"gift-editor-state--error": editorState.value === "error",
|
||||
}));
|
||||
const detailRows = computed(() =>
|
||||
fields
|
||||
.slice(1)
|
||||
.map((f) => ({ label: f.label, value: giftForm[f.key] || "未填写" })),
|
||||
);
|
||||
onLoad((query) => {
|
||||
giftId.value = String(query.giftId || "");
|
||||
mode.value = ["view", "edit"].includes(query.mode) ? query.mode : "create";
|
||||
forceSaveFailure.value = query.saveResult === "error";
|
||||
const selected = records.find((x) => x.id === giftId.value);
|
||||
if (selected) Object.assign(giftForm, selected);
|
||||
else if (mode.value !== "create") editorState.value = "error";
|
||||
if (query.state === "loading") editorState.value = "loading";
|
||||
});
|
||||
const validateGift = () => {
|
||||
giftErrors.title = giftForm.title.trim() ? "" : "请填写贺礼名称";
|
||||
giftErrors.from = giftForm.from.trim() ? "" : "请填写赠送人";
|
||||
giftErrors.date = giftForm.date.trim() ? "" : "请填写日期";
|
||||
return !giftErrors.title && !giftErrors.from && !giftErrors.date;
|
||||
};
|
||||
const saveGift = () => {
|
||||
if (editorState.value === "saving" || !validateGift()) return;
|
||||
editorState.value = "saving";
|
||||
saveTimer = setTimeout(() => {
|
||||
editorState.value = forceSaveFailure.value ? "error" : "success";
|
||||
forceSaveFailure.value = false;
|
||||
}, 320);
|
||||
};
|
||||
const confirmDelete = () => {
|
||||
deleteVisible.value = true;
|
||||
};
|
||||
const deleteGift = () => {
|
||||
deleteVisible.value = false;
|
||||
backToGifts();
|
||||
};
|
||||
const backToGifts = () =>
|
||||
uni.redirectTo({ url: "/pages/records/r03-gift-list" });
|
||||
onUnmounted(() => {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.gift-editor-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 {
|
||||
padding: 18rpx 24rpx 72rpx;
|
||||
}
|
||||
.form-card,
|
||||
.detail-card,
|
||||
.state-card {
|
||||
box-sizing: border-box;
|
||||
padding: 46rpx;
|
||||
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.form-card > text:first-child,
|
||||
.detail-card > text:first-child,
|
||||
.state-card > text:first-child {
|
||||
display: block;
|
||||
color: $ink;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.field-row,
|
||||
.detail-card > view {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12rpx 20rpx;
|
||||
min-height: 82rpx;
|
||||
margin-top: 14rpx;
|
||||
padding: 14rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.field-row > text:first-child,
|
||||
.detail-card > view > text:first-child {
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.field-row input,
|
||||
.detail-card > view > text:last-child {
|
||||
min-width: 0;
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.field-row > text:last-child {
|
||||
grid-column: 1/-1;
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.form-card .app-button,
|
||||
.detail-card .app-button {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.save-error {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
color: $brand-red;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding-top: 80rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.state-card > text:nth-child(2) {
|
||||
display: block;
|
||||
margin-top: 18rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.state-card .app-button {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,181 @@
|
||||
<!-- 页面编号:R-05;用途:礼仪活动列表。 -->
|
||||
<template><ModulePage page-id="r05" /></template>
|
||||
<!-- 页面编号:R-05;用途:礼仪活动列表、状态与创建入口。 -->
|
||||
<template>
|
||||
<view class="ritual-page" :class="stateClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader title="礼仪活动" action="新建" @action="createRitual" />
|
||||
</view>
|
||||
<view v-if="ritualState === 'loading'" class="page-loading">
|
||||
<AppLoading
|
||||
text="正在整理礼仪活动"
|
||||
description="请稍候,正在读取时间与地点。"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="page-content">
|
||||
<template v-if="ritualState === 'ready' && rituals.length">
|
||||
<view
|
||||
v-for="ritual in rituals"
|
||||
:key="ritual.id"
|
||||
class="record-card"
|
||||
@click="openRitual(ritual)"
|
||||
>
|
||||
<text>{{ ritual.status }}</text>
|
||||
<text>{{ ritual.name }}</text>
|
||||
<text>{{ ritual.date }} · {{ ritual.place }}</text>
|
||||
<text>查看活动详情</text>
|
||||
</view>
|
||||
<AppButton block label="新建礼仪" @click="createRitual" />
|
||||
</template>
|
||||
<view v-else class="state-card">
|
||||
<text>
|
||||
{{ ritualState === "error" ? "礼仪活动暂不可用" : "还没有礼仪活动" }}
|
||||
</text>
|
||||
<text>
|
||||
{{
|
||||
ritualState === "error"
|
||||
? "请稍后重新查看,已有活动不会受到影响。"
|
||||
: "从一次祭祖、家宴或团拜开始安排。"
|
||||
}}
|
||||
</text>
|
||||
<AppButton
|
||||
:type="ritualState === 'error' ? 'secondary' : 'primary'"
|
||||
block
|
||||
:label="ritualState === 'error' ? '重新查看' : '新建礼仪'"
|
||||
@click="ritualState === 'error' ? restoreRituals() : createRitual()"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
const base = [
|
||||
{
|
||||
id: "501",
|
||||
name: "清明祭祖",
|
||||
status: "报名中",
|
||||
date: "2025 年 4 月 4 日",
|
||||
place: "汤氏宗祠",
|
||||
},
|
||||
{
|
||||
id: "502",
|
||||
name: "中秋家宴",
|
||||
status: "筹备中",
|
||||
date: "2025 年 9 月 17 日",
|
||||
place: "祖居院落",
|
||||
},
|
||||
{
|
||||
id: "503",
|
||||
name: "新春团拜",
|
||||
status: "已结束",
|
||||
date: "2025 年 1 月 29 日",
|
||||
place: "家族礼堂",
|
||||
},
|
||||
];
|
||||
const rituals = ref([...base]);
|
||||
const ritualState = ref("loading");
|
||||
const stateClasses = computed(() => ({
|
||||
"ritual-state--loading": ritualState.value === "loading",
|
||||
"ritual-state--empty": ritualState.value === "empty",
|
||||
"ritual-state--error": ritualState.value === "error",
|
||||
}));
|
||||
onLoad((q) => {
|
||||
const n = Math.max(1, Math.min(Number(q.count) || base.length, 50));
|
||||
rituals.value = Array.from({ length: n }, (_, i) => ({
|
||||
...base[i % 3],
|
||||
id: String(501 + i),
|
||||
name: n > 3 ? `${base[i % 3].name}(${i + 1})` : base[i].name,
|
||||
}));
|
||||
ritualState.value = ["loading", "empty", "error"].includes(q.state)
|
||||
? q.state
|
||||
: "ready";
|
||||
});
|
||||
const openRitual = (r) =>
|
||||
uni.navigateTo({ url: `/pages/records/r06-ritual-detail?ritualId=${r.id}` });
|
||||
const createRitual = () =>
|
||||
uni.navigateTo({ url: "/pages/records/r07-ritual-editor?mode=create" });
|
||||
const restoreRituals = () => {
|
||||
ritualState.value = "ready";
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ritual-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;
|
||||
}
|
||||
.record-card,
|
||||
.state-card {
|
||||
box-sizing: border-box;
|
||||
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.record-card {
|
||||
min-height: 190rpx;
|
||||
padding: 32rpx 46rpx;
|
||||
}
|
||||
.record-card > text,
|
||||
.state-card > text {
|
||||
display: block;
|
||||
}
|
||||
.record-card > text:first-child {
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
.record-card > text:nth-child(2) {
|
||||
margin-top: 6rpx;
|
||||
color: $ink;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.record-card > text:nth-child(3) {
|
||||
margin-top: 9rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.record-card > text:last-child {
|
||||
margin-top: 9rpx;
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding: 78rpx 52rpx 50rpx;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,222 @@
|
||||
<!-- 页面编号:R-06;用途:礼仪详情。 -->
|
||||
<template><ModulePage page-id="r06" /></template>
|
||||
<!-- 页面编号:R-06;用途:礼仪详情、参与者与受控状态。 -->
|
||||
<template>
|
||||
<view class="ritual-detail-page" :class="stateClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader
|
||||
title="礼仪详情"
|
||||
:action="ritualState === 'ready' ? '编辑' : ''"
|
||||
@action="editRitual"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="ritualState === 'loading'" class="page-loading">
|
||||
<AppLoading
|
||||
text="正在读取礼仪详情"
|
||||
description="请稍候,正在整理活动与参与信息。"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="page-content">
|
||||
<template v-if="ritualState === 'ready'">
|
||||
<view class="detail-card">
|
||||
<text>{{ ritualDetail.status }}</text>
|
||||
<text>{{ ritualDetail.name }}</text>
|
||||
<text>{{ ritualDetail.date }} · {{ ritualDetail.place }}</text>
|
||||
<text>{{ ritualDetail.description }}</text>
|
||||
</view>
|
||||
<view class="participant-card">
|
||||
<view>
|
||||
<text>参与家人</text>
|
||||
<text>{{ participants.length }} 人</text>
|
||||
</view>
|
||||
<view v-for="person in participants" :key="person.id">
|
||||
<text>{{ person.name }}</text>
|
||||
<text>{{ person.role }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<AppButton block label="编辑活动" @click="editRitual" />
|
||||
</template>
|
||||
<view v-else class="state-card">
|
||||
<text>{{ stateCopy.title }}</text>
|
||||
<text>{{ stateCopy.copy }}</text>
|
||||
<AppButton
|
||||
:type="ritualState === 'error' ? 'secondary' : 'primary'"
|
||||
block
|
||||
:label="ritualState === 'error' ? '重新查看' : '返回礼仪列表'"
|
||||
@click="ritualState === 'error' ? restoreRitual() : backToRituals()"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
const records = [
|
||||
{
|
||||
id: "501",
|
||||
name: "清明祭祖",
|
||||
status: "报名中",
|
||||
date: "2025 年 4 月 4 日",
|
||||
place: "汤氏宗祠",
|
||||
description: "缅怀先祖,整理祭扫礼序,并由长辈讲述家族往事。",
|
||||
},
|
||||
{
|
||||
id: "502",
|
||||
name: "中秋家宴",
|
||||
status: "筹备中",
|
||||
date: "2025 年 9 月 17 日",
|
||||
place: "祖居院落",
|
||||
description: "家人团聚,共叙近况并整理年度家族影像。",
|
||||
},
|
||||
];
|
||||
const ritualDetail = reactive({ ...records[0] });
|
||||
const ritualId = ref("501");
|
||||
const ritualState = ref("loading");
|
||||
const participants = ref([
|
||||
{ id: 1, name: "汤文正", role: "主理人" },
|
||||
{ id: 2, name: "汤淑华", role: "家族长辈" },
|
||||
{ id: 3, name: "汤文清", role: "影像记录" },
|
||||
]);
|
||||
const stateClasses = computed(() => ({
|
||||
"ritual-state--expired": ritualState.value === "expired",
|
||||
"ritual-state--privacy": ritualState.value === "privacy",
|
||||
"ritual-state--error": ritualState.value === "error",
|
||||
}));
|
||||
const stateCopy = computed(
|
||||
() =>
|
||||
({
|
||||
expired: {
|
||||
title: "活动已失效",
|
||||
copy: "这项礼仪活动已取消或结束归档,请返回列表查看其他活动。",
|
||||
},
|
||||
privacy: {
|
||||
title: "活动信息未公开",
|
||||
copy: "当前活动只向受邀家人展示,请返回礼仪列表。",
|
||||
},
|
||||
error: {
|
||||
title: "礼仪详情暂不可用",
|
||||
copy: "请稍后重新查看,已有活动不会受到影响。",
|
||||
},
|
||||
})[ritualState.value] || {},
|
||||
);
|
||||
onLoad((q) => {
|
||||
ritualId.value = String(q.ritualId || "501");
|
||||
const selected = records.find((x) => x.id === ritualId.value);
|
||||
if (selected) Object.assign(ritualDetail, selected);
|
||||
ritualState.value = ["loading", "expired", "privacy", "error"].includes(
|
||||
q.state,
|
||||
)
|
||||
? q.state
|
||||
: selected
|
||||
? "ready"
|
||||
: "expired";
|
||||
});
|
||||
const editRitual = () =>
|
||||
uni.navigateTo({
|
||||
url: `/pages/records/r07-ritual-editor?mode=edit&ritualId=${ritualId.value}`,
|
||||
});
|
||||
const restoreRitual = () => {
|
||||
ritualState.value = "ready";
|
||||
};
|
||||
const backToRituals = () =>
|
||||
uni.redirectTo({ url: "/pages/records/r05-ritual-list" });
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ritual-detail-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;
|
||||
}
|
||||
.detail-card,
|
||||
.participant-card,
|
||||
.state-card {
|
||||
box-sizing: border-box;
|
||||
padding: 42rpx 46rpx;
|
||||
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.detail-card > text {
|
||||
display: block;
|
||||
}
|
||||
.detail-card > text:first-child {
|
||||
color: $brand-red;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
.detail-card > text:nth-child(2) {
|
||||
margin-top: 8rpx;
|
||||
color: $ink;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.detail-card > text:nth-child(3) {
|
||||
margin-top: 10rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
.detail-card > text:last-child {
|
||||
margin-top: 20rpx;
|
||||
color: $ink;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.participant-card > view {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 8rpx 18rpx;
|
||||
min-height: 52rpx;
|
||||
align-items: center;
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
.participant-card > view:first-child {
|
||||
color: $brand-red;
|
||||
font-weight: 700;
|
||||
}
|
||||
.participant-card > view + view {
|
||||
margin-top: 9rpx;
|
||||
padding-top: 9rpx;
|
||||
border-top: 1px solid rgba(136, 84, 42, 0.18);
|
||||
}
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding-top: 78rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.state-card > text {
|
||||
display: block;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,227 @@
|
||||
<!-- 页面编号:R-07;用途:新建与编辑礼仪。 -->
|
||||
<template><ModulePage page-id="r07" /></template>
|
||||
<!-- 页面编号:R-07;用途:礼仪创建、编辑、校验、保存与删除确认。 -->
|
||||
<template>
|
||||
<view class="ritual-editor-page" :class="editorClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader :title="mode === 'create' ? '新建礼仪' : '编辑礼仪'" />
|
||||
</view>
|
||||
<view class="page-content">
|
||||
<view v-if="editorState === 'success'" class="state-card">
|
||||
<text>礼仪活动已保存</text>
|
||||
<text>时间、地点与活动说明已整理完成。</text>
|
||||
<AppButton block label="返回礼仪列表" @click="backToRituals" />
|
||||
</view>
|
||||
<view v-else class="form-card">
|
||||
<text>
|
||||
{{ mode === "create" ? "安排一次家族礼仪" : "修改活动信息" }}
|
||||
</text>
|
||||
<view v-for="field in fields" :key="field.key" class="field-row">
|
||||
<text>{{ field.label }}</text>
|
||||
<textarea
|
||||
v-if="field.long"
|
||||
v-model="ritualForm[field.key]"
|
||||
auto-height
|
||||
:placeholder="`请输入${field.label}`"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="ritualForm[field.key]"
|
||||
:placeholder="`请输入${field.label}`"
|
||||
/>
|
||||
<text v-if="ritualErrors[field.key]">
|
||||
{{ ritualErrors[field.key] }}
|
||||
</text>
|
||||
</view>
|
||||
<text v-if="editorState === 'error'" class="save-error">
|
||||
保存失败,请保留内容后重试。
|
||||
</text>
|
||||
<AppButton
|
||||
block
|
||||
:disabled="editorState === 'saving'"
|
||||
:label="editorState === 'saving' ? '正在保存' : '保存活动'"
|
||||
@click="saveRitual"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="mode === 'edit'"
|
||||
type="secondary"
|
||||
block
|
||||
label="删除活动"
|
||||
@click="confirmDelete"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="deleteVisible"
|
||||
eyebrow="删除确认"
|
||||
title="删除这项礼仪活动?"
|
||||
message="删除后将返回礼仪列表。"
|
||||
confirm-text="确认删除"
|
||||
cancel-text="保留活动"
|
||||
show-cancel
|
||||
@confirm="deleteRitual"
|
||||
@cancel="deleteVisible = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
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 ModulePageBackground from "@/components/ModulePageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
const records = [
|
||||
{
|
||||
id: "501",
|
||||
name: "清明祭祖",
|
||||
date: "2025-04-04",
|
||||
place: "汤氏宗祠",
|
||||
description: "缅怀先祖,凝聚家人,共叙家风传承。",
|
||||
},
|
||||
];
|
||||
const ritualId = ref("");
|
||||
const mode = ref("create");
|
||||
const editorState = ref("ready");
|
||||
const deleteVisible = ref(false);
|
||||
const forceSaveFailure = ref(false);
|
||||
const ritualForm = reactive({ name: "", date: "", place: "", description: "" });
|
||||
const ritualErrors = reactive({
|
||||
name: "",
|
||||
date: "",
|
||||
place: "",
|
||||
description: "",
|
||||
});
|
||||
const fields = [
|
||||
{ key: "name", label: "活动名称" },
|
||||
{ key: "date", label: "活动日期" },
|
||||
{ key: "place", label: "举办地点" },
|
||||
{ key: "description", label: "活动说明", long: true },
|
||||
];
|
||||
let saveTimer = null;
|
||||
const editorClasses = computed(() => ({
|
||||
"ritual-editor-state--saving": editorState.value === "saving",
|
||||
"ritual-editor-state--error": editorState.value === "error",
|
||||
}));
|
||||
onLoad((q) => {
|
||||
ritualId.value = String(q.ritualId || "");
|
||||
mode.value = q.mode === "edit" ? "edit" : "create";
|
||||
forceSaveFailure.value = q.saveResult === "error";
|
||||
const selected = records.find((x) => x.id === ritualId.value);
|
||||
if (selected) Object.assign(ritualForm, selected);
|
||||
});
|
||||
const validateRitual = () => {
|
||||
for (const f of fields)
|
||||
ritualErrors[f.key] = String(ritualForm[f.key]).trim()
|
||||
? ""
|
||||
: `请填写${f.label}`;
|
||||
return fields.every((f) => !ritualErrors[f.key]);
|
||||
};
|
||||
const saveRitual = () => {
|
||||
if (editorState.value === "saving" || !validateRitual()) return;
|
||||
editorState.value = "saving";
|
||||
saveTimer = setTimeout(() => {
|
||||
editorState.value = forceSaveFailure.value ? "error" : "success";
|
||||
forceSaveFailure.value = false;
|
||||
}, 320);
|
||||
};
|
||||
const confirmDelete = () => {
|
||||
deleteVisible.value = true;
|
||||
};
|
||||
const deleteRitual = () => {
|
||||
deleteVisible.value = false;
|
||||
backToRituals();
|
||||
};
|
||||
const backToRituals = () =>
|
||||
uni.redirectTo({ url: "/pages/records/r05-ritual-list" });
|
||||
onUnmounted(() => {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ritual-editor-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: $paper;
|
||||
}
|
||||
.page-header,
|
||||
.page-content {
|
||||
z-index: 1;
|
||||
}
|
||||
.page-content {
|
||||
padding: 18rpx 24rpx 72rpx;
|
||||
}
|
||||
.form-card,
|
||||
.state-card {
|
||||
box-sizing: border-box;
|
||||
padding: 46rpx;
|
||||
background: url("/static/assets/modules/records/transparent/module-content-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.form-card > text:first-child,
|
||||
.state-card > text:first-child {
|
||||
display: block;
|
||||
color: $ink;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.field-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10rpx 18rpx;
|
||||
min-height: 82rpx;
|
||||
margin-top: 14rpx;
|
||||
padding: 14rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
background: url("/static/assets/modules/records/transparent/module-field-frame.png")
|
||||
center/100% 100% no-repeat;
|
||||
}
|
||||
.field-row > text:first-child {
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.field-row input,
|
||||
.field-row textarea {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
color: $ink;
|
||||
font-size: 23rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.field-row textarea {
|
||||
min-height: 76rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.field-row > text:last-child {
|
||||
grid-column: 1/-1;
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.form-card .app-button {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
.save-error {
|
||||
display: block;
|
||||
margin-top: 14rpx;
|
||||
color: $brand-red;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding-top: 80rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.state-card > text:nth-child(2) {
|
||||
display: block;
|
||||
margin-top: 18rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.state-card .app-button {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,253 @@
|
||||
<!-- 页面编号:R-08;用途:成长日志。 -->
|
||||
<template><ModulePage page-id="r08" /></template>
|
||||
<!-- 页面编号: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 ModulePage from "@/components/ModulePage.vue";
|
||||
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>
|
||||
|
||||
@@ -1,5 +1,260 @@
|
||||
<!-- 页面编号:R-09;用途:人生事。 -->
|
||||
<template><ModulePage page-id="r09" /></template>
|
||||
<!-- 页面编号:R-09;用途:人物人生事时间轴与同页新增。 -->
|
||||
<template>
|
||||
<view class="timeline-page" :class="stateClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader title="人生事" action="新增" @action="createLifeEvent" />
|
||||
</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' && lifeEvents.length">
|
||||
<view v-for="event in lifeEvents" :key="event.id" class="timeline-card">
|
||||
<text>{{ event.year }}</text>
|
||||
<text>{{ event.title }}</text>
|
||||
<text>{{ event.place }}</text>
|
||||
<text>{{ event.description }}</text>
|
||||
</view>
|
||||
<AppButton block label="新增人生事" @click="createLifeEvent" />
|
||||
</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')
|
||||
: createLifeEvent()
|
||||
"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="dialogVisible"
|
||||
eyebrow="人生事"
|
||||
title="记录一个人生节点"
|
||||
confirm-text="保存记录"
|
||||
cancel-text="取消"
|
||||
show-cancel
|
||||
@confirm="saveLifeEvent"
|
||||
@cancel="dialogVisible = false"
|
||||
>
|
||||
<view class="dialog-form">
|
||||
<input v-model="lifeEventForm.title" placeholder="事件名称" />
|
||||
<input v-model="lifeEventForm.year" placeholder="年份或日期" />
|
||||
<input v-model="lifeEventForm.place" placeholder="地点(选填)" />
|
||||
<textarea
|
||||
v-model="lifeEventForm.description"
|
||||
auto-height
|
||||
placeholder="写下这段经历"
|
||||
/>
|
||||
<text v-if="formError">{{ formError }}</text>
|
||||
</view>
|
||||
</AppDialog>
|
||||
<AppToast :visible="toastVisible" message="人生事已保存" />
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
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 lifeEvents = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "大学毕业",
|
||||
year: "2018 年 6 月",
|
||||
place: "杭州",
|
||||
description: "完成学业,带着家人的祝福走向新的生活。",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "结为连理",
|
||||
year: "2022 年 10 月",
|
||||
place: "汤氏祖居",
|
||||
description: "在家人见证下组成新的家庭。",
|
||||
},
|
||||
]);
|
||||
const timelineState = ref("loading");
|
||||
const dialogVisible = ref(false);
|
||||
const toastVisible = ref(false);
|
||||
const formError = ref("");
|
||||
const lifeEventForm = reactive({
|
||||
title: "",
|
||||
year: "",
|
||||
place: "",
|
||||
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 createLifeEvent = () => {
|
||||
Object.assign(lifeEventForm, {
|
||||
title: "",
|
||||
year: "",
|
||||
place: "",
|
||||
description: "",
|
||||
});
|
||||
formError.value = "";
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
const saveLifeEvent = () => {
|
||||
if (!lifeEventForm.title.trim() || !lifeEventForm.year.trim()) {
|
||||
formError.value = "请填写事件名称和年份";
|
||||
return;
|
||||
}
|
||||
lifeEvents.value.unshift({ id: Date.now(), ...lifeEventForm });
|
||||
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: 66rpx;
|
||||
margin-top: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
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: 8rpx;
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,246 @@
|
||||
<!-- 页面编号:R-10;用途:家族备忘录。 -->
|
||||
<template><ModulePage page-id="r10" /></template>
|
||||
<!-- 页面编号:R-10;用途:家族备忘列表、完成状态与同页新增。 -->
|
||||
<template>
|
||||
<view class="memo-page" :class="stateClasses">
|
||||
<ModulePageBackground module="records" />
|
||||
<view class="page-header">
|
||||
<PageHeader title="家族备忘" action="新增" @action="createMemo" />
|
||||
</view>
|
||||
<view v-if="memoState === 'loading'" class="page-loading">
|
||||
<AppLoading
|
||||
text="正在读取家族备忘"
|
||||
description="请稍候,正在整理待办事项。"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="page-content">
|
||||
<template v-if="memoState === 'ready' && memos.length">
|
||||
<view
|
||||
v-for="memo in memos"
|
||||
:key="memo.id"
|
||||
class="memo-card"
|
||||
:class="{ 'memo-card--done': memo.done }"
|
||||
@click="toggleMemo(memo)"
|
||||
>
|
||||
<view>
|
||||
<text>{{ memo.done ? "已完成" : "待办理" }}</text>
|
||||
<text>{{ memo.due }}</text>
|
||||
</view>
|
||||
<text>{{ memo.title }}</text>
|
||||
<text>{{ memo.description }}</text>
|
||||
<text>{{ memo.done ? "点击恢复待办" : "点击标记完成" }}</text>
|
||||
</view>
|
||||
<AppButton block label="新增备忘" @click="createMemo" />
|
||||
</template>
|
||||
<view v-else class="state-card">
|
||||
<text>
|
||||
{{ memoState === "error" ? "家族备忘暂不可用" : "还没有备忘" }}
|
||||
</text>
|
||||
<text>
|
||||
{{
|
||||
memoState === "error"
|
||||
? "请稍后重新查看,已有备忘不会受到影响。"
|
||||
: "把需要家人共同记住的事情写在这里。"
|
||||
}}
|
||||
</text>
|
||||
<AppButton
|
||||
:type="memoState === 'error' ? 'secondary' : 'primary'"
|
||||
block
|
||||
:label="memoState === 'error' ? '重新查看' : '新增备忘'"
|
||||
@click="memoState === 'error' ? (memoState = 'ready') : createMemo()"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<AppDialog
|
||||
:visible="dialogVisible"
|
||||
eyebrow="家族备忘"
|
||||
title="新增一项备忘"
|
||||
confirm-text="保存备忘"
|
||||
cancel-text="取消"
|
||||
show-cancel
|
||||
@confirm="saveMemo"
|
||||
@cancel="dialogVisible = false"
|
||||
>
|
||||
<view class="dialog-form">
|
||||
<input v-model="memoForm.title" placeholder="备忘标题" />
|
||||
<input v-model="memoForm.due" placeholder="截止日期或时间" />
|
||||
<textarea
|
||||
v-model="memoForm.description"
|
||||
auto-height
|
||||
placeholder="补充具体事项"
|
||||
/>
|
||||
<text v-if="formError">{{ formError }}</text>
|
||||
</view>
|
||||
</AppDialog>
|
||||
<AppToast :visible="toastVisible" message="备忘已更新" />
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import ModulePage from "@/components/ModulePage.vue";
|
||||
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 memos = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: "修谱资料整理",
|
||||
due: "本月底前",
|
||||
description: "补充老照片中的人物姓名和拍摄时间。",
|
||||
done: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "重阳敬老活动",
|
||||
due: "10 月 11 日上午",
|
||||
description: "在祠堂集合,并确认接送长辈的车辆。",
|
||||
done: true,
|
||||
},
|
||||
]);
|
||||
const memoState = ref("loading");
|
||||
const dialogVisible = ref(false);
|
||||
const toastVisible = ref(false);
|
||||
const formError = ref("");
|
||||
const memoForm = reactive({ title: "", due: "", description: "" });
|
||||
let timer = null;
|
||||
const stateClasses = computed(() => ({
|
||||
"memo-state--loading": memoState.value === "loading",
|
||||
"memo-state--empty": memoState.value === "empty",
|
||||
"memo-state--error": memoState.value === "error",
|
||||
}));
|
||||
onLoad((q) => {
|
||||
memoState.value = ["loading", "empty", "error"].includes(q.state)
|
||||
? q.state
|
||||
: "ready";
|
||||
});
|
||||
const showToast = () => {
|
||||
toastVisible.value = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => (toastVisible.value = false), 1800);
|
||||
};
|
||||
const toggleMemo = (memo) => {
|
||||
memo.done = !memo.done;
|
||||
showToast();
|
||||
};
|
||||
const createMemo = () => {
|
||||
Object.assign(memoForm, { title: "", due: "", description: "" });
|
||||
formError.value = "";
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
const saveMemo = () => {
|
||||
if (!memoForm.title.trim()) {
|
||||
formError.value = "请填写备忘标题";
|
||||
return;
|
||||
}
|
||||
memos.value.unshift({ id: Date.now(), ...memoForm, done: false });
|
||||
memoState.value = "ready";
|
||||
dialogVisible.value = false;
|
||||
showToast();
|
||||
};
|
||||
onUnmounted(() => {
|
||||
if (timer) clearTimeout(timer);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.memo-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;
|
||||
}
|
||||
.memo-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;
|
||||
}
|
||||
.memo-card > view {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 6rpx 18rpx;
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.memo-card > text,
|
||||
.state-card > text {
|
||||
display: block;
|
||||
}
|
||||
.memo-card > text:nth-child(2) {
|
||||
margin-top: 8rpx;
|
||||
color: $ink;
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.memo-card > text:nth-child(3) {
|
||||
margin-top: 9rpx;
|
||||
color: $ink-muted;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.memo-card > text:last-child {
|
||||
margin-top: 10rpx;
|
||||
color: $brand-red;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
.memo-card--done {
|
||||
opacity: 0.68;
|
||||
}
|
||||
.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: 68rpx;
|
||||
margin-top: 9rpx;
|
||||
padding: 13rpx 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>
|
||||
|
||||
@@ -1,5 +1,266 @@
|
||||
<!-- 页面编号:R-11;用途:功德记录。 -->
|
||||
<template><ModulePage page-id="r11" /></template>
|
||||
<!-- 页面编号: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 ModulePage from "@/components/ModulePage.vue";
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user