feat: migrate app routes and business modules

This commit is contained in:
2026-08-12 18:22:59 +08:00
parent 555aa00043
commit cc706378c2
247 changed files with 28623 additions and 14988 deletions
+932
View File
@@ -0,0 +1,932 @@
<template>
<view
class="edit-member-page"
:class="{
'edit-state--form': editState === 'form',
'edit-state--loading': editState === 'loading',
'edit-state--error': editState === 'error',
}"
>
<ModulePageBackground module="tree" />
<view class="edit-member-page__header"
><PageHeader title="编辑成员" custom-back @back="requestBack"
/></view>
<view class="edit-member-panel">
<AppLoading
v-if="editState === 'loading'"
text="正在读取成员资料"
description="请稍候,正在打开编辑资料。"
/>
<view v-else-if="editState === 'form'" class="edit-member-form">
<text class="form-eyebrow">成员档案维护</text>
<text class="form-title">完善{{ originalMember.name }}的生命记录</text>
<text class="form-copy"
>基础身份用于世系展示生平说明会显示在有权限查看的成员档案中</text
>
<view class="member-context">
<text>成员身份</text
><text
> {{ originalMember.generation }} ·
{{ originalMember.branch }}</text
>
</view>
<view class="form-field">
<text>姓名</text>
<input
v-model="editForm.name"
maxlength="20"
placeholder="请输入姓名"
placeholder-class="form-placeholder"
@input="clearError('name')"
/>
</view>
<text v-if="fieldErrors.name" class="field-error">{{
fieldErrors.name
}}</text>
<picker
:range="sexOptions.map((item) => item.label)"
:value="optionIndex(sexOptions, editForm.sex)"
@change="selectOption('sex', sexOptions, $event)"
>
<view class="form-field form-field--picker">
<text>性别</text
><text>{{
optionLabel(sexOptions, editForm.sex) || "请选择"
}}</text>
</view>
</picker>
<picker
:range="bindingModeOptions.map((item) => item.label)"
:value="optionIndex(bindingModeOptions, editForm.bindingMode)"
@change="selectBindingMode"
>
<view class="form-field form-field--picker">
<text>身份认领</text
><text>{{
optionLabel(bindingModeOptions, editForm.bindingMode)
}}</text>
</view>
</picker>
<text v-if="fieldErrors.bindingMode" class="field-error">{{
fieldErrors.bindingMode
}}</text>
<picker
v-if="editForm.bindingMode === 'SPECIFIED'"
:range="memberOptionLabels"
:value="memberOptionIndex(editForm.appUserId)"
:disabled="memberOptionsState !== 'ready' || !memberBindingOptions.length"
@change="selectBoundMember"
>
<view class="form-field form-field--picker">
<text><text class="required-mark">*</text>指定成员</text>
<text>{{ memberOptionLabel(editForm.appUserId) }}</text>
</view>
</picker>
<text
v-if="editForm.bindingMode === 'SPECIFIED' && memberOptionsState === 'loading'"
class="form-note"
>正在读取可绑定成员</text
>
<text
v-else-if="editForm.bindingMode === 'SPECIFIED' && memberOptionsState === 'error'"
class="field-error"
>可绑定成员暂不可用请稍后重试</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
>
</view>
</view>
<text v-if="avatarUploadError" class="field-error">{{
avatarUploadError
}}</text>
<view class="form-field">
<text>别名</text>
<input
v-model="editForm.aliasName"
placeholder="别名或曾用名"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>字辈</text>
<input
v-model="editForm.generationName"
maxlength="12"
placeholder="例如:文字辈"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>世代</text>
<input
v-model="editForm.generation"
type="number"
placeholder="正整数"
placeholder-class="form-placeholder"
/>
</view>
<picker
:range="personOptionLabels"
:value="personOptionIndex(editForm.fatherId)"
@change="selectPerson('fatherId', $event)"
>
<view class="form-field form-field--picker"
><text>父亲</text
><text>{{ personOptionLabel(editForm.fatherId) }}</text></view
>
</picker>
<picker
:range="personOptionLabels"
:value="personOptionIndex(editForm.motherId)"
@change="selectPerson('motherId', $event)"
>
<view class="form-field form-field--picker"
><text>母亲</text
><text>{{ personOptionLabel(editForm.motherId) }}</text></view
>
</picker>
<picker
mode="date"
:value="editForm.birthDate"
@change="selectDate('birthDate', $event)"
>
<view class="form-field form-field--picker"
><text>出生日期</text
><text>{{ editForm.birthDate || "未填写" }}</text></view
>
</picker>
<picker
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, editForm.birthLunar)"
@change="selectOption('birthLunar', lunarOptions, $event)"
>
<view class="form-field form-field--picker">
<text>出生农历</text
><text>{{
optionLabel(lunarOptions, editForm.birthLunar) || "请选择"
}}</text>
</view>
</picker>
<view class="form-field">
<text>出生地</text>
<input
v-model="editForm.birthPlace"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<picker
v-if="isDeceased"
mode="date"
:value="editForm.deathDate"
@change="selectDate('deathDate', $event)"
>
<view class="form-field form-field--picker"
><text>离世日期</text
><text>{{ editForm.deathDate || "在世或未填写" }}</text></view
>
</picker>
<picker
v-if="isDeceased"
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, editForm.deathLunar)"
@change="selectOption('deathLunar', lunarOptions, $event)"
>
<view class="form-field form-field--picker">
<text>逝世农历</text
><text>{{
optionLabel(lunarOptions, editForm.deathLunar) || "请选择"
}}</text>
</view>
</picker>
<view v-if="isDeceased" class="form-field">
<text>逝世地</text>
<input
v-model="editForm.deathPlace"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<view v-if="isDeceased" class="form-field">
<text>安葬地</text>
<input
v-model="editForm.burialPlace"
placeholder="按家谱记载填写"
placeholder-class="form-placeholder"
/>
</view>
<picker
:range="personStatusOptions.map((item) => item.label)"
:value="optionIndex(personStatusOptions, editForm.personStatus)"
@change="selectOption('personStatus', personStatusOptions, $event)"
>
<view class="form-field form-field--picker">
<text>人物状态</text
><text>{{
optionLabel(personStatusOptions, editForm.personStatus) ||
"请选择"
}}</text>
</view>
</picker>
<view class="form-field">
<text>排序值</text>
<input
v-model="editForm.sortOrder"
type="number"
placeholder="可选整数,值越小越靠前"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea
v-model="editForm.summary"
auto-height
maxlength="500"
placeholder="记录生平、迁徙或重要经历"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field form-field--summary">
<text>备注</text>
<textarea
v-model="editForm.remark"
auto-height
placeholder="记录其他需要保留的信息"
placeholder-class="form-placeholder"
/>
</view>
<text v-if="fieldErrors.dates" class="field-error">{{
fieldErrors.dates
}}</text>
<text class="form-note"
>私密信息仅你本人和可维护家谱的管理员可见</text
>
<view class="form-action" @click="saveMember">
<image
src="/static/assets/foundation/transparent/scroll-primary.png"
mode="scaleToFill"
/>
<text>{{ isSubmitting ? "正在保存…" : "保存资料" }}</text>
</view>
</view>
<view v-else class="edit-result">
<text class="form-eyebrow">{{ resultCopy.eyebrow }}</text>
<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/scroll-primary.png"
mode="scaleToFill"
/>
<text>{{ resultCopy.action }}</text>
</view>
</view>
</view>
<AppDialog
:visible="discardDialogVisible"
eyebrow="资料尚未保存"
title="要放弃本次修改吗"
message="返回后,本次对成员档案的修改不会保留。"
cancel-text="继续编辑"
confirm-text="放弃修改"
show-cancel
:close-on-mask="false"
@cancel="cancelDiscard"
@confirm="confirmDiscard"
/>
</view>
</template>
<script setup>
import { computed, reactive, ref } from "vue";
import { onBackPress, onLoad, onUnload } from "@dcloudio/uni-app";
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 {
createRequestController,
isRequestCancelled
} from "@/services/api/request-controller.js";
import { genealogyMemberApi } from "@/services/api/genealogy-member-service.js";
import { lineageApi } from "@/services/api/lineage-service.js";
import { getRequestErrorMessage } from "@/services/api/request-error-message.js";
import {
isImagePickCancelled,
pickAndUploadImage,
} from "@/utils/media-upload.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import {
finishPage,
goBack,
handleBackPress,
runBackGuard,
} from "@/utils/navigation/gateway.js";
import {
findMemberOptionIndex,
findMemberOptionLabel,
memberFormOptions,
updateMemberBindingMode,
updateMemberFormOption,
} from "@/utils/tree/member-form.js";
const editState = ref("loading");
const genealogyId = ref("");
const personId = ref("");
const isSubmitting = ref(false);
const isAvatarUploading = ref(false);
const avatarFileName = ref("");
const avatarUploadError = ref("");
const discardDialogVisible = ref(false);
const errorMessage = ref("");
const failedAction = ref("load");
const memberDetailRequestController = createRequestController();
const personOptionsRequestController = createRequestController();
const memberOptionsRequestController = createRequestController();
const avatarUploadRequestController = createRequestController();
const memberUpdateRequestController = createRequestController();
let pageActive = true;
let loadSequence = 0;
const originalMember = ref(null);
const baseline = ref("");
const committedMemberSnapshot = ref("");
const editForm = reactive({
appUserId: "",
bindingMode: "NONE",
name: "",
aliasName: "",
sex: "",
generation: "",
generationName: "",
fatherId: "",
motherId: "",
avatarOssId: "",
birthDate: "",
birthLunar: "",
birthPlace: "",
deathDate: "",
deathLunar: "",
deathPlace: "",
burialPlace: "",
personStatus: "",
summary: "",
remark: "",
sortOrder: "",
});
const fieldErrors = reactive({ name: "", dates: "", bindingMode: "" });
const sexOptions = memberFormOptions.sex;
const lunarOptions = memberFormOptions.lunar;
const personStatusOptions = memberFormOptions.personStatus;
const bindingModeOptions = memberFormOptions.bindingMode;
const personOptions = ref([{ label: "不选择", value: "" }]);
const personOptionLabels = computed(() =>
personOptions.value.map((item) => item.label),
);
const memberOptionsState = ref("loading");
const memberOptions = ref([]);
const memberBindingOptions = computed(() => {
const options = memberOptions.value.slice();
if (
editForm.bindingMode === "SPECIFIED" &&
editForm.appUserId &&
!options.some((item) => item.value === editForm.appUserId)
) {
options.unshift({ value: editForm.appUserId, label: "当前已绑定成员" });
}
return options;
});
const memberOptionLabels = computed(() =>
memberBindingOptions.value.map((item) => item.label),
);
const isDeceased = computed(() => editForm.personStatus === "1");
const formSnapshot = computed(() => JSON.stringify(editForm));
const hasValidContext = computed(() =>
Boolean(genealogyId.value && personId.value && originalMember.value),
);
const isDirty = computed(
() =>
editState.value === "form" &&
Boolean(baseline.value) &&
formSnapshot.value !== baseline.value,
);
const resultCopy = computed(
() =>
({
error:
failedAction.value === "navigate"
? {
eyebrow: "保存完成",
title: "成员档案已经保存",
copy:
errorMessage.value ||
"资料已保存,但页面暂时没有返回成员档案。",
action: "返回成员档案",
}
: failedAction.value === "save"
? {
eyebrow: "保存失败",
title: "成员档案尚未保存",
copy:
errorMessage.value || "资料尚未保存,请检查填写后重试。",
action: "返回修改",
}
: hasValidContext.value
? {
eyebrow: "资料读取失败",
title: "暂时无法读取成员档案",
copy: errorMessage.value || "请返回后重试。",
action: "重新读取",
}
: {
eyebrow: "暂时无法打开成员页面",
title: "没有找到要编辑的成员",
copy: errorMessage.value || "请从成员档案重新进入。",
action: "返回上一页",
},
})[editState.value] || {},
);
const discardConfirmation = createDiscardConfirmation((visible) => {
discardDialogVisible.value = visible;
});
const requestDiscardConfirmation = discardConfirmation.request;
const confirmDiscard = discardConfirmation.confirm;
const cancelDiscard = discardConfirmation.cancel;
const datePart = (value) => String(value || "").slice(0, 10);
const loadMember = async () => {
const activeLoad = ++loadSequence;
editState.value = "loading";
try {
const member = await lineageApi.getPerson(genealogyId.value, personId.value, {
requestController: memberDetailRequestController,
});
if (!pageActive || activeLoad !== loadSequence) return;
originalMember.value = member;
const deceased = member.personStatus === "1";
Object.assign(editForm, {
appUserId: member.appUserId || "",
bindingMode:
member.bindingMode || (member.appUserId ? "SPECIFIED" : "NONE"),
name: member.name,
aliasName: member.aliasName || "",
sex: member.sex || "",
generation: member.generation || "",
generationName: member.generationName || "",
fatherId:
member.relatives.find((item) => item.relation === "父亲")?.id || "",
motherId:
member.relatives.find((item) => item.relation === "母亲")?.id || "",
avatarOssId: member.avatarOssId || "",
birthDate: datePart(member.birthDate),
birthLunar: member.birthLunar || "",
birthPlace: member.birthplace || "",
deathDate: deceased ? datePart(member.deathDate) : "",
deathLunar: deceased ? member.deathLunar || "" : "",
deathPlace: deceased ? member.deathPlace || "" : "",
burialPlace: deceased ? member.burialPlace || "" : "",
personStatus: member.personStatus || "",
summary: member.biography || "",
remark: member.remark || "",
sortOrder: member.sortOrder ?? "",
});
baseline.value = formSnapshot.value;
committedMemberSnapshot.value = "";
errorMessage.value = "";
failedAction.value = "load";
editState.value = "form";
} catch (error) {
if (!pageActive || activeLoad !== loadSequence || isRequestCancelled(error)) return;
originalMember.value = null;
failedAction.value = "load";
errorMessage.value = getRequestErrorMessage(error, "成员资料暂不可用。");
editState.value = "error";
}
};
const loadPersonOptions = async () => {
try {
const rows = await lineageApi.getLineagePersonOptions(genealogyId.value, {
requestController: personOptionsRequestController,
});
if (!pageActive) return;
const options = rows
.map((item) => {
const personOptionId = item.id;
if (
!/^[1-9]\d*$/.test(personOptionId) ||
personOptionId === personId.value
) {
return null;
}
const name = item.name;
return { value: personOptionId, label: `${name}${personOptionId}` };
})
.filter(Boolean);
personOptions.value = [{ label: "不选择", value: "" }, ...options];
} catch (error) {
if (pageActive && !isRequestCancelled(error))
personOptions.value = [{ label: "暂无可选人物", value: "" }];
}
};
const loadMemberOptions = async () => {
memberOptionsState.value = "loading";
try {
const rows = await genealogyMemberApi.getMemberOptions(genealogyId.value, {
requestController: memberOptionsRequestController,
});
if (!pageActive) return;
memberOptions.value = rows
.filter((item) => item.eligible)
.map((item) => ({
value: item.appUserId,
label: item.relationName
? `${item.memberName} · ${item.relationName}`
: item.memberName,
}));
memberOptionsState.value = "ready";
} catch (error) {
if (!pageActive || isRequestCancelled(error)) return;
memberOptions.value = [];
memberOptionsState.value = "error";
}
};
onLoad((query) => {
genealogyId.value = String(query.genealogyId || "");
personId.value = String(query.personId || "");
if (!genealogyId.value || !personId.value) {
editState.value = "error";
errorMessage.value = "这个页面已经过期,请从成员档案重新进入。";
return;
}
void loadMember();
void loadPersonOptions();
void loadMemberOptions();
});
onUnload(() => {
pageActive = false;
loadSequence += 1;
memberDetailRequestController.abort();
personOptionsRequestController.abort();
memberOptionsRequestController.abort();
avatarUploadRequestController.abort();
memberUpdateRequestController.abort();
discardConfirmation.dispose();
});
const requestBack = () =>
runBackGuard({
transientOpen: discardDialogVisible.value,
dirty: isDirty.value,
submitting: isSubmitting.value || isAvatarUploading.value,
"close-transient": cancelDiscard,
"block-submitting": () => true,
"confirm-discard": requestDiscardConfirmation,
});
onBackPress((event) => handleBackPress(event, requestBack));
const clearError = (field) => {
fieldErrors[field] = "";
};
const optionIndex = findMemberOptionIndex;
const optionLabel = findMemberOptionLabel;
const selectOption = (field, options, event) => {
updateMemberFormOption(editForm, field, options, event);
};
const selectBindingMode = (event) => {
updateMemberBindingMode(editForm, event);
fieldErrors.bindingMode = "";
};
const memberOptionIndex = (value) =>
Math.max(
0,
memberBindingOptions.value.findIndex((item) => item.value === value),
);
const memberOptionLabel = (value) =>
memberBindingOptions.value.find((item) => item.value === value)?.label ||
(memberOptionsState.value === "loading"
? "正在读取"
: memberBindingOptions.value.length
? "请选择可绑定成员"
: "暂无可绑定成员");
const selectBoundMember = (event) => {
editForm.appUserId =
memberBindingOptions.value[Number(event.detail.value)]?.value || "";
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 selectPerson = (field, event) => {
editForm[field] =
personOptions.value[Number(event.detail.value)]?.value || "";
};
const uploadAvatar = async () => {
if (isAvatarUploading.value || isSubmitting.value) return;
isAvatarUploading.value = true;
avatarUploadError.value = "";
try {
const receipt = await pickAndUploadImage({
requestController: avatarUploadRequestController,
});
if (!pageActive) return;
editForm.avatarOssId = receipt.ossId;
avatarFileName.value = receipt.fileName || "头像图片";
} catch (error) {
if (!pageActive) return;
if (!isImagePickCancelled(error) && !isRequestCancelled(error))
avatarUploadError.value = getRequestErrorMessage(error, "头像上传失败,请稍后重试");
} finally {
if (pageActive) isAvatarUploading.value = false;
}
};
const selectDate = (field, event) => {
editForm[field] = event.detail.value || "";
fieldErrors.dates = "";
};
const validateEditForm = () => {
fieldErrors.name = editForm.name.trim() ? "" : "请填写成员姓名";
fieldErrors.dates =
editForm.birthDate &&
editForm.deathDate &&
editForm.deathDate < editForm.birthDate
? "离世日期不能早于出生日期"
: "";
fieldErrors.bindingMode =
editForm.bindingMode === "SPECIFIED" && !editForm.appUserId
? memberOptionsState.value === "error"
? "可绑定成员暂不可用,请稍后重试"
: "请选择可绑定成员"
: "";
return !fieldErrors.name && !fieldErrors.dates && !fieldErrors.bindingMode;
};
const saveMember = async () => {
if (isSubmitting.value || isAvatarUploading.value || !validateEditForm())
return;
isSubmitting.value = true;
try {
const currentSnapshot = formSnapshot.value;
if (committedMemberSnapshot.value !== currentSnapshot) {
await lineageApi.updatePerson(
genealogyId.value,
personId.value,
{
bindingMode: editForm.bindingMode,
...(editForm.bindingMode === "SPECIFIED"
? { appUserId: editForm.appUserId }
: {}),
name: editForm.name,
aliasName: editForm.aliasName,
sex: editForm.sex,
generation: editForm.generation
? Number(editForm.generation)
: undefined,
generationName: editForm.generationName,
fatherId: editForm.fatherId,
motherId: editForm.motherId,
avatarOssId: editForm.avatarOssId,
birthDate: editForm.birthDate,
birthLunar: editForm.birthLunar,
birthPlace: editForm.birthPlace,
...(isDeceased.value
? {
deathDate: editForm.deathDate,
deathLunar: editForm.deathLunar,
deathPlace: editForm.deathPlace,
burialPlace: editForm.burialPlace,
}
: {}),
personStatus: editForm.personStatus,
biography: editForm.summary,
remark: editForm.remark,
sortOrder: editForm.sortOrder,
},
{ requestController: memberUpdateRequestController },
);
if (!pageActive) return;
committedMemberSnapshot.value = currentSnapshot;
baseline.value = currentSnapshot;
}
await finishPage(
"T03",
{
genealogyId: genealogyId.value,
personId: personId.value,
},
{
operation: "member-updated",
entityId: personId.value,
refresh: true,
},
);
} catch (error) {
if (!pageActive || isRequestCancelled(error)) return;
const updateCommitted =
committedMemberSnapshot.value === formSnapshot.value;
failedAction.value = updateCommitted ? "navigate" : "save";
errorMessage.value = updateCommitted
? "资料已经保存,但页面返回失败。请重试返回成员档案。"
: getRequestErrorMessage(error, "资料尚未保存,请稍后重试。");
editState.value = "error";
} finally {
if (pageActive) isSubmitting.value = false;
}
};
const returnToMember = async () => {
const confirmed = isDirty.value ? await requestDiscardConfirmation() : true;
if (!confirmed) return false;
return goBack();
};
const handleResultAction = () => {
if (failedAction.value === "navigate") return saveMember();
if (failedAction.value === "save") {
editState.value = "form";
return;
}
if (editState.value === "error") {
if (!genealogyId.value || !personId.value) return returnToMember();
return loadMember();
}
editState.value = "form";
};
</script>
<style scoped lang="scss">
@import "../../styles/adaptive-frame-profiles.scss";
.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-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: clamp(14px, 22rpx, 17px);
letter-spacing: 3rpx;
}
.form-title {
display: block;
margin-top: 10rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: clamp(19px, 34rpx, 24px);
font-weight: 700;
line-height: 1.35;
}
.form-copy,
.form-note {
display: block;
margin-top: 10rpx;
color: $ink-muted;
font-size: clamp(15px, 24rpx, 18px);
line-height: 1.5;
}
.member-context,
.form-field {
@include 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: clamp(15px, 24rpx, 18px);
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: clamp(15px, 24rpx, 18px);
line-height: 1.45;
text-align: right;
}
.form-field textarea {
width: auto;
min-height: 54rpx;
text-align: left;
}
.form-field__hint,
.upload-receipt {
color: $ink-muted;
font-size: clamp(13px, 21rpx, 16px);
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: clamp(14px, 22rpx, 17px);
}
.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: clamp(14px, 22rpx, 17px);
line-height: max(1.35em, clamp(17px, 32rpx, 22px));
}
.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: clamp(15px, 24rpx, 18px);
font-weight: 700;
}
.edit-result {
margin-top: 30%;
text-align: center;
}
.edit-result .form-eyebrow,
.edit-result .form-copy {
text-align: center;
}
.edit-result .form-action {
width: 420rpx;
max-width: 100%;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 400px) {
.edit-member-panel {
width: calc(100% - 48rpx);
}
}
</style>