feat: migrate app routes and business modules
This commit is contained in:
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<view class="member-page">
|
||||
<GenealogyPageBackground />
|
||||
<view class="page-header">
|
||||
<PageHeader title="家谱成员" custom-back @back="requestBack" />
|
||||
</view>
|
||||
|
||||
<view class="page-content">
|
||||
<view v-if="!hasValidGenealogyId" class="state-card">
|
||||
<text>暂时无法打开成员页面</text>
|
||||
<text>没有找到家谱信息,请返回家谱总览后重新进入。</text>
|
||||
<AppButton block label="返回家谱总览" @click="returnToOverview" />
|
||||
</view>
|
||||
<view v-else-if="memberListState === 'loading'" class="state-card">
|
||||
<AppLoading text="正在读取家谱成员" description="请稍候,正在同步成员和管理权限。" />
|
||||
</view>
|
||||
<view v-else-if="memberListState === 'error'" class="state-card">
|
||||
<text>暂时无法读取成员</text>
|
||||
<text>{{ memberListError || "请检查网络后重试。" }}</text>
|
||||
<AppButton block type="secondary" label="重新加载" @click="loadMembers" />
|
||||
</view>
|
||||
<view v-else-if="memberListState === 'empty'" class="state-card">
|
||||
<text>{{ hasLeftGenealogy ? "已退出这部家谱" : "还没有成员记录" }}</text>
|
||||
<text>{{ hasLeftGenealogy ? "你的退出操作已经完成。" : "家人加入后,会显示在这里。" }}</text>
|
||||
<AppButton
|
||||
block
|
||||
:label="hasLeftGenealogy ? '返回我的家谱' : '返回家谱总览'"
|
||||
@click="returnToOverview"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="member-list">
|
||||
<view class="member-intro">
|
||||
<text>共 {{ members.length }} 位成员</text>
|
||||
<text>这里管理的是已加入家谱的账号成员;家谱中的人物资料请到“世系树”维护。</text>
|
||||
</view>
|
||||
<text v-if="feedbackMessage" class="page-feedback" role="status">
|
||||
{{ feedbackMessage }}
|
||||
</text>
|
||||
|
||||
<view
|
||||
v-for="member in members"
|
||||
:key="member.memberId"
|
||||
class="member-card"
|
||||
>
|
||||
<view class="member-card__heading">
|
||||
<view class="member-card__identity">
|
||||
<text>{{ member.memberName }}</text>
|
||||
<text
|
||||
v-if="member.appUserNickName && member.appUserNickName !== member.memberName"
|
||||
>
|
||||
账号昵称:{{ member.appUserNickName }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="role-badge">{{ roleLabel(member.roleType) }}</text>
|
||||
</view>
|
||||
<view class="member-card__details">
|
||||
<text>与家谱关系:{{ member.relationName || "未填写" }}</text>
|
||||
<text>世系人物:{{ lineageLabel(member) }}</text>
|
||||
<text v-if="member.joinTime">
|
||||
加入时间:{{ formatMinuteTimestamp(member.joinTime) }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="hasMemberActions(member)" class="member-card__actions">
|
||||
<AppButton
|
||||
v-if="member.capabilities.canEdit"
|
||||
compact
|
||||
type="secondary"
|
||||
label="编辑资料"
|
||||
@click="openMemberEditor(member)"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="member.capabilities.canEdit && member.lineagePersonId"
|
||||
compact
|
||||
type="secondary"
|
||||
label="解除人物绑定"
|
||||
@click="openConfirmation('unlink', member)"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="member.capabilities.canTransferOwner"
|
||||
compact
|
||||
type="secondary"
|
||||
label="转让谱主"
|
||||
@click="openConfirmation('transfer', member)"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="member.capabilities.canRemove"
|
||||
compact
|
||||
type="secondary"
|
||||
label="移出家谱"
|
||||
@click="openConfirmation('remove', member)"
|
||||
/>
|
||||
<AppButton
|
||||
v-if="member.capabilities.canLeave"
|
||||
compact
|
||||
type="secondary"
|
||||
label="退出家谱"
|
||||
@click="openConfirmation('leave', member)"
|
||||
/>
|
||||
</view>
|
||||
<text v-else class="member-card__readonly">当前账号只能查看这位成员。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<AppDialog
|
||||
:visible="Boolean(editTarget)"
|
||||
eyebrow="成员资料"
|
||||
:title="`编辑${editTarget?.memberName || '成员'}`"
|
||||
message="称呼和关系需要填写;角色、世系人物请从已有选项中选择。"
|
||||
:confirm-text="operationPending ? '正在保存…' : '保存修改'"
|
||||
cancel-text="取消"
|
||||
show-cancel
|
||||
:close-on-mask="!operationPending"
|
||||
@confirm="saveEdit"
|
||||
@cancel="closeEdit"
|
||||
>
|
||||
<view class="edit-form">
|
||||
<label class="edit-field">
|
||||
<text>成员称呼</text>
|
||||
<input v-model.trim="editForm.memberName" maxlength="50" placeholder="例如:王叔叔" />
|
||||
</label>
|
||||
<label class="edit-field">
|
||||
<text>与家谱关系</text>
|
||||
<input v-model.trim="editForm.relationName" maxlength="100" placeholder="例如:本族成员" />
|
||||
</label>
|
||||
<picker
|
||||
:disabled="editTarget?.roleType === GENEALOGY_MEMBER_ROLE.OWNER"
|
||||
:range="roleOptions.map((roleOption) => roleOption.label)"
|
||||
:value="roleIndex"
|
||||
@change="selectRole"
|
||||
>
|
||||
<view class="edit-field edit-field--picker">
|
||||
<text>成员角色</text><text>{{ roleLabel(editForm.roleType) }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<picker :disabled="personOptionsState === 'loading'" :range="personOptionLabels" :value="personOptionIndex" @change="selectPerson">
|
||||
<view class="edit-field edit-field--picker">
|
||||
<text>绑定世系人物</text>
|
||||
<text>{{ selectedPersonLabel }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text v-if="personOptionsState === 'loading'" class="edit-hint">正在读取世系人物…</text>
|
||||
<text v-else-if="personOptionsState === 'error'" class="edit-error">人物选项暂时无法读取,本次可先修改其他资料。</text>
|
||||
<text v-if="editError" class="edit-error" role="alert">{{ editError }}</text>
|
||||
</view>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog
|
||||
:visible="Boolean(confirmTarget)"
|
||||
eyebrow="请确认"
|
||||
:title="confirmationCopy.title"
|
||||
:message="confirmationCopy.message"
|
||||
:confirm-text="operationPending ? '正在处理…' : confirmationCopy.confirm"
|
||||
cancel-text="取消"
|
||||
show-cancel
|
||||
:close-on-mask="!operationPending"
|
||||
@confirm="runConfirmedOperation"
|
||||
@cancel="closeConfirmation"
|
||||
>
|
||||
<text v-if="operationError" class="edit-error" role="alert">{{ operationError }}</text>
|
||||
</AppDialog>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { onBackPress, onLoad, onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import AppButton from "@/components/AppButton.vue";
|
||||
import AppDialog from "@/components/AppDialog.vue";
|
||||
import AppLoading from "@/components/AppLoading.vue";
|
||||
import GenealogyPageBackground from "@/components/genealogy/PageBackground.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import {
|
||||
GENEALOGY_MEMBER_ROLE,
|
||||
GENEALOGY_MEMBER_ROLE_LABELS
|
||||
} from "@/services/api/genealogy-member-contract.js";
|
||||
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 { formatMinuteTimestamp } from "@/utils/display-time.js";
|
||||
import { handleBackPress, returnTo, runBackGuard } from "@/utils/navigation/gateway.js";
|
||||
|
||||
const genealogyId = ref("");
|
||||
const members = ref([]);
|
||||
const memberListState = ref("loading");
|
||||
const memberListError = ref("");
|
||||
const feedbackMessage = ref("");
|
||||
const editTarget = ref(null);
|
||||
const confirmTarget = ref(null);
|
||||
const editError = ref("");
|
||||
const operationError = ref("");
|
||||
const operationPending = ref(false);
|
||||
const hasLeftGenealogy = ref(false);
|
||||
const personOptions = ref([]);
|
||||
const personOptionsState = ref("idle");
|
||||
const memberListController = createRequestController();
|
||||
const personOptionsController = createRequestController();
|
||||
const memberUpdateController = createRequestController();
|
||||
const membershipOperationController = createRequestController();
|
||||
let isPageActive = true;
|
||||
|
||||
const editForm = reactive({
|
||||
memberName: "",
|
||||
relationName: "",
|
||||
roleType: GENEALOGY_MEMBER_ROLE.MEMBER,
|
||||
lineagePersonId: "",
|
||||
});
|
||||
const hasValidGenealogyId = computed(() => /^[1-9]\d*$/.test(genealogyId.value));
|
||||
const isOwnerViewer = computed(() =>
|
||||
members.value.some((member) => member.capabilities.canTransferOwner),
|
||||
);
|
||||
const roleOptions = computed(() => {
|
||||
const options = [
|
||||
...(isOwnerViewer.value
|
||||
? [{ label: GENEALOGY_MEMBER_ROLE_LABELS[GENEALOGY_MEMBER_ROLE.ADMIN], value: GENEALOGY_MEMBER_ROLE.ADMIN }]
|
||||
: []),
|
||||
{ label: GENEALOGY_MEMBER_ROLE_LABELS[GENEALOGY_MEMBER_ROLE.EDITOR], value: GENEALOGY_MEMBER_ROLE.EDITOR },
|
||||
{ label: GENEALOGY_MEMBER_ROLE_LABELS[GENEALOGY_MEMBER_ROLE.MEMBER], value: GENEALOGY_MEMBER_ROLE.MEMBER },
|
||||
];
|
||||
const currentRole = editTarget.value?.roleType;
|
||||
if (currentRole && !options.some((option) => option.value === currentRole)) {
|
||||
options.unshift({
|
||||
label: GENEALOGY_MEMBER_ROLE_LABELS[currentRole] || "当前角色",
|
||||
value: currentRole,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
});
|
||||
const roleIndex = computed(() =>
|
||||
Math.max(
|
||||
0,
|
||||
roleOptions.value.findIndex(
|
||||
(roleOption) => roleOption.value === editForm.roleType,
|
||||
),
|
||||
),
|
||||
);
|
||||
const personOptionLabels = computed(() => [
|
||||
"保持当前绑定",
|
||||
...personOptions.value.map(
|
||||
(person) =>
|
||||
`${person.name}${person.generation ? ` · 第${person.generation}世` : ""}`,
|
||||
),
|
||||
]);
|
||||
const personOptionIndex = computed(() => {
|
||||
if (!editForm.lineagePersonId || editForm.lineagePersonId === editTarget.value?.lineagePersonId) return 0;
|
||||
const index = personOptions.value.findIndex(
|
||||
(person) => person.id === editForm.lineagePersonId,
|
||||
);
|
||||
return index < 0 ? 0 : index + 1;
|
||||
});
|
||||
const selectedPersonLabel = computed(
|
||||
() => personOptionLabels.value[personOptionIndex.value] || "保持当前绑定",
|
||||
);
|
||||
const confirmationCopy = computed(() => {
|
||||
const memberName = confirmTarget.value?.member?.memberName || "这位成员";
|
||||
const copyByOperation = {
|
||||
unlink: {
|
||||
title: "解除人物绑定?",
|
||||
message: `解除后,“${memberName}”的账号仍在家谱中,但不再对应世系树人物。`,
|
||||
confirm: "确认解除",
|
||||
},
|
||||
remove: {
|
||||
title: "将成员移出家谱?",
|
||||
message: `移出后,“${memberName}”将不能再以成员身份访问这部家谱。`,
|
||||
confirm: "确认移出",
|
||||
},
|
||||
leave: {
|
||||
title: "退出这部家谱?",
|
||||
message: "退出后,你将不能再查看仅对成员开放的内容。",
|
||||
confirm: "确认退出",
|
||||
},
|
||||
transfer: {
|
||||
title: "转让谱主身份?",
|
||||
message: `转让后,“${memberName}”将成为新谱主,你会变为管理员。`,
|
||||
confirm: "确认转让",
|
||||
},
|
||||
};
|
||||
return copyByOperation[confirmTarget.value?.kind] || {
|
||||
title: "确认操作?",
|
||||
message: "请确认是否继续。",
|
||||
confirm: "确认",
|
||||
};
|
||||
});
|
||||
|
||||
const roleLabel = (role) => GENEALOGY_MEMBER_ROLE_LABELS[role] || "成员";
|
||||
const lineageLabel = (member) =>
|
||||
member.lineagePersonName || (member.lineagePersonId ? "已绑定" : "未绑定");
|
||||
const hasMemberActions = (member) =>
|
||||
Object.values(member.capabilities).some(Boolean);
|
||||
|
||||
const loadMembers = async () => {
|
||||
if (!hasValidGenealogyId.value) return;
|
||||
memberListController.abort();
|
||||
memberListState.value = "loading";
|
||||
memberListError.value = "";
|
||||
try {
|
||||
const loadedMembers = await genealogyMemberApi.getMembers(genealogyId.value, {
|
||||
requestController: memberListController,
|
||||
});
|
||||
if (!isPageActive) return;
|
||||
members.value = loadedMembers;
|
||||
memberListState.value = members.value.length ? "ready" : "empty";
|
||||
} catch (error) {
|
||||
if (!isPageActive || isRequestCancelled(error)) return;
|
||||
memberListError.value = getRequestErrorMessage(error, "成员加载失败,请稍后重试。");
|
||||
memberListState.value = "error";
|
||||
}
|
||||
};
|
||||
const loadPersonOptions = async () => {
|
||||
if (personOptionsState.value === "loading" || personOptionsState.value === "ready") return;
|
||||
personOptionsState.value = "loading";
|
||||
try {
|
||||
const loadedPersonOptions = await lineageApi.getLineagePersonOptions(genealogyId.value, {
|
||||
requestController: personOptionsController,
|
||||
});
|
||||
if (!isPageActive) return;
|
||||
personOptions.value = loadedPersonOptions;
|
||||
personOptionsState.value = "ready";
|
||||
} catch (error) {
|
||||
if (!isPageActive || isRequestCancelled(error)) return;
|
||||
personOptionsState.value = "error";
|
||||
}
|
||||
};
|
||||
const openMemberEditor = (member) => {
|
||||
if (!member.capabilities.canEdit || operationPending.value) return;
|
||||
editTarget.value = member;
|
||||
editForm.memberName = member.memberName;
|
||||
editForm.relationName = member.relationName;
|
||||
editForm.roleType = member.roleType;
|
||||
editForm.lineagePersonId = member.lineagePersonId || "";
|
||||
editError.value = "";
|
||||
void loadPersonOptions();
|
||||
};
|
||||
const closeEdit = () => {
|
||||
if (operationPending.value) return;
|
||||
editTarget.value = null;
|
||||
editError.value = "";
|
||||
};
|
||||
const selectRole = (event) => {
|
||||
editForm.roleType = roleOptions.value[Number(event.detail.value)]?.value || editForm.roleType;
|
||||
};
|
||||
const selectPerson = (event) => {
|
||||
const index = Number(event.detail.value);
|
||||
editForm.lineagePersonId = index > 0 ? personOptions.value[index - 1]?.id || "" : editTarget.value?.lineagePersonId || "";
|
||||
};
|
||||
const saveEdit = async () => {
|
||||
const target = editTarget.value;
|
||||
if (!target || operationPending.value) return;
|
||||
if (!editForm.memberName.trim()) {
|
||||
editError.value = "请填写成员称呼。";
|
||||
return;
|
||||
}
|
||||
operationPending.value = true;
|
||||
editError.value = "";
|
||||
try {
|
||||
const updated = await genealogyMemberApi.updateMember(
|
||||
genealogyId.value,
|
||||
target.memberId,
|
||||
{
|
||||
memberName: editForm.memberName,
|
||||
relationName: editForm.relationName,
|
||||
...(editForm.roleType !== target.roleType
|
||||
? { roleType: editForm.roleType }
|
||||
: {}),
|
||||
...(editForm.lineagePersonId &&
|
||||
editForm.lineagePersonId !== target.lineagePersonId
|
||||
? { lineagePersonId: editForm.lineagePersonId }
|
||||
: {}),
|
||||
},
|
||||
{ requestController: memberUpdateController },
|
||||
);
|
||||
if (!isPageActive) return;
|
||||
members.value = members.value.map((member) =>
|
||||
member.memberId === updated.memberId ? updated : member,
|
||||
);
|
||||
feedbackMessage.value = `“${updated.memberName}”的成员资料已更新。`;
|
||||
editTarget.value = null;
|
||||
} catch (error) {
|
||||
if (!isPageActive || isRequestCancelled(error)) return;
|
||||
editError.value = getRequestErrorMessage(error, "保存失败,请稍后重试。");
|
||||
} finally {
|
||||
if (isPageActive) operationPending.value = false;
|
||||
}
|
||||
};
|
||||
const openConfirmation = (kind, member) => {
|
||||
if (operationPending.value) return;
|
||||
confirmTarget.value = { kind, member };
|
||||
operationError.value = "";
|
||||
};
|
||||
const closeConfirmation = () => {
|
||||
if (operationPending.value) return;
|
||||
confirmTarget.value = null;
|
||||
operationError.value = "";
|
||||
};
|
||||
const runConfirmedOperation = async () => {
|
||||
const target = confirmTarget.value;
|
||||
if (!target || operationPending.value) return;
|
||||
operationPending.value = true;
|
||||
operationError.value = "";
|
||||
let operationCommitted = false;
|
||||
try {
|
||||
const requestOptions = {
|
||||
requestController: membershipOperationController,
|
||||
};
|
||||
switch (target.kind) {
|
||||
case "unlink":
|
||||
await genealogyMemberApi.unlinkMemberLineagePerson(
|
||||
genealogyId.value,
|
||||
target.member.memberId,
|
||||
requestOptions,
|
||||
);
|
||||
break;
|
||||
case "remove":
|
||||
await genealogyMemberApi.removeMember(
|
||||
genealogyId.value,
|
||||
target.member.memberId,
|
||||
requestOptions,
|
||||
);
|
||||
break;
|
||||
case "leave":
|
||||
await genealogyMemberApi.leaveGenealogy(genealogyId.value, requestOptions);
|
||||
break;
|
||||
case "transfer":
|
||||
await genealogyMemberApi.transferGenealogyOwner(
|
||||
genealogyId.value,
|
||||
target.member.memberId,
|
||||
requestOptions,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的成员操作:${target.kind}`);
|
||||
}
|
||||
operationCommitted = true;
|
||||
if (!isPageActive) return;
|
||||
confirmTarget.value = null;
|
||||
if (target.kind === "leave") {
|
||||
hasLeftGenealogy.value = true;
|
||||
members.value = [];
|
||||
memberListState.value = "empty";
|
||||
await returnTo("G01");
|
||||
return;
|
||||
}
|
||||
feedbackMessage.value =
|
||||
({
|
||||
unlink: "人物绑定已解除。",
|
||||
remove: "成员已移出家谱。",
|
||||
transfer: "谱主身份已转让。",
|
||||
})[target.kind] || "操作已完成。";
|
||||
await loadMembers();
|
||||
} catch (error) {
|
||||
if (!isPageActive) return;
|
||||
if (operationCommitted && target.kind === "leave") {
|
||||
hasLeftGenealogy.value = true;
|
||||
members.value = [];
|
||||
memberListState.value = "empty";
|
||||
confirmTarget.value = null;
|
||||
return;
|
||||
}
|
||||
if (isRequestCancelled(error)) return;
|
||||
operationError.value = getRequestErrorMessage(error, "操作失败,请稍后重试。");
|
||||
} finally {
|
||||
if (isPageActive) operationPending.value = false;
|
||||
}
|
||||
};
|
||||
const returnToOverview = () =>
|
||||
hasLeftGenealogy.value
|
||||
? returnTo("G01")
|
||||
: returnTo("G05", { genealogyId: genealogyId.value });
|
||||
const requestBack = () =>
|
||||
runBackGuard({
|
||||
transientOpen: Boolean(editTarget.value || confirmTarget.value),
|
||||
submitting: operationPending.value,
|
||||
"close-transient": () =>
|
||||
editTarget.value ? closeEdit() : closeConfirmation(),
|
||||
"block-submitting": () => true,
|
||||
});
|
||||
|
||||
onLoad((query) => {
|
||||
genealogyId.value = String(query?.genealogyId || "");
|
||||
if (hasValidGenealogyId.value) void loadMembers();
|
||||
});
|
||||
onShow(() => {
|
||||
if (
|
||||
!hasLeftGenealogy.value &&
|
||||
hasValidGenealogyId.value &&
|
||||
memberListState.value !== "loading" &&
|
||||
!operationPending.value
|
||||
) {
|
||||
void loadMembers();
|
||||
}
|
||||
});
|
||||
onBackPress((event) => handleBackPress(event, requestBack));
|
||||
onUnload(() => {
|
||||
isPageActive = false;
|
||||
memberListController.abort();
|
||||
personOptionsController.abort();
|
||||
memberUpdateController.abort();
|
||||
membershipOperationController.abort();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
|
||||
.member-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: $paper;
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.page-content {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 18rpx 24rpx 72rpx;
|
||||
}
|
||||
|
||||
.state-card,
|
||||
.member-intro,
|
||||
.member-card {
|
||||
box-sizing: border-box;
|
||||
@include adaptive.adaptive-genealogy-state-panel;
|
||||
}
|
||||
|
||||
.state-card {
|
||||
min-height: 340rpx;
|
||||
padding: 78rpx 52rpx 50rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-card > text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.state-card > text:first-child {
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(19px, 35rpx, 24px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.state-card > text:nth-child(2) {
|
||||
margin-top: 18rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(15px, 24rpx, 18px);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.state-card .app-button {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.member-intro {
|
||||
padding: 24rpx 28rpx;
|
||||
}
|
||||
|
||||
.member-intro text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.member-intro text:first-child {
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(18px, 31rpx, 22px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.member-intro text:last-child {
|
||||
margin-top: 10rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(14px, 22rpx, 17px);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.page-feedback {
|
||||
display: block;
|
||||
padding: 18rpx 22rpx;
|
||||
border: 1rpx solid rgba(66, 107, 88, 0.32);
|
||||
border-radius: 12rpx;
|
||||
background: rgba(66, 107, 88, 0.08);
|
||||
color: #426b58;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
}
|
||||
|
||||
.member-card {
|
||||
padding: 26rpx 28rpx;
|
||||
}
|
||||
|
||||
.member-card__heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.member-card__identity {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-card__identity text {
|
||||
display: block;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.member-card__identity text:first-child {
|
||||
color: $ink;
|
||||
font-family: STKaiti, KaiTi, serif;
|
||||
font-size: clamp(18px, 32rpx, 23px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.member-card__identity text:last-child {
|
||||
margin-top: 6rpx;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 7rpx 14rpx;
|
||||
border: 1rpx solid rgba(174, 113, 39, 0.38);
|
||||
border-radius: 999rpx;
|
||||
background: rgba(205, 161, 84, 0.12);
|
||||
color: #8f160f;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
}
|
||||
|
||||
.member-card__details {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.member-card__details text,
|
||||
.member-card__readonly {
|
||||
display: block;
|
||||
margin-top: 7rpx;
|
||||
overflow-wrap: anywhere;
|
||||
color: $ink-muted;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.member-card__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 12rpx;
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.member-card__actions .app-button {
|
||||
width: auto;
|
||||
min-width: 138rpx;
|
||||
}
|
||||
|
||||
.member-card__readonly {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
width: 100%;
|
||||
margin-top: 22rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.edit-field {
|
||||
@include adaptive.adaptive-genealogy-form-field;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 86rpx;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
margin-top: 12rpx;
|
||||
padding: 14rpx 18rpx;
|
||||
}
|
||||
|
||||
.edit-field > text:first-child {
|
||||
flex: 0 0 auto;
|
||||
color: $ink;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.edit-field input,
|
||||
.edit-field > text:last-child {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow-wrap: anywhere;
|
||||
color: $ink;
|
||||
font-size: clamp(14px, 23rpx, 17px);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.edit-field--picker > text:last-child {
|
||||
color: #8f160f;
|
||||
}
|
||||
|
||||
.edit-hint,
|
||||
.edit-error {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 12rpx;
|
||||
font-size: clamp(13px, 21rpx, 16px);
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.edit-hint {
|
||||
color: $ink-muted;
|
||||
}
|
||||
|
||||
.edit-error {
|
||||
color: $brand-red;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user