(function (root, factory) { // 世系人物模块同时支持浏览器页面和 Node 单元测试。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.LineagePages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.LineagePages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; var selectedPersonId = ''; var editingPersonId = ''; var relationMode = ''; var peopleById = Object.create(null); var currentPageNum = 1; var currentPageSize = 20; var currentPageTotal = null; var managementAccess = false; var writePending = false; var currentUserId = ''; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function shouldRedirectToLogin(api, error) { var status = error && (error.status || error.code); return !api || !api.getToken || !api.getToken() || Number(status) === 401; } function isForbidden(error) { return Number(error && (error.status || error.code)) === 403; } function redirectUnauthorized(api, error) { if (!shouldRedirectToLogin(api, error)) return false; if (api && api.clearToken) api.clearToken(); if (!root.location) return true; if (typeof root.location.replace === 'function') { root.location.replace('login.html'); return true; } root.location.href = 'login.html'; return true; } function query(selector, rootNode) { return documentRef ? (rootNode || documentRef).querySelector(selector) : null; } function queryAll(selector, rootNode) { return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : []; } function showMessage(message) { if (root.layui && root.layui.layer) { root.layui.layer.msg(message); return; } if (root.alert) root.alert(message); } function getQueryParam(search, name) { var params = new URLSearchParams(String(search || '').replace(/^\?/, '')); return params.get(name) || ''; } function getCurrentGenealogyId(search) { var page = query('[data-lineage-page]'); var source = search === undefined && root.location ? root.location.search : search; var profileGenealogyId; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { profileGenealogyId = root.ProfileUI.getGenealogyId(); if (profileGenealogyId) return profileGenealogyId; } return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || ''; } function trimOrUndefined(value) { var text = String(value === undefined || value === null ? '' : value).trim(); return text || undefined; } function toIntegerOrUndefined(value) { var text = trimOrUndefined(value); var number; if (!text) return undefined; number = Number(text); return Number.isInteger(number) && Number.isSafeInteger(number) ? number : undefined; } function normalizeId(value) { var text; if (value === undefined || value === null || value === '') return ''; if (typeof value === 'number' && !Number.isSafeInteger(value)) return ''; text = String(value); return /^\d+$/.test(text) ? text : ''; } function getPersonId(item) { return normalizeId(item && item.personId); } function formatLineageDate(value) { var text = trimOrUndefined(value); if (!text) return undefined; if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(text)) return text.replace('T', ' ') + ':00'; if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(text)) return text.replace('T', ' '); return text; } function buildLineagePersonBody(values) { var source = values || {}; var bindingMode = trimOrUndefined(source.bindingMode); var body = { bindingMode: bindingMode, name: trimOrUndefined(source.name) }; var optionalFields = [ 'personNo', 'aliasName', 'sex', 'generationName', 'birthLunar', 'birthPlace', 'deathLunar', 'deathPlace', 'burialPlace', 'personStatus', 'biography', 'remark', 'relationName' ]; var idFields = ['avatarOssId', 'fatherId', 'motherId']; var generation = trimOrUndefined(source.generation); var sortOrder = trimOrUndefined(source.sortOrder); var birthDate = formatLineageDate(source.birthDate); var deathDate = formatLineageDate(source.deathDate); optionalFields.forEach(function (field) { var value = trimOrUndefined(source[field]); if (value !== undefined) body[field] = value; }); idFields.forEach(function (field) { var value = normalizeId(source[field]); if (value) body[field] = value; }); if (bindingMode === 'SPECIFIED') { body.appUserId = normalizeId(source.appUserId) || trimOrUndefined(source.appUserId); } if (birthDate !== undefined) body.birthDate = birthDate; if (deathDate !== undefined) body.deathDate = deathDate; if (generation !== undefined) body.generation = Number(generation); if (sortOrder !== undefined) body.sortOrder = Number(sortOrder); return body; } function validateLineagePersonBody(body) { if (!body || !body.name) return '请填写成员姓名'; if (!body.bindingMode) return '请选择账号绑定方式'; if (['NONE', 'SELF', 'SPECIFIED'].indexOf(body.bindingMode) === -1) return '账号绑定方式无效'; if (body.bindingMode === 'SPECIFIED' && !normalizeId(body.appUserId)) return '指定账号绑定缺少用户选项'; if (body.bindingMode !== 'SPECIFIED' && body.appUserId !== undefined) return '当前绑定方式不能提交指定用户'; if (body.sex !== undefined && ['0', '1', '2'].indexOf(body.sex) === -1) return '性别选项无效'; if (body.personStatus !== undefined && ['0', '1', '2'].indexOf(body.personStatus) === -1) return '人物状态选项无效'; if (body.birthLunar !== undefined && ['0', '1'].indexOf(body.birthLunar) === -1) return '出生历法选项无效'; if (body.deathLunar !== undefined && ['0', '1'].indexOf(body.deathLunar) === -1) return '逝世历法选项无效'; if (['avatarOssId', 'fatherId', 'motherId'].some(function (field) { return body[field] !== undefined && !normalizeId(body[field]); })) return '人物关联字段无效'; if (body.generation !== undefined && (!Number.isInteger(body.generation) || !Number.isSafeInteger(body.generation))) { return '世代序号必须是整数'; } if (body.sortOrder !== undefined && (!Number.isInteger(body.sortOrder) || !Number.isSafeInteger(body.sortOrder))) { return '排序值必须是整数'; } return ''; } function normalizeLineagePerson(item) { var personId = getPersonId(item); var name = trimOrUndefined(item && item.name); var person; var invalidId = false; if (!personId || !name) return null; person = { personId: personId, name: name }; [ 'genealogyName', 'genealogyNo', 'appUserNickName', 'personNo', 'aliasName', 'sex', 'generationName', 'fatherName', 'motherName', 'spouseNames', 'birthDate', 'birthLunar', 'birthPlace', 'deathDate', 'deathLunar', 'deathPlace', 'burialPlace', 'personStatus', 'biography', 'status', 'remark', 'relationType', 'relationName' ].forEach(function (field) { if (item && item[field] !== undefined && item[field] !== null) person[field] = String(item[field]); }); ['appUserId', 'genealogyId', 'fatherId', 'motherId', 'avatarOssId'].forEach(function (field) { if (item && item[field] !== undefined && item[field] !== null) { var value = normalizeId(item[field]); if (!value) { invalidId = true; return; } person[field] = value; } }); if (invalidId) return null; if (toIntegerOrUndefined(item && item.generation) !== undefined) person.generation = toIntegerOrUndefined(item.generation); if (toIntegerOrUndefined(item && item.sortOrder) !== undefined) person.sortOrder = toIntegerOrUndefined(item.sortOrder); return person; } function normalizeLineageAccess(genealogy) { return { canEditContent: Boolean(genealogy && genealogy.canEditContent === true) }; } function normalizeGenerationOption(item) { var generation = toIntegerOrUndefined(item && item.generationNo); var generationName = trimOrUndefined(item && item.generationText); if (!generation || !generationName) return null; return { generation: generation, generationName: generationName }; } function normalizeGenealogyMemberOption(item) { var memberId = normalizeId(item && item.memberId); var genealogyId = normalizeId(item && item.genealogyId); var appUserId = normalizeId(item && item.appUserId); var status = trimOrUndefined(item && item.status); if (!memberId || !genealogyId || !appUserId || status !== '0') return null; return { memberId: memberId, genealogyId: genealogyId, appUserId: appUserId, appUserNickName: String(item.appUserNickName === undefined || item.appUserNickName === null ? '' : item.appUserNickName), memberName: String(item.memberName === undefined || item.memberName === null ? '' : item.memberName), roleType: String(item.roleType === undefined || item.roleType === null ? '' : item.roleType), status: status }; } function normalizeGenealogyMemberOptions(data) { if (!Array.isArray(data)) return []; return data.map(normalizeGenealogyMemberOption).filter(Boolean); } function normalizeLineageBinding(person, userId) { var appUserId = normalizeId(person && person.appUserId); var normalizedUserId = normalizeId(userId); if (!appUserId) return { bindingMode: 'NONE', appUserId: '' }; if (normalizedUserId && appUserId === normalizedUserId) return { bindingMode: 'SELF', appUserId: '' }; return { bindingMode: 'SPECIFIED', appUserId: appUserId }; } function normalizeList(data) { if (Array.isArray(data)) return data; if (data && Array.isArray(data.rows)) return data.rows; return []; } function escapeHtml(value) { return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getPersonSummary(person) { var values = []; if (person.generation !== undefined) values.push('第 ' + person.generation + ' 世'); if (person.generationName) values.push('字辈:' + person.generationName); if (person.aliasName) values.push('别名:' + person.aliasName); if (person.relationName) values.push('关系称谓:' + person.relationName); if (person.personStatus) values.push('人物状态:' + person.personStatus); return values.join(' · ') || '未填写世代信息'; } function renderLineageList(data) { var container = query('[data-lineage-list]'); var normalized = normalizeList(data).map(normalizeLineagePerson); var invalidCount = normalized.filter(function (item) { return !item; }).length; var people = normalized.filter(Boolean); peopleById = Object.create(null); people.forEach(function (person) { peopleById[person.personId] = person; }); if (!container) return; if (invalidCount) { container.innerHTML = '
人物响应缺少可安全使用的 personId 或 name,请联系后端补充 DTO。
'; return; } if (!people.length) { container.innerHTML = '
暂无世系人物
'; return; } container.innerHTML = people.map(function (person) { var selected = person.personId === selectedPersonId ? ' is-selected' : ''; return ''; }).join(''); } function renderTreeNode(item, ancestry) { var person = normalizeLineagePerson(item); var nextAncestry; var spouses; var children; var branches = ''; if (!person) return ''; if (ancestry[person.personId]) { return '
  • ' + escapeHtml(person.name) + ' 已在上级关系中展示
  • '; } nextAncestry = Object.assign({}, ancestry); nextAncestry[person.personId] = true; spouses = normalizeList(item && item.spouses).map(function (node) { return renderTreeNode(node, nextAncestry); }).filter(Boolean).join(''); children = normalizeList(item && item.children).map(function (node) { return renderTreeNode(node, nextAncestry); }).filter(Boolean).join(''); if (spouses) branches += ''; if (children) branches += ''; return '
  • ' + branches + '
  • '; } function renderLineageTreeHtml(data) { var nodes = normalizeList(data).map(function (item) { return renderTreeNode(item, {}); }).filter(Boolean); if (!nodes.length) { return '
    暂无世系树
    '; } return ''; } function renderTree(data) { var container = query('[data-lineage-tree]'); if (container) container.innerHTML = renderLineageTreeHtml(data); } function renderOptions(data) { var select = query('[data-lineage-person-options]'); var people = normalizeList(data).map(normalizeLineagePerson).filter(Boolean); var options = people.map(function (person) { return ''; }).join(''); if (!select) return; select.innerHTML = '' + options; select.value = selectedPersonId; queryAll('[data-lineage-parent-option]').forEach(function (parentSelect) { var emptyLabel = parentSelect.getAttribute('data-empty-label') || '请选择人物'; parentSelect.innerHTML = '' + options; }); } function renderGenerationOptions(data) { var select = query('[data-lineage-generation]'); var filter = query('[data-lineage-generation-filter]'); var selectedGeneration = select ? select.value : ''; var selectedFilter = filter ? filter.value : ''; var options = normalizeList(data).map(normalizeGenerationOption).filter(Boolean); var optionHtml = options.map(function (option) { return ''; }).join(''); if (select) { select.innerHTML = '' + optionHtml; select.value = selectedGeneration; } if (filter) { filter.innerHTML = '' + optionHtml; filter.value = selectedFilter; } } function updateBindingFields() { var bindingMode = query('[name="bindingMode"]'); var memberSelect = query('[data-lineage-member-options]'); var specified = Boolean(bindingMode && bindingMode.value === 'SPECIFIED'); if (!memberSelect) return; memberSelect.hidden = !specified; memberSelect.required = specified; if (!specified) memberSelect.value = ''; } function renderGenealogyMemberOptions(data) { var select = query('[data-lineage-member-options]'); var selectedValue = select ? select.value : ''; var options = normalizeGenealogyMemberOptions(data).filter(function (option) { return option.appUserId !== currentUserId; }); if (!select) return options; select.innerHTML = '' + options.map(function (option) { var label = option.memberName || option.appUserNickName || '家谱成员'; return ''; }).join(''); select.value = selectedValue; updateBindingFields(); return options; } function renderListSummary(data) { var container = query('[data-lineage-list-summary]'); var count; if (!container) return; count = normalizeList(data).map(normalizeLineagePerson).filter(Boolean).length; container.textContent = count ? '成员总览已返回 ' + count + ' 人;下方结果按页展示。' : '成员总览暂无可展示人物。'; } function updatePaginationControls() { var container = query('[data-lineage-pagination]'); var previous = query('[data-lineage-page-action="previous"]'); var next = query('[data-lineage-page-action="next"]'); var status = query('[data-lineage-page-status]'); var disabled = writePending; if (!container) return; if (currentPageTotal === null) { container.hidden = true; return; } container.hidden = false; if (previous) previous.disabled = disabled || currentPageNum <= 1; if (next) next.disabled = disabled || currentPageNum * currentPageSize >= currentPageTotal; if (status) status.textContent = '第 ' + currentPageNum + ' 页,共 ' + currentPageTotal + ' 人'; } function canUseLineagePagination(pending) { return !Boolean(pending); } function renderPagination(data) { var total = data && Number(data.total); currentPageTotal = Number.isFinite(total) && total >= 0 ? total : null; updatePaginationControls(); } function renderDetail(data) { var container = query('[data-lineage-detail]'); var person = normalizeLineagePerson(data); var values; var actions = ''; if (!container) return; if (!person) { container.innerHTML = '
    人物详情缺少可安全使用的 personId 或 name,请联系后端补充 DTO。
    '; return; } selectedPersonId = person.personId; peopleById[person.personId] = person; values = [ person.sex !== undefined ? '性别:' + ({ '0': '男', '1': '女', '2': '未知' }[person.sex] || person.sex) : '', person.generation !== undefined ? '世代:' + person.generation : '', person.generationName ? '字辈:' + person.generationName : '', person.personNo ? '人物编号:' + person.personNo : '', person.aliasName ? '别名:' + person.aliasName : '', person.fatherName ? '父亲:' + person.fatherName : '', person.motherName ? '母亲:' + person.motherName : '', person.spouseNames ? '配偶:' + person.spouseNames : '', person.appUserNickName ? '绑定账号:' + person.appUserNickName : '', person.birthDate ? '出生:' + person.birthDate : '', person.birthPlace ? '出生地:' + person.birthPlace : '', person.deathDate ? '逝世:' + person.deathDate : '', person.deathPlace ? '逝世地:' + person.deathPlace : '', person.burialPlace ? '安葬地:' + person.burialPlace : '', person.personStatus !== undefined ? '人物状态:' + ({ '0': '健在', '1': '已故', '2': '未知' }[person.personStatus] || person.personStatus) : '', person.biography ? '简介:' + person.biography : '', person.remark ? '备注:' + person.remark : '' ].filter(Boolean); if (managementAccess) { actions = '
    ' + '
    '; } container.innerHTML = '

    ' + escapeHtml(person.name) + '

    ' + escapeHtml(values.join(' · ') || '暂无更多资料') + '

    ' + actions + '
    '; renderLineageList(Object.keys(peopleById).map(function (id) { return peopleById[id]; })); if (query('[data-lineage-person-options]')) query('[data-lineage-person-options]').value = person.personId; refreshControls(); } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } function setFormStatus(message) { var target = query('[data-lineage-form-status]'); if (target) target.textContent = message || ''; } function relationLabel(mode) { return { parents: '父母', spouses: '配偶', children: '子女', siblings: '兄弟姐妹' }[mode] || ''; } function syncGenealogyLinks(genealogyId) { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) { root.ProfileUI.syncGenealogyContextLinks(); return; } queryAll('[data-genealogy-context-link]').forEach(function (link) { var href = link.getAttribute('href'); var base; if (!href || href === '#') return; base = href.split('?')[0]; link.href = base + '?genealogyId=' + encodeURIComponent(genealogyId); }); } function renderNoContext() { ['[data-lineage-tree]', '[data-lineage-list]', '[data-lineage-detail]'].forEach(function (selector) { var container = query(selector); if (container) container.innerHTML = '
    请从具体家谱进入世系管理
    '; }); if (query('[data-lineage-list-summary]')) query('[data-lineage-list-summary]').textContent = ''; currentPageTotal = null; setLineageEnabled(false); updatePaginationControls(); } function requireGenealogyId() { var genealogyId = getCurrentGenealogyId(); if (!genealogyId) { renderNoContext(); showMessage('请从具体家谱进入世系管理'); } return genealogyId; } function refreshControls() { var writeDisabled = !managementAccess || writePending; queryAll('[data-lineage-management]').forEach(function (element) { element.hidden = !managementAccess; }); queryAll('[data-lineage-relation], [data-lineage-form] button, [data-lineage-form] input, [data-lineage-form] select, [data-lineage-form] textarea, [data-lineage-edit], [data-lineage-disable]').forEach(function (control) { control.disabled = writeDisabled; }); queryAll('[data-lineage-search], [data-lineage-person-options]').forEach(function (control) { control.disabled = writePending; }); updatePaginationControls(); } function setLineageEnabled(enabled) { managementAccess = Boolean(enabled); refreshControls(); } function setWritePending(pending) { writePending = Boolean(pending); refreshControls(); } function renderForbiddenState() { ['[data-lineage-tree]', '[data-lineage-list]', '[data-lineage-detail]'].forEach(function (selector) { var container = query(selector); if (container) container.innerHTML = '
    当前账号没有世系人物管理权限
    '; }); if (query('[data-lineage-list-summary]')) query('[data-lineage-list-summary]').textContent = ''; currentPageTotal = null; setLineageEnabled(false); setFormStatus('当前账号没有世系人物管理权限'); } function getCurrentKeyword() { var input = query('[data-lineage-keyword]'); return input ? input.value : ''; } function buildLineagePageQuery(values, pageNum, pageSize) { var source = values || {}; var queryValues = { pageNum: Number(pageNum), pageSize: Number(pageSize) }; var keyword = trimOrUndefined(source.keyword); var generation = toIntegerOrUndefined(source.generation); var personStatus = trimOrUndefined(source.personStatus); if (keyword !== undefined) queryValues.keyword = keyword; if (generation !== undefined && generation > 0) queryValues.generation = generation; if (['0', '1', '2'].indexOf(personStatus) !== -1) queryValues.personStatus = personStatus; return queryValues; } function getCurrentFilters(keyword) { var generation = query('[data-lineage-generation-filter]'); var status = query('[data-lineage-status-filter]'); return { keyword: keyword, generation: generation ? generation.value : '', personStatus: status ? status.value : '' }; } async function loadLineage(keyword, pageNum) { var api = getApi(); var genealogyId; var results; var access; var searchKeyword = trimOrUndefined(keyword); var requestedPage = Number(pageNum); if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; currentPageNum = Number.isInteger(requestedPage) && requestedPage > 0 ? requestedPage : 1; syncGenealogyLinks(genealogyId); try { results = await Promise.all([ api.genealogyDetail(genealogyId), api.currentProfile(), api.lineageTree(genealogyId), api.lineagePersons(genealogyId), api.lineagePersonsPage(genealogyId, buildLineagePageQuery( getCurrentFilters(searchKeyword), currentPageNum, currentPageSize )), api.lineagePersonOptions(genealogyId), api.generationPoems(genealogyId) ]); access = normalizeLineageAccess(results[0]); currentUserId = normalizeId(results[1] && results[1].userId); setLineageEnabled(access.canEditContent); renderTree(results[2]); renderListSummary(results[3]); renderLineageList(results[4]); renderPagination(results[4]); renderOptions(results[5]); renderGenerationOptions(results[6]); if (access.canEditContent) { renderGenealogyMemberOptions(await api.genealogyMemberOptions(genealogyId)); } } catch (error) { if (redirectUnauthorized(api, error)) return; if (isForbidden(error)) { renderForbiddenState(); return; } ['[data-lineage-tree]', '[data-lineage-list]'].forEach(function (selector) { var container = query(selector); if (container) container.innerHTML = '
    世系人物加载失败,请稍后重试
    '; }); showMessage(error.message || '世系人物加载失败'); } } async function loadPersonDetail(personId) { var api = getApi(); var genealogyId; if (redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !personId) return; try { renderDetail(await api.lineagePersonDetail(genealogyId, personId)); } catch (error) { if (redirectUnauthorized(api, error)) return; if (isForbidden(error)) { renderForbiddenState(); return; } showMessage(error.message || '人物详情加载失败'); } } function setRelationMode(mode) { var person = peopleById[selectedPersonId]; var submitButton = query('[data-lineage-form] button[type="submit"]'); if (!managementAccess || writePending) return; if (!person) { showMessage('请先从成员列表、世系树或下拉框选择当前成员'); return; } relationMode = mode; editingPersonId = ''; setRelationFieldVisibility(mode === 'spouses'); if (submitButton) submitButton.textContent = '新增' + relationLabel(mode); setFormStatus('当前将为“' + person.name + '”新增' + relationLabel(mode)); } function toLocalDateTime(value) { var text = trimOrUndefined(value); return text ? text.slice(0, 16).replace(' ', 'T') : ''; } function setRelationFieldVisibility(visible) { var field = query('[data-lineage-spouse-field]'); if (field) field.hidden = !visible; } function fillForm(person) { var form = query('[data-lineage-form]'); var values; var submitButton; var binding; if (!form || !person) return; binding = normalizeLineageBinding(person, currentUserId); values = { bindingMode: binding.bindingMode, appUserId: binding.appUserId, avatarOssId: person.avatarOssId, name: person.name, sex: person.sex, generation: person.generation, generationName: person.generationName, fatherId: person.fatherId, motherId: person.motherId, personNo: person.personNo, aliasName: person.aliasName, birthDate: toLocalDateTime(person.birthDate), birthLunar: person.birthLunar, birthPlace: person.birthPlace, deathDate: toLocalDateTime(person.deathDate), deathLunar: person.deathLunar, deathPlace: person.deathPlace, burialPlace: person.burialPlace, personStatus: person.personStatus, sortOrder: person.sortOrder, relationName: person.relationName, biography: person.biography, remark: person.remark }; queryAll('[name]', form).forEach(function (field) { field.value = values[field.name] === undefined || values[field.name] === null ? '' : values[field.name]; }); updateBindingFields(); relationMode = ''; editingPersonId = person.personId; setRelationFieldVisibility(false); submitButton = query('[data-lineage-form] button[type="submit"]'); if (submitButton) submitButton.textContent = '保存修改'; setFormStatus('正在编辑“' + person.name + '”'); } function resetEditor() { var submitButton = query('[data-lineage-form] button[type="submit"]'); relationMode = ''; editingPersonId = ''; setRelationFieldVisibility(false); if (submitButton) submitButton.textContent = '保存成员'; setFormStatus(''); if (root.setTimeout) root.setTimeout(updateBindingFields, 0); } async function submitPerson(form) { var api = getApi(); var genealogyId; var body = buildLineagePersonBody(getFormValues(form)); var validation = validateLineagePersonBody(body); var result; if (writePending || !managementAccess || redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId) return; if (validation) { setFormStatus(validation); return; } setWritePending(true); setFormStatus('正在保存成员...'); try { if (editingPersonId) { result = await api.updateLineagePerson(genealogyId, editingPersonId, body); } else if (relationMode === 'parents') { result = await api.createLineageParent(genealogyId, selectedPersonId, body); } else if (relationMode === 'spouses') { result = await api.createLineageSpouse(genealogyId, selectedPersonId, body); } else if (relationMode === 'children') { result = await api.createLineageChild(genealogyId, selectedPersonId, body); } else if (relationMode === 'siblings') { result = await api.createLineageSibling(genealogyId, selectedPersonId, body); } else { result = await api.createLineagePerson(genealogyId, body); } form.reset(); resetEditor(); setFormStatus('成员已保存'); await loadLineage(getCurrentKeyword(), currentPageNum); if (getPersonId(result)) await loadPersonDetail(getPersonId(result)); } catch (error) { if (redirectUnauthorized(api, error)) return; if (isForbidden(error)) { renderForbiddenState(); return; } setFormStatus(error.message || '成员保存失败'); } finally { setWritePending(false); } } async function disablePerson(personId) { var api = getApi(); var genealogyId; var person = peopleById[personId]; if (writePending || !managementAccess || redirectUnauthorized(api)) return; genealogyId = requireGenealogyId(); if (!genealogyId || !personId) return; if (root.confirm && !root.confirm('确认停用“' + (person ? person.name : '该成员') + '”吗?有正常子女时后端会拒绝停用。')) return; setWritePending(true); try { await api.disableLineagePerson(genealogyId, personId); selectedPersonId = ''; resetEditor(); await loadLineage(getCurrentKeyword(), currentPageNum); showMessage('成员已停用'); } catch (error) { if (redirectUnauthorized(api, error)) return; if (isForbidden(error)) { renderForbiddenState(); return; } showMessage(error.message || '成员停用失败'); } finally { setWritePending(false); } } function bindActions() { if (!documentRef) return; documentRef.addEventListener('submit', function (event) { var form = event.target.closest('[data-lineage-form]'); if (!form) return; event.preventDefault(); submitPerson(form); }); documentRef.addEventListener('reset', function (event) { if (event.target.closest('[data-lineage-form]')) resetEditor(); }); documentRef.addEventListener('change', function (event) { var select = event.target.closest('[data-lineage-person-options]'); var generationSelect = event.target.closest('[data-lineage-generation]'); var bindingSelect = event.target.closest('[name="bindingMode"]'); var selectedOption; var generationName; var appUserId; if (select && select.value) loadPersonDetail(select.value); if (generationSelect) { selectedOption = generationSelect.options[generationSelect.selectedIndex]; generationName = query('[name="generationName"]', generationSelect.form); if (generationName) generationName.value = selectedOption ? selectedOption.getAttribute('data-generation-name') || '' : ''; } if (bindingSelect) { appUserId = query('[name="appUserId"]', bindingSelect.form); if (bindingSelect.value !== 'SPECIFIED' && appUserId) appUserId.value = ''; updateBindingFields(); } }); documentRef.addEventListener('click', function (event) { var target = event.target; var personButton = target.closest('[data-lineage-person]'); var relationButton = target.closest('[data-lineage-relation]'); var editButton = target.closest('[data-lineage-edit]'); var disableButton = target.closest('[data-lineage-disable]'); var pageButton = target.closest('[data-lineage-page-action]'); if (target.closest('[data-lineage-search]')) { event.preventDefault(); loadLineage(getCurrentKeyword(), 1); return; } if (pageButton) { event.preventDefault(); if (!canUseLineagePagination(writePending)) return; if (pageButton.getAttribute('data-lineage-page-action') === 'previous' && currentPageNum > 1) { loadLineage(getCurrentKeyword(), currentPageNum - 1); } if (pageButton.getAttribute('data-lineage-page-action') === 'next' && currentPageTotal !== null && currentPageNum * currentPageSize < currentPageTotal) { loadLineage(getCurrentKeyword(), currentPageNum + 1); } return; } if (personButton) { event.preventDefault(); loadPersonDetail(personButton.getAttribute('data-lineage-person')); return; } if (relationButton) { event.preventDefault(); setRelationMode(relationButton.getAttribute('data-lineage-relation')); return; } if (editButton) { event.preventDefault(); fillForm(peopleById[editButton.getAttribute('data-lineage-edit')]); return; } if (disableButton) { event.preventDefault(); disablePerson(disableButton.getAttribute('data-lineage-disable')); } }); } function init() { if (!documentRef) return; bindActions(); if (query('[data-lineage-page]')) loadLineage(getCurrentKeyword(), 1); } return { getCurrentGenealogyId: getCurrentGenealogyId, getPersonId: getPersonId, buildLineagePersonBody: buildLineagePersonBody, validateLineagePersonBody: validateLineagePersonBody, normalizeLineagePerson: normalizeLineagePerson, normalizeLineageAccess: normalizeLineageAccess, canUseLineagePagination: canUseLineagePagination, normalizeGenerationOption: normalizeGenerationOption, normalizeGenealogyMemberOption: normalizeGenealogyMemberOption, normalizeGenealogyMemberOptions: normalizeGenealogyMemberOptions, normalizeLineageBinding: normalizeLineageBinding, buildLineagePageQuery: buildLineagePageQuery, normalizeList: normalizeList, renderLineageTreeHtml: renderLineageTreeHtml, relationLabel: relationLabel, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });