修改部分样式

This commit is contained in:
rain
2026-07-27 17:29:58 +08:00
parent 04287e6777
commit 1e9e25fe67
53 changed files with 11562 additions and 2617 deletions
+448 -111
View File
@@ -10,19 +10,33 @@
>
<ModulePageBackground module="tree" />
<view class="add-relative-page__header">
<PageHeader :title="isFirstMember ? '录入首位成员' : '新增亲属'" custom-back @back="requestBack" />
<PageHeader
:title="isFirstMember ? '录入首位成员' : '新增亲属'"
custom-back
@back="requestBack"
/>
</view>
<view class="add-relative-panel">
<AppLoading v-if="addState === 'loading'" text="正在读取成员资料" description="请稍候,正在确认当前人物。" />
<AppLoading
v-if="addState === 'loading'"
text="正在读取成员资料"
description="请稍候,正在确认当前人物。"
/>
<view v-else-if="addState === 'form'" class="add-relative-form">
<text class="form-eyebrow">{{ isFirstMember ? "建立世系起点" : "补全家族关系" }}</text>
<text class="form-eyebrow">{{
isFirstMember ? "建立世系起点" : "补全家族关系"
}}</text>
<text class="form-title">{{ formTitle }}</text>
<text class="form-copy">{{ formCopy }}</text>
<view v-if="!isFirstMember" class="member-context">
<text>当前成员</text><text>{{ currentMember.name }} · {{ currentMember.generation }} </text>
<text>当前成员</text
><text
>{{ currentMember.name }} ·
{{ currentMember.generation }} </text
>
</view>
<view class="form-field">
@@ -35,35 +49,66 @@
@input="clearError('name')"
/>
</view>
<text v-if="fieldErrors.name" class="field-error">{{ fieldErrors.name }}</text>
<text v-if="fieldErrors.name" class="field-error">{{
fieldErrors.name
}}</text>
<view class="form-field">
<text>人物编号</text>
<input v-model="addForm.personNo" maxlength="40" placeholder="不填则由服务端生成" placeholder-class="form-placeholder" />
<input
v-model="addForm.personNo"
maxlength="40"
placeholder="不填则由服务端生成"
placeholder-class="form-placeholder"
/>
</view>
<picker :range="sexOptions.map((item) => item.label)" :value="optionIndex(sexOptions, addForm.sex)" @change="selectOption('sex', sexOptions, $event)">
<picker
:range="sexOptions.map((item) => item.label)"
:value="optionIndex(sexOptions, addForm.sex)"
@change="selectOption('sex', sexOptions, $event)"
>
<view class="form-field form-field--picker">
<text>性别</text><text>{{ optionLabel(sexOptions, addForm.sex) || "请选择" }}</text>
<text>性别</text
><text>{{ optionLabel(sexOptions, addForm.sex) || "请选择" }}</text>
</view>
</picker>
<picker :range="bindingModeOptions.map((item) => item.label)" :value="optionIndex(bindingModeOptions, addForm.bindingMode)" @change="selectBindingMode">
<picker
:range="bindingModeOptions.map((item) => item.label)"
:value="optionIndex(bindingModeOptions, addForm.bindingMode)"
@change="selectBindingMode"
>
<view class="form-field form-field--picker">
<text>身份认领</text><text>{{ optionLabel(bindingModeOptions, addForm.bindingMode) }}</text>
<text>身份认领</text
><text>{{
optionLabel(bindingModeOptions, addForm.bindingMode)
}}</text>
</view>
</picker>
<view class="form-field">
<text>业务用户</text>
<text class="form-field__hint">{{ appUserBindingHint }}</text>
</view>
<text v-if="fieldErrors.bindingMode" class="field-error">{{ fieldErrors.bindingMode }}</text>
<text v-if="fieldErrors.bindingMode" class="field-error">{{
fieldErrors.bindingMode
}}</text>
<view class="form-field form-field--upload">
<text>头像</text>
<view>
<button class="upload-button" :disabled="isAvatarUploading || isSubmitting" @click="uploadAvatar">{{ isAvatarUploading ? "上传中" : "选择图片" }}</button>
<text v-if="avatarFileName" class="upload-receipt">已上传{{ avatarFileName }}</text>
<button
class="upload-button"
:disabled="isAvatarUploading || isSubmitting"
@click="uploadAvatar"
>
{{ isAvatarUploading ? "上传中…" : "选择图片" }}
</button>
<text v-if="avatarFileName" class="upload-receipt"
>已上传{{ avatarFileName }}</text
>
</view>
</view>
<text v-if="avatarUploadError" class="field-error">{{ avatarUploadError }}</text>
<text v-if="avatarUploadError" class="field-error">{{
avatarUploadError
}}</text>
<picker
v-if="!isFirstMember"
@@ -76,73 +121,162 @@
<text>{{ addForm.relation || "请选择亲属关系" }}</text>
</view>
</picker>
<text v-if="fieldErrors.relation" class="field-error">{{ fieldErrors.relation }}</text>
<text v-if="fieldErrors.relation" class="field-error">{{
fieldErrors.relation
}}</text>
<view class="form-field">
<text>别名</text>
<input v-model="addForm.aliasName" maxlength="40" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
<input
v-model="addForm.aliasName"
maxlength="40"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>字辈</text>
<input v-model="addForm.generationName" maxlength="12" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
<input
v-model="addForm.generationName"
maxlength="12"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>世代</text>
<input v-if="!isFirstMember" v-model="addForm.generation" type="number" placeholder="可选正整数" placeholder-class="form-placeholder" />
<input
v-if="!isFirstMember"
v-model="addForm.generation"
type="number"
placeholder="可选正整数"
placeholder-class="form-placeholder"
/>
<text v-else>首位成员固定为 1</text>
</view>
<picker :range="personOptionLabels" :value="personOptionIndex(addForm.fatherId)" @change="selectPerson('fatherId', $event)">
<view class="form-field form-field--picker"><text>父亲</text><text>{{ personOptionLabel(addForm.fatherId) }}</text></view>
<picker
:range="personOptionLabels"
:value="personOptionIndex(addForm.fatherId)"
@change="selectPerson('fatherId', $event)"
>
<view class="form-field form-field--picker"
><text>父亲</text
><text>{{ personOptionLabel(addForm.fatherId) }}</text></view
>
</picker>
<picker :range="personOptionLabels" :value="personOptionIndex(addForm.motherId)" @change="selectPerson('motherId', $event)">
<view class="form-field form-field--picker"><text>母亲</text><text>{{ personOptionLabel(addForm.motherId) }}</text></view>
<picker
:range="personOptionLabels"
:value="personOptionIndex(addForm.motherId)"
@change="selectPerson('motherId', $event)"
>
<view class="form-field form-field--picker"
><text>母亲</text
><text>{{ personOptionLabel(addForm.motherId) }}</text></view
>
</picker>
<picker mode="date" :value="addForm.birthDate" @change="selectBirthDate">
<picker
mode="date"
:value="addForm.birthDate"
@change="selectBirthDate"
>
<view class="form-field form-field--picker">
<text>出生日期</text><text>{{ addForm.birthDate || "请选择" }}</text>
<text>出生日期</text
><text>{{ addForm.birthDate || "请选择" }}</text>
</view>
</picker>
<picker :range="lunarOptions.map((item) => item.label)" :value="optionIndex(lunarOptions, addForm.birthLunar)" @change="selectOption('birthLunar', lunarOptions, $event)">
<picker
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, addForm.birthLunar)"
@change="selectOption('birthLunar', lunarOptions, $event)"
>
<view class="form-field form-field--picker">
<text>出生农历</text><text>{{ optionLabel(lunarOptions, addForm.birthLunar) || "请选择" }}</text>
<text>出生农历</text
><text>{{
optionLabel(lunarOptions, addForm.birthLunar) || "请选择"
}}</text>
</view>
</picker>
<view class="form-field">
<text>出生地</text>
<input v-model="addForm.birthPlace" maxlength="60" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
<input
v-model="addForm.birthPlace"
maxlength="60"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<picker mode="date" :value="addForm.deathDate" @change="selectDeathDate">
<picker
mode="date"
:value="addForm.deathDate"
@change="selectDeathDate"
>
<view class="form-field form-field--picker">
<text>逝世日期</text><text>{{ addForm.deathDate || "请选择" }}</text>
<text>逝世日期</text
><text>{{ addForm.deathDate || "请选择" }}</text>
</view>
</picker>
<picker :range="lunarOptions.map((item) => item.label)" :value="optionIndex(lunarOptions, addForm.deathLunar)" @change="selectOption('deathLunar', lunarOptions, $event)">
<picker
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, addForm.deathLunar)"
@change="selectOption('deathLunar', lunarOptions, $event)"
>
<view class="form-field form-field--picker">
<text>逝世农历</text><text>{{ optionLabel(lunarOptions, addForm.deathLunar) || "请选择" }}</text>
<text>逝世农历</text
><text>{{
optionLabel(lunarOptions, addForm.deathLunar) || "请选择"
}}</text>
</view>
</picker>
<view class="form-field">
<text>逝世地</text>
<input v-model="addForm.deathPlace" maxlength="60" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
<input
v-model="addForm.deathPlace"
maxlength="60"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>安葬地</text>
<input v-model="addForm.burialPlace" maxlength="60" placeholder="按家谱记载填写" placeholder-class="form-placeholder" />
<input
v-model="addForm.burialPlace"
maxlength="60"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<picker :range="personStatusOptions.map((item) => item.label)" :value="optionIndex(personStatusOptions, addForm.personStatus)" @change="selectOption('personStatus', personStatusOptions, $event)">
<picker
:range="personStatusOptions.map((item) => item.label)"
:value="optionIndex(personStatusOptions, addForm.personStatus)"
@change="selectOption('personStatus', personStatusOptions, $event)"
>
<view class="form-field form-field--picker">
<text>人物状态</text><text>{{ optionLabel(personStatusOptions, addForm.personStatus) || "请选择" }}</text>
<text>人物状态</text
><text>{{
optionLabel(personStatusOptions, addForm.personStatus) || "请选择"
}}</text>
</view>
</picker>
<view class="form-field">
<text>排序值</text>
<input v-model="addForm.sortOrder" type="number" placeholder="可选整数,值越小越靠前" placeholder-class="form-placeholder" />
<input
v-model="addForm.sortOrder"
type="number"
placeholder="可选整数,值越小越靠前"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>关系名称</text>
<input v-if="isFirstMember" v-model="addForm.relationName" maxlength="30" placeholder="可选关系显示名称" placeholder-class="form-placeholder" />
<input
v-if="isFirstMember"
v-model="addForm.relationName"
maxlength="30"
placeholder="可选关系显示名称"
placeholder-class="form-placeholder"
/>
<text v-else>{{ relationLabel }}</text>
</view>
@@ -169,7 +303,10 @@
<text class="form-note">{{ formNote }}</text>
<view class="form-action" @click="submitAdd">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="scaleToFill"
/>
<text>{{ isSubmitting ? "正在保存…" : "保存成员" }}</text>
</view>
</view>
@@ -179,7 +316,10 @@
<text class="form-title">{{ resultCopy.title }}</text>
<text class="form-copy">{{ resultCopy.copy }}</text>
<view class="form-action" @click="handleResultAction">
<image src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png" mode="scaleToFill" />
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="scaleToFill"
/>
<text>{{ resultCopy.action }}</text>
</view>
</view>
@@ -207,14 +347,17 @@ import AppLoading from "@/components/AppLoading.vue";
import AppDialog from "@/components/AppDialog.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import { appApi, createRequestController, isRequestCancelled } from "@/utils/api.js";
import { isImagePickCancelled, pickAndUploadImage } from "@/utils/resumable-image-upload.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import {
handleBackPress,
returnTo,
runBackGuard,
} from "@/utils/navigation.js";
appApi,
createRequestController,
isRequestCancelled,
} from "@/utils/api.js";
import {
isImagePickCancelled,
pickAndUploadImage,
} from "@/utils/resumable-image-upload.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import { handleBackPress, returnTo, runBackGuard } from "@/utils/navigation.js";
const addState = ref("loading");
const genealogyId = ref("");
@@ -240,7 +383,14 @@ const relationIntents = Object.freeze({
SON: { label: "儿子" },
DAUGHTER: { label: "女儿" },
});
const genericRelationTypes = Object.freeze(["FATHER", "MOTHER", "SPOUSE", "SIBLING", "SON", "DAUGHTER"]);
const genericRelationTypes = Object.freeze([
"FATHER",
"MOTHER",
"SPOUSE",
"SIBLING",
"SON",
"DAUGHTER",
]);
const sexOptions = Object.freeze([
{ label: "男", value: "0" },
{ label: "女", value: "1" },
@@ -261,7 +411,9 @@ const bindingModeOptions = Object.freeze([
{ label: "绑定指定用户", value: "SPECIFIED" },
]);
const personOptions = ref([{ label: "不选择", value: "" }]);
const personOptionLabels = computed(() => personOptions.value.map((item) => item.label));
const personOptionLabels = computed(() =>
personOptions.value.map((item) => item.label),
);
const addForm = reactive({
bindingMode: "NONE",
personNo: "",
@@ -290,15 +442,22 @@ const addForm = reactive({
const fieldErrors = reactive({ name: "", relation: "", bindingMode: "" });
const isFirstMember = computed(() => mode.value === "first");
const activeRelationIntent = computed(() => relationIntents[relationType.value] || null);
const activeRelationIntent = computed(
() => relationIntents[relationType.value] || null,
);
const relationOptions = computed(() => {
const types = activeRelationIntent.value ? [relationType.value] : genericRelationTypes;
const types = activeRelationIntent.value
? [relationType.value]
: genericRelationTypes;
return types.map((type) => relationIntents[type].label);
});
const relationLabel = computed(() => activeRelationIntent.value?.label || addForm.relation || "亲属关系");
const relationLabel = computed(
() => activeRelationIntent.value?.label || addForm.relation || "亲属关系",
);
const appUserBindingHint = computed(() => {
if (addForm.bindingMode === "NONE") return "不绑定账号,不提交业务用户 ID";
if (addForm.bindingMode === "SELF") return "由服务端从 Token 绑定当前登录账号,不提交业务用户 ID";
if (addForm.bindingMode === "SELF")
return "由服务端从 Token 绑定当前登录账号,不提交业务用户 ID";
return "需先选择指定业务用户;当前没有可用候选接口";
});
const hasValidContext = computed(
@@ -306,18 +465,20 @@ const hasValidContext = computed(
Boolean(genealogyId.value) &&
(isFirstMember.value ? !personId.value : Boolean(currentMember.value)),
);
const relationIndex = computed(() => Math.max(0, relationOptions.value.indexOf(addForm.relation)));
const relationIndex = computed(() =>
Math.max(0, relationOptions.value.indexOf(addForm.relation)),
);
const formTitle = computed(() =>
isFirstMember.value ? "录入家谱中的第一位成员" : `${currentMember.value?.name || "当前成员"}添加${relationLabel.value}`,
isFirstMember.value
? "录入家谱中的第一位成员"
: `${currentMember.value?.name || "当前成员"}添加${relationLabel.value}`,
);
const formCopy = computed(() =>
isFirstMember.value
? "首位成员将成为世系起点,后续可从此人继续补充配偶、子女和后代。"
: "先确认新成员与当前成员的关系,再填写可核实的身份信息。",
);
const formNote = computed(() =>
"红色 * 为必填项,其余内容可按家谱记载补充。",
);
const formNote = computed(() => "红色 * 为必填项,其余内容可按家谱记载补充。");
const resultCopy = computed(() => {
if (hasValidContext.value) {
return {
@@ -335,7 +496,9 @@ const resultCopy = computed(() => {
};
});
const hasDraft = computed(() =>
Object.entries(addForm).some(([field, value]) => field !== "bindingMode" && String(value).trim()),
Object.entries(addForm).some(
([field, value]) => field !== "bindingMode" && String(value).trim(),
),
);
const discardConfirmation = createDiscardConfirmation((visible) => {
discardDialogVisible.value = visible;
@@ -363,18 +526,23 @@ const loadCurrentMember = async () => {
};
const loadPersonOptions = async () => {
try {
const rows = await appApi.getLineagePersonOptions(genealogyId.value, { requestController: personOptionsRequestController });
const rows = await appApi.getLineagePersonOptions(genealogyId.value, {
requestController: personOptionsRequestController,
});
const options = rows
.map((item) => {
const value = String(item?.personId ?? item?.id ?? "");
if (!/^[1-9]\d*$/.test(value)) return null;
const name = String(item?.name || item?.personName || item?.personNo || "未命名人物");
const name = String(
item?.name || item?.personName || item?.personNo || "未命名人物",
);
return { value, label: `${name}${value}` };
})
.filter(Boolean);
personOptions.value = [{ label: "不选择", value: "" }, ...options];
} catch (error) {
if (!isRequestCancelled(error)) personOptions.value = [{ label: "暂无可选人物", value: "" }];
if (!isRequestCancelled(error))
personOptions.value = [{ label: "暂无可选人物", value: "" }];
}
};
onLoad((query) => {
@@ -382,7 +550,11 @@ onLoad((query) => {
personId.value = String(query.personId || "");
mode.value = query.mode === "first" ? "first" : "relative";
relationType.value = String(query.relationType || "");
if (!genealogyId.value || query.state === "error" || (!isFirstMember.value && !personId.value)) {
if (
!genealogyId.value ||
query.state === "error" ||
(!isFirstMember.value && !personId.value)
) {
addState.value = "error";
errorMessage.value = "当前家谱或成员上下文无效,请从世系树重新进入。";
return;
@@ -421,22 +593,35 @@ const requestBack = () =>
onBackPress((event) => handleBackPress(event, requestBack));
const clearError = (field) => { fieldErrors[field] = ""; };
const clearError = (field) => {
fieldErrors[field] = "";
};
const selectRelation = (event) => {
addForm.relation = relationOptions.value[Number(event.detail.value)] || "";
clearError("relation");
};
const optionIndex = (options, value) => Math.max(0, options.findIndex((item) => item.value === value));
const optionLabel = (options, value) => options.find((item) => item.value === value)?.label || "";
const optionIndex = (options, value) =>
Math.max(
0,
options.findIndex((item) => item.value === value),
);
const optionLabel = (options, value) =>
options.find((item) => item.value === value)?.label || "";
const selectOption = (field, options, event) => {
addForm[field] = options[Number(event.detail.value)]?.value || "";
};
const selectBindingMode = (event) => {
addForm.bindingMode = bindingModeOptions[Number(event.detail.value)]?.value || "NONE";
addForm.bindingMode =
bindingModeOptions[Number(event.detail.value)]?.value || "NONE";
fieldErrors.bindingMode = "";
};
const personOptionIndex = (value) => Math.max(0, personOptions.value.findIndex((item) => item.value === value));
const personOptionLabel = (value) => personOptions.value.find((item) => item.value === value)?.label || "不选择";
const personOptionIndex = (value) =>
Math.max(
0,
personOptions.value.findIndex((item) => item.value === value),
);
const personOptionLabel = (value) =>
personOptions.value.find((item) => item.value === value)?.label || "不选择";
const selectPerson = (field, event) => {
addForm[field] = personOptions.value[Number(event.detail.value)]?.value || "";
};
@@ -445,27 +630,37 @@ const uploadAvatar = async () => {
isAvatarUploading.value = true;
avatarUploadError.value = "";
try {
const receipt = await pickAndUploadImage({ requestController: addRequestController });
const receipt = await pickAndUploadImage({
requestController: addRequestController,
});
addForm.avatarOssId = receipt.ossId;
avatarFileName.value = receipt.fileName || "头像图片";
} catch (error) {
if (!isImagePickCancelled(error) && !isRequestCancelled(error)) avatarUploadError.value = error?.message || "头像上传失败,请稍后重试";
if (!isImagePickCancelled(error) && !isRequestCancelled(error))
avatarUploadError.value = error?.message || "头像上传失败,请稍后重试";
} finally {
isAvatarUploading.value = false;
}
};
const selectBirthDate = (event) => { addForm.birthDate = event.detail.value || ""; };
const selectDeathDate = (event) => { addForm.deathDate = event.detail.value || ""; };
const selectBirthDate = (event) => {
addForm.birthDate = event.detail.value || "";
};
const selectDeathDate = (event) => {
addForm.deathDate = event.detail.value || "";
};
const validateAddForm = () => {
fieldErrors.name = addForm.name.trim() ? "" : "请填写成员姓名";
fieldErrors.relation = isFirstMember.value || addForm.relation ? "" : "请选择与当前成员的关系";
fieldErrors.bindingMode = addForm.bindingMode === "SPECIFIED"
? "当前未提供可选业务用户,不能指定认领"
: "";
fieldErrors.relation =
isFirstMember.value || addForm.relation ? "" : "请选择与当前成员的关系";
fieldErrors.bindingMode =
addForm.bindingMode === "SPECIFIED"
? "当前未提供可选业务用户,不能指定认领"
: "";
return !fieldErrors.name && !fieldErrors.relation && !fieldErrors.bindingMode;
};
const submitAdd = async () => {
if (isSubmitting.value || isAvatarUploading.value || !validateAddForm()) return;
if (isSubmitting.value || isAvatarUploading.value || !validateAddForm())
return;
isSubmitting.value = true;
try {
const payload = {
@@ -492,17 +687,27 @@ const submitAdd = async () => {
...(isFirstMember.value
? { generation: 1, relationName: addForm.relationName }
: {
...(addForm.generation ? { generation: Number(addForm.generation) } : {}),
relationName: relationLabel.value,
}),
...(addForm.generation
? { generation: Number(addForm.generation) }
: {}),
relationName: relationLabel.value,
}),
};
if (isFirstMember.value) {
await appApi.createPerson(genealogyId.value, payload, { requestController: addRequestController });
await appApi.createPerson(genealogyId.value, payload, {
requestController: addRequestController,
});
} else {
const type = activeRelationIntent.value
? relationType.value
: genericRelationTypes[relationOptions.value.indexOf(addForm.relation)];
await appApi.createRelatedPerson(genealogyId.value, personId.value, type, payload, { requestController: addRequestController });
await appApi.createRelatedPerson(
genealogyId.value,
personId.value,
type,
payload,
{ requestController: addRequestController },
);
}
Object.assign(addForm, {
bindingMode: "NONE",
@@ -539,11 +744,10 @@ const submitAdd = async () => {
isSubmitting.value = false;
}
};
const handleResultAction = () => hasValidContext.value ? (addState.value = "form") : returnToTree();
const handleResultAction = () =>
hasValidContext.value ? (addState.value = "form") : returnToTree();
const returnToTree = async () => {
const confirmed = hasDraft.value
? await requestDiscardConfirmation()
: true;
const confirmed = hasDraft.value ? await requestDiscardConfirmation() : true;
if (!confirmed) return false;
return returnTo("T01", { genealogyId: genealogyId.value });
};
@@ -552,29 +756,162 @@ const returnToTree = async () => {
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.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); 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; }
.member-context, .form-field { @include adaptive.adaptive-tree-field; display: grid; grid-template-columns: auto minmax(0, 1fr); min-height: 78rpx; align-items: center; gap: 20rpx; margin-top: 14rpx; padding: 12rpx 22rpx; }
.member-context text:first-child, .form-field > text:first-child { color: $ink; font-size: 24rpx; font-weight: 700; }
.member-context text:last-child, .form-field > text:last-child, .form-field input, .form-field textarea { min-width: 0; color: $ink; font-size: 24rpx; line-height: 1.45; text-align: right; }
.required-mark { display: inline-block; margin-right: 4rpx; color: $brand-red; font-size: 22rpx; line-height: 1; transform: translateY(-2rpx); }
.form-field textarea { width: auto; min-height: 54rpx; text-align: left; }
.form-field__hint, .upload-receipt { color: $ink-muted; font-size: 21rpx; line-height: 1.4; text-align: right; }
.form-field--upload > view { display: grid; justify-items: end; gap: 6rpx; }
.upload-button { min-height: 54rpx; margin: 0; padding: 0 16rpx; border: 1rpx solid rgba(159, 23, 15, .42); border-radius: 8rpx; background: transparent; color: $brand-red; font-size: 22rpx; }
.form-field--summary { align-items: start; }
.form-placeholder { color: #a79884; }
.field-error { display: block; margin: 5rpx 18rpx 0; color: $brand-red; font-size: 22rpx; line-height: 32rpx; }
.form-note { text-align: center; }
.form-action { display: grid; width: 100%; min-height: 76rpx; margin-top: 18rpx; }
.form-action image, .form-action text { grid-area: 1 / 1; width: 100%; height: 100%; }
.form-action text { z-index: 1; display: flex; align-items: center; justify-content: center; color: #fff9ed; font-size: 24rpx; font-weight: 700; }
.add-result { margin-top: 30%; text-align: center; }
.add-result .form-eyebrow, .add-result .form-copy { text-align: center; }
.add-result .form-action { width: 420rpx; max-width: 100%; margin-right: auto; margin-left: auto; }
@media (min-width: 400px) { .add-relative-panel { width: calc(100% - 48rpx); } }
.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);
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;
}
.member-context,
.form-field {
@include adaptive.adaptive-tree-field;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
min-height: 78rpx;
align-items: center;
gap: 20rpx;
margin-top: 14rpx;
padding: 12rpx 22rpx;
}
.member-context text:first-child,
.form-field > text:first-child {
color: $ink;
font-size: 24rpx;
font-weight: 700;
}
.member-context text:last-child,
.form-field > text:last-child,
.form-field input,
.form-field textarea {
min-width: 0;
color: $ink;
font-size: 24rpx;
line-height: 1.45;
text-align: right;
}
.required-mark {
display: inline-block;
margin-right: 4rpx;
color: $brand-red;
font-size: 22rpx;
line-height: 1;
transform: translateY(-2rpx);
}
.form-field textarea {
width: auto;
min-height: 54rpx;
text-align: left;
}
.form-field__hint,
.upload-receipt {
color: $ink-muted;
font-size: 21rpx;
line-height: 1.4;
text-align: right;
}
.form-field--upload > view {
display: grid;
justify-items: end;
gap: 6rpx;
}
.upload-button {
min-height: 54rpx;
margin: 0;
padding: 0 16rpx;
border: 1rpx solid rgba(159, 23, 15, 0.42);
border-radius: 8rpx;
background: transparent;
color: $brand-red;
font-size: 22rpx;
}
.form-field--summary {
align-items: start;
min-height: 200rpx;
}
.form-placeholder {
color: #a79884;
}
.field-error {
display: block;
margin: 5rpx 18rpx 0;
color: $brand-red;
font-size: 22rpx;
line-height: 32rpx;
}
.form-note {
text-align: center;
}
.form-action {
display: grid;
width: 100%;
min-height: 76rpx;
margin-top: 18rpx;
}
.form-action image,
.form-action text {
grid-area: 1 / 1;
width: 100%;
height: 100%;
}
.form-action text {
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
color: #fff9ed;
font-size: 24rpx;
font-weight: 700;
}
.add-result {
margin-top: 30%;
text-align: center;
}
.add-result .form-eyebrow,
.add-result .form-copy {
text-align: center;
}
.add-result .form-action {
width: 420rpx;
max-width: 100%;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 400px) {
.add-relative-panel {
width: calc(100% - 48rpx);
}
}
</style>