632 lines
25 KiB
JavaScript
632 lines
25 KiB
JavaScript
(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, '"')
|
|
.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 = '<button class="pill" type="button" data-member-edit-id="' + escapeHtml(member.memberId) +
|
|
'">编辑</button><button class="pill is-danger" type="button" data-member-remove-id="' +
|
|
escapeHtml(member.memberId) + '">移出家谱</button>';
|
|
}
|
|
return '<article class="module-row member-admin-row"><div><h3>' + escapeHtml(memberDisplayName(member)) +
|
|
(member.memberId === settings.currentMemberId ? ' <span class="pill">当前账号</span>' : '') +
|
|
'</h3><p>' + escapeHtml(meta) + '</p></div><div class="row-actions">' + actions + '</div></article>';
|
|
}
|
|
|
|
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 = '<div class="api-state api-state--' + escapeHtml(type) + '" data-api-state="' + escapeHtml(type) + '">' +
|
|
escapeHtml(message) + '</div>';
|
|
}
|
|
|
|
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 = '<option value="">不修改世系人物绑定</option>' + options.map(function (option) {
|
|
var label = option.name + (option.generationName ? ' · ' + option.generationName : '');
|
|
|
|
return '<option value="' + escapeHtml(option.personId) + '">' + escapeHtml(label) + '</option>';
|
|
}).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 = '<option value="">请选择新谱主</option>' + targets.map(function (member) {
|
|
return '<option value="' + escapeHtml(member.memberId) + '">' +
|
|
escapeHtml(memberDisplayName(member) + ' · ' + (roleLabels[member.roleType] || member.roleType)) +
|
|
'</option>';
|
|
}).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 '<option value="' + role + '">' + roleLabels[role] + '</option>';
|
|
}).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
|
|
};
|
|
});
|