完成全项目响应式审核与换机交接

This commit is contained in:
2026-07-22 07:41:27 +08:00
parent a127280ce6
commit eced3d1e6e
77 changed files with 2052 additions and 378 deletions
+6 -1
View File
@@ -150,6 +150,10 @@
import { ref } from "vue";
import { onUnload } from "@dcloudio/uni-app";
import AuthPageShell from "@/components/AuthPageShell.vue";
import {
PASSWORD_POLICY_MESSAGE,
validatePassword,
} from "@/utils/validation.js";
const phone = ref("");
const password = ref("");
@@ -200,7 +204,8 @@ const clearFieldError = (field) => {
const validateForm = () => {
const nextErrors = { phone: "", password: "", confirmPassword: "" };
if (!/^1\d{10}$/.test(phone.value)) nextErrors.phone = "请输入正确手机号";
if (!password.value) nextErrors.password = "请设置密码";
const passwordResult = validatePassword(password.value);
if (!passwordResult.valid) nextErrors.password = PASSWORD_POLICY_MESSAGE;
if (!confirmPassword.value) nextErrors.confirmPassword = "请再次输入密码";
else if (password.value !== confirmPassword.value)
nextErrors.confirmPassword = "两次输入的密码不一致";
+6 -1
View File
@@ -179,6 +179,10 @@
import { ref } from "vue";
import { onUnload } from "@dcloudio/uni-app";
import AuthPageShell from "@/components/AuthPageShell.vue";
import {
PASSWORD_POLICY_MESSAGE,
validatePassword,
} from "@/utils/validation.js";
const phone = ref("");
const verificationCode = ref("");
@@ -237,7 +241,8 @@ const validateForm = () => {
if (!/^1\d{10}$/.test(phone.value)) nextErrors.phone = "请输入正确手机号";
if (!/^\d{6}$/.test(verificationCode.value))
nextErrors.verificationCode = "请输入 6 位验证码";
if (!password.value) nextErrors.password = "请设置新密码";
const passwordResult = validatePassword(password.value);
if (!passwordResult.valid) nextErrors.password = PASSWORD_POLICY_MESSAGE;
if (!confirmPassword.value) nextErrors.confirmPassword = "请再次输入新密码";
else if (password.value !== confirmPassword.value)
nextErrors.confirmPassword = "两次密码输入不一致";
+1 -1
View File
@@ -18,6 +18,7 @@
><view class="publish-field"
><textarea
v-model="content"
auto-height
maxlength="300"
placeholder="写下想对家人说的话"
placeholder-class="publish-placeholder"
@@ -95,7 +96,6 @@ onUnmounted(() => {
}
.publish-panel {
width: calc(100% - 32rpx);
min-height: min(640px, calc((100vw - 16px) * 1.48));
margin: 18rpx auto 0;
padding: 9%;
box-sizing: border-box;
+1
View File
@@ -87,6 +87,7 @@
>
<textarea
v-model="form.content"
auto-height
maxlength="1200"
placeholder="记录家训、往事或想留给后人的话"
placeholder-class="editor-placeholder"
+2
View File
@@ -118,6 +118,7 @@
</view>
<textarea
v-model="batchDescription"
auto-height
:disabled="isLocked"
maxlength="120"
placeholder="例如:2024 年春节全家团圆留影"
@@ -151,6 +152,7 @@
</view>
<textarea
:value="activePhoto.note"
auto-height
:disabled="isLocked"
maxlength="80"
placeholder="补充人物、时间或拍摄地点"
+4 -13
View File
@@ -14,11 +14,6 @@
</view>
<view class="video-status-card">
<image
class="video-status-card__skin"
src="/static/assets/modules/family/transparent/module-content-frame.png"
mode="scaleToFill"
/>
<view class="video-status-card__body">
<text class="video-status-card__eyebrow">视频 · 服务说明</text>
<text class="video-status-card__title">视频服务暂未开放</text>
@@ -45,6 +40,8 @@ const returnToFamily = () => {
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.video-status-page {
min-height: 100vh;
background: $paper;
@@ -74,19 +71,13 @@ const returnToFamily = () => {
height: 100%;
}
.video-status-card {
display: grid;
@include adaptive.adaptive-family-panel;
width: 100%;
min-height: 330rpx;
box-sizing: border-box;
text-align: center;
}
.video-status-card__skin {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.video-status-card__body {
z-index: 1;
grid-area: 1 / 1;
padding: 70rpx 60rpx 48rpx;
}
.video-status-card__eyebrow,
+8 -19
View File
@@ -340,7 +340,7 @@
<script setup>
import { computed, nextTick, ref } from "vue";
import { onBackPress, onLoad, onShow } from "@dcloudio/uni-app";
import { onBackPress, onLoad } from "@dcloudio/uni-app";
import AppLoading from "@/components/AppLoading.vue";
import AppTabbar from "@/components/AppTabbar.vue";
import AppButton from "@/components/AppButton.vue";
@@ -365,19 +365,12 @@ const selectedGenealogyId = ref(
const listScrollCommand = ref(0);
const currentListScrollTop = ref(0);
const readPresentationState = () => {
if (typeof location !== "undefined") {
const query = new URLSearchParams(location.hash.split("?")[1] || "");
return query.get("state") || "default";
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage?.options?.state || "default";
};
const syncEmptyStateFromRoute = () => {
const presentationState = readPresentationState();
const syncEmptyStateFromRoute = (query = {}) => {
const presentationState = ["empty", "loading", "error"].includes(
query?.state,
)
? query.state
: "default";
forceEmptyState.value = presentationState === "empty";
isLoading.value = presentationState === "loading";
hasError.value = presentationState === "error";
@@ -391,11 +384,7 @@ onLoad((query) => {
} else if (selectedGenealogyId.value) {
genealogyContext.setCurrentGenealogyId(selectedGenealogyId.value);
}
syncEmptyStateFromRoute();
});
onShow(() => {
syncEmptyStateFromRoute();
syncEmptyStateFromRoute(query);
});
const unreadCount = computed(
+7 -35
View File
@@ -235,8 +235,8 @@
</template>
<script setup>
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { computed, onUnmounted, reactive, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
@@ -287,48 +287,20 @@ const changeAncestorBirthDate = (event) => {
const isAncestorStep = computed(() => currentStep.value === "ancestor");
const getFlowQuery = () => {
if (typeof location !== "undefined") {
return new URLSearchParams(location.hash.split("?")[1] || "");
}
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
return currentPage?.options || {};
};
const syncFlowFromRoute = () => {
const query = getFlowQuery();
const isAncestorRoute = query.get
? query.get("step") === "ancestor"
: query.step === "ancestor";
const routeGenealogyId = query.get
? query.get("genealogyId")
: query.genealogyId;
const syncFlowFromRoute = (query = {}) => {
const isAncestorRoute = query?.step === "ancestor";
const routeGenealogyId = query?.genealogyId || "";
currentStep.value = isAncestorRoute ? "ancestor" : "create";
genealogyId.value =
routeGenealogyId || (isAncestorStep.value ? "local-created-genealogy" : "");
};
onLoad(() => {
syncFlowFromRoute();
});
onShow(() => {
syncFlowFromRoute();
});
onMounted(() => {
if (typeof window !== "undefined") {
window.addEventListener("hashchange", syncFlowFromRoute);
}
onLoad((query) => {
syncFlowFromRoute(query);
});
onUnmounted(() => {
if (submitTimer) clearTimeout(submitTimer);
if (typeof window !== "undefined") {
window.removeEventListener("hashchange", syncFlowFromRoute);
}
});
const goBack = () => {
+5 -18
View File
@@ -222,7 +222,7 @@
<script setup>
import { computed, ref } from "vue";
import { onLoad, onUnload, onShow } from "@dcloudio/uni-app";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import AppLoading from "@/components/AppLoading.vue";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
@@ -332,26 +332,13 @@ const resultFixtures = [
const inviteTarget = computed(() => resultFixtures[0]);
const syncModeFromRoute = () => {
let requestedMode = "search";
let requestedState = "";
if (typeof location !== "undefined") {
const routeParams = new URLSearchParams(location.hash.split("?")[1] || "");
requestedMode = routeParams.get("mode") || "search";
requestedState = routeParams.get("state") || "";
} else {
const pages = getCurrentPages();
const routeOptions = pages[pages.length - 1]?.options || {};
requestedMode = routeOptions.mode || "search";
requestedState = routeOptions.state || "";
}
mode.value = requestedMode === "invite" ? "invite" : "search";
if (mode.value === "search" && requestedState === "loading")
const syncModeFromRoute = (query = {}) => {
mode.value = query?.mode === "invite" ? "invite" : "search";
if (mode.value === "search" && query?.state === "loading")
searchState.value = "loading";
};
onLoad(syncModeFromRoute);
onShow(syncModeFromRoute);
onLoad((query) => syncModeFromRoute(query));
onUnload(() => {
if (searchTimer) clearTimeout(searchTimer);
});
+23 -6
View File
@@ -112,15 +112,23 @@
<view v-if="confirmation && !confirmation.approved" class="rejection-field">
<text>拒绝原因</text>
<textarea
id="g10-rejection-reason"
v-model="rejectionReason"
auto-height
maxlength="120"
placeholder="请说明需要补充或核实的信息"
@input="rejectionError = ''"
:aria-invalid="!!rejectionError"
aria-describedby="g10-rejection-error"
:focus="rejectionFocused"
@input="clearRejectionError"
/>
<text v-if="rejectionError" class="rejection-field__error">{{
rejectionError
}}</text>
<text
v-if="rejectionError"
id="g10-rejection-error"
class="rejection-field__error"
role="alert"
>{{ rejectionError }}</text
>
</view>
</AppDialog>
@@ -131,7 +139,7 @@
</template>
<script setup>
import { computed, ref } from "vue";
import { computed, nextTick, ref } from "vue";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import AppDialog from "@/components/AppDialog.vue";
import AppButton from "@/components/AppButton.vue";
@@ -165,6 +173,7 @@ const errorMessage = ref("");
const confirmation = ref(null);
const rejectionReason = ref("");
const rejectionError = ref("");
const rejectionFocused = ref(false);
const helpVisible = ref(false);
const feedbackVisible = ref(false);
const feedbackMessage = ref("");
@@ -225,12 +234,14 @@ onLoad(loadApplications);
const confirmAudit = (item, approved) => {
rejectionReason.value = "";
rejectionError.value = "";
rejectionFocused.value = false;
confirmation.value = { item, approved };
};
const closeDialog = () => {
confirmation.value = null;
rejectionReason.value = "";
rejectionError.value = "";
rejectionFocused.value = false;
helpVisible.value = false;
};
const showFeedback = (message) => {
@@ -242,11 +253,17 @@ const showFeedback = (message) => {
feedbackTimer = null;
}, 1800);
};
const applyAudit = () => {
const clearRejectionError = () => {
rejectionError.value = "";
};
const applyAudit = async () => {
const current = confirmation.value;
if (!current) return;
if (!current.approved && !rejectionReason.value.trim()) {
rejectionError.value = "请填写拒绝原因,方便申请人补充资料";
rejectionFocused.value = false;
await nextTick();
rejectionFocused.value = true;
return;
}
current.item.status = current.approved ? "APPROVED" : "REJECTED";
@@ -223,7 +223,6 @@ const saveSettings = () => {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(620px, calc((100vw - 16px) * 1.43));
margin: 18rpx auto 0;
padding: 70rpx 8%;
box-sizing: border-box;
-1
View File
@@ -251,7 +251,6 @@ const savePoems = () => {
@include adaptive.adaptive-genealogy-state-panel;
z-index: 2;
width: calc(100% - 32rpx);
min-height: min(650px, calc((100vw - 16px) * 1.5));
margin: 18rpx auto 0;
padding: 76rpx 8%;
box-sizing: border-box;
+10 -3
View File
@@ -27,6 +27,10 @@ import AppButton from "@/components/AppButton.vue";
import AppToast from "@/components/AppToast.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
PASSWORD_POLICY_MESSAGE,
validatePassword,
} from "@/utils/validation.js";
const passwordForm = reactive({ current: "", next: "", confirm: "" });
const passwordVisible = reactive({ current: false, next: false, confirm: false });
@@ -40,14 +44,17 @@ const passwordFields = [
const toastVisible = ref(false); const toastMessage = ref(""); let timer = null;
const showToast = (message) => { toastMessage.value = message; toastVisible.value = true; clearTimeout(timer); timer = setTimeout(() => (toastVisible.value = false), 1800); };
const togglePassword = (key) => { passwordVisible[key] = !passwordVisible[key]; };
const validatePassword = () => {
const validateForm = () => {
errors.current = passwordForm.current ? "" : "请输入当前密码";
errors.next = /^(?=.*[A-Za-z])(?=.*\d).{8,32}$/.test(passwordForm.next) ? "" : "新密码需为 8–32 位,并同时包含字母和数字";
const nextPassword = validatePassword(passwordForm.next);
errors.next = nextPassword.valid ? "" : PASSWORD_POLICY_MESSAGE;
if (!errors.next && passwordForm.next === passwordForm.current)
errors.next = "新密码不能与当前密码相同";
errors.confirm = !passwordForm.confirm ? "请再次输入新密码" : passwordForm.confirm !== passwordForm.next ? "两次输入的新密码不一致" : "";
return !Object.values(errors).some(Boolean);
};
const savePassword = () => {
if (!validatePassword() || passwordState.value === "saving") return;
if (!validateForm() || passwordState.value === "saving") return;
passwordState.value = "saving";
timer = setTimeout(() => { passwordState.value = "ready"; passwordForm.current = ""; passwordForm.next = ""; passwordForm.confirm = ""; showToast("密码已修改,请妥善保管新密码"); }, 500);
};
+9 -18
View File
@@ -92,6 +92,7 @@
</template>
<script setup>
import { onLoad } from "@dcloudio/uni-app";
import { computed, onUnmounted, ref } from "vue";
import AppButton from "@/components/AppButton.vue";
import AppToast from "@/components/AppToast.vue";
@@ -104,22 +105,7 @@ const people = [
{ id: 3, name: "汤文清", role: "青年代表", generation: 19 },
];
const query = (() => {
if (typeof location !== "undefined")
return Object.fromEntries(
new URLSearchParams(location.hash.split("?")[1] || ""),
);
const pages = getCurrentPages();
return pages[pages.length - 1]?.options || {};
})();
const peopleState = ref(
query.state === "empty"
? "empty"
: query.state === "error"
? "error"
: "ready",
);
const peopleState = ref("ready");
const keywordInput = ref("");
const keyword = ref("");
const toastVisible = ref(false);
@@ -157,6 +143,12 @@ const showCreateNotice = () => {
uni.navigateTo({ url: "/pages/records/r02-person-detail?mode=create" });
};
onLoad((query) => {
peopleState.value = ["empty", "error"].includes(query.state)
? query.state
: "ready";
});
onUnmounted(() => {
if (toastTimer) clearTimeout(toastTimer);
});
@@ -219,10 +211,9 @@ onUnmounted(() => {
@include adaptive.adaptive-records-person;
display: flex;
width: 100%;
height: clamp(92px, 190rpx, 108px);
min-height: clamp(92px, 190rpx, 108px);
align-items: center;
margin-top: 12px;
padding: 16% 10%;
}
.person-card:first-child {
margin-top: 0;
+69 -9
View File
@@ -20,6 +20,15 @@
<text>主支 · 1214 </text>
</view>
<view class="tree-toolbar__actions">
<text
v-if="treeState === 'tree' && treeHasDrifted && selected"
class="tree-recenter"
role="button"
aria-label="回到当前成员"
hover-class="tree-action--pressed"
@click="recenterSelectedMember"
>回到当前</text
>
<text @click="treeState = 'landscape'">阅读提示</text>
<text @click="toRelationship">关系维护</text>
</view>
@@ -51,18 +60,23 @@
</view>
</view>
<view v-if="treeState === 'tree'" class="lineage-pan-cue">
<text>同代分支可左右查看</text>
</view>
<scroll-view
class="tree-scroll"
:class="{ 'tree-scroll--lineage': treeState === 'tree' }"
scroll-x
:scroll-left="initialScrollLeft"
:scroll-left="treeState === 'tree' ? treeScrollLeft : 0"
:show-scrollbar="false"
:style="treeScrollStyle"
@scroll="handleTreeScroll"
>
<view
class="tree-canvas"
:class="{ 'tree-canvas--state': treeState !== 'tree' }"
:style="treeMetricsStyle"
:style="treeState === 'tree' ? treeMetricsStyle : undefined"
>
<AppLoading
v-if="treeState === 'loading'"
@@ -71,10 +85,6 @@
/>
<template v-else-if="treeState === 'tree'">
<view class="lineage-pan-cue">
<text>同代分支可左右查看</text>
</view>
<view
v-for="connector in lineageConnectors"
:key="connector.id"
@@ -85,6 +95,7 @@
<view
v-for="member in layoutMembers"
:key="member.id"
:id="`tree-member-${member.id}`"
class="member-node"
:class="{ 'member-node--selected': selected?.id === member.id }"
:style="nodeGridStyle(member)"
@@ -166,7 +177,7 @@
<script setup>
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { onLoad, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
@@ -176,7 +187,12 @@ import { genealogyContext } from "@/utils/genealogy-context.js";
const genealogyId = ref("");
const treeState = ref("loading");
const selected = ref(null);
const initialScrollLeft = ref(90);
const treeScrollLeft = ref(90);
const currentTreeScrollLeft = ref(90);
const centeredTreeScrollLeft = ref(90);
const treeHasDrifted = ref(false);
let ignoreTreeScroll = false;
let recenterTimer = null;
const GRID_UNIT = 5;
const NODE_HALF_HEIGHT = 47;
const MEMBER_GAP = 250;
@@ -409,6 +425,46 @@ onLoad((query) => {
treeState.value = "tree";
});
onUnload(() => {
if (recenterTimer) clearTimeout(recenterTimer);
});
const handleTreeScroll = (event) => {
if (ignoreTreeScroll || treeState.value !== "tree") return;
const scrollLeft = Number(event?.detail?.scrollLeft || 0);
currentTreeScrollLeft.value = scrollLeft;
treeHasDrifted.value = Math.abs(scrollLeft - centeredTreeScrollLeft.value) > 80;
};
const recenterSelectedMember = async () => {
if (!selected.value) return;
ignoreTreeScroll = true;
const [scrollRect, nodeRect] = await new Promise((resolve) => {
const query = uni.createSelectorQuery();
query.select(".tree-scroll").boundingClientRect();
query
.select(`#tree-member-${selected.value.id}`)
.boundingClientRect();
query.exec(resolve);
});
if (scrollRect && nodeRect) {
const centeredScrollLeft = Math.max(
0,
currentTreeScrollLeft.value +
nodeRect.left + nodeRect.width / 2 - (scrollRect.left + scrollRect.width / 2),
);
treeScrollLeft.value = centeredScrollLeft;
currentTreeScrollLeft.value = centeredScrollLeft;
centeredTreeScrollLeft.value = centeredScrollLeft;
}
treeHasDrifted.value = false;
if (recenterTimer) clearTimeout(recenterTimer);
recenterTimer = setTimeout(() => {
ignoreTreeScroll = false;
recenterTimer = null;
}, 320);
};
const nodeGridStyle = (member) => ({
gridColumn: `${member.x / 5 + 1}`,
gridRow: `${member.y / 5 + 1}`,
@@ -484,6 +540,9 @@ const toAddRelative = () =>
min-height: 0;
overflow-y: auto;
}
.tree-action--pressed {
opacity: 0.72;
}
.tree-stage--lineage {
display: grid;
grid-template-columns: 190rpx minmax(0, 1fr);
@@ -503,6 +562,7 @@ const toAddRelative = () =>
border-right: 1rpx solid rgba(143, 108, 63, 0.2);
}
.tree-scroll--lineage {
grid-area: 1 / 2;
min-width: 0;
}
.tree-canvas {
@@ -514,7 +574,7 @@ const toAddRelative = () =>
width: calc(100vw - 32rpx);
}
.lineage-pan-cue {
grid-area: 1 / 1 / -1 / -1;
grid-area: 1 / 2;
align-self: start;
justify-self: end;
z-index: 3;
+1 -1
View File
@@ -141,7 +141,7 @@ const toTree = () => uni.navigateBack();
.member-context { @include adaptive.adaptive-tree-field; width: calc(100% - 32rpx); margin: 18rpx auto 0; padding: 16rpx 24rpx; }
.member-context text { display: block; color: $ink-muted; font-size: 23rpx; line-height: 1.45; }
.member-context text:first-child { color: $ink; font-size: 26rpx; font-weight: 700; }
.member-panel { @include adaptive.adaptive-tree-panel; width: calc(100% - 32rpx); min-height: min(620px, calc((100vw - 16px) * 1.42)); margin: 16rpx auto 0; padding: 9% 8%; }
.member-panel { @include adaptive.adaptive-tree-panel; width: calc(100% - 32rpx); margin: 16rpx auto 0; padding: 9% 8%; }
.member-panel > .app-loading { margin-top: 30%; }
.member-heading { display: flex; align-items: center; gap: 20rpx; }
.member-heading__seal { display: flex; width: 74rpx; height: 74rpx; flex: 0 0 74rpx; align-items: center; justify-content: center; background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png") center / contain no-repeat; }
+1 -1
View File
@@ -224,7 +224,7 @@ const discardAndBack = () => {
.add-relative-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.add-relative-page__header { z-index: 3; }
.add-relative-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(680px, calc((100vw - 16px) * 1.5)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.add-relative-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
+1 -1
View File
@@ -173,7 +173,7 @@ const discardAndBack = () => {
.edit-member-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.edit-member-page__header { z-index: 3; }
.edit-member-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(690px, calc((100vw - 16px) * 1.52)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.edit-member-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }
+1 -1
View File
@@ -192,7 +192,7 @@ const handleResultAction = () => {
.relationship-page { display: flex; min-height: 100vh; flex-direction: column; background: $paper; }
.relationship-page__header { z-index: 3; }
.relationship-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); min-height: min(650px, calc((100vw - 16px) * 1.46)); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.relationship-panel { @include adaptive.adaptive-tree-panel; z-index: 2; width: calc(100% - 32rpx); margin: 18rpx auto 28rpx; padding: 7.5% 8%; }
.form-eyebrow { display: block; color: $brand-red; font-size: 22rpx; letter-spacing: 3rpx; }
.form-title { display: block; margin-top: 10rpx; color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 34rpx; font-weight: 700; line-height: 1.35; }
.form-copy, .form-note { display: block; margin-top: 10rpx; color: $ink-muted; font-size: 24rpx; line-height: 1.5; }