完成70%

This commit is contained in:
2026-07-24 07:56:22 +08:00
parent bb6431b319
commit c7f278fe79
92 changed files with 4741 additions and 14440 deletions
+9 -311
View File
@@ -1,321 +1,19 @@
<!-- 页面编号R-07用途礼仪创建编辑校验与不写库的本地预览 -->
<!-- 页面编号R-07用途礼仪活动创建/编辑创建 owner 缺失编辑无可靠详情来源 -->
<template>
<view class="ritual-editor-page" :class="editorClasses">
<ModulePageBackground module="records" />
<view class="page-header">
<PageHeader
:title="mode === 'create' ? '礼仪预览' : '编辑预览'"
custom-back
@back="requestBack"
/>
</view>
<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-if="editorState === 'ready'" 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>
<AppButton
block
label="生成本地预览"
@click="createCeremonyPreview"
/>
<AppButton
type="secondary"
block
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="discardVisible"
:close-on-mask="false"
eyebrow="放弃确认"
title="放弃当前填写?"
message="尚未提交的预览内容将从当前页面清除。"
confirm-text="确认放弃"
cancel-text="继续填写"
show-cancel
@confirm="confirmDiscard"
@cancel="cancelDiscard"
/>
</view>
<view class="ritual-editor-page"><ModulePageBackground module="records" /><view class="page-header"><PageHeader title="礼仪活动" custom-back @back="returnToList" /></view><view class="page-content"><view class="state-card"><text>礼仪活动维护暂未开放</text><text>当前没有新建活动 operation修改虽有 operation但没有可消费的详情 DTO 或可靠活动 ID 来源本页不再生成本地创建或编辑预览</text><AppButton block :label="hasValidContext ? '返回礼仪活动' : '返回上一页'" @click="returnToList" /></view></view></view>
</template>
<script setup>
import { computed, onUnmounted, reactive, ref } from "vue";
import { onBackPress, onLoad } from "@dcloudio/uni-app";
import { computed, 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";
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({
ceremonyType: "",
ceremonyTitle: "",
ceremonyTime: "",
location: "",
ceremonyDesc: "",
});
const fields = [
{ key: "ceremonyType", label: "礼仪类型" },
{ key: "ceremonyTitle", label: "活动标题" },
{ key: "ceremonyTime", label: "活动时间" },
{ key: "location", label: "举办地点" },
{ key: "ceremonyDesc", label: "活动说明", long: true },
];
const baseline = ref("");
const editorClasses = computed(() => ({
"ceremony-editor-state--preview": editorState.value === "preview",
"ceremony-editor-state--invalid": editorState.value === "invalid",
}));
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 validateCeremony = () => {
ritualErrors.ceremonyType = ceremonyForm.ceremonyType.trim()
? ""
: "请填写礼仪类型";
ritualErrors.ceremonyTitle = ceremonyForm.ceremonyTitle.trim()
? ""
: "请填写活动标题";
return fields.every((field) => !ritualErrors[field.key]);
};
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 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(() => {
discardConfirmation.dispose();
});
import { goBack, returnTo } from "@/utils/navigation.js";
const genealogyId = ref(""); const hasValidContext = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
onLoad((query) => { genealogyId.value = String(query?.genealogyId || ""); });
const returnToList = () => hasValidContext.value ? returnTo("R05", { genealogyId: genealogyId.value }) : goBack();
</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;
}
.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; }.state-card { box-sizing:border-box; min-height:340rpx; padding:78rpx 52rpx 50rpx; text-align:center; @include adaptive.adaptive-records-content; }.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>