(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.MemberAdminPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.MemberAdminPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; function confirmAction(message) { if (root.ProfileUI && typeof root.ProfileUI.confirmAction === 'function') { return root.ProfileUI.confirmAction(message); } return Promise.resolve(!root['confirm'] || root['confirm'](message)); } var documentRef = root.document; var membersById = Object.create(null); var selectedMemberId = ''; var currentMemberId = ''; var actorRole = ''; var managementAccess = false; var writePending = false; var responseRoles = ['owner', 'admin', 'editor', 'member', 'visitor']; var assignableRoles = ['admin', 'editor', 'member']; var roleLabels = { owner: '谱主', admin: '管理员', editor: '编辑', member: '成员', visitor: '访客' }; function getApi() { return root.GenealogyApi && root.GenealogyApi.defaultClient; } function query(selector, node) { return documentRef ? (node || documentRef).querySelector(selector) : null; } function queryAll(selector, node) { return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : []; } function stringValue(value) { return value === undefined || value === null ? '' : String(value); } function trimOrUndefined(value) { var text = stringValue(value).trim(); return text || undefined; } function normalizeId(value) { if (value === undefined || value === null || value === '') return ''; if (typeof value === 'number' && !Number.isSafeInteger(value)) return ''; return /^[1-9][0-9]*$/.test(String(value)) ? String(value) : ''; } function getCurrentGenealogyId(search) { var source = search === undefined && root.location ? root.location.search : search; var contextId; var params; if (search === undefined && root.ProfileUI && root.ProfileUI.getGenealogyId) { contextId = root.ProfileUI.getGenealogyId(); if (contextId) return normalizeId(contextId); } params = new URLSearchParams(stringValue(source).replace(/^\?/, '')); return normalizeId(params.get('genealogyId')); } function normalizeMember(item) { var memberId = normalizeId(item && item.memberId); var genealogyId = normalizeId(item && item.genealogyId); var appUserId = normalizeId(item && item.appUserId); var lineagePersonId = normalizeId(item && item.lineagePersonId); var inviterUserId = normalizeId(item && item.inviterUserId); var roleType = stringValue(item && item.roleType); var status = stringValue(item && item.status); var member; if (!memberId || !genealogyId || !appUserId || responseRoles.indexOf(roleType) < 0 || status !== '0') return null; if (item.lineagePersonId !== undefined && item.lineagePersonId !== null && item.lineagePersonId !== '' && !lineagePersonId) return null; if (item.inviterUserId !== undefined && item.inviterUserId !== null && item.inviterUserId !== '' && !inviterUserId) return null; member = { memberId: memberId, genealogyId: genealogyId, genealogyNo: stringValue(item.genealogyNo), genealogyName: stringValue(item.genealogyName), surname: stringValue(item.surname), appUserId: appUserId, appUserNickName: stringValue(item.appUserNickName), appUserPhone: stringValue(item.appUserPhone), lineagePersonNo: stringValue(item.lineagePersonNo), lineagePersonName: stringValue(item.lineagePersonName), memberName: stringValue(item.memberName), roleType: roleType, relationName: stringValue(item.relationName), joinSource: stringValue(item.joinSource), inviterNickName: stringValue(item.inviterNickName), inviterPhone: stringValue(item.inviterPhone), joinTime: stringValue(item.joinTime), status: status }; if (lineagePersonId) member.lineagePersonId = lineagePersonId; if (inviterUserId) member.inviterUserId = inviterUserId; return member; } function normalizeMembers(data) { var normalized; if (!Array.isArray(data)) return []; normalized = data.map(normalizeMember); return normalized.some(function (member) { return !member; }) ? [] : normalized; } function normalizeLineageOption(item) { var personId = normalizeId(item && item.personId); var name = trimOrUndefined(item && item.name); if (!personId || !name) return null; return { personId: personId, name: name, generationName: stringValue(item.generationName) }; } function buildMemberUpdateBody(values) { var source = values || {}; var body = {}; var memberName = trimOrUndefined(source.memberName); var relationName = trimOrUndefined(source.relationName); var roleType = trimOrUndefined(source.roleType); var lineagePersonId = normalizeId(source.lineagePersonId); if (roleType !== undefined && assignableRoles.indexOf(roleType) < 0) { throw new Error('成员角色只能是管理员、编辑或成员'); } if (memberName !== undefined) body.memberName = memberName; if (relationName !== undefined) body.relationName = relationName; if (roleType !== undefined) body.roleType = roleType; if (source.lineagePersonId !== undefined && source.lineagePersonId !== null && source.lineagePersonId !== '') { if (lineagePersonId) body.lineagePersonId = lineagePersonId; else body.invalidLineagePersonId = true; } return body; } function validateMemberUpdateBody(body) { if (!body) return '成员修改内容无效'; if (body.memberName !== undefined && body.memberName.length > 50) return '成员名称不能超过 50 个字符'; if (body.relationName !== undefined && body.relationName.length > 100) return '关系名称不能超过 100 个字符'; if (body.roleType !== undefined && assignableRoles.indexOf(body.roleType) < 0) return '成员角色只能是管理员、编辑或成员'; if (body.invalidLineagePersonId) return '世系人物选项无效'; return ''; } function canEditMember(role, targetRole) { if (targetRole === 'owner') return false; if (targetRole === 'admin') return role === 'owner'; return role === 'owner' || role === 'admin'; } function canRemoveMember(role, targetRole) { return canEditMember(role, targetRole); } function canLeaveGenealogy(role) { return Boolean(role && role !== 'owner'); } function canTransferOwner(role, canManage) { return role === 'owner' && canManage === true; } function buildOwnerTransferBody(targetMemberId) { return { targetMemberId: normalizeId(targetMemberId) }; } function validateOwnerTransferBody(body, ownerMemberId) { if (!body || !body.targetMemberId) return '请选择新谱主'; if (normalizeId(ownerMemberId) && body.targetMemberId === normalizeId(ownerMemberId)) return '不能转让给自己'; return ''; } function escapeHtml(value) { return stringValue(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function memberDisplayName(member) { return member.memberName || member.lineagePersonName || member.appUserNickName || '未命名成员'; } function renderMemberRow(member, context) { var settings = context || {}; var meta = [ roleLabels[member.roleType] || member.roleType, member.relationName, member.lineagePersonName ? '世系人物:' + member.lineagePersonName : '', member.joinTime ? '加入:' + member.joinTime : '' ].filter(Boolean).join(' · '); var actions = ''; if (settings.canManage && canEditMember(settings.actorRole, member.roleType)) { actions = ''; } return '

' + escapeHtml(memberDisplayName(member)) + (member.memberId === settings.currentMemberId ? ' 当前账号' : '') + '

' + escapeHtml(meta) + '

' + actions + '
'; } 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'); else root.location.href = 'login.html'; return true; } function showMessage(message) { if (root.layui && root.layui.layer) root.layui.layer.msg(message); else if (root.alert) root.alert(message); } function setStatus(selector, message, type) { var target = query(selector); if (!target) return; if (!message) { target.innerHTML = ''; return; } if (type && root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type, message); return; } target.textContent = message; } function setMemberListState(type, message) { var target = query('[data-member-list]'); if (!target) return; if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type, message); return; } target.innerHTML = '
' + escapeHtml(message) + '
'; } function setPendingButton(selector, pending, pendingLabel) { queryAll(selector).forEach(function (button) { if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent; button.textContent = pending ? pendingLabel : button.dataset.defaultLabel; button.setAttribute('aria-busy', pending ? 'true' : 'false'); }); } function syncLinks() { if (root.ProfileUI && root.ProfileUI.syncGenealogyContextLinks) root.ProfileUI.syncGenealogyContextLinks(); } function setWritePending(value) { writePending = Boolean(value); queryAll('[data-member-form] input, [data-member-form] select, [data-member-form] button, [data-owner-transfer-form] select, [data-owner-transfer-form] button, [data-member-edit-id], [data-member-remove-id], [data-member-leave]').forEach(function (control) { control.disabled = writePending; }); setPendingButton('[data-member-submit]', writePending, '正在保存…'); setPendingButton('[data-owner-transfer-submit]', writePending, '正在转让…'); setPendingButton('[data-member-leave]', writePending, '正在退出…'); } function renderLineageOptions(data) { var select = query('[data-member-lineage-options]'); var options = Array.isArray(data) ? data.map(normalizeLineageOption).filter(Boolean) : []; if (!select) return options; select.innerHTML = '' + options.map(function (option) { var label = option.name + (option.generationName ? ' · ' + option.generationName : ''); return ''; }).join(''); return options; } function renderTransferOptions(members) { var select = query('[data-owner-transfer-target]'); var targets = members.filter(function (member) { return member.memberId !== currentMemberId && member.roleType !== 'owner'; }); if (!select) return targets; select.innerHTML = '' + targets.map(function (member) { return ''; }).join(''); return targets; } function renderMembers(data) { var container = query('[data-member-list]'); var members = normalizeMembers(data); membersById = Object.create(null); members.forEach(function (member) { membersById[member.memberId] = member; }); if (!container) return members; if (!Array.isArray(data) || (data.length && !members.length)) { setMemberListState('error', '成员响应缺少稳定 GenealogyMemberVo 字段,请联系后端核对。'); return []; } if (!members.length) { setMemberListState('empty', '暂无正常成员'); renderTransferOptions(members); return members; } container.innerHTML = members.map(function (member) { return renderMemberRow(member, { actorRole: actorRole, currentMemberId: currentMemberId, canManage: managementAccess }); }).join(''); renderTransferOptions(members); return members; } function renderRoleOptions(targetRole) { var select = query('[data-member-role-options]'); var roles = actorRole === 'owner' ? assignableRoles : ['editor', 'member']; if (!select) return; select.innerHTML = roles.map(function (role) { return ''; }).join(''); select.value = roles.indexOf(targetRole) >= 0 ? targetRole : roles[roles.length - 1]; } function fillMemberForm(member) { var form = query('[data-member-form]'); if (!form || !member || !managementAccess || !canEditMember(actorRole, member.roleType)) return; selectedMemberId = member.memberId; form.hidden = false; query('[name="memberName"]', form).value = member.memberName; query('[name="relationName"]', form).value = member.relationName; renderRoleOptions(member.roleType); query('[name="lineagePersonId"]', form).value = member.lineagePersonId || ''; if (query('[data-member-form-title]')) query('[data-member-form-title]').textContent = '编辑“' + memberDisplayName(member) + '”'; setStatus('[data-member-form-status]', ''); } function resetMemberForm() { var form = query('[data-member-form]'); selectedMemberId = ''; if (form) { form.reset(); form.hidden = true; } setStatus('[data-member-form-status]', ''); } async function fetchAndRenderMembers(api, genealogyId) { var data = await api.genealogyMembers(genealogyId); var members = renderMembers(data); if (Array.isArray(data) && data.length && !members.length) throw new Error('成员响应字段不完整'); return members; } function renderNoContext() { setMemberListState('forbidden', '请先选择家谱,再管理成员。'); setStatus('[data-member-overview]', '当前没有家谱上下文。', 'forbidden'); setStatus('[data-member-action-status]', '请先选择家谱,再进行成员操作。', 'forbidden'); queryAll('[data-member-management], [data-member-leave-section], [data-owner-transfer-section]').forEach(function (element) { element.hidden = true; }); } async function loadPage() { var api = getApi(); var genealogyId; var results; var detail; var profileUserId; var members; var currentMember; if (redirectUnauthorized(api)) return; genealogyId = getCurrentGenealogyId(); if (!genealogyId) { renderNoContext(); return; } syncLinks(); setStatus('[data-member-overview]', '正在加载家谱和当前权限…', 'loading'); setMemberListState('loading', '正在加载成员…'); setStatus('[data-member-action-status]', ''); try { results = await Promise.all([ api.genealogyDetail(genealogyId), api.currentProfile(), api.genealogyMembers(genealogyId), api.lineagePersonOptions(genealogyId) ]); detail = results[0] || {}; profileUserId = normalizeId(results[1] && results[1].userId); actorRole = responseRoles.indexOf(stringValue(detail.roleType)) >= 0 ? stringValue(detail.roleType) : ''; managementAccess = detail.canManage === true; members = normalizeMembers(results[2]); currentMember = members.find(function (member) { return member.appUserId === profileUserId; }) || null; currentMemberId = currentMember ? currentMember.memberId : ''; if (!actorRole && currentMember) actorRole = currentMember.roleType; setStatus('[data-member-overview]', (detail.genealogyName || '当前家谱') + ' · ' + members.length + ' 位正常成员 · 当前角色:' + (roleLabels[actorRole] || '成员')); queryAll('[data-member-management]').forEach(function (element) { element.hidden = !managementAccess; }); queryAll('[data-member-leave-section]').forEach(function (element) { element.hidden = !currentMember || !canLeaveGenealogy(actorRole); }); queryAll('[data-owner-transfer-section]').forEach(function (element) { element.hidden = !currentMember || !canTransferOwner(actorRole, managementAccess); }); renderLineageOptions(results[3]); renderMembers(results[2]); } catch (error) { if (redirectUnauthorized(api, error)) return; setMemberListState(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '当前账号无权查看该家谱成员。' : '成员加载失败,请稍后重试。'); setStatus('[data-member-overview]', isForbidden(error) ? '当前账号无权查看该家谱成员。' : '成员加载失败,请稍后重试。', isForbidden(error) ? 'forbidden' : 'error'); showMessage(isForbidden(error) ? '当前账号无权查看该家谱成员。' : (error.message || '成员加载失败')); } } function getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } async function submitMember(form) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var target = membersById[selectedMemberId]; var body; var validation; var saved; if (writePending || !genealogyId || !target || !managementAccess || !canEditMember(actorRole, target.roleType) || redirectUnauthorized(api)) return; body = buildMemberUpdateBody(getFormValues(form)); validation = validateMemberUpdateBody(body); if (validation) { setStatus('[data-member-form-status]', validation, 'error'); return; } delete body.invalidLineagePersonId; setWritePending(true); setStatus('[data-member-form-status]', '正在保存成员…', 'loading'); try { saved = normalizeMember(await api.updateGenealogyMember(genealogyId, selectedMemberId, body)); if (!saved || saved.memberId !== selectedMemberId) throw new Error('成员修改响应缺少稳定 memberId'); if (!(await fetchAndRenderMembers(api, genealogyId)).some(function (member) { return member.memberId === saved.memberId; })) { throw new Error('修改后成员列表未返回同一成员'); } resetMemberForm(); showMessage('成员信息已更新'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-member-form-status]', isForbidden(error) ? '当前账号无权修改该成员。' : (error.message || '成员修改失败'), isForbidden(error) ? 'forbidden' : 'error'); } finally { setWritePending(false); } } async function removeMember(memberId) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var target = membersById[memberId]; if (writePending || !genealogyId || !target || !managementAccess || !canRemoveMember(actorRole, target.roleType) || redirectUnauthorized(api)) return; if (!await confirmAction('确认将“' + memberDisplayName(target) + '”移出当前家谱吗?该操作不会删除其世系人物资料。')) return; setWritePending(true); setStatus('[data-member-action-status]', '正在移出成员…', 'loading'); try { await api.removeGenealogyMember(genealogyId, memberId); if ((await fetchAndRenderMembers(api, genealogyId)).some(function (member) { return member.memberId === memberId; })) { throw new Error('移出后成员仍存在于正常成员列表'); } if (selectedMemberId === memberId) resetMemberForm(); showMessage('成员已移出家谱'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-member-action-status]', isForbidden(error) ? '当前账号无权移出该成员。' : (error.message || '移出成员失败'), isForbidden(error) ? 'forbidden' : 'error'); showMessage(isForbidden(error) ? '当前账号无权移出该成员。' : (error.message || '移出成员失败')); } finally { setWritePending(false); } } async function leaveGenealogy() { var api = getApi(); var genealogyId = getCurrentGenealogyId(); if (writePending || !genealogyId || !currentMemberId || !canLeaveGenealogy(actorRole) || redirectUnauthorized(api)) return; if (!await confirmAction('确认退出当前家谱吗?退出后需重新受邀才能进入。世系人物资料不会被删除。')) return; setWritePending(true); setStatus('[data-member-action-status]', '正在退出当前家谱…', 'loading'); try { await api.leaveGenealogy(genealogyId); if (root.location) root.location.href = 'profile-families.html'; } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-member-action-status]', isForbidden(error) ? '当前账号无权退出该家谱。' : (error.message || '退出家谱失败'), isForbidden(error) ? 'forbidden' : 'error'); showMessage(isForbidden(error) ? '当前账号无权退出该家谱。' : (error.message || '退出家谱失败')); } finally { setWritePending(false); } } async function transferOwner(form) { var api = getApi(); var genealogyId = getCurrentGenealogyId(); var body = buildOwnerTransferBody(query('[name="targetMemberId"]', form).value); var validation = validateOwnerTransferBody(body, currentMemberId); var target = membersById[body.targetMemberId]; if (writePending || !genealogyId || !canTransferOwner(actorRole, managementAccess) || redirectUnauthorized(api)) return; if (validation || !target) { setStatus('[data-owner-transfer-status]', validation || '请选择成员列表中的新谱主', 'error'); return; } if (!await confirmAction('确认将谱主转让给“' + memberDisplayName(target) + '”吗?转让后当前账号将变为管理员。')) return; setWritePending(true); setStatus('[data-owner-transfer-status]', '正在转让谱主…', 'loading'); try { await api.transferGenealogyOwner(genealogyId, body); await loadPage(); setStatus('[data-owner-transfer-status]', '谱主已转让'); } catch (error) { if (redirectUnauthorized(api, error)) return; setStatus('[data-owner-transfer-status]', isForbidden(error) ? '只有当前谱主可以执行转让。' : (error.message || '谱主转让失败'), isForbidden(error) ? 'forbidden' : 'error'); } finally { setWritePending(false); } } function bindActions() { if (!documentRef) return; documentRef.addEventListener('click', function (event) { var editButton = event.target.closest('[data-member-edit-id]'); var removeButton = event.target.closest('[data-member-remove-id]'); var leaveButton = event.target.closest('[data-member-leave]'); if (editButton) { event.preventDefault(); fillMemberForm(membersById[editButton.getAttribute('data-member-edit-id')]); } else if (removeButton) { event.preventDefault(); removeMember(removeButton.getAttribute('data-member-remove-id')); } else if (leaveButton) { event.preventDefault(); leaveGenealogy(); } }); documentRef.addEventListener('submit', function (event) { var memberForm = event.target.closest('[data-member-form]'); var transferForm = event.target.closest('[data-owner-transfer-form]'); if (memberForm) { event.preventDefault(); submitMember(memberForm); } else if (transferForm) { event.preventDefault(); transferOwner(transferForm); } }); documentRef.addEventListener('reset', function (event) { if (event.target.closest('[data-member-form]')) resetMemberForm(); }); } function init() { if (!documentRef || !query('[data-member-admin-page]')) return; bindActions(); loadPage(); } return { getCurrentGenealogyId: getCurrentGenealogyId, normalizeMember: normalizeMember, normalizeMembers: normalizeMembers, normalizeLineageOption: normalizeLineageOption, buildMemberUpdateBody: buildMemberUpdateBody, validateMemberUpdateBody: validateMemberUpdateBody, canEditMember: canEditMember, canRemoveMember: canRemoveMember, canLeaveGenealogy: canLeaveGenealogy, canTransferOwner: canTransferOwner, buildOwnerTransferBody: buildOwnerTransferBody, validateOwnerTransferBody: validateOwnerTransferBody, renderMemberRow: renderMemberRow, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });