228 lines
6.1 KiB
Vue
228 lines
6.1 KiB
Vue
<!-- 页面编号: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 { 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">
|
||
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
|
||
|
||
.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;
|
||
@include adaptive.adaptive-records-content;
|
||
}
|
||
.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;
|
||
@include adaptive.adaptive-records-field;
|
||
}
|
||
.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>
|