Files
jiapuapp/pages/genealogy/g11-genealogy-settings.vue
T
2026-07-23 08:24:07 +08:00

486 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 页面编号G-11用途家谱设置访问预设与家谱简介页面设计阶段使用本地模拟交互)。 -->
<template>
<view class="settings-page">
<GenealogyPageBackground />
<view class="settings-page__header">
<PageHeader title="家谱设置" custom-back @back="requestBack" />
</view>
<view
class="settings-panel"
:class="{
'settings-state--form': settingsState === 'form',
'settings-state--success': settingsState === 'success',
'settings-state--error': settingsState === 'error',
'settings-state--no-permission': settingsState === 'no-permission',
}"
>
<AppLoading
v-if="settingsState === 'loading'"
text="正在读取家谱设置"
description="请稍候,正在准备名称与访问规则。"
/>
<view v-else-if="settingsState === 'form'" class="settings-form">
<text class="settings-form__eyebrow">谱主可见 · 基础设置</text>
<text class="settings-form__title">完善家谱访问规则</text>
<text class="settings-form__copy"
>这里维护名称访问规则与家谱简介访问规则会同时决定公开范围和加入方式管理权转让在成员场景中单独处理</text
>
<view class="settings-field">
<text>家谱名称</text>
<input
v-model="genealogyDraft.name"
maxlength="20"
placeholder="请输入家谱名称"
placeholder-class="settings-placeholder"
@input="nameError = ''"
/>
</view>
<text v-if="nameError" class="settings-field-error">{{
nameError
}}</text>
<view class="visibility-block">
<text class="visibility-block__label">访问规则</text>
<view class="visibility-options">
<view
v-for="option in accessPresetOptions"
:key="option.value"
class="visibility-option"
@click="genealogyDraft.accessPreset = option.value"
>
<image
:src="
genealogyDraft.accessPreset === option.value
? '/static/assets/foundation/transparent/a01-scroll-primary-v3.png'
: '/static/assets/foundation/transparent/a01-scroll-secondary-v3.png'
"
mode="aspectFit"
/>
<text
:class="{
'visibility-option__text--active':
genealogyDraft.accessPreset === option.value,
}"
>{{ option.label }}</text
>
</view>
</view>
<text class="visibility-block__hint">{{ visibilityHint }}</text>
</view>
<view class="settings-field settings-field--note">
<text>家谱简介</text>
<textarea
v-model="genealogyDraft.intro"
auto-height
maxlength="80"
placeholder="简要介绍家谱来源与支系"
placeholder-class="settings-placeholder"
/>
</view>
<view class="settings-action" @click="saveSettings">
<text>保存设置</text>
</view>
</view>
<view v-else class="settings-result">
<text class="settings-result__eyebrow">{{
settingsState === "success"
? "设置已保存"
: settingsState === "no-permission"
? "权限不足"
: "设置未保存"
}}</text>
<text class="settings-result__title">{{
settingsState === "success"
? "本页设置草稿已更新"
: settingsState === "no-permission"
? "当前账号不能修改家谱"
: "暂时无法打开家谱设置"
}}</text>
<text class="settings-result__copy">{{
settingsState === "success"
? `本地预览已更新,尚未提交服务器;当前只保留在本页,返回总览不会改变原资料 · ${genealogyDraft.name} · ${accessPresetLabel}`
: settingsState === "no-permission"
? "只有家谱所有者可以修改名称、访问规则和家谱简介。"
: "请从家谱总览重新进入,当前修改不会被保留。"
}}</text>
<view class="settings-action" @click="handleResultAction">
<text>{{
settingsState === "success" ? "继续调整" : "重新查看"
}}</text>
</view>
</view>
</view>
<AppDialog
:visible="discardVisible"
title="放弃设置修改?"
message="当前修改尚未保存,确认返回后将清空。"
confirm-text="放弃并返回"
cancel-text="继续修改"
show-cancel
compact-actions
:close-on-mask="false"
@confirm="confirmDiscard"
@cancel="cancelDiscard"
/>
<view v-if="feedbackVisible" class="settings-feedback">
<text>本页草稿已更新</text>
</view>
</view>
</template>
<script setup>
import { computed, reactive, ref } from "vue";
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
import AppDialog from "@/components/AppDialog.vue";
import AppLoading from "@/components/AppLoading.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
findGenealogyFixture,
getGenealogyFixtureAccess,
} from "@/data/mock.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import {
GENEALOGY_ACCESS_PRESET,
GENEALOGY_ACCESS_PRESET_OPTIONS,
isGenealogyAccessPreset,
} from "@/utils/genealogy-contracts.js";
import { goBack, handleBackPress, runBackGuard } from "@/utils/navigation.js";
const genealogyId = ref("");
const settingsState = ref("loading");
const nameError = ref("");
const feedbackVisible = ref(false);
const discardVisible = ref(false);
let feedbackTimer = null;
const genealogyDraft = reactive({
name: "",
accessPreset: GENEALOGY_ACCESS_PRESET.MEMBER_ONLY,
intro: "",
});
const originalDraft = ref("");
const isDirty = computed(
() =>
settingsState.value === "form" &&
JSON.stringify(genealogyDraft) !== originalDraft.value,
);
const discardConfirmation = createDiscardConfirmation((visible) => {
discardVisible.value = visible;
});
const requestDiscardConfirmation = discardConfirmation.request;
const confirmDiscard = discardConfirmation.confirm;
const cancelDiscard = discardConfirmation.cancel;
const accessPresetOptions = GENEALOGY_ACCESS_PRESET_OPTIONS;
const accessPresetLabel = computed(
() =>
accessPresetOptions.find((item) => item.value === genealogyDraft.accessPreset)
?.label || "",
);
const visibilityHint = computed(() =>
genealogyDraft.accessPreset === GENEALOGY_ACCESS_PRESET.MEMBER_ONLY
? "只有已加入本家谱的成员可以查看谱系和家族资料。"
: "访客可检索到家谱并提交入谱申请,资料仍需审核后查看。",
);
const loadSettings = (query = {}) => {
genealogyId.value = String(query.genealogyId || "");
if (!genealogyId.value) {
settingsState.value = "error";
return;
}
if (
getGenealogyFixtureAccess(genealogyId.value).accessRole !== "owner"
) {
settingsState.value = "no-permission";
return;
}
const fixture = findGenealogyFixture(genealogyId.value);
if (!fixture) {
settingsState.value = "error";
return;
}
if (!isGenealogyAccessPreset(fixture.accessPreset)) {
settingsState.value = "error";
return;
}
Object.assign(genealogyDraft, {
name: fixture.name,
accessPreset: fixture.accessPreset,
intro: fixture.publicDescription || "家族资料,请妥善保存",
});
originalDraft.value = JSON.stringify(genealogyDraft);
settingsState.value =
query.state === "loading"
? "loading"
: query.state === "success"
? "success"
: query.state === "no-permission"
? "no-permission"
: query.state === "error" || !genealogyId.value
? "error"
: "form";
};
onLoad(loadSettings);
onUnload(() => {
const timer = feedbackTimer;
feedbackTimer = null;
if (timer) clearTimeout(timer);
discardConfirmation.dispose();
});
const requestBack = () =>
runBackGuard({
transientOpen: discardVisible.value,
dirty: isDirty.value,
"close-transient": cancelDiscard,
"confirm-discard": requestDiscardConfirmation,
});
onBackPress((event) => handleBackPress(event, requestBack));
const continueEditing = () => {
if (
getGenealogyFixtureAccess(genealogyId.value).accessRole === "owner"
) {
settingsState.value = "form";
}
};
const handleResultAction = () => {
if (settingsState.value === "success") return continueEditing();
if (settingsState.value === "error")
return loadSettings({ genealogyId: genealogyId.value });
return goBack();
};
const saveSettings = () => {
if (!genealogyDraft.name.trim()) {
nameError.value = "请填写家谱名称";
return;
}
if (genealogyDraft.name.trim().length > 20) {
nameError.value = "家谱名称不能超过 20 个字";
return;
}
originalDraft.value = JSON.stringify(genealogyDraft);
settingsState.value = "success";
feedbackVisible.value = true;
if (feedbackTimer) clearTimeout(feedbackTimer);
const timer = setTimeout(() => {
if (feedbackTimer !== timer) return;
feedbackVisible.value = false;
feedbackTimer = null;
}, 1800);
feedbackTimer = timer;
};
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.settings-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.settings-page__header {
z-index: 3;
}
.settings-panel {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
margin: 18rpx auto 0;
padding: 70rpx 8%;
box-sizing: border-box;
}
.settings-panel > .app-loading {
grid-area: 1 / 1 / -1 / -1;
}
.settings-form {
min-width: 0;
}
.settings-form__eyebrow,
.settings-result__eyebrow {
display: block;
color: $brand-red;
font-size: 23rpx;
letter-spacing: 3rpx;
}
.settings-form__title,
.settings-result__title {
display: block;
margin-top: 10rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 35rpx;
font-weight: 700;
}
.settings-form__copy,
.settings-result__copy {
display: block;
margin-top: 10rpx;
color: $ink-muted;
font-size: 24rpx;
line-height: 1.55;
}
.settings-field {
@include adaptive.adaptive-genealogy-form-field;
display: grid;
min-height: 92rpx;
margin-top: 17rpx;
}
.settings-field > text {
z-index: 1;
grid-area: 1 / 1;
align-self: center;
margin-left: 24rpx;
color: $ink;
font-size: 24rpx;
font-weight: 700;
}
.settings-field input,
.settings-field textarea {
z-index: 1;
grid-area: 1 / 1;
width: auto;
min-width: 0;
min-height: 92rpx;
margin-right: 20rpx;
margin-left: 164rpx;
color: $ink;
font-size: 24rpx;
line-height: 92rpx;
}
.settings-field textarea {
box-sizing: border-box;
padding-top: 26rpx;
line-height: 1.5;
}
.settings-placeholder {
color: #a79884;
}
.settings-field-error {
display: block;
margin-top: 3rpx;
color: $brand-red;
font-size: 24rpx;
line-height: 34rpx;
text-align: right;
}
.visibility-block {
margin-top: 17rpx;
}
.visibility-block__label {
display: block;
color: $ink;
font-size: 24rpx;
font-weight: 700;
}
.visibility-options {
display: flex;
gap: 14rpx;
margin-top: 10rpx;
}
.visibility-option {
display: grid;
width: calc(50% - 7rpx);
min-height: 64rpx;
}
.visibility-option image {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.visibility-option text {
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
color: $ink;
font-size: 24rpx;
font-weight: 700;
}
.visibility-option .visibility-option__text--active {
color: #fff9ed;
}
.visibility-block__hint {
display: block;
margin-top: 9rpx;
color: $ink-muted;
font-size: 22rpx;
line-height: 1.45;
}
.settings-field--note {
margin-top: 14rpx;
}
.settings-action {
display: flex;
width: 100%;
min-height: 78rpx;
align-items: center;
justify-content: center;
margin-top: 19rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.settings-action text {
z-index: 1;
color: #fff9ed;
font-size: 26rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
.settings-result {
text-align: center;
}
.settings-state--success,
.settings-state--error,
.settings-state--no-permission {
padding: 180rpx 12% 70rpx;
}
.settings-result__eyebrow {
text-align: center;
}
.settings-result__copy {
margin-top: 22rpx;
}
.settings-result .settings-action {
width: 420rpx;
max-width: 100%;
margin: 34rpx auto 0;
}
.settings-feedback {
position: fixed;
z-index: 30;
top: 138rpx;
left: 50%;
display: flex;
min-width: 240rpx;
min-height: 74rpx;
align-items: center;
justify-content: center;
padding: 0 34rpx;
transform: translateX(-50%);
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")
center / contain no-repeat;
}
.settings-feedback text {
z-index: 1;
color: $ink;
font-size: 22rpx;
}
@media (min-width: 400px) {
.settings-panel {
width: calc(100% - 48rpx);
}
}
</style>