feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询 - 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作 - 集成通知详情获取方法和通知ID安全验证机制 - 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约 - 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作 - 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示 - 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
@@ -0,0 +1,581 @@
|
||||
(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';
|
||||
|
||||
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 = source.relationName === undefined || source.relationName === null
|
||||
? undefined
|
||||
: stringValue(source.relationName).trim();
|
||||
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) {
|
||||
return role === 'owner';
|
||||
}
|
||||
|
||||
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) {
|
||||
var target = query(selector);
|
||||
|
||||
if (target) target.textContent = message || '';
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
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)) {
|
||||
container.innerHTML = '<div class="api-empty">成员响应缺少稳定 GenealogyMemberVo 字段,请联系后端核对。</div>';
|
||||
return [];
|
||||
}
|
||||
container.innerHTML = members.length ? members.map(function (member) {
|
||||
return renderMemberRow(member, {
|
||||
actorRole: actorRole,
|
||||
currentMemberId: currentMemberId,
|
||||
canManage: managementAccess
|
||||
});
|
||||
}).join('') : '<div class="api-empty">暂无正常成员</div>';
|
||||
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() {
|
||||
if (query('[data-member-list]')) query('[data-member-list]').innerHTML = '<div class="api-empty">请先选择家谱,再管理成员。</div>';
|
||||
setStatus('[data-member-overview]', '当前没有家谱上下文。');
|
||||
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();
|
||||
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); });
|
||||
renderLineageOptions(results[3]);
|
||||
renderMembers(results[2]);
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
if (query('[data-member-list]')) query('[data-member-list]').innerHTML = '<div class="api-empty">' +
|
||||
(isForbidden(error) ? '当前账号无权查看该家谱成员。' : '成员加载失败,请稍后重试。') + '</div>';
|
||||
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);
|
||||
return;
|
||||
}
|
||||
delete body.invalidLineagePersonId;
|
||||
setWritePending(true);
|
||||
setStatus('[data-member-form-status]', '正在保存成员…');
|
||||
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 || '成员修改失败'));
|
||||
} 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 (root.confirm && !root.confirm('确认将“' + memberDisplayName(target) + '”移出当前家谱吗?该操作不会删除其世系人物资料。')) return;
|
||||
setWritePending(true);
|
||||
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;
|
||||
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 (root.confirm && !root.confirm('确认退出当前家谱吗?退出后需重新受邀才能进入。世系人物资料不会被删除。')) return;
|
||||
setWritePending(true);
|
||||
try {
|
||||
await api.leaveGenealogy(genealogyId);
|
||||
if (root.location) root.location.href = 'profile-families.html';
|
||||
} catch (error) {
|
||||
if (redirectUnauthorized(api, error)) return;
|
||||
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) || redirectUnauthorized(api)) return;
|
||||
if (validation || !target) {
|
||||
setStatus('[data-owner-transfer-status]', validation || '请选择成员列表中的新谱主');
|
||||
return;
|
||||
}
|
||||
if (root.confirm && !root.confirm('确认将谱主转让给“' + memberDisplayName(target) + '”吗?转让后当前账号将变为管理员。')) return;
|
||||
setWritePending(true);
|
||||
setStatus('[data-owner-transfer-status]', '正在转让谱主…');
|
||||
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 || '谱主转让失败'));
|
||||
} 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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user