修改完成

This commit is contained in:
2026-07-21 07:53:03 +08:00
parent 01d246c47b
commit ff60119277
172 changed files with 7982 additions and 2999 deletions
+70 -194
View File
@@ -21,11 +21,6 @@
</view>
<view v-else-if="hasError" class="state-panel state-panel--error">
<image
class="error-panel__frame"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view class="error-panel__content">
<image
class="error-panel__seal"
@@ -44,11 +39,6 @@
hover-class="action-hover"
@click="retryLoad"
>
<image
class="state-retry__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="state-retry__copy">重新加载</text>
</view>
</view>
@@ -57,18 +47,8 @@
<template v-else-if="hasGenealogies">
<view class="genealogy-fixed-zone">
<view class="current-slip" @click="openSwitcher">
<image
class="current-frame"
src="/static/assets/modules/genealogy/transparent/current-slip-frame.png"
mode="scaleToFill"
/>
<view class="current-summary">
<view class="current-seal">
<image
class="current-seal-frame"
src="/static/assets/modules/genealogy/transparent/current-seal-frame.png"
mode="scaleToFill"
/>
<text class="current-seal-title">家谱</text>
</view>
<text class="current-name">{{ currentGenealogy.name }}</text>
@@ -165,11 +145,6 @@
hover-class="action-hover"
@click="openApplication(item)"
>
<image
class="application-record__skin"
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="application-record__main">
<view class="application-record__title-row">
<text class="application-record__name">{{
@@ -217,18 +192,8 @@
<view v-else class="genealogy-empty-state">
<view class="empty-panel">
<image
class="empty-panel__frame"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view class="empty-panel__content">
<view class="empty-seal">
<image
class="empty-seal__skin"
src="/static/assets/modules/genealogy/transparent/current-seal-frame.png"
mode="scaleToFill"
/>
<text class="empty-seal__text">家谱</text>
</view>
<text class="empty-title">还没有加入任何家谱</text>
@@ -245,11 +210,6 @@
hover-class="action-hover"
@click="applyToJoin"
>
<image
class="empty-search-action__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text
class="empty-search-action__copy empty-search-action__copy--primary"
>搜索家谱</text
@@ -260,11 +220,6 @@
hover-class="action-hover"
@click="joinByInvite"
>
<image
class="empty-invite-action__skin"
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
mode="aspectFit"
/>
<text class="empty-invite-action__copy">邀请码加入</text>
</view>
<view
@@ -393,6 +348,7 @@ import GenealogyCard from "@/components/GenealogyCard.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { genealogies, notifications } from "@/data/mock.js";
import { genealogyContext } from "@/utils/genealogy-context.js";
const isLoading = ref(false);
const hasError = ref(false);
@@ -400,7 +356,12 @@ const list = ref(genealogies);
const forceEmptyState = ref(false);
const addDialogVisible = ref(false);
const switcherVisible = ref(false);
const selectedGenealogyId = ref(genealogies[0]?.id || null);
const storedGenealogyId = Number(genealogyContext.getCurrentGenealogyId());
const selectedGenealogyId = ref(
genealogies.some((item) => item.id === storedGenealogyId)
? storedGenealogyId
: genealogies[0]?.id || null,
);
const listScrollCommand = ref(0);
const currentListScrollTop = ref(0);
@@ -422,7 +383,14 @@ const syncEmptyStateFromRoute = () => {
hasError.value = presentationState === "error";
};
onLoad(() => {
onLoad((query) => {
const requestedId = Number(query?.genealogyId);
if (genealogies.some((item) => item.id === requestedId)) {
selectedGenealogyId.value = requestedId;
genealogyContext.setCurrentGenealogyId(requestedId);
} else if (selectedGenealogyId.value) {
genealogyContext.setCurrentGenealogyId(selectedGenealogyId.value);
}
syncEmptyStateFromRoute();
});
@@ -440,10 +408,10 @@ const isListLayout = computed(
() => !isLoading.value && !hasError.value && hasGenealogies.value,
);
const createdGenealogies = computed(() =>
list.value.filter((item, index) => index === 0),
list.value.filter((item) => item.membership === "created"),
);
const joinedGenealogies = computed(() =>
list.value.filter((item, index) => index > 0),
list.value.filter((item) => item.membership === "joined"),
);
const availableGenealogies = computed(() => list.value);
const currentGenealogy = computed(
@@ -501,10 +469,12 @@ const shortcuts = [
},
];
const openGenealogy = (genealogy) =>
const openGenealogy = (genealogy) => {
genealogyContext.setCurrentGenealogyId(genealogy.id);
uni.navigateTo({
url: `/pages/genealogy/g05-genealogy-overview?genealogyId=${genealogy.id}`,
});
};
const createGenealogy = () =>
uni.navigateTo({ url: "/pages/genealogy/g03-create-genealogy" });
const applyToJoin = () =>
@@ -550,6 +520,7 @@ const resetListScroll = async () => {
};
const selectGenealogy = async (genealogy) => {
selectedGenealogyId.value = genealogy.id;
genealogyContext.setCurrentGenealogyId(genealogy.id);
closeSwitcher();
await resetListScroll();
};
@@ -565,11 +536,14 @@ const retryLoad = () => {
};
const openShortcut = (key) => {
if (!currentGenealogy.value) return;
const genealogyId = currentGenealogy.value.id;
genealogyContext.setCurrentGenealogyId(genealogyId);
const paths = {
tree: "/pages/tree/t01-tree-overview",
tree: `/pages/tree/t01-tree-overview?genealogyId=${currentGenealogy.value.id}`,
members: `/pages/genealogy/g05-genealogy-overview?genealogyId=${currentGenealogy.value.id}`,
poem: "/pages/genealogy/g12-generation-poems",
applications: "/pages/genealogy/g10-application-review",
poem: `/pages/genealogy/g12-generation-poems?genealogyId=${currentGenealogy.value.id}`,
applications: `/pages/genealogy/g10-application-review?genealogyId=${currentGenealogy.value.id}`,
};
uni.navigateTo({ url: paths[key] });
};
@@ -577,15 +551,15 @@ const openShortcut = (key) => {
<style scoped lang="scss">
.genealogy-index {
position: relative;
display: flex;
min-height: 100vh;
flex-direction: column;
overflow: hidden;
background: #f9f6ef;
}
.genealogy-content {
position: relative;
z-index: 2;
z-index: 1;
padding: 24rpx 32rpx 176rpx;
}
@@ -618,29 +592,14 @@ const openShortcut = (key) => {
}
.current-slip {
position: relative;
display: block;
min-height: 268rpx;
padding: 30rpx 34rpx 28rpx;
box-sizing: border-box;
background: transparent;
}
.current-frame {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: url("/static/assets/modules/genealogy/transparent/current-slip-frame.png") center / 100% 100% no-repeat;
}
.current-seal {
position: relative;
z-index: 1;
display: flex;
width: 80rpx;
height: 128rpx;
@@ -648,22 +607,10 @@ const openShortcut = (key) => {
align-items: center;
justify-content: center;
margin-right: 24rpx;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / 100% 100% no-repeat;
color: #fff7e7;
}
.current-seal-frame {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
}
.current-seal-title {
position: relative;
z-index: 1;
color: #fff7e7;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
@@ -672,20 +619,16 @@ const openShortcut = (key) => {
writing-mode: vertical-rl;
}
.current-summary {
position: relative;
z-index: 1;
display: flex;
min-width: 0;
align-items: center;
}
.current-name {
overflow: hidden;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 50rpx;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
overflow-wrap: anywhere;
}
.current-switch-copy {
flex: 0 0 auto;
@@ -694,8 +637,6 @@ const openShortcut = (key) => {
font-size: 28rpx;
}
.current-info-divider {
position: relative;
z-index: 1;
width: 100%;
height: 1rpx;
margin: 14rpx 0 16rpx;
@@ -703,8 +644,6 @@ const openShortcut = (key) => {
}
.current-meta {
position: relative;
z-index: 1;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
@@ -719,10 +658,10 @@ const openShortcut = (key) => {
display: flex;
align-items: center;
margin: 0;
white-space: nowrap;
flex-wrap: wrap;
.current-meta-item-text {
white-space: nowrap;
overflow-wrap: anywhere;
}
}
.current-meta-icon {
@@ -752,7 +691,6 @@ const openShortcut = (key) => {
height: 82rpx;
}
.shortcut-label {
overflow: hidden;
width: 100%;
margin-top: 12rpx;
color: $ink;
@@ -760,8 +698,7 @@ const openShortcut = (key) => {
font-size: 29rpx;
font-weight: 700;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow-wrap: anywhere;
}
.section-divider {
@@ -803,25 +740,17 @@ const openShortcut = (key) => {
}
.application-record {
position: relative;
display: flex;
min-height: 138rpx;
align-items: center;
box-sizing: border-box;
padding: 22rpx 26rpx;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
}
.application-record + .application-record {
margin-top: 12rpx;
}
.application-record__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.application-record__main {
position: relative;
z-index: 1;
min-width: 0;
flex: 1;
}
@@ -831,13 +760,11 @@ const openShortcut = (key) => {
justify-content: space-between;
}
.application-record__name {
overflow: hidden;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
overflow-wrap: anywhere;
}
.application-record__status {
flex: 0 0 auto;
@@ -861,8 +788,6 @@ const openShortcut = (key) => {
line-height: 1.4;
}
.application-record__chevron {
position: relative;
z-index: 1;
width: 32rpx;
height: 32rpx;
margin-left: 14rpx;
@@ -907,20 +832,18 @@ const openShortcut = (key) => {
}
.create-action > text {
flex: 0 0 auto;
white-space: nowrap;
text-align: center;
}
.create-cloud--right {
transform: scaleX(-1);
}
.state-panel {
position: relative;
display: flex;
min-height: 540rpx;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 42rpx;
box-sizing: border-box;
text-align: center;
@@ -941,25 +864,15 @@ const openShortcut = (key) => {
}
.state-panel--error {
height: 1120rpx;
min-height: 1120rpx;
padding: 0;
}
.error-panel__frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat;
}
.error-panel__content {
position: relative;
z-index: 1;
display: flex;
width: 100%;
height: 100%;
min-height: 1120rpx;
flex-direction: column;
align-items: center;
justify-content: center;
@@ -1000,7 +913,7 @@ const openShortcut = (key) => {
.state-panel--error .state-retry {
width: 560rpx;
height: 124rpx;
min-height: 124rpx;
margin-top: 22rpx;
}
@@ -1009,23 +922,13 @@ const openShortcut = (key) => {
}
.empty-panel {
position: relative;
height: 1120rpx;
}
.empty-panel__frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
min-height: 1120rpx;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat;
}
.empty-panel__content {
position: relative;
z-index: 1;
display: flex;
height: 100%;
min-height: 1120rpx;
flex-direction: column;
align-items: center;
justify-content: center;
@@ -1035,24 +938,15 @@ const openShortcut = (key) => {
}
.empty-seal {
position: relative;
display: flex;
width: 120rpx;
height: 184rpx;
align-items: center;
justify-content: center;
}
.empty-seal__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / 100% 100% no-repeat;
}
.empty-seal__text {
position: relative;
z-index: 1;
color: #fff7e7;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
@@ -1089,35 +983,25 @@ const openShortcut = (key) => {
.empty-search-action,
.empty-invite-action {
position: relative;
display: flex;
width: 560rpx;
height: 124rpx;
min-height: 124rpx;
align-items: center;
justify-content: center;
overflow: hidden;
}
.empty-search-action {
margin-top: 26rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat;
}
.empty-invite-action {
margin-top: 22rpx;
}
.empty-search-action__skin,
.empty-invite-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png") center / contain no-repeat;
}
.empty-search-action__copy,
.empty-invite-action__copy {
position: relative;
z-index: 1;
color: #7b4e24;
font-family: "STKaiti", "KaiTi", serif;
font-size: 34rpx;
@@ -1147,24 +1031,15 @@ const openShortcut = (key) => {
}
.state-retry {
position: relative;
display: flex;
width: 360rpx;
height: 96rpx;
min-height: 96rpx;
align-items: center;
justify-content: center;
margin-top: 30rpx;
overflow: hidden;
}
.state-retry__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat;
}
.state-retry__copy {
position: relative;
z-index: 1;
color: #fff9ec;
font-family: "STKaiti", "KaiTi", serif;
font-size: 29rpx;
@@ -1181,7 +1056,6 @@ const openShortcut = (key) => {
background: rgba(34, 20, 12, 0.68);
}
.add-dialog {
position: relative;
width: 100%;
min-height: 780rpx;
max-height: calc(100vh - 80rpx);
@@ -1193,8 +1067,6 @@ const openShortcut = (key) => {
border-image-repeat: stretch;
}
.add-dialog__content {
position: relative;
z-index: 2;
display: flex;
min-height: 780rpx;
max-height: calc(100vh - 80rpx);
@@ -1208,24 +1080,26 @@ const openShortcut = (key) => {
margin: auto 0;
}
.add-dialog__heading {
position: relative;
padding-right: 96rpx;
display: grid;
grid-template-columns: minmax(0, 1fr) 96rpx;
}
.add-dialog .dialog-title,
.add-dialog .dialog-copy {
display: block;
grid-column: 1;
text-align: left;
}
.add-dialog__close {
position: absolute;
z-index: 3;
top: 0;
right: -22rpx;
display: flex;
grid-column: 2;
grid-row: 1 / span 2;
align-self: start;
justify-self: end;
width: 80rpx;
height: 80rpx;
align-items: center;
justify-content: center;
margin-right: -22rpx;
}
.add-dialog__close-icon {
width: 80rpx;
@@ -1258,7 +1132,6 @@ const openShortcut = (key) => {
background: rgba(34, 20, 12, 0.58);
}
.genealogy-switcher {
position: relative;
width: 670rpx;
max-width: 100%;
min-height: 600rpx;
@@ -1271,32 +1144,35 @@ const openShortcut = (key) => {
border-image-repeat: stretch;
}
.genealogy-switcher__content {
position: relative;
z-index: 1;
display: flex;
display: grid;
min-height: 600rpx;
max-height: calc(100vh - 120rpx);
flex-direction: column;
grid-template-rows: auto minmax(0, 1fr);
align-items: center;
box-sizing: border-box;
padding: 120rpx 58rpx 140rpx;
}
.genealogy-switcher__content > .dialog-title {
grid-area: 1 / 1;
}
.genealogy-switcher__close {
position: absolute;
z-index: 3;
top: 78rpx;
right: 34rpx;
display: flex;
grid-area: 1 / 1;
align-self: start;
justify-self: end;
width: 80rpx;
height: 80rpx;
align-items: center;
justify-content: center;
margin-top: -42rpx;
margin-right: -24rpx;
}
.genealogy-switcher__close-icon {
width: 80rpx;
height: 80rpx;
}
.genealogy-switcher__list {
grid-area: 2 / 1;
width: 100%;
min-height: 0;
max-height: calc(100vh - 456rpx);
+58 -104
View File
@@ -4,11 +4,6 @@
<GenealogyPageBackground />
<view class="flow-header">
<image
class="flow-header__skin"
src="/static/assets/foundation/opaque/root-header-cinnabar.jpg"
mode="scaleToFill"
/>
<view class="flow-header__content">
<view class="flow-header__back" @click="goBack">
<image
@@ -26,12 +21,6 @@
<view class="flow-content">
<view class="create-flow-panel">
<image
class="create-flow-panel__skin"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view v-if="!isAncestorStep" class="create-flow-panel__content">
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
<text class="flow-heading">为家族立一部可传承的谱</text>
@@ -125,11 +114,6 @@
hover-class="action-hover"
@click="submitCreate"
>
<image
class="flow-primary-action__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="flow-primary-action__copy">{{
createState === "submitting" ? "正在创建…" : "创建并录入首代"
}}</text>
@@ -161,13 +145,30 @@
<text>世代</text>
<text class="fixed-value">第一世</text>
</view>
<picker
mode="selector"
:range="sexOptions"
range-key="label"
@change="changeAncestorSex"
>
<view class="field-row">
<text>性别</text>
<text class="fixed-value">{{ sexLabel }}</text>
</view>
</picker>
<view class="field-row">
<text>出生日期</text>
<input
v-model="ancestorForm.birthDate"
placeholder="如:1940年"
placeholder-class="placeholder"
/>
<picker
mode="date"
:value="ancestorForm.birthDate"
start="1800-01-01"
:end="new Date().toISOString().slice(0, 10)"
@change="changeAncestorBirthDate"
>
<text class="fixed-value">{{
ancestorForm.birthDate || "请选择日期"
}}</text>
</picker>
</view>
</view>
<text v-if="ancestorState === 'error'" class="flow-error"
@@ -178,6 +179,7 @@
<text>生平简述</text>
<textarea
v-model="ancestorForm.introduction"
auto-height
maxlength="200"
placeholder="可选,记录家训、迁徙或重要经历"
placeholder-class="placeholder"
@@ -189,11 +191,6 @@
hover-class="action-hover"
@click="submitAncestor"
>
<image
class="flow-primary-action__skin"
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text class="flow-primary-action__copy">{{
ancestorState === "submitting" ? "正在保存…" : "保存并进入家谱"
}}</text>
@@ -208,11 +205,6 @@
@click="closeDuplicateReminder"
>
<view class="duplicate-reminder" @click.stop>
<image
class="duplicate-reminder__skin"
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
mode="aspectFit"
/>
<view class="duplicate-reminder__content">
<text class="duplicate-reminder__title">先确认是否已有家谱</text>
<text class="duplicate-reminder__copy"
@@ -222,17 +214,9 @@
class="duplicate-reminder__search"
@click="searchExistingGenealogy"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
mode="aspectFit"
/>
<text>先搜索已有家谱</text>
</view>
<view class="duplicate-reminder__confirm" @click="confirmCreate">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>确认没有继续创建</text>
</view>
<view
@@ -246,21 +230,12 @@
<view v-if="ancestorState === 'success'" class="flow-success-layer">
<view class="flow-success-dialog">
<image
class="flow-success-dialog__skin"
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
mode="aspectFit"
/>
<view class="flow-success-dialog__content">
<text class="flow-success-dialog__title">家谱创建完成</text>
<text class="flow-success-dialog__copy"
>首代人物已保存接下来进入家谱总览继续完善资料</text
>
<view class="flow-success-dialog__action" @click="enterOverview">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>进入家谱总览</text>
</view>
</view>
@@ -302,6 +277,22 @@ const ancestorForm = reactive({
birthDate: "",
introduction: "",
});
const sexOptions = [
{ value: "0", label: "男" },
{ value: "1", label: "女" },
{ value: "2", label: "暂不填写" },
];
const sexLabel = computed(
() =>
sexOptions.find((option) => option.value === ancestorForm.sex)?.label ||
"请选择",
);
const changeAncestorSex = (event) => {
ancestorForm.sex = sexOptions[Number(event.detail.value)]?.value || "2";
};
const changeAncestorBirthDate = (event) => {
ancestorForm.birthDate = event.detail.value;
};
const isAncestorStep = computed(() => currentStep.value === "ancestor");
@@ -426,29 +417,21 @@ const enterOverview = () =>
<style scoped lang="scss">
.flow-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: #f9f6ef;
}
.flow-header {
position: relative;
z-index: 3;
height: 112rpx;
overflow: hidden;
}
.flow-header__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")
center / 100% 100% no-repeat;
}
.flow-header__content {
position: relative;
z-index: 1;
display: flex;
height: 100%;
@@ -482,26 +465,17 @@ const enterOverview = () =>
}
.flow-content {
position: relative;
z-index: 2;
padding: 24rpx 32rpx 48rpx;
}
.create-flow-panel {
position: relative;
min-height: 1000rpx;
}
.create-flow-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.create-flow-panel__content {
position: relative;
z-index: 1;
display: flex;
min-height: 1000rpx;
@@ -641,7 +615,7 @@ const enterOverview = () =>
.intro-field textarea {
width: 100%;
height: 142rpx;
min-height: 142rpx;
margin-top: 8rpx;
color: #392719;
font-family: "STKaiti", "KaiTi", serif;
@@ -650,25 +624,17 @@ const enterOverview = () =>
}
.flow-primary-action {
position: relative;
display: flex;
width: 100%;
height: 96rpx;
min-height: 96rpx;
align-items: center;
justify-content: center;
margin-top: auto;
overflow: hidden;
}
.flow-primary-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.flow-primary-action__copy {
position: relative;
z-index: 1;
color: #fff9ec;
font-family: "STKaiti", "KaiTi", serif;
@@ -694,21 +660,14 @@ const enterOverview = () =>
}
.duplicate-reminder,
.flow-success-dialog {
position: relative;
width: 100%;
max-width: 670rpx;
min-height: 650rpx;
}
.duplicate-reminder__skin,
.flow-success-dialog__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")
center / contain no-repeat;
}
.duplicate-reminder__content,
.flow-success-dialog__content {
position: relative;
z-index: 1;
display: flex;
min-height: 650rpx;
@@ -735,32 +694,25 @@ const enterOverview = () =>
.duplicate-reminder__search,
.duplicate-reminder__confirm,
.flow-success-dialog__action {
position: relative;
display: flex;
width: 100%;
height: 92rpx;
min-height: 92rpx;
align-items: center;
justify-content: center;
overflow: hidden;
}
.duplicate-reminder__search {
margin-top: 38rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")
center / contain no-repeat;
}
.duplicate-reminder__confirm {
margin-top: 14rpx;
}
.duplicate-reminder__search image,
.duplicate-reminder__confirm image,
.flow-success-dialog__action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.duplicate-reminder__search text,
.duplicate-reminder__confirm text,
.flow-success-dialog__action text {
position: relative;
z-index: 1;
color: #fff9ec;
font-size: 25rpx;
@@ -779,5 +731,7 @@ const enterOverview = () =>
}
.flow-success-dialog__action {
margin-top: 44rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
</style>
+86 -133
View File
@@ -20,12 +20,6 @@
class="overview-surface"
:class="{ 'overview-surface--state': overviewState !== 'ready' }"
>
<image
class="overview-surface__skin"
src="/static/assets/modules/genealogy/opaque/g05-overview-surface.png"
mode="scaleToFill"
/>
<template
v-if="overviewState === 'ready' && genealogy && viewMode === 'member'"
>
@@ -44,6 +38,10 @@
<text>已激活 {{ genealogy.activeCount || 0 }} </text>
<text>{{ genealogy.visibility || "仅成员可见" }}</text>
</view>
<view class="overview-hero__stats">
<text>始祖 {{ genealogy.ancestorName }}</text>
<text>更新于 {{ genealogy.updatedAt }}</text>
</view>
</view>
<view
@@ -62,10 +60,6 @@
<text class="overview-action__title">录入族人</text
><text>从首代开始完善</text>
</view>
<view v-else class="overview-action-lock">
<text class="overview-action__title">录入族人</text
><text>仅管理员可用</text>
</view>
<view
class="overview-action overview-action--poem"
@click="toGenerationPoems"
@@ -81,10 +75,6 @@
<text class="overview-action__title">入谱审核</text
><text>处理加入申请</text>
</view>
<view v-else class="overview-action-lock">
<text class="overview-action__title">入谱审核</text
><text>仅管理员可用</text>
</view>
</view>
<view class="overview-family" @click="toFamily">
@@ -130,10 +120,6 @@
<text>{{ genealogy.publicDescription }}</text>
</view>
<view class="overview-public__action" @click="applyToJoin">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>申请加入这部家谱</text>
</view>
</view>
@@ -142,11 +128,6 @@
v-else-if="overviewState === 'loading'"
class="overview-state overview-state--loading"
>
<image
class="overview-state__frame"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<AppLoading
text="正在展开家谱"
description="请稍候,正在读取家谱概览。"
@@ -162,11 +143,6 @@
'overview-state--no-permission': overviewState === 'no-permission',
}"
>
<image
class="overview-state__frame"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view class="overview-state__content">
<image
class="overview-state__seal"
@@ -181,10 +157,6 @@
overviewState === 'error' ? reloadOverview() : toGenealogies()
"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
overviewState === "error" ? "重新查看" : "返回我的家谱"
}}</text>
@@ -223,6 +195,28 @@ const overviewFixture = {
memberCount: 428,
activeCount: 316,
visibility: "仅成员可见",
ancestorName: "汤文远",
updatedAt: "2026-07-12",
};
const overviewFixtures = {
1001: {
...overviewFixture,
id: "1001",
name: "汤氏家谱",
location: "河南·洛阳",
memberCount: 158,
activeCount: 108,
},
1002: {
...overviewFixture,
id: "1002",
name: "汤氏宗谱",
hall: "承志堂",
location: "山东·济宁",
memberCount: 286,
activeCount: 215,
},
2001: overviewFixture,
};
const stateTitle = computed(
@@ -268,7 +262,13 @@ const loadGenealogy = (query = {}) => {
}
if (query.state === "loading") return;
genealogy.value = { ...overviewFixture, id: genealogyId.value };
genealogy.value = {
...(overviewFixtures[genealogyId.value] || overviewFixture),
id: genealogyId.value,
};
if (query.genealogyName) {
genealogy.value.name = decodeURIComponent(query.genealogyName);
}
overviewState.value = "ready";
};
@@ -305,43 +305,40 @@ const applyToJoin = () =>
<style scoped lang="scss">
.overview-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.overview-page__header {
position: relative;
z-index: 3;
}
.overview-surface {
position: relative;
z-index: 2;
width: 100%;
height: min(483px, calc(100vw * 1.3422));
}
.overview-surface__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
min-height: min(483px, calc(100vw * 1.3422));
flex: 0 0 auto;
background: url("/static/assets/modules/genealogy/opaque/g05-overview-surface.png")
center / 100% 100% no-repeat;
}
.overview-surface--state {
display: flex;
height: calc(100vh - 112rpx);
min-height: 1040rpx;
}
.overview-surface--state .overview-surface__skin {
display: none;
padding: 28rpx;
box-sizing: border-box;
background: none;
}
.overview-ready {
position: absolute;
inset: 0;
display: flex;
min-height: min(483px, calc(100vw * 1.3422));
flex-direction: column;
gap: 34rpx;
padding: 58rpx 0 42rpx;
box-sizing: border-box;
}
.overview-hero {
position: absolute;
top: 6.5%;
right: 8%;
left: 8%;
margin: 0 8%;
color: #fff8ec;
}
.overview-hero__kicker {
@@ -372,19 +369,15 @@ const applyToJoin = () =>
font-size: 22rpx;
}
.overview-actions {
position: absolute;
top: 29.2%;
right: 7.2%;
left: 7.2%;
display: grid;
min-height: 386rpx;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-template-rows: auto auto;
gap: 3.4% 3%;
height: 36.2%;
margin: 0 7.2%;
}
.overview-actions--member {
grid-template-rows: 1fr 1fr;
height: 36.2%;
min-height: 386rpx;
}
.overview-action,
.overview-action-lock {
@@ -392,7 +385,7 @@ const applyToJoin = () =>
flex-direction: column;
justify-content: center;
min-width: 0;
padding: 0 22rpx;
padding: 24rpx 22rpx;
box-sizing: border-box;
color: $ink-muted;
font-size: 22rpx;
@@ -408,14 +401,12 @@ const applyToJoin = () =>
font-weight: 700;
}
.overview-family {
position: absolute;
top: 70.2%;
right: 7.5%;
left: 7.5%;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
align-self: start;
gap: 16rpx;
margin: 0 7.5%;
color: $ink-muted;
font-size: 22rpx;
}
@@ -429,17 +420,14 @@ const applyToJoin = () =>
color: $brand-red;
}
.overview-note {
position: absolute;
top: 80.5%;
right: 8.5%;
left: 8.5%;
display: flex;
flex-direction: column;
justify-content: center;
height: 15%;
margin: 0 8.5%;
color: $ink-muted;
font-size: 22rpx;
line-height: 1.6;
margin-top: 100rpx;
}
.overview-note__title {
margin-bottom: 8rpx;
@@ -449,25 +437,16 @@ const applyToJoin = () =>
font-weight: 700;
}
.overview-state {
position: absolute;
top: 28rpx;
right: 28rpx;
bottom: 28rpx;
left: 28rpx;
display: flex;
width: 100%;
min-height: 0;
align-items: center;
justify-content: center;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
text-align: center;
}
.overview-state__frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.overview-state__content {
position: relative;
z-index: 1;
display: flex;
width: 100%;
@@ -501,37 +480,31 @@ const applyToJoin = () =>
line-height: 1.7;
}
.overview-state-panel__action {
position: relative;
width: 560rpx;
height: 124rpx;
margin-top: 34rpx;
}
.overview-state-panel__action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.overview-state-panel__action text {
position: relative;
z-index: 1;
display: flex;
width: 560rpx;
min-height: 124rpx;
margin-top: 34rpx;
align-items: center;
justify-content: center;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.overview-state-panel__action text {
z-index: 1;
color: #fff9ed;
font-size: 31rpx;
font-weight: 700;
}
.overview-public {
position: absolute;
inset: 0;
display: flex;
min-height: min(483px, calc(100vw * 1.3422));
flex-direction: column;
gap: 0;
padding: 58rpx 0 28rpx;
box-sizing: border-box;
}
.overview-public__hero {
position: absolute;
top: 6.5%;
right: 8%;
left: 8%;
margin: 0 8%;
color: #fff8ec;
}
.overview-public__eyebrow {
@@ -555,24 +528,19 @@ const applyToJoin = () =>
font-size: 22rpx;
}
.overview-public__details {
position: absolute;
top: 30%;
right: 9%;
left: 9%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx 28rpx;
min-height: 240rpx;
margin: 126rpx 9% 0;
}
.overview-public__details > view {
display: flex;
flex-direction: column;
}
.overview-public__details > view:nth-child(5) {
position: absolute;
top: 250rpx;
right: 0;
left: 0;
grid-column: 1 / -1;
align-self: start;
flex-direction: row;
align-items: center;
justify-content: space-between;
@@ -596,11 +564,8 @@ const applyToJoin = () =>
font-size: 27rpx;
}
.overview-public__notice {
position: absolute;
top: 78%;
right: 9%;
left: 9%;
display: flex;
margin: 54rpx 9% 0;
color: $ink-muted;
font-size: 19rpx;
line-height: 26rpx;
@@ -612,24 +577,15 @@ const applyToJoin = () =>
font-weight: 700;
}
.overview-public__action {
position: absolute;
right: 9%;
bottom: 2%;
left: 9%;
display: flex;
height: 82rpx;
min-height: 82rpx;
align-items: center;
justify-content: center;
overflow: hidden;
}
.overview-public__action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 204rpx 9% 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.overview-public__action text {
position: relative;
z-index: 1;
color: #fff9ed;
font-size: 27rpx;
@@ -637,9 +593,6 @@ const applyToJoin = () =>
letter-spacing: 3rpx;
}
@media (min-width: 400px) {
.overview-hero {
top: 7%;
}
.overview-action {
padding: 0 28rpx;
}
+40 -112
View File
@@ -6,11 +6,6 @@
<view class="search-page__content">
<view class="search-title-strip">
<image
class="search-title-strip__skin"
src="/static/assets/modules/genealogy/opaque/g06-search-title-strip.png"
mode="scaleToFill"
/>
<image
class="search-title-strip__seal"
src="/static/assets/foundation/transparent/brand-seal.png"
@@ -37,11 +32,6 @@
<template v-if="mode === 'search'">
<view class="search-controls">
<view class="search-field">
<image
class="search-field__skin"
src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png"
mode="scaleToFill"
/>
<input
v-model.trim="keyword"
class="search-input"
@@ -54,11 +44,6 @@
>
</view>
<view class="search-action" @click="search">
<image
class="search-action__skin"
src="/static/assets/modules/genealogy/opaque/g06-search-button.png"
mode="scaleToFill"
/>
<text class="search-action__label">搜索</text>
</view>
</view>
@@ -110,11 +95,6 @@
class="result-card genealogy-card"
@click="openPreview(item)"
>
<image
class="result-card__skin"
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="result-card__content">
<view class="result-card__head">
<view>
@@ -169,10 +149,6 @@
>当前仅展示失败样式请稍后重新搜索</text
>
<view class="status-retry" @click="search">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>重新搜索</text>
</view>
</view>
@@ -186,11 +162,6 @@
>
<view class="invite-controls">
<view class="invite-field">
<image
class="search-field__skin"
src="/static/assets/modules/genealogy/opaque/g06-search-input-wide.png"
mode="scaleToFill"
/>
<input
v-model.trim="inviteCode"
class="invite-input"
@@ -200,11 +171,6 @@
/>
</view>
<view class="search-action" @click="verifyInvite">
<image
class="search-action__skin"
src="/static/assets/modules/genealogy/opaque/g06-search-button.png"
mode="scaleToFill"
/>
<text class="search-action__label">验证</text>
</view>
</view>
@@ -237,11 +203,6 @@
<view v-else class="invite-result">
<text class="invite-result__label">已定位目标家谱</text>
<view class="result-card genealogy-card">
<image
class="result-card__skin"
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="result-card__content">
<text class="result-card__name">{{ inviteTarget.name }}</text>
<view class="result-card__facts">
@@ -444,7 +405,7 @@ const clearSearch = () => {
const openPreview = (item) => {
if (item.relation === "available") {
uni.navigateTo({
url: `/pages/genealogy/g05-genealogy-overview?mode=public&genealogyId=${item.id}`,
url: `/pages/genealogy/g05-genealogy-overview?mode=public&genealogyId=${item.id}&genealogyName=${encodeURIComponent(item.name)}`,
});
}
};
@@ -452,7 +413,7 @@ const openPreview = (item) => {
const handleResultAction = (item) => {
if (item.relation === "available")
return uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=search&genealogyId=${item.id}`,
url: `/pages/genealogy/g08-join-application?source=search&genealogyId=${item.id}&genealogyName=${encodeURIComponent(item.name)}`,
});
if (item.relation === "pending")
return uni.navigateTo({
@@ -460,7 +421,7 @@ const handleResultAction = (item) => {
});
if (item.relation === "rejected" || item.relation === "removed")
return uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=search&previous=${item.relation}&genealogyId=${item.id}`,
url: `/pages/genealogy/g08-join-application?source=search&previous=${item.relation}&genealogyId=${item.id}&genealogyName=${encodeURIComponent(item.name)}`,
});
return uni.reLaunch({
url: `/pages/genealogy/g01-my-genealogies?genealogyId=${item.id}`,
@@ -473,51 +434,42 @@ const verifyInvite = () => {
};
const confirmInvite = () =>
uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=invite&genealogyId=${inviteTarget.value.id}`,
url: `/pages/genealogy/g08-join-application?source=invite&genealogyId=${inviteTarget.value.id}&genealogyName=${encodeURIComponent(inviteTarget.value.name)}`,
});
</script>
<style scoped lang="scss">
.search-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.search-page__header {
position: relative;
z-index: 2;
}
.search-page__content {
position: relative;
z-index: 3;
min-height: calc(100vh - 104rpx);
padding: 30rpx 24rpx calc(58rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.search-title-strip {
position: relative;
display: grid;
width: 100%;
height: 138rpx;
}
.search-title-strip__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
grid-template-columns: 156rpx 1fr 170rpx;
align-items: center;
background: url("/static/assets/modules/genealogy/opaque/g06-search-title-strip.png")
center / 100% 100% no-repeat;
}
.search-title-strip__seal {
position: absolute;
top: 27rpx;
left: 52rpx;
z-index: 1;
width: 72rpx;
height: 84rpx;
margin-left: 52rpx;
}
.search-title-strip__text {
position: absolute;
inset: 0 170rpx 0 156rpx;
z-index: 1;
display: flex;
align-items: center;
@@ -535,7 +487,6 @@ const confirmInvite = () =>
border-bottom: 1rpx solid rgba(181, 138, 75, 0.45);
}
.mode-tab {
position: relative;
display: flex;
flex: 1;
align-items: center;
@@ -546,18 +497,12 @@ const confirmInvite = () =>
font-size: 28rpx;
}
.mode-tab--active {
margin-bottom: -2rpx;
background: linear-gradient($brand-red, $brand-red) center bottom /
calc(100% - 84rpx) 4rpx no-repeat;
color: $brand-red;
font-weight: 700;
}
.mode-tab--active::after {
position: absolute;
right: 42rpx;
bottom: -2rpx;
left: 42rpx;
height: 4rpx;
background: $brand-red;
content: "";
}
.search-controls {
display: flex;
align-items: center;
@@ -571,21 +516,15 @@ const confirmInvite = () =>
margin-top: 32rpx;
}
.search-field {
position: relative;
display: grid;
width: 430rpx;
height: 95rpx;
}
.search-field__skin,
.search-action__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
min-height: 95rpx;
background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")
center / 100% 100% no-repeat;
}
.search-input {
position: relative;
z-index: 1;
grid-area: 1 / 1;
width: 100%;
height: 100%;
padding: 0 82rpx 0 30rpx;
@@ -597,26 +536,26 @@ const confirmInvite = () =>
color: #ab9c83;
}
.search-clear {
position: absolute;
z-index: 2;
top: 0;
right: 20rpx;
display: flex;
grid-area: 1 / 1;
justify-self: end;
height: 100%;
align-items: center;
margin-right: 20rpx;
color: $brand-red;
font-size: 21rpx;
}
.search-action {
position: relative;
display: flex;
width: 200rpx;
height: 88rpx;
min-height: 88rpx;
align-items: center;
justify-content: center;
background: url("/static/assets/modules/genealogy/opaque/g06-search-button.png")
center / 100% 100% no-repeat;
}
.search-action__label {
position: relative;
z-index: 1;
color: #fff4dc;
font-family: "STKaiti", "KaiTi", serif;
@@ -690,23 +629,16 @@ const confirmInvite = () =>
filter: grayscale(1);
}
.status-retry {
position: relative;
display: flex;
width: 300rpx;
height: 76rpx;
min-height: 76rpx;
align-items: center;
justify-content: center;
margin-top: 24rpx;
overflow: hidden;
}
.status-retry image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.status-retry text {
position: relative;
z-index: 1;
color: #fff9ed;
font-size: 25rpx;
@@ -733,20 +665,13 @@ const confirmInvite = () =>
font-weight: 700;
}
.result-card {
position: relative;
min-height: 314rpx;
box-sizing: border-box;
padding: 28rpx 30rpx;
overflow: hidden;
}
.result-card__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.result-card__content {
position: relative;
z-index: 1;
}
.result-card__head {
@@ -823,13 +748,15 @@ const confirmInvite = () =>
line-height: 35rpx;
}
.invite-field {
position: relative;
display: grid;
flex: 1;
height: 95rpx;
min-height: 95rpx;
background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")
center / 100% 100% no-repeat;
}
.invite-input {
position: relative;
z-index: 1;
grid-area: 1 / 1;
width: 100%;
height: 100%;
padding: 0 30rpx;
@@ -866,11 +793,12 @@ const confirmInvite = () =>
padding-left: 20rpx;
}
.search-title-strip__seal {
left: 44rpx;
margin-left: 44rpx;
}
.search-title-strip {
grid-template-columns: 138rpx 1fr 148rpx;
}
.search-title-strip__text {
right: 148rpx;
left: 138rpx;
font-size: 37rpx;
}
.search-field {
+59 -79
View File
@@ -14,24 +14,17 @@
'join-state--error': joinState === 'error',
}"
>
<image
class="join-panel__skin"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<view v-if="joinState === 'form'" class="join-form">
<text class="join-form__eyebrow">{{ sourceContract.eyebrow }}</text>
<text class="join-form__title"
>{{ sourceContract.formTitle }} {{ genealogyName }}</text
>
<text class="join-form__copy">{{ sourceContract.formCopy }}</text>
<text v-if="previousNotice" class="join-form__previous">{{
previousNotice
}}</text>
<view class="join-field">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>真实姓名</text
><input
v-model="form.realName"
@@ -44,10 +37,6 @@
fieldErrors.realName
}}</text>
<view class="join-field">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>与家谱关系</text
><input
v-model="form.relation"
@@ -63,13 +52,10 @@
>请以家谱中一位已知长辈为参照例如汤正华堂侄</text
>
<view class="join-field join-field--message">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>{{ sourceContract.thirdFieldLabel }}</text
><textarea
v-model="form.message"
auto-height
maxlength="80"
:placeholder="sourceContract.thirdFieldPlaceholder"
placeholder-class="join-placeholder"
@@ -78,10 +64,6 @@
<text class="join-form__note">{{ sourceContract.note }}</text>
<view class="join-action" @click="submitJoin">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
isSubmitting
? sourceContract.submittingLabel
@@ -106,10 +88,6 @@
class="join-action"
@click="joinState === 'success' ? completeFlow() : retryForm()"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
joinState === "success" ? sourceContract.nextLabel : "重新填写"
}}</text>
@@ -127,6 +105,7 @@ import PageHeader from "@/components/PageHeader.vue";
const genealogyId = ref("");
const source = ref("search");
const previousRelation = ref("");
const genealogyName = ref("这部家谱");
const genealogyPreview = {
1001: "汤氏家谱",
@@ -137,6 +116,15 @@ const isSubmitting = ref(false);
const errorMessage = ref("");
const form = reactive({ realName: "", relation: "", message: "" });
const fieldErrors = reactive({ realName: "", relation: "" });
const previousNotice = computed(() =>
previousRelation.value === "rejected"
? "上次申请未通过,请补充更准确的长辈姓名、祖居地或支系信息。"
: previousRelation.value === "removed"
? "你曾退出或被移出这部家谱,请重新确认身份关系后申请。"
: previousRelation.value === "withdrawn"
? "上次申请已撤回;如仍希望加入,请重新确认关系并提交。"
: "",
);
const sourceContract = computed(() =>
source.value === "invite"
? {
@@ -185,6 +173,9 @@ const resultCopy = computed(() =>
onLoad((query) => {
genealogyId.value = query.genealogyId || "";
source.value = query.source === "invite" ? "invite" : "search";
previousRelation.value = ["rejected", "removed", "withdrawn"].includes(query.previous)
? query.previous
: "";
if (query.state === "success") {
joinState.value = "success";
return;
@@ -194,7 +185,9 @@ onLoad((query) => {
joinState.value = "error";
return;
}
genealogyName.value = genealogyPreview[genealogyId.value] || "这部家谱";
genealogyName.value = query.genealogyName
? decodeURIComponent(query.genealogyName)
: genealogyPreview[genealogyId.value] || "这部家谱";
});
const submitJoin = () => {
@@ -232,31 +225,26 @@ const completeFlow = () =>
<style scoped lang="scss">
.join-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.join-page__header {
position: relative;
z-index: 3;
}
.join-panel {
position: relative;
z-index: 2;
width: calc(100% - 32rpx);
height: min(590px, calc((100vw - 16px) * 1.337));
min-height: min(590px, calc((100vw - 16px) * 1.337));
margin: 22rpx auto 0;
}
.join-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
padding: 70rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.join-form {
position: absolute;
inset: 8.5% 8%;
min-width: 0;
}
.join-form__eyebrow,
.join-result__eyebrow {
@@ -283,34 +271,37 @@ const completeFlow = () =>
line-height: 1.6;
}
.join-field {
position: relative;
height: 92rpx;
display: grid;
min-height: 92rpx;
margin-top: 18rpx;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.join-field > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
.join-form__previous {
display: block;
margin-top: 14rpx;
color: $brand-red;
font-size: 22rpx;
line-height: 1.55;
}
.join-field > text {
position: absolute;
top: 31rpx;
left: 24rpx;
z-index: 1;
grid-area: 1 / 1;
align-self: center;
margin-left: 24rpx;
color: $ink;
font-size: 23rpx;
font-weight: 700;
}
.join-field input,
.join-field textarea {
position: absolute;
top: 0;
right: 20rpx;
bottom: 0;
left: 170rpx;
z-index: 1;
height: 92rpx;
grid-area: 1 / 1;
width: auto;
min-width: 0;
min-height: 92rpx;
margin-right: 20rpx;
margin-left: 170rpx;
color: $ink;
font-size: 23rpx;
line-height: 92rpx;
@@ -346,36 +337,29 @@ const completeFlow = () =>
text-align: center;
}
.join-action {
position: relative;
width: 100%;
height: 82rpx;
margin-top: 20rpx;
}
.join-action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.join-action text {
position: relative;
z-index: 1;
display: flex;
width: 100%;
min-height: 82rpx;
align-items: center;
justify-content: center;
height: 100%;
margin-top: 20rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.join-action text {
z-index: 1;
color: #fff9ed;
font-size: 27rpx;
font-weight: 700;
letter-spacing: 3rpx;
}
.join-result {
position: absolute;
top: 28%;
right: 12%;
left: 12%;
text-align: center;
}
.join-state--success,
.join-state--error {
padding: 170rpx 12% 70rpx;
}
.join-result__eyebrow {
text-align: center;
}
@@ -391,9 +375,5 @@ const completeFlow = () =>
.join-panel {
width: calc(100% - 48rpx);
}
.join-form {
right: 9%;
left: 9%;
}
}
</style>
+71 -71
View File
@@ -23,11 +23,6 @@
:key="item.id"
class="application-card"
>
<image
class="application-card__skin"
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="application-card__body">
<text class="application-card__name">{{ item.genealogyName }}</text>
<text class="application-card__time">{{ item.appliedAt }}</text>
@@ -56,10 +51,6 @@
description="请稍候,正在同步审核状态。"
/>
<view v-else class="application-state-card">
<image
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="application-state-card__copy">
<text>{{ stateTitle }}</text>
<text>{{ stateCopy }}</text>
@@ -72,10 +63,6 @@
applicationState === 'error' ? loadApplications({}) : toSearch()
"
>
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
applicationState === "error" ? "重新查看" : "查找公开家谱"
}}</text>
@@ -156,7 +143,7 @@ const statusHint = (status) =>
})[status] || "";
const actionFor = (item) =>
({ PENDING: "撤回申请", APPROVED: "进入家谱", REJECTED: "修改后重新提交" })[
({ PENDING: "撤回申请", APPROVED: "进入家谱", REJECTED: "修改后重新提交", WITHDRAWN: "重新申请" })[
item.status
] || "";
const stateTitle = computed(() =>
@@ -190,7 +177,14 @@ const loadApplications = (query = {}) => {
return;
}
applications.value = applicationSamples.map((item) => ({ ...item }));
applicationState.value = "list";
if (query.status) {
const requestedStatus = String(query.status).toUpperCase();
applications.value = applications.value.filter(
(item) => item.status === requestedStatus,
);
if (!applications.value.length) applicationState.value = "empty";
}
if (applicationState.value !== "empty") applicationState.value = "list";
};
onLoad(loadApplications);
@@ -201,7 +195,11 @@ const handleApplicationAction = (item) => {
});
if (item.status === "REJECTED")
return uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=search&previous=rejected&genealogyId=${item.genealogyId}`,
url: `/pages/genealogy/g08-join-application?source=search&previous=rejected&genealogyId=${item.genealogyId}&genealogyName=${encodeURIComponent(item.genealogyName)}`,
});
if (item.status === "WITHDRAWN")
return uni.navigateTo({
url: `/pages/genealogy/g08-join-application?source=search&previous=withdrawn&genealogyId=${item.genealogyId}&genealogyName=${encodeURIComponent(item.genealogyName)}`,
});
if (item.status === "PENDING") withdrawTarget.value = item;
};
@@ -221,17 +219,15 @@ const toSearch = () =>
<style scoped lang="scss">
.application-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.application-page__header {
position: relative;
z-index: 3;
}
.application-content {
position: relative;
z-index: 2;
padding: 28rpx 24rpx 60rpx;
}
@@ -252,47 +248,57 @@ const toSearch = () =>
font-size: 24rpx;
}
.application-card {
position: relative;
display: grid;
width: 100%;
height: calc((100vw - 24px) * 0.34286);
min-height: 228rpx;
max-height: 282rpx;
margin-bottom: 18rpx;
}
.application-card__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.application-card__body {
position: absolute;
inset: 18% 7% 14% 8.5%;
display: grid;
min-width: 0;
min-height: 228rpx;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12rpx 18rpx;
padding: 42rpx 52rpx 34rpx 64rpx;
box-sizing: border-box;
}
.application-card__name {
grid-row: 1;
grid-column: 1;
align-self: start;
justify-self: start;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 30rpx;
font-weight: 700;
}
.application-card__time {
position: absolute;
top: 3rpx;
right: 0;
grid-row: 1;
grid-column: 2;
align-self: start;
justify-self: end;
margin-top: 3rpx;
color: #998873;
font-size: 23rpx;
}
.application-card__relation {
display: block;
max-width: 70%;
margin-top: 14rpx;
grid-row: 2;
grid-column: 1 / -1;
align-self: start;
justify-self: start;
margin-top: 0;
color: $ink-muted;
font-size: 24rpx;
}
.application-card__status {
position: absolute;
right: 3%;
bottom: 38%;
grid-row: 2;
grid-column: 2;
align-self: center;
justify-self: end;
margin-right: 3%;
color: $brand-red;
font-size: 25rpx;
font-weight: 700;
@@ -304,41 +310,42 @@ const toSearch = () =>
color: #7e6f62;
}
.application-card__hint {
position: absolute;
bottom: 5%;
left: 0;
max-width: 62%;
grid-row: 3;
grid-column: 1;
align-self: center;
justify-self: start;
margin-bottom: 4rpx;
color: #766653;
font-size: 23rpx;
}
.application-card__action {
position: absolute;
right: 2%;
bottom: 2%;
z-index: 2;
display: flex;
grid-row: 3;
grid-column: 2;
align-self: center;
justify-self: end;
min-height: 54rpx;
align-items: center;
margin-right: 2%;
margin-bottom: 0;
color: $brand-red;
font-size: 23rpx;
font-weight: 700;
}
.application-state-card {
position: relative;
display: flex;
width: 100%;
height: calc((100vw - 24px) * 0.34286);
min-height: 228rpx;
align-items: center;
justify-content: center;
margin-top: 80rpx;
}
.application-state-card > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
padding: 48rpx 12%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")
center / 100% 100% no-repeat;
}
.application-state-card__copy {
position: absolute;
inset: 22% 12%;
display: flex;
flex-direction: column;
justify-content: center;
@@ -357,24 +364,17 @@ const toSearch = () =>
line-height: 1.6;
}
.application-page__action {
position: relative;
width: 420rpx;
height: 82rpx;
margin: 34rpx auto 0;
}
.application-page__action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.application-page__action text {
position: relative;
z-index: 1;
display: flex;
width: 420rpx;
min-height: 82rpx;
align-items: center;
justify-content: center;
height: 100%;
margin: 34rpx auto 0;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")
center / contain no-repeat;
}
.application-page__action text {
z-index: 1;
color: #fff9ed;
font-size: 26rpx;
font-weight: 700;
+72 -91
View File
@@ -18,10 +18,6 @@
<view v-if="reviewState !== 'loading'" class="review-intro">
<text>核实亲属关系后再决定</text>
<text>审核结果会通过消息告知申请人</text>
<image
src="/static/assets/modules/genealogy/transparent/section-divider.png"
mode="scaleToFill"
/>
</view>
<template v-if="reviewState === 'list'">
@@ -30,11 +26,6 @@
:key="item.id"
class="application-card"
>
<image
class="application-card__skin"
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="application-card__body">
<text class="application-card__name">{{ item.name }}</text>
<text class="application-card__phone">{{ item.phone }}</text>
@@ -85,10 +76,6 @@
description="请稍候,正在读取待审核记录。"
/>
<view v-else class="review-state-card">
<image
src="/static/assets/modules/genealogy/transparent/list-slip-frame.png"
mode="scaleToFill"
/>
<view class="review-state-card__copy">
<text>{{ stateTitle }}</text>
<text>{{ stateCopy }}</text>
@@ -133,13 +120,23 @@
@confirm="helpVisible ? closeDialog() : applyAudit()"
@cancel="closeDialog"
@close="closeDialog"
/>
>
<view v-if="confirmation && !confirmation.approved" class="rejection-field">
<text>拒绝原因</text>
<textarea
v-model="rejectionReason"
auto-height
maxlength="120"
placeholder="请说明需要补充或核实的信息"
@input="rejectionError = ''"
/>
<text v-if="rejectionError" class="rejection-field__error">{{
rejectionError
}}</text>
</view>
</AppDialog>
<view v-if="feedbackVisible" class="review-feedback">
<image
src="/static/assets/foundation/transparent/a01-scroll-toast-v3.png"
mode="scaleToFill"
/>
<text>{{ feedbackMessage }}</text>
</view>
</view>
@@ -177,6 +174,8 @@ const genealogyId = ref("");
const reviewState = ref("loading");
const errorMessage = ref("");
const confirmation = ref(null);
const rejectionReason = ref("");
const rejectionError = ref("");
const helpVisible = ref(false);
const feedbackVisible = ref(false);
const feedbackMessage = ref("");
@@ -235,10 +234,14 @@ const loadApplications = (query = {}) => {
onLoad(loadApplications);
const confirmAudit = (item, approved) => {
rejectionReason.value = "";
rejectionError.value = "";
confirmation.value = { item, approved };
};
const closeDialog = () => {
confirmation.value = null;
rejectionReason.value = "";
rejectionError.value = "";
helpVisible.value = false;
};
const showFeedback = (message) => {
@@ -253,7 +256,12 @@ const showFeedback = (message) => {
const applyAudit = () => {
const current = confirmation.value;
if (!current) return;
if (!current.approved && !rejectionReason.value.trim()) {
rejectionError.value = "请填写拒绝原因,方便申请人补充资料";
return;
}
current.item.status = current.approved ? "APPROVED" : "REJECTED";
if (!current.approved) current.item.rejectionReason = rejectionReason.value.trim();
closeDialog();
showFeedback(current.approved ? "已通过申请" : "已拒绝申请");
};
@@ -264,24 +272,26 @@ const showHelp = () => {
<style scoped lang="scss">
.review-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.review-page__header {
position: relative;
z-index: 3;
z-index: 1;
}
.rejection-field { width: 100%; margin: 20rpx 0; text-align: left; }
.rejection-field > text:first-child { display: block; color: $ink; font-size: 23rpx; font-weight: 700; }
.rejection-field textarea { width: 100%; min-height: 110rpx; margin-top: 10rpx; padding: 18rpx; box-sizing: border-box; color: $ink; font-size: 23rpx; line-height: 1.55; background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png") center / 100% 100% no-repeat; }
.rejection-field__error { display: block; margin-top: 8rpx; color: $brand-red; font-size: 21rpx; }
.review-content {
position: relative;
z-index: 2;
z-index: 1;
padding: 24rpx 24rpx 60rpx;
}
.review-intro {
position: relative;
margin: 0 8rpx 24rpx;
padding-bottom: 22rpx;
background: url("/static/assets/modules/genealogy/transparent/section-divider.png") bottom center / 100% 14rpx no-repeat;
}
.review-intro text {
display: block;
@@ -297,80 +307,72 @@ const showHelp = () => {
color: $ink-muted;
font-size: 24rpx;
}
.review-intro image {
position: absolute;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 14rpx;
}
.application-card {
position: relative;
width: 100%;
height: calc((100vw - 24px) * 0.34286);
min-height: 228rpx;
max-height: 282rpx;
margin-bottom: 18rpx;
}
.application-card__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
}
.application-card__body {
position: absolute;
inset: 18% 7% 12% 8.5%;
display: grid;
width: 100%;
min-height: 228rpx;
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-rows: auto auto auto;
gap: 12rpx 0;
padding: 44rpx 52rpx 29rpx 64rpx;
box-sizing: border-box;
}
.application-card__name {
grid-column: 1;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 30rpx;
font-weight: 700;
}
.application-card__phone {
grid-column: 2;
margin-left: 14rpx;
color: $ink-muted;
font-size: 23rpx;
}
.application-card__time {
position: absolute;
top: 3rpx;
right: 0;
grid-column: 3;
justify-self: end;
margin-top: 3rpx;
color: #998873;
font-size: 23rpx;
}
.application-card__relation {
display: block;
max-width: 52%;
grid-column: 1 / span 2;
grid-row: 2;
margin-top: 15rpx;
color: $ink-muted;
font-size: 24rpx;
line-height: 1.35;
}
.review-actions {
position: absolute;
right: 0;
bottom: 0;
display: flex;
grid-column: 2 / 4;
grid-row: 3;
justify-self: end;
gap: 12rpx;
}
.review-action {
position: relative;
display: grid;
width: 148rpx;
height: 68rpx;
min-height: 68rpx;
}
.review-action image {
position: absolute;
inset: 0;
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.review-action text {
position: relative;
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
@@ -384,22 +386,22 @@ const showHelp = () => {
color: #fff9ed;
}
.review-result {
position: absolute;
right: 0;
bottom: 0;
display: grid;
grid-column: 2 / 4;
grid-row: 3;
justify-self: end;
width: 308rpx;
height: 68rpx;
min-height: 68rpx;
}
.review-result image {
position: absolute;
inset: 0;
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.application-card__status {
position: relative;
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
@@ -414,24 +416,18 @@ const showHelp = () => {
color: $ink;
}
.review-state-card {
position: relative;
width: 100%;
height: calc((100vw - 24px) * 0.34286);
min-height: 228rpx;
margin-top: 70rpx;
}
.review-state-card > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat;
}
.review-state-card__copy {
position: absolute;
inset: 22% 12%;
display: flex;
min-height: 228rpx;
flex-direction: column;
justify-content: center;
padding: 48rpx 12%;
box-sizing: border-box;
text-align: center;
}
.review-state-card__copy text:first-child {
@@ -447,20 +443,12 @@ const showHelp = () => {
line-height: 1.6;
}
.review-page__retry {
position: relative;
width: 420rpx;
height: 82rpx;
min-height: 82rpx;
margin: 32rpx auto 0;
}
.review-page__retry image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat;
}
.review-page__retry text {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
@@ -480,17 +468,10 @@ const showHelp = () => {
align-items: center;
justify-content: center;
padding: 0 36rpx;
background: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png") center / 100% 100% no-repeat;
transform: translateX(-50%);
}
.review-feedback image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.review-feedback text {
position: relative;
z-index: 1;
color: $ink;
font-size: 23rpx;
}
+69 -90
View File
@@ -13,12 +13,6 @@
'settings-state--no-permission': settingsState === 'no-permission',
}"
>
<image
class="settings-panel__skin"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<AppLoading
v-if="settingsState === 'loading'"
text="正在读取家谱设置"
@@ -32,10 +26,6 @@
>
<view class="settings-field">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>家谱名称</text>
<input
v-model="genealogyDraft.name"
@@ -79,13 +69,10 @@
</view>
<view class="settings-field settings-field--note">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>访问说明</text>
<textarea
v-model="genealogyDraft.accessNote"
auto-height
maxlength="80"
placeholder="向访问者说明家谱用途"
placeholder-class="settings-placeholder"
@@ -93,10 +80,6 @@
</view>
<view class="settings-action" @click="saveSettings">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>保存设置</text>
</view>
</view>
@@ -124,10 +107,6 @@
: "请从家谱总览重新进入,当前修改不会被保留。"
}}</text>
<view class="settings-action" @click="settingsState = 'form'">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{
settingsState === "success" ? "继续调整" : "重新查看"
}}</text>
@@ -135,10 +114,6 @@
</view>
</view>
<view v-if="feedbackVisible" class="settings-feedback">
<image
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
mode="aspectFit"
/>
<text>设置已保存</text>
</view>
</view>
@@ -156,11 +131,27 @@ const settingsState = ref("loading");
const nameError = ref("");
const feedbackVisible = ref(false);
let feedbackTimer = null;
const settingsFixtures = {
1001: {
name: "汤氏家谱",
visibility: "MEMBER_ONLY",
accessNote: "家族资料,请妥善保存",
},
1002: {
name: "汤氏宗谱",
visibility: "PUBLIC_APPLY",
accessNote: "公开家谱身份,成员资料需审核后查看",
},
};
const genealogyDraft = reactive({
name: "汤氏家谱",
visibility: "MEMBER_ONLY",
accessNote: "家族资料,请妥善保存",
});
const originalDraft = ref("");
const isDirty = computed(
() => JSON.stringify(genealogyDraft) !== originalDraft.value,
);
const visibilityOptions = [
{ value: "MEMBER_ONLY", label: "仅成员可见" },
{ value: "PUBLIC_APPLY", label: "公开可申请" },
@@ -178,6 +169,11 @@ const visibilityHint = computed(() =>
onLoad((query) => {
genealogyId.value = query.genealogyId || "";
Object.assign(
genealogyDraft,
settingsFixtures[genealogyId.value] || settingsFixtures[1001],
);
originalDraft.value = JSON.stringify(genealogyDraft);
settingsState.value =
query.state === "loading"
? "loading"
@@ -198,6 +194,11 @@ const saveSettings = () => {
nameError.value = "请填写家谱名称";
return;
}
if (genealogyDraft.name.trim().length > 30) {
nameError.value = "家谱名称不能超过 30 个字";
return;
}
originalDraft.value = JSON.stringify(genealogyDraft);
settingsState.value = "success";
feedbackVisible.value = true;
feedbackTimer = setTimeout(() => {
@@ -209,31 +210,29 @@ const saveSettings = () => {
<style scoped lang="scss">
.settings-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.settings-page__header {
position: relative;
z-index: 3;
}
.settings-panel {
position: relative;
z-index: 2;
width: calc(100% - 32rpx);
height: min(620px, calc((100vw - 16px) * 1.43));
min-height: min(620px, calc((100vw - 16px) * 1.43));
margin: 18rpx auto 0;
padding: 70rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.settings-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
.settings-panel > .app-loading {
grid-area: 1 / 1 / -1 / -1;
}
.settings-form {
position: absolute;
inset: 7.5% 8%;
min-width: 0;
}
.settings-form__eyebrow,
.settings-result__eyebrow {
@@ -260,34 +259,30 @@ const saveSettings = () => {
line-height: 1.55;
}
.settings-field {
position: relative;
height: 92rpx;
display: grid;
min-height: 92rpx;
margin-top: 17rpx;
}
.settings-field > image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.settings-field > text {
position: absolute;
top: 31rpx;
left: 24rpx;
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 {
position: absolute;
top: 0;
right: 20rpx;
bottom: 0;
left: 164rpx;
z-index: 1;
height: 92rpx;
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;
@@ -323,20 +318,19 @@ const saveSettings = () => {
margin-top: 10rpx;
}
.visibility-option {
position: relative;
display: grid;
width: calc(50% - 7rpx);
height: 64rpx;
min-height: 64rpx;
}
.visibility-option image {
position: absolute;
inset: 0;
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.visibility-option text {
position: relative;
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
@@ -358,36 +352,30 @@ const saveSettings = () => {
margin-top: 14rpx;
}
.settings-action {
position: relative;
width: 100%;
height: 78rpx;
margin-top: 19rpx;
}
.settings-action image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.settings-action text {
position: relative;
z-index: 1;
display: flex;
width: 100%;
min-height: 78rpx;
align-items: center;
justify-content: center;
height: 100%;
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 {
position: absolute;
top: 29%;
right: 12%;
left: 12%;
text-align: center;
}
.settings-state--success,
.settings-state--error,
.settings-state--no-permission {
padding: 180rpx 12% 70rpx;
}
.settings-result__eyebrow {
text-align: center;
}
@@ -411,15 +399,10 @@ const saveSettings = () => {
justify-content: center;
padding: 0 34rpx;
transform: translateX(-50%);
}
.settings-feedback image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")
center / contain no-repeat;
}
.settings-feedback text {
position: relative;
z-index: 1;
color: $ink;
font-size: 22rpx;
@@ -428,9 +411,5 @@ const saveSettings = () => {
.settings-panel {
width: calc(100% - 48rpx);
}
.settings-form {
right: 9%;
left: 9%;
}
}
</style>
+71 -95
View File
@@ -19,12 +19,6 @@
'poem-state--no-permission': poemState === 'no-permission',
}"
>
<image
class="poem-panel__skin"
src="/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png"
mode="scaleToFill"
/>
<AppLoading
v-if="poemState === 'loading'"
text="正在整理字辈诗"
@@ -43,10 +37,6 @@
class="poem-row"
:class="{ 'poem-row--current': item.current }"
>
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text class="poem-row__number"> {{ item.generationNo }} </text>
<text class="poem-row__character">{{ item.character }}</text>
<text class="poem-row__status">{{
@@ -69,14 +59,11 @@
>按照接口支持的连续文本录入每个汉字对应一代保存前可预览新字序</text
>
<view class="poem-field">
<image
src="/static/assets/modules/genealogy/transparent/g-form-field-frame.png"
mode="scaleToFill"
/>
<text>字辈内容</text>
<textarea
v-model="poemDraft"
maxlength="24"
auto-height
maxlength="50"
placeholder="例如:启宗敦本继世传芳"
placeholder-class="poem-placeholder"
@input="poemError = ''"
@@ -158,10 +145,6 @@
</view>
</view>
<view v-if="feedbackVisible" class="poem-feedback">
<image
src="/static/assets/foundation/transparent/a01-scroll-secondary-v3.png"
mode="aspectFit"
/>
<text>字辈预览已更新</text>
</view>
</view>
@@ -181,6 +164,8 @@ const feedbackVisible = ref(false);
let feedbackTimer = null;
const poemDraft = ref("启宗敦本继世传芳");
const stopMissingOldGeneration = ref(true);
const startGeneration = ref(12);
const currentGeneration = ref(14);
const poemRows = ref([
{ generationNo: 12, character: "启", current: false },
{ generationNo: 13, character: "宗", current: false },
@@ -193,6 +178,11 @@ const previewCharacters = computed(() =>
onLoad((query) => {
genealogyId.value = query.genealogyId || "";
startGeneration.value = Math.max(1, Number(query.startGeneration) || 12);
currentGeneration.value = Math.max(
startGeneration.value,
Number(query.currentGeneration) || 14,
);
poemState.value =
query.state === "loading"
? "loading"
@@ -223,13 +213,20 @@ const savePoems = () => {
poemError.value = "字辈保存失败,请稍后重试";
return;
}
poemRows.value = characters
.slice(0, 4)
.map((character, index) => ({
generationNo: 12 + index,
const preservedRows = stopMissingOldGeneration.value
? []
: poemRows.value.filter(
(item) => item.generationNo < startGeneration.value,
);
const nextRows = characters.map((character, index) => {
const generationNo = startGeneration.value + index;
return {
generationNo,
character,
current: index === 2,
}));
current: generationNo === currentGeneration.value,
};
});
poemRows.value = [...preservedRows, ...nextRows];
poemState.value = "list";
feedbackVisible.value = true;
feedbackTimer = setTimeout(() => {
@@ -241,33 +238,31 @@ const savePoems = () => {
<style scoped lang="scss">
.poem-page {
position: relative;
display: flex;
min-height: 100vh;
overflow-x: hidden;
flex-direction: column;
background: $paper;
}
.poem-page__header {
position: relative;
z-index: 3;
}
.poem-panel {
position: relative;
z-index: 2;
width: calc(100% - 32rpx);
height: min(650px, calc((100vw - 16px) * 1.5));
min-height: min(650px, calc((100vw - 16px) * 1.5));
margin: 18rpx auto 0;
padding: 76rpx 8%;
box-sizing: border-box;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
.poem-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
.poem-panel > .app-loading {
grid-area: 1 / 1 / -1 / -1;
}
.poem-list,
.poem-editor,
.poem-state-card {
position: absolute;
inset: 7.5% 8%;
min-width: 0;
}
.poem-list__eyebrow {
display: block;
@@ -294,39 +289,33 @@ const savePoems = () => {
margin-top: 18rpx;
}
.poem-row {
position: relative;
height: 72rpx;
display: grid;
min-height: 72rpx;
margin-top: 10rpx;
}
.poem-row image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
grid-template-columns: 48% auto 1fr auto;
align-items: center;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.poem-row__number {
position: absolute;
top: 24rpx;
left: 24rpx;
z-index: 1;
grid-column: 1;
margin-left: 24rpx;
color: $ink-muted;
font-size: 23rpx;
}
.poem-row__character {
position: absolute;
top: 14rpx;
left: 48%;
z-index: 1;
grid-column: 2;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 32rpx;
font-weight: 700;
}
.poem-row__status {
position: absolute;
top: 25rpx;
right: 22rpx;
z-index: 1;
grid-column: 4;
margin-right: 22rpx;
color: $ink-muted;
font-size: 22rpx;
}
@@ -335,21 +324,20 @@ const savePoems = () => {
color: $brand-red;
}
.poem-action {
position: relative;
display: grid;
width: 100%;
height: 76rpx;
min-height: 76rpx;
margin-top: 20rpx;
}
.poem-action image {
position: absolute;
inset: 0;
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.poem-action text {
position: relative;
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
@@ -359,34 +347,30 @@ const savePoems = () => {
letter-spacing: 2rpx;
}
.poem-field {
position: relative;
height: 118rpx;
display: grid;
min-height: 118rpx;
margin-top: 22rpx;
}
.poem-field image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/modules/genealogy/transparent/g-form-field-frame.png")
center / 100% 100% no-repeat;
}
.poem-field > text {
position: absolute;
top: 47rpx;
left: 24rpx;
z-index: 1;
grid-area: 1 / 1;
align-self: center;
margin-left: 24rpx;
color: $ink;
font-size: 22rpx;
font-weight: 700;
}
.poem-field textarea {
position: absolute;
top: 0;
right: 20rpx;
bottom: 0;
left: 164rpx;
z-index: 1;
grid-area: 1 / 1;
box-sizing: border-box;
height: 118rpx;
width: auto;
min-width: 0;
min-height: 118rpx;
margin-right: 20rpx;
margin-left: 164rpx;
padding-top: 34rpx;
color: $ink;
font-size: 22rpx;
@@ -415,20 +399,19 @@ const savePoems = () => {
font-weight: 700;
}
.poem-policy__option {
position: relative;
display: grid;
width: 248rpx;
height: 62rpx;
min-height: 62rpx;
}
.poem-policy__option image {
position: absolute;
inset: 0;
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.poem-policy__option text {
position: relative;
z-index: 1;
display: flex;
grid-area: 1 / 1;
align-items: center;
justify-content: center;
height: 100%;
@@ -459,9 +442,13 @@ const savePoems = () => {
color: $ink;
}
.poem-state-card {
top: 29%;
text-align: center;
}
.poem-state--empty,
.poem-state--error,
.poem-state--no-permission {
padding: 180rpx 4% 70rpx;
}
.poem-state-card .poem-list__eyebrow {
text-align: center;
}
@@ -485,15 +472,10 @@ const savePoems = () => {
justify-content: center;
padding: 0 34rpx;
transform: translateX(-50%);
}
.poem-feedback image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")
center / contain no-repeat;
}
.poem-feedback text {
position: relative;
z-index: 1;
color: $ink;
font-size: 22rpx;
@@ -502,11 +484,5 @@ const savePoems = () => {
.poem-panel {
width: calc(100% - 48rpx);
}
.poem-list,
.poem-editor,
.poem-state-card {
right: 9%;
left: 9%;
}
}
</style>