完成50%

This commit is contained in:
2026-07-23 08:23:59 +08:00
parent 9b0ad62df4
commit f1edc6b533
218 changed files with 24318 additions and 5514 deletions
+176 -82
View File
@@ -1,19 +1,33 @@
<!-- 页面编号R-07用途礼仪创建编辑校验保存与删除确认 -->
<!-- 页面编号R-07用途礼仪创建编辑校验与不写库的本地预览 -->
<template>
<view class="ritual-editor-page" :class="editorClasses">
<ModulePageBackground module="records" />
<view class="page-header">
<PageHeader :title="mode === 'create' ? '新建礼仪' : '编辑礼仪'" />
<PageHeader
:title="mode === 'create' ? '礼仪预览' : '编辑预览'"
custom-back
@back="requestBack"
/>
</view>
<view class="page-content">
<view v-if="editorState === 'success'" class="state-card">
<text>礼仪活动已保存</text>
<text>时间地点与活动说明已整理完成</text>
<AppButton block label="返回礼仪列表" @click="backToRituals" />
<view v-if="editorState === 'loading'" class="page-loading">
<AppLoading
text="正在读取礼仪资料"
description="请稍候,正在核对当前家谱与活动身份。"
/>
</view>
<view v-else class="page-content">
<view v-if="editorState === 'preview'" class="state-card preview-card">
<text>本地预览尚未提交服务器</text>
<text>这份礼仪内容只存在于当前页面不会新增覆盖或删除正式活动</text>
<view v-for="item in previewRows" :key="item.label" class="preview-row">
<text>{{ item.label }}</text>
<text>{{ item.value }}</text>
</view>
<AppButton block :label="returnLabel" @click="returnAfterPreview" />
</view>
<view v-else class="form-card">
<view v-else-if="editorState === 'ready'" class="form-card">
<text>
{{ mode === "create" ? "安排一次家族礼仪" : "修改活动信息" }}
{{ mode === "create" ? "填写一份家族礼仪预览" : "调整活动预览" }}
</text>
<view v-for="field in fields" :key="field.key" class="field-row">
<text>{{ field.label }}</text>
@@ -32,110 +46,190 @@
{{ ritualErrors[field.key] }}
</text>
</view>
<text v-if="editorState === 'error'" class="save-error">
保存失败请保留内容后重试
</text>
<AppButton
block
:disabled="editorState === 'saving'"
:label="editorState === 'saving' ? '正在保存' : '保存活动'"
@click="saveRitual"
label="生成本地预览"
@click="createCeremonyPreview"
/>
<AppButton
v-if="mode === 'edit'"
type="secondary"
block
label="删除活动"
@click="confirmDelete"
label="取消填写"
@click="requestBack"
/>
<AppButton v-if="mode === 'edit'" type="secondary" block disabled label="删除暂未开放" />
</view>
<view v-else class="state-card">
<text>礼仪活动不可用</text>
<text>活动不存在缺少身份或不属于当前家谱页面不会回退到其他活动</text>
<AppButton type="secondary" block label="返回礼仪列表" @click="returnToCeremonies" />
</view>
</view>
<AppDialog
:visible="deleteVisible"
eyebrow="删除确认"
title="删除这项礼仪活动?"
message="删除后将返回礼仪列表。"
confirm-text="确认删除"
cancel-text="保留活动"
:visible="discardVisible"
:close-on-mask="false"
eyebrow="放弃确认"
title="放弃当前填写?"
message="尚未提交的预览内容将从当前页面清除。"
confirm-text="确认放弃"
cancel-text="继续填写"
show-cancel
@confirm="deleteRitual"
@cancel="deleteVisible = false"
@confirm="confirmDiscard"
@cancel="cancelDiscard"
/>
</view>
</template>
<script setup>
import { computed, onUnmounted, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { onBackPress, 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: "" });
import {
findCeremonyFixture,
getGenealogyFixtureAccess,
} from "@/data/mock.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import {
goBack,
handleBackPress,
returnTo,
runBackGuard,
} from "@/utils/navigation.js";
const genealogyId = ref("");
const ceremonyId = ref("");
const mode = ref("");
const editorState = ref("loading");
const discardVisible = ref(false);
const localCeremonyPreview = ref(null);
const ceremonyForm = reactive({
ceremonyType: "",
ceremonyTitle: "",
ceremonyTime: "",
location: "",
ceremonyDesc: "",
});
const ritualErrors = reactive({
name: "",
date: "",
place: "",
description: "",
ceremonyType: "",
ceremonyTitle: "",
ceremonyTime: "",
location: "",
ceremonyDesc: "",
});
const fields = [
{ key: "name", label: "活动名称" },
{ key: "date", label: "活动日期" },
{ key: "place", label: "举办地点" },
{ key: "description", label: "活动说明", long: true },
{ key: "ceremonyType", label: "礼仪类型" },
{ key: "ceremonyTitle", label: "活动标题" },
{ key: "ceremonyTime", label: "活动时间" },
{ key: "location", label: "举办地点" },
{ key: "ceremonyDesc", label: "活动说明", long: true },
];
let saveTimer = null;
const baseline = ref("");
const editorClasses = computed(() => ({
"ritual-editor-state--saving": editorState.value === "saving",
"ritual-editor-state--error": editorState.value === "error",
"ceremony-editor-state--preview": editorState.value === "preview",
"ceremony-editor-state--invalid": editorState.value === "invalid",
}));
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 formSnapshot = computed(() => JSON.stringify({ ...ceremonyForm }));
const isDirty = computed(() =>
editorState.value === "preview" ||
(["create", "edit"].includes(mode.value) && formSnapshot.value !== baseline.value),
);
const previewRows = computed(() =>
fields.map((field) => ({
label: field.label,
value: localCeremonyPreview.value?.[field.key] || "未填写",
})),
);
const returnLabel = computed(() =>
mode.value === "edit" ? "返回礼仪详情" : "返回礼仪列表",
);
const copyCeremonyToForm = (record) => {
Object.assign(ceremonyForm, {
ceremonyType: record.ceremonyType,
ceremonyTitle: record.ceremonyTitle,
ceremonyTime: record.ceremonyTime,
location: record.location,
ceremonyDesc: record.ceremonyDesc,
});
baseline.value = formSnapshot.value;
};
onLoad((query) => {
genealogyId.value = String(query.genealogyId || "");
ceremonyId.value = String(query.ceremonyId || "");
mode.value = String(query.mode || "");
if (query.state === "loading") return;
const access = getGenealogyFixtureAccess(genealogyId.value);
const hasValidGenealogy = ["owner", "member"].includes(access.accessRole);
const isCreateContract = mode.value === "create" && !ceremonyId.value;
const isEditContract = mode.value === "edit" && Boolean(ceremonyId.value);
if (!hasValidGenealogy || (!isCreateContract && !isEditContract)) {
editorState.value = "invalid";
return;
}
if (isCreateContract) {
baseline.value = formSnapshot.value;
editorState.value = "ready";
return;
}
const selected = findCeremonyFixture(genealogyId.value, ceremonyId.value);
if (!selected) {
editorState.value = "invalid";
return;
}
copyCeremonyToForm(selected);
editorState.value = "ready";
});
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 validateCeremony = () => {
ritualErrors.ceremonyType = ceremonyForm.ceremonyType.trim()
? ""
: "请填写礼仪类型";
ritualErrors.ceremonyTitle = ceremonyForm.ceremonyTitle.trim()
? ""
: "请填写活动标题";
return fields.every((field) => !ritualErrors[field.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 createCeremonyPreview = () => {
if (!validateCeremony()) return false;
// 线上写接口的枚举、成员权限与错误语义尚未形成完整合同,因此这里只生成
// 与表单分离的不可变快照;后续接入真实写接口时由该入口唯一替换。
localCeremonyPreview.value = Object.freeze({
ceremonyType: ceremonyForm.ceremonyType.trim(),
ceremonyTitle: ceremonyForm.ceremonyTitle.trim(),
ceremonyTime: ceremonyForm.ceremonyTime.trim(),
location: ceremonyForm.location.trim(),
ceremonyDesc: ceremonyForm.ceremonyDesc.trim(),
});
editorState.value = "preview";
return true;
};
const confirmDelete = () => {
deleteVisible.value = true;
};
const deleteRitual = () => {
deleteVisible.value = false;
backToRituals();
};
const backToRituals = () =>
uni.redirectTo({ url: "/pages/records/r05-ritual-list" });
const discardConfirmation = createDiscardConfirmation(
(visible) => { discardVisible.value = visible; },
);
const confirmDiscard = discardConfirmation.confirm;
const cancelDiscard = discardConfirmation.cancel;
const requestBack = () =>
runBackGuard({
transientOpen: discardVisible.value,
dirty: isDirty.value,
"close-transient": cancelDiscard,
"confirm-discard": discardConfirmation.request,
});
const returnToCeremonies = () =>
genealogyId.value
? returnTo("R05", { genealogyId: genealogyId.value })
: goBack();
const returnAfterPreview = () =>
mode.value === "edit" && ceremonyId.value
? returnTo("R06", {
genealogyId: genealogyId.value,
ceremonyId: ceremonyId.value,
})
: returnToCeremonies();
onBackPress((event) => handleBackPress(event, requestBack));
onUnmounted(() => {
if (saveTimer) clearTimeout(saveTimer);
discardConfirmation.dispose();
});
</script>
<style scoped lang="scss">