修改未完成

This commit is contained in:
rain
2026-07-29 18:15:59 +08:00
parent 6fbcf21024
commit 8c2355a52c
59 changed files with 7995 additions and 1118 deletions
+3 -1
View File
@@ -11,7 +11,7 @@
>
<ModulePageBackground module="tree" />
<view class="tree-page__header">
<PageHeader title="世系树" action="成员目录" @action="toDirectory" />
<PageHeader title="树状图" action="世系谱" @action="toPedigree" />
</view>
<view v-if="treeState !== 'loading'" class="tree-toolbar">
@@ -782,6 +782,8 @@ const toMember = () => {
};
const toDirectory = () =>
openPage("T07", { genealogyId: genealogyId.value }, "T01");
const toPedigree = () =>
openPage("T02", { genealogyId: genealogyId.value }, "T01");
const toRank = () =>
selected.value
? openPage(
+586
View File
@@ -0,0 +1,586 @@
<!-- 页面编号T-02用途世系谱表阅读同世成员横向滑页 -->
<template>
<view class="pedigree-page">
<ModulePageBackground module="tree" />
<view class="pedigree-page__header">
<PageHeader title="世系谱" action="树状图" @action="toTree" />
</view>
<view class="pedigree-stage">
<AppLoading
v-if="treeState === 'loading'"
text="正在整理世系谱"
description="请稍候,正在准备家谱成员关系。"
/>
<template v-else-if="treeState === 'tree'">
<view class="pedigree-layout">
<swiper
class="pedigree-swiper"
:current="pedigreePageIndex"
:duration="260"
:disable-touch="false"
@change="handlePedigreePageChange"
>
<swiper-item
v-for="(page, pageIndex) in pedigreePages"
:key="`generation-${page.generation}-page-${pageIndex}`"
>
<scroll-view
class="pedigree-scroll"
scroll-y
:show-scrollbar="false"
:style="pedigreeScrollStyle"
>
<view class="pedigree-sheet" :style="pedigreeSheetStyle">
<view class="pedigree-column pedigree-column--legend">
<view class="pedigree-column__relation"><text>身份</text></view>
<view class="pedigree-column__name"><text>{{ page.label }}</text></view>
<view class="pedigree-column__detail"><text>{{ page.summary }}</text></view>
</view>
<view
v-for="(member, memberIndex) in page.members"
:key="member?.id || `empty-${pageIndex}-${memberIndex}`"
class="member-node"
:class="{
'member-node--blank': !member,
'member-node--selected': selectedId === String(member?.id || ''),
'member-node--spouse': Boolean(member?.spouseOf),
}"
>
<view v-if="member" class="member-node__surface">
<view class="member-node__relation"><text>{{ memberRelation(member) }}</text></view>
<view
class="member-node__name"
role="button"
:aria-label="`查看${member.name}的资料`"
@click.stop="openMemberProfile(member)"
><text>{{ member.name }}</text></view>
<view
class="member-node__copy"
role="button"
:aria-label="`查看${member.name}的生平详情`"
@click.stop="openMemberDetail(member)"
>
<text class="node-relation">{{ member.branch }}</text>
<text class="node-years">{{ memberDetail(member) }}</text>
</view>
</view>
<view v-else class="member-node__surface">
<view class="member-node__relation"></view>
<view class="member-node__name"></view>
<view class="member-node__copy"></view>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
<view class="generation-rail" :style="pedigreeSheetStyle">
<view class="generation-band">
<image
class="generation-band__arrow generation-band__arrow--previous"
src="/static/assets/foundation/transparent/chevron-right.png"
mode="aspectFit"
aria-hidden="true"
/>
<view class="generation-band__copy">
<text>{{ activePage?.label || '未定世代' }}</text>
<text>{{ activePage?.memberCount || 0 }} 位成员</text>
</view>
<image
class="generation-band__arrow"
src="/static/assets/foundation/transparent/chevron-right.png"
mode="aspectFit"
aria-hidden="true"
/>
</view>
</view>
</view>
</template>
<view v-else class="pedigree-state-card">
<image
class="pedigree-state-card__skin"
src="/static/assets/modules/tree/transparent/t01-state-panel.png"
mode="scaleToFill"
/>
<view class="pedigree-state-card__content">
<text class="pedigree-state-card__eyebrow">{{ stateCopy.eyebrow }}</text>
<text class="pedigree-state-card__title">{{ stateCopy.title }}</text>
<text class="pedigree-state-card__copy">{{ stateCopy.copy }}</text>
<view class="pedigree-state-card__action" @click="handleStateAction">
<image
src="/static/assets/foundation/transparent/a01-scroll-primary-v3.png"
mode="aspectFit"
/>
<text>{{ stateCopy.action }}</text>
</view>
</view>
</view>
</view>
<AppDialog
:visible="detailVisible"
eyebrow="人物生平"
:title="detailMember?.name || '人物详情'"
:message="detailMessage"
confirm-text="关闭"
@confirm="closeMemberDetail"
@cancel="closeMemberDetail"
/>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import AppDialog from "@/components/AppDialog.vue";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
appApi,
createRequestController,
isRequestCancelled,
} from "@/utils/api.js";
import { genealogyContext } from "@/utils/genealogy-context.js";
import { openPage } from "@/utils/navigation.js";
const PEDIGREE_PAGE_SIZE = 5;
const genealogyId = ref("");
const treeState = ref("loading");
const treeLoadError = ref("");
const members = ref([]);
const selectedId = ref("");
const pedigreePageIndex = ref(0);
const detailVisible = ref(false);
const detailMember = ref(null);
const detailMessage = ref("");
const pedigreeRequestController = createRequestController();
const detailRequestController = createRequestController();
let loadGeneration = 0;
let detailLoadGeneration = 0;
let pageActive = true;
let skipNextShowRefresh = true;
const compareMembers = (left, right) =>
Number(left.generation) - Number(right.generation) ||
String(left.id).localeCompare(String(right.id));
const generationRows = computed(() => {
const groups = new Map();
members.value.forEach((member) => {
const generation = Number(member.generation) || 0;
const group = groups.get(generation) || [];
group.push(member);
groups.set(generation, group);
});
return Array.from(groups.entries())
.sort(([left], [right]) => left - right)
.map(([generation, group]) => ({
generation,
label: generation ? `${generation}` : "未定世代",
members: group.sort(compareMembers),
}));
});
const pedigreePages = computed(() => {
const pages = [];
generationRows.value.forEach((row) => {
const branch = row.members.find((member) => member.branch)?.branch;
for (let index = 0; index < row.members.length; index += PEDIGREE_PAGE_SIZE) {
const pageMembers = row.members.slice(index, index + PEDIGREE_PAGE_SIZE);
while (pageMembers.length < PEDIGREE_PAGE_SIZE) pageMembers.push(null);
pages.push({
generation: row.generation,
label: row.label,
memberCount: row.members.length,
summary: [branch, `${row.members.length} 位成员`]
.filter(Boolean)
.join(" · "),
members: pageMembers,
});
}
});
return pages;
});
const activePage = computed(
() => pedigreePages.value[pedigreePageIndex.value] || null,
);
const detailLength = computed(() =>
Math.max(
0,
...members.value.map((member) =>
Array.from(`${member.branch || ""}${memberDetail(member)}`).length,
),
),
);
const sheetHeight = computed(() => Math.max(1100, 720 + detailLength.value * 20));
const pedigreeSheetStyle = computed(() => ({
height: `${sheetHeight.value}rpx`,
}));
const pedigreeScrollStyle = computed(() => ({
height: `${sheetHeight.value}rpx`,
}));
const stateCopy = computed(
() =>
({
landscape: {
eyebrow: "世系谱阅读提示",
title: "左右滑动查看成员",
copy: "同一世代每页展示五位成员,继续向左滑动即可查看下一页。",
action: "返回树状图",
},
empty: {
eyebrow: "尚未建立世系",
title: "从第一位先祖开始记录",
copy: "录入首代成员后,即可在世系树与世系谱中查看关系。",
action: "添加首位成员",
},
error: {
eyebrow: "世系谱读取失败",
title: "暂时无法展示家谱成员",
copy: treeLoadError.value || "请稍后重试。",
action: "重新查看",
},
})[treeState.value] || {},
);
const handlePedigreePageChange = (event) => {
pedigreePageIndex.value = Number(event?.detail?.current || 0);
};
const memberRelation = (member) =>
member.spouseOf ? "配偶" : member.relation || "家谱成员";
const memberDetail = (member) =>
[member.years, member.sex, member.personStatus]
.filter((item) => item && item !== "生卒待补")
.join(" · ") || "生卒待补";
const openMemberProfile = (member) =>
openPage(
"T03",
{ genealogyId: genealogyId.value, personId: String(member.id) },
"T02",
);
const closeMemberDetail = () => {
detailVisible.value = false;
detailRequestController.abort();
};
const openMemberDetail = async (member) => {
if (!member) return;
const generation = ++detailLoadGeneration;
detailRequestController.abort();
detailMember.value = member;
detailMessage.value = "正在读取人物生平…";
detailVisible.value = true;
try {
const detail = await appApi.getPerson(genealogyId.value, member.id, {
requestController: detailRequestController,
});
if (generation !== detailLoadGeneration || !detailVisible.value) return;
detailMember.value = detail;
detailMessage.value =
[detail.biography, detail.remark].filter(Boolean).join("\n\n") ||
"暂未填写生平说明。";
} catch (error) {
if (isRequestCancelled(error) || generation !== detailLoadGeneration) return;
detailMessage.value = error?.message || "人物生平读取失败,请稍后重试。";
}
};
const toTree = () => {
const params = { genealogyId: genealogyId.value };
if (selectedId.value) params.selectedId = selectedId.value;
return openPage("T01", params, "T02");
};
const handleStateAction = () => {
if (treeState.value === "landscape") return toTree();
if (treeState.value === "empty") {
return openPage(
"T04",
{ genealogyId: genealogyId.value, mode: "first" },
"T02",
);
}
return loadPedigree({ genealogyId: genealogyId.value });
};
const loadPedigree = async (query = {}) => {
const generation = ++loadGeneration;
pedigreeRequestController.abort();
treeLoadError.value = "";
genealogyId.value = String(
query.genealogyId || genealogyContext.getCurrentGenealogyId() || "",
);
selectedId.value = String(query.selectedId || "");
if (!genealogyId.value) {
members.value = [];
treeState.value = "empty";
return;
}
if (["landscape", "empty", "error"].includes(query.state)) {
treeState.value = query.state;
return;
}
treeState.value = "loading";
try {
const result = await appApi.getTree(genealogyId.value, {
requestController: pedigreeRequestController,
});
if (!pageActive || generation !== loadGeneration) return;
members.value = result;
genealogyContext.setCurrentGenealogyId(genealogyId.value);
const selectedPage = pedigreePages.value.findIndex((page) =>
page.members.some((member) => String(member?.id) === selectedId.value),
);
pedigreePageIndex.value = selectedPage < 0 ? 0 : selectedPage;
treeState.value = members.value.length ? "tree" : "empty";
} catch (error) {
if (!pageActive || generation !== loadGeneration || isRequestCancelled(error)) {
return;
}
members.value = [];
treeLoadError.value = error?.message || "世系谱读取失败,请稍后重试。";
treeState.value = "error";
}
};
onLoad(loadPedigree);
onShow(() => {
if (skipNextShowRefresh) {
skipNextShowRefresh = false;
return;
}
if (genealogyId.value) loadPedigree({ genealogyId: genealogyId.value });
});
onUnload(() => {
pageActive = false;
loadGeneration += 1;
detailLoadGeneration += 1;
pedigreeRequestController.abort();
detailRequestController.abort();
});
</script>
<style scoped lang="scss">
.pedigree-page {
display: grid;
min-height: 100vh;
grid-template-rows: auto minmax(0, 1fr);
overflow: hidden;
background: $paper;
}
.pedigree-page__header,
.pedigree-stage {
z-index: 1;
}
.pedigree-stage {
min-height: 0;
}
.pedigree-layout {
display: grid;
height: 100%;
grid-template-columns: minmax(0, 1fr) 92rpx;
background: #fbfbfb;
}
.pedigree-swiper {
width: 100%;
height: 100%;
}
.pedigree-scroll {
width: 100%;
}
.pedigree-sheet {
display: grid;
width: 100%;
min-height: 100%;
grid-template-columns: repeat(6, minmax(0, 1fr));
align-items: stretch;
border-top: 1rpx solid #e2e2e2;
border-bottom: 1rpx solid #e2e2e2;
background: #fbfbfb;
box-sizing: border-box;
}
.pedigree-column,
.member-node {
display: grid;
min-width: 0;
min-height: 100%;
grid-template-rows: 144rpx 274rpx minmax(0, 1fr);
border-right: 1rpx solid #e2e2e2;
box-sizing: border-box;
color: #38312d;
}
.pedigree-column--legend {
border-left: 1rpx solid #e2e2e2;
background: #f6f6f6;
color: #665d56;
}
.pedigree-column__relation,
.pedigree-column__name,
.pedigree-column__detail,
.member-node__relation,
.member-node__name,
.member-node__copy {
display: flex;
justify-content: center;
box-sizing: border-box;
writing-mode: vertical-rl;
text-orientation: upright;
}
.pedigree-column__relation,
.member-node__relation {
align-items: center;
border-bottom: 1rpx solid #e2e2e2;
font-size: 22rpx;
}
.pedigree-column__name,
.member-node__name {
align-items: center;
border-bottom: 1rpx solid #e2e2e2;
color: #2f2925;
font-family: "STKaiti", "KaiTi", serif;
font-size: 34rpx;
font-weight: 700;
}
.pedigree-column__detail,
.member-node__copy {
align-items: flex-start;
gap: 18rpx;
padding: 28rpx 20rpx;
font-size: 21rpx;
line-height: 1.7;
white-space: normal;
}
.pedigree-column__detail {
font-size: 20rpx;
}
.member-node__surface {
display: contents;
}
.member-node--blank {
pointer-events: none;
}
.member-node--selected .member-node__relation,
.member-node--selected .member-node__name,
.member-node--selected .member-node__copy {
background: rgba(181, 46, 34, 0.06);
}
.member-node--selected .member-node__name {
color: #a7241d;
}
.member-node--spouse .member-node__relation {
color: #7a4d43;
}
.node-relation {
color: #675f57;
}
.node-years {
color: #3f3934;
}
.generation-rail {
width: 92rpx;
border-left: 1rpx solid #e2e2e2;
background: rgba(252, 252, 252, 0.96);
box-sizing: border-box;
}
.generation-band {
display: flex;
width: 72rpx;
min-height: 250rpx;
margin: 250rpx auto 0;
padding: 12rpx 0;
flex-direction: column;
align-items: center;
justify-content: space-between;
border: 1rpx solid #b52e22;
background: rgba(255, 255, 255, 0.88);
box-sizing: border-box;
}
.generation-band__arrow {
width: 26rpx;
height: 26rpx;
filter: sepia(1) saturate(3) hue-rotate(318deg);
transform: rotate(90deg);
}
.generation-band__arrow--previous {
transform: rotate(-90deg);
}
.generation-band__copy {
display: flex;
gap: 12rpx;
flex-direction: column;
align-items: center;
color: #a7241d;
font-size: 21rpx;
writing-mode: vertical-rl;
text-orientation: upright;
}
.generation-band__copy text:last-child {
font-size: 18rpx;
}
.pedigree-state-card {
display: grid;
width: calc(100% - 48rpx);
min-height: 360rpx;
margin: 150rpx 24rpx 0;
text-align: center;
}
.pedigree-state-card__skin {
width: 100%;
height: 100%;
}
.pedigree-state-card__skin,
.pedigree-state-card__content {
grid-area: 1 / 1;
}
.pedigree-state-card__content {
z-index: 1;
padding: 62rpx 58rpx 42rpx;
}
.pedigree-state-card__eyebrow,
.pedigree-state-card__title,
.pedigree-state-card__copy {
display: block;
}
.pedigree-state-card__eyebrow {
color: $brand-red;
font-size: 22rpx;
letter-spacing: 3rpx;
}
.pedigree-state-card__title {
margin-top: 12rpx;
color: $ink;
font-family: "STKaiti", "KaiTi", serif;
font-size: 34rpx;
font-weight: 700;
}
.pedigree-state-card__copy {
margin-top: 16rpx;
color: $ink-muted;
font-size: 24rpx;
line-height: 1.65;
}
.pedigree-state-card__action {
display: grid;
width: 360rpx;
min-height: 70rpx;
margin: 22rpx auto 0;
}
.pedigree-state-card__action image,
.pedigree-state-card__action text {
grid-area: 1 / 1;
}
.pedigree-state-card__action image {
width: 100%;
height: 100%;
}
.pedigree-state-card__action text {
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
color: #fff9ed;
font-size: 24rpx;
font-weight: 700;
}
</style>
+61 -63
View File
@@ -158,69 +158,59 @@ const trailIndex = ref(-1);
const memberRequestController = createRequestController();
let loadSequence = 0;
const details = computed(() =>
member.value
? [
{ label: "别名", value: member.value.aliasName },
{ label: "字辈", value: member.value.generationName },
{ label: "性别(字典值)", value: member.value.sex },
{ label: "人物状态(字典值)", value: member.value.personStatus },
{
label: "出生日期",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.birthDate,
},
{
label: "出生农历",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.birthLunar,
},
{ label: "生卒信息", value: member.value.years },
{
label: "出生地",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.birthplace,
},
{
label: "逝世农历",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.deathLunar,
},
{
label: "逝世地",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.deathPlace,
},
{
label: "安葬地",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.burialPlace,
},
{ label: "配偶", value: member.value.spouseNames },
{ label: "所属支系", value: member.value.branch },
{
label: "生平",
value:
member.value.status === "forbidden"
? "按权限隐藏"
: member.value.biography,
},
{ label: "备注", value: member.value.remark },
]
: [],
);
const dictionaryLabel = (value, labels) => labels[String(value || "")] || "";
const isDeceased = computed(() => member.value?.personStatus === "1");
const details = computed(() => {
if (!member.value) return [];
const protectedValue = (value) =>
member.value.status === "forbidden" ? "按权限隐藏" : value;
return [
{ label: "别名", value: member.value.aliasName },
{ label: "字辈", value: member.value.generationName },
{
label: "性别",
value: dictionaryLabel(member.value.sex, {
0: "男",
1: "女",
2: "未知",
}),
},
{
label: "人物状态",
value: dictionaryLabel(member.value.personStatus, {
0: "健在",
1: "已故",
2: "未知",
}),
},
{ label: "出生日期", value: protectedValue(member.value.birthDate) },
{
label: "出生农历",
value: protectedValue(
dictionaryLabel(member.value.birthLunar, { 0: "否", 1: "是" }),
),
},
{ label: "生卒信息", value: member.value.years },
{ label: "出生地", value: protectedValue(member.value.birthplace) },
...(isDeceased.value
? [
{ label: "逝世日期", value: protectedValue(member.value.deathDate) },
{
label: "逝世农历",
value: protectedValue(
dictionaryLabel(member.value.deathLunar, { 0: "否", 1: "是" }),
),
},
{ label: "逝世地", value: protectedValue(member.value.deathPlace) },
{ label: "安葬地", value: protectedValue(member.value.burialPlace) },
]
: []),
{ label: "配偶", value: member.value.spouseNames },
{ label: "所属支系", value: member.value.branch },
{ label: "生平", value: protectedValue(member.value.biography) },
{ label: "备注", value: member.value.remark },
];
});
const canPreviewEdit = computed(() => memberState.value === "detail");
const restrictedCopy = computed(() =>
member.value?.status === "forbidden"
@@ -377,6 +367,14 @@ onShow(() => {
const result = consumeNavigationResult("T03");
if (result?.operation === "member-open-requested" && result.entityId) {
void openRelative(result.entityId);
return;
}
if (
result?.operation === "member-updated" &&
result.refresh &&
result.entityId === String(personId.value)
) {
void loadMember(result.entityId, { preserveCurrent: true });
}
});
+17 -96
View File
@@ -53,15 +53,6 @@
fieldErrors.name
}}</text>
<view class="form-field">
<text>人物编号</text>
<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)"
@@ -154,27 +145,6 @@
/>
<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>
<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"
@@ -208,6 +178,7 @@
</view>
<picker
v-if="isDeceased"
mode="date"
:value="addForm.deathDate"
@change="selectDeathDate"
@@ -218,6 +189,7 @@
</view>
</picker>
<picker
v-if="isDeceased"
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, addForm.deathLunar)"
@change="selectOption('deathLunar', lunarOptions, $event)"
@@ -229,7 +201,7 @@
}}</text>
</view>
</picker>
<view class="form-field">
<view v-if="isDeceased" class="form-field">
<text>逝世地</text>
<input
v-model="addForm.deathPlace"
@@ -238,7 +210,7 @@
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<view v-if="isDeceased" class="form-field">
<text>安葬地</text>
<input
v-model="addForm.burialPlace"
@@ -268,18 +240,6 @@
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"
/>
<text v-else>{{ relationLabel }}</text>
</view>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea
@@ -372,7 +332,6 @@ const discardDialogVisible = ref(false);
const currentMember = ref(null);
const errorMessage = ref("");
const addRequestController = createRequestController();
const personOptionsRequestController = createRequestController();
let loadSequence = 0;
const relationIntents = Object.freeze({
@@ -408,23 +367,15 @@ const personStatusOptions = Object.freeze([
const bindingModeOptions = Object.freeze([
{ label: "不绑定账号", value: "NONE" },
{ label: "绑定当前账号", value: "SELF" },
{ label: "绑定指定用户", value: "SPECIFIED" },
]);
const personOptions = ref([{ label: "不选择", value: "" }]);
const personOptionLabels = computed(() =>
personOptions.value.map((item) => item.label),
);
const addForm = reactive({
bindingMode: "NONE",
personNo: "",
name: "",
relation: "",
aliasName: "",
sex: "",
generation: "",
generationName: "",
fatherId: "",
motherId: "",
avatarOssId: "",
birthDate: "",
birthLunar: "",
@@ -437,11 +388,11 @@ const addForm = reactive({
biography: "",
remark: "",
sortOrder: "",
relationName: "",
});
const fieldErrors = reactive({ name: "", relation: "", bindingMode: "" });
const isFirstMember = computed(() => mode.value === "first");
const isDeceased = computed(() => addForm.personStatus === "1");
const activeRelationIntent = computed(
() => relationIntents[relationType.value] || null,
);
@@ -524,27 +475,6 @@ const loadCurrentMember = async () => {
errorMessage.value = error?.message || "当前成员暂不可用。";
}
};
const loadPersonOptions = async () => {
try {
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 || "未命名人物",
);
return { value, label: `${name}${value}` };
})
.filter(Boolean);
personOptions.value = [{ label: "不选择", value: "" }, ...options];
} catch (error) {
if (!isRequestCancelled(error))
personOptions.value = [{ label: "暂无可选人物", value: "" }];
}
};
onLoad((query) => {
genealogyId.value = String(query.genealogyId || "");
personId.value = String(query.personId || "");
@@ -567,7 +497,6 @@ onLoad((query) => {
if (activeRelationIntent.value) {
addForm.relation = activeRelationIntent.value.label;
}
void loadPersonOptions();
if (isFirstMember.value) {
addState.value = "form";
return;
@@ -577,7 +506,6 @@ onLoad((query) => {
onUnload(() => {
loadSequence += 1;
addRequestController.abort();
personOptionsRequestController.abort();
discardConfirmation.dispose();
});
@@ -609,22 +537,20 @@ const optionLabel = (options, value) =>
options.find((item) => item.value === value)?.label || "";
const selectOption = (field, options, event) => {
addForm[field] = options[Number(event.detail.value)]?.value || "";
if (field === "personStatus" && addForm.personStatus !== "1") {
Object.assign(addForm, {
deathDate: "",
deathLunar: "",
deathPlace: "",
burialPlace: "",
});
}
};
const selectBindingMode = (event) => {
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 selectPerson = (field, event) => {
addForm[field] = personOptions.value[Number(event.detail.value)]?.value || "";
};
const uploadAvatar = async () => {
if (isAvatarUploading.value || isSubmitting.value) return;
isAvatarUploading.value = true;
@@ -665,13 +591,10 @@ const submitAdd = async () => {
try {
const payload = {
bindingMode: addForm.bindingMode,
personNo: addForm.personNo,
name: addForm.name,
aliasName: addForm.aliasName,
sex: addForm.sex,
generationName: addForm.generationName,
fatherId: addForm.fatherId,
motherId: addForm.motherId,
avatarOssId: addForm.avatarOssId,
birthDate: addForm.birthDate,
birthLunar: addForm.birthLunar,
@@ -685,12 +608,14 @@ const submitAdd = async () => {
remark: addForm.remark,
sortOrder: addForm.sortOrder,
...(isFirstMember.value
? { generation: 1, relationName: addForm.relationName }
? { generation: 1 }
: {
...(addForm.generation
? { generation: Number(addForm.generation) }
: {}),
relationName: relationLabel.value,
...(relationType.value === "SPOUSE"
? { relationName: relationLabel.value }
: {}),
}),
};
if (isFirstMember.value) {
@@ -711,15 +636,12 @@ const submitAdd = async () => {
}
Object.assign(addForm, {
bindingMode: "NONE",
personNo: "",
name: "",
relation: "",
aliasName: "",
sex: "",
generation: "",
generationName: "",
fatherId: "",
motherId: "",
avatarOssId: "",
birthDate: "",
birthLunar: "",
@@ -732,7 +654,6 @@ const submitAdd = async () => {
biography: "",
remark: "",
sortOrder: "",
relationName: "",
});
avatarFileName.value = "";
await returnTo("T01", { genealogyId: genealogyId.value });
+52 -34
View File
@@ -49,12 +49,7 @@
<view class="form-field">
<text>人物编号</text>
<input
v-model="editForm.personNo"
maxlength="40"
placeholder="不填则由服务端生成"
placeholder-class="form-placeholder"
/>
<text>{{ originalMember.personNo || "服务端生成" }}</text>
</view>
<picker
:range="sexOptions.map((item) => item.label)"
@@ -68,7 +63,14 @@
}}</text>
</view>
</picker>
<view
v-if="editForm.bindingMode === 'SPECIFIED'"
class="form-field form-field--picker"
>
<text>身份认领</text><text>已绑定指定用户</text>
</view>
<picker
v-else
:range="bindingModeOptions.map((item) => item.label)"
:value="optionIndex(bindingModeOptions, editForm.bindingMode)"
@change="selectBindingMode"
@@ -183,6 +185,7 @@
/>
</view>
<picker
v-if="isDeceased"
mode="date"
:value="editForm.deathDate"
@change="selectDate('deathDate', $event)"
@@ -193,6 +196,7 @@
>
</picker>
<picker
v-if="isDeceased"
:range="lunarOptions.map((item) => item.label)"
:value="optionIndex(lunarOptions, editForm.deathLunar)"
@change="selectOption('deathLunar', lunarOptions, $event)"
@@ -204,7 +208,7 @@
}}</text>
</view>
</picker>
<view class="form-field">
<view v-if="isDeceased" class="form-field">
<text>逝世地</text>
<input
v-model="editForm.deathPlace"
@@ -212,7 +216,7 @@
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<view v-if="isDeceased" class="form-field">
<text>安葬地</text>
<input
v-model="editForm.burialPlace"
@@ -242,15 +246,6 @@
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field">
<text>关系名称</text>
<input
v-model="editForm.relationName"
maxlength="30"
placeholder="可选关系显示名称"
placeholder-class="form-placeholder"
/>
</view>
<view class="form-field form-field--summary">
<text>人物简介</text>
<textarea
@@ -332,7 +327,12 @@ import {
pickAndUploadImage,
} from "@/utils/resumable-image-upload.js";
import { createDiscardConfirmation } from "@/utils/discard-confirmation.js";
import { goBack, handleBackPress, runBackGuard } from "@/utils/navigation.js";
import {
finishPage,
goBack,
handleBackPress,
runBackGuard,
} from "@/utils/navigation.js";
const editState = ref("loading");
const genealogyId = ref("");
@@ -353,7 +353,6 @@ const baseline = ref("");
const editForm = reactive({
appUserId: "",
bindingMode: "NONE",
personNo: "",
name: "",
aliasName: "",
sex: "",
@@ -373,7 +372,6 @@ const editForm = reactive({
summary: "",
remark: "",
sortOrder: "",
relationName: "",
});
const fieldErrors = reactive({ name: "", dates: "", bindingMode: "" });
const sexOptions = Object.freeze([
@@ -393,7 +391,6 @@ const personStatusOptions = Object.freeze([
const bindingModeOptions = Object.freeze([
{ label: "不绑定账号", value: "NONE" },
{ label: "绑定当前账号", value: "SELF" },
{ label: "绑定指定用户", value: "SPECIFIED" },
]);
const personOptions = ref([{ label: "不选择", value: "" }]);
const personOptionLabels = computed(() =>
@@ -408,6 +405,7 @@ const appUserBindingHint = computed(() => {
? "保留当前已认领的指定业务用户"
: "需先选择指定业务用户;当前没有可用候选接口";
});
const isDeceased = computed(() => editForm.personStatus === "1");
const formSnapshot = computed(() => JSON.stringify(editForm));
const hasValidContext = computed(() =>
@@ -463,11 +461,11 @@ const loadMember = async () => {
});
if (activeLoad !== loadSequence) return;
originalMember.value = member;
const deceased = member.personStatus === "1";
Object.assign(editForm, {
appUserId: member.appUserId || "",
bindingMode:
member.bindingMode || (member.appUserId ? "SPECIFIED" : "NONE"),
personNo: member.personNo || "",
name: member.name,
aliasName: member.aliasName || "",
sex: member.sex || "",
@@ -481,15 +479,14 @@ const loadMember = async () => {
birthDate: datePart(member.birthDate),
birthLunar: member.birthLunar || "",
birthPlace: member.birthplace || "",
deathDate: datePart(member.deathDate),
deathLunar: member.deathLunar || "",
deathPlace: member.deathPlace || "",
burialPlace: member.burialPlace || "",
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 ?? "",
relationName: member.relationName || "",
});
baseline.value = formSnapshot.value;
errorMessage.value = "";
@@ -567,6 +564,14 @@ const optionLabel = (options, value) =>
options.find((item) => item.value === value)?.label || "";
const selectOption = (field, options, event) => {
editForm[field] = options[Number(event.detail.value)]?.value || "";
if (field === "personStatus" && editForm.personStatus !== "1") {
Object.assign(editForm, {
deathDate: "",
deathLunar: "",
deathPlace: "",
burialPlace: "",
});
}
};
const selectBindingMode = (event) => {
editForm.bindingMode =
@@ -632,7 +637,6 @@ const saveMember = async () => {
...(editForm.bindingMode === "SPECIFIED"
? { appUserId: editForm.appUserId }
: {}),
personNo: editForm.personNo,
name: editForm.name,
aliasName: editForm.aliasName,
sex: editForm.sex,
@@ -646,20 +650,34 @@ const saveMember = async () => {
birthDate: editForm.birthDate,
birthLunar: editForm.birthLunar,
birthPlace: editForm.birthPlace,
deathDate: editForm.deathDate,
deathLunar: editForm.deathLunar,
deathPlace: editForm.deathPlace,
burialPlace: editForm.burialPlace,
...(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,
relationName: editForm.relationName,
},
{ requestController: editRequestController },
);
baseline.value = formSnapshot.value;
await goBack();
await finishPage(
"T03",
{
genealogyId: genealogyId.value,
personId: personId.value,
},
{
operation: "member-updated",
entityId: personId.value,
refresh: true,
},
);
} catch (error) {
if (isRequestCancelled(error)) return;
failedAction.value = "save";