(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.JoinPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.JoinPages.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 APPLY_STATUS = { '0': '待审核', '1': '已通过', '2': '已拒绝', '3': '已撤销' }; function escapeHtml(value) { return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function renderApiState(type, message) { if (root.ProfileUI && root.ProfileUI.renderApiState) { return root.ProfileUI.renderApiState(type, message); } return '
' + escapeHtml(message) + '
'; } function normalizeId(value) { var text; if (typeof value === 'number' && !Number.isSafeInteger(value)) return ''; if (value === undefined || value === null) return ''; text = String(value).trim(); return /^[1-9][0-9]*$/.test(text) ? text : ''; } function optionalText(value) { var text; if (value === undefined || value === null) return ''; text = String(value).trim(); return text; } function buildJoinApplyBody(values) { var source = values || {}; var body = {}; ['applicantName', 'phone', 'relationDesc', 'applyReason'].forEach(function (name) { var value = optionalText(source[name]); if (value) body[name] = value; }); return body; } function validateJoinApplyBody(body) { var source = body || {}; var limits = { applicantName: [50, '申请人姓名不能超过 50 个字符'], phone: [30, '联系电话不能超过 30 个字符'], relationDesc: [100, '关系说明不能超过 100 个字符'], applyReason: [500, '申请理由不能超过 500 个字符'] }; var names = Object.keys(limits); var index; for (index = 0; index < names.length; index += 1) { if (String(source[names[index]] || '').length > limits[names[index]][0]) { return limits[names[index]][1]; } } return ''; } function normalizeJoinGenealogy(item) { var source = item || {}; var genealogyId = normalizeId(source.genealogyId); var genealogyName = optionalText(source.genealogyName); var surname = optionalText(source.surname); var status = source.status === undefined || source.status === null ? '' : String(source.status); if (!genealogyId || !genealogyName || !surname || (status && status !== '0')) return null; return { genealogyId: genealogyId, genealogyName: genealogyName, surname: surname, regionFullName: optionalText(source.regionFullName), memberCount: source.memberCount, joinMode: source.joinMode === undefined || source.joinMode === null ? '' : String(source.joinMode) }; } function normalizeJoinApply(item) { var source = item || {}; var applyId = normalizeId(source.applyId); var genealogyId = normalizeId(source.genealogyId); var genealogyName = optionalText(source.genealogyName); var status = source.status === undefined || source.status === null ? '' : String(source.status); if (!applyId || !genealogyId || !genealogyName || !Object.prototype.hasOwnProperty.call(APPLY_STATUS, status)) { return null; } return { applyId: applyId, genealogyId: genealogyId, genealogyName: genealogyName, surname: optionalText(source.surname), relationDesc: optionalText(source.relationDesc), applyReason: optionalText(source.applyReason), auditRemark: optionalText(source.auditRemark), auditTime: optionalText(source.auditTime), status: status }; } function normalizeJoinApplies(data) { var items; if (!Array.isArray(data)) return []; items = data.map(normalizeJoinApply); return items.some(function (item) { return !item; }) ? [] : items; } function renderJoinGenealogyOptions(data) { var items; if (!Array.isArray(data)) return renderApiState('error', '家谱列表数据无效,请刷新重试'); items = data.map(normalizeJoinGenealogy).filter(Boolean); if (!items.length) return renderApiState('empty', '没有找到可申请加入的家谱'); return items.map(function (item) { var meta = [ item.surname + '氏', item.regionFullName, item.memberCount === undefined || item.memberCount === null ? '' : item.memberCount + ' 位成员' ].filter(Boolean).join(' · '); return ''; }).join(''); } function renderMyJoinApplies(data) { var items = normalizeJoinApplies(data); if (!items.length) return renderApiState('empty', '当前没有加入申请'); return items.map(function (item) { var details = [ item.surname ? item.surname + '氏' : '', item.relationDesc, item.applyReason, item.auditRemark ? '审核说明:' + item.auditRemark : '', item.auditTime ? '审核时间:' + item.auditTime : '' ].filter(Boolean).join(' · '); var action = item.status === '0' ? '' : ''; return '

' + escapeHtml(item.genealogyName) + '

' + escapeHtml(details || '等待申请状态更新') + '

' + APPLY_STATUS[item.status] + '' + action + '
'; }).join(''); } function shouldRedirectToLogin(api, error) { var status = error && (error.status || error.code); return !api || !api.getToken || !api.getToken() || Number(status) === 401; } function redirectToLogin(api) { if (api && api.clearToken) api.clearToken(); if (!root.location) return; if (typeof root.location.replace === 'function') root.location.replace('login.html'); else root.location.href = 'login.html'; } function getApiStateType(error) { return Number(error && (error.status || error.code)) === 403 ? 'forbidden' : 'error'; } function setText(selector, message, type) { var element = root.document && root.document.querySelector(selector); if (!element) return; if (!message) { element.innerHTML = ''; return; } if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(element, type || 'error', message); return; } element.textContent = message; } function setOptionsState(options, type, message) { if (!options) return; if (root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(options, type, message); return; } options.innerHTML = renderApiState(type, message); } function setSubmitPending(button, pending, pendingLabel) { if (!button) return; if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent; button.disabled = Boolean(pending); button.textContent = pending ? pendingLabel : button.dataset.defaultLabel; button.setAttribute('aria-busy', pending ? 'true' : 'false'); } function getFormValues(form) { var values = {}; Array.prototype.forEach.call(form.querySelectorAll('[name]'), function (field) { values[field.name] = field.value; }); return values; } function canJoinGenealogy(quota) { var remaining = Number(quota && quota.joinRemaining); if (quota && typeof quota.canJoin === 'boolean') return quota.canJoin; return Number.isInteger(remaining) && (remaining === -1 || remaining > 0); } async function initJoinApplyPage() { var page = root.document && root.document.querySelector('[data-join-apply-page]'); var searchForm = root.document && root.document.querySelector('[data-join-genealogy-search]'); var options = root.document && root.document.querySelector('[data-join-genealogy-options]'); var applyForm = root.document && root.document.querySelector('[data-join-apply-form]'); var selected = root.document && root.document.querySelector('[data-join-selected-genealogy]'); var api = root.GenealogyApi && root.GenealogyApi.defaultClient; var selectedGenealogyId = ''; var writePending = false; if (!page) return; if (shouldRedirectToLogin(api)) { redirectToLogin(api); return; } function clearSelectedGenealogy() { selectedGenealogyId = ''; if (selected) selected.textContent = '尚未选择家谱'; } async function loadOptions(keyword) { var data; clearSelectedGenealogy(); setText('[data-join-options-status]', '正在读取可申请家谱…', 'loading'); setOptionsState(options, 'loading', '正在读取可申请家谱…'); try { data = await api.genealogyOptions(keyword ? { keyword: keyword } : {}); options.innerHTML = renderJoinGenealogyOptions(data); setText('[data-join-options-status]', ''); } catch (error) { if (shouldRedirectToLogin(api, error)) { redirectToLogin(api); return; } setOptionsState(options, getApiStateType(error), '读取家谱列表失败'); setText('[data-join-options-status]', error.message || '读取家谱列表失败,请稍后重试', getApiStateType(error)); } } try { if (!canJoinGenealogy(await api.genealogyQuota())) { setText('[data-join-apply-status]', '当前没有可用的家谱加入额度', 'empty'); applyForm.querySelector('[data-join-apply-submit]').disabled = true; return; } } catch (error) { if (shouldRedirectToLogin(api, error)) { redirectToLogin(api); return; } setText('[data-join-apply-status]', error.message || '无法读取家谱加入额度', getApiStateType(error)); applyForm.querySelector('[data-join-apply-submit]').disabled = true; return; } searchForm.addEventListener('submit', function (event) { event.preventDefault(); loadOptions(optionalText(searchForm.querySelector('[name="keyword"]').value)); }); options.addEventListener('click', function (event) { var button = event.target.closest('[data-join-genealogy-id]'); if (!button || !options.contains(button)) return; selectedGenealogyId = normalizeId(button.getAttribute('data-join-genealogy-id')); if (!selectedGenealogyId) return; selected.textContent = '已选择:' + button.getAttribute('data-join-genealogy-name'); Array.prototype.forEach.call(options.querySelectorAll('[data-join-genealogy-id]'), function (item) { item.classList.toggle('is-selected', item === button); }); }); applyForm.addEventListener('submit', async function (event) { var body; var validation; var created; var refreshed; event.preventDefault(); if (writePending) return; if (!selectedGenealogyId) { setText('[data-join-apply-status]', '请先从家谱列表中选择要加入的家谱', 'error'); return; } body = buildJoinApplyBody(getFormValues(applyForm)); validation = validateJoinApplyBody(body); if (validation) { setText('[data-join-apply-status]', validation, 'error'); return; } writePending = true; setSubmitPending(applyForm.querySelector('[data-join-apply-submit]'), true, '正在提交…'); setText('[data-join-apply-status]', '正在提交申请…', 'loading'); try { created = normalizeJoinApply(await api.applyToGenealogy(selectedGenealogyId, body)); if (!created) throw new Error('申请响应缺少有效申请信息'); refreshed = normalizeJoinApplies(await api.myGenealogyJoinApplies()); if (!refreshed.some(function (item) { return item.applyId === created.applyId; })) { throw new Error('提交后无法读取同一申请'); } if (root.location) root.location.href = 'profile-join-family.html'; } catch (error) { if (shouldRedirectToLogin(api, error)) { redirectToLogin(api); return; } setText('[data-join-apply-status]', error.message || '提交加入申请失败,请稍后重试', getApiStateType(error)); } finally { writePending = false; setSubmitPending(applyForm.querySelector('[data-join-apply-submit]'), false, '正在提交…'); } }); await loadOptions(''); } async function initMyJoinAppliesPage() { var page = root.document && root.document.querySelector('[data-my-join-applies-page]'); var list = root.document && root.document.querySelector('[data-join-apply-list="mine"]'); var refresh = root.document && root.document.querySelector('[data-join-apply-refresh]'); var api = root.GenealogyApi && root.GenealogyApi.defaultClient; var writePending = false; if (!page) return; if (shouldRedirectToLogin(api)) { redirectToLogin(api); return; } async function loadApplies() { var data; setText('[data-my-join-applies-status]', '正在读取申请记录…', 'loading'); try { data = await api.myGenealogyJoinApplies(); list.innerHTML = renderMyJoinApplies(data); setText('[data-my-join-applies-status]', ''); return normalizeJoinApplies(data); } catch (error) { if (shouldRedirectToLogin(api, error)) { redirectToLogin(api); return []; } list.innerHTML = renderApiState(getApiStateType(error), '读取加入申请失败'); setText('[data-my-join-applies-status]', error.message || '读取加入申请失败,请稍后重试', getApiStateType(error)); return []; } } refresh.addEventListener('click', function () { loadApplies(); }); list.addEventListener('click', async function (event) { var button = event.target.closest('[data-join-cancel-id]'); var applyId; var refreshed; if (!button || !list.contains(button) || writePending) return; applyId = normalizeId(button.getAttribute('data-join-cancel-id')); if (!applyId) return; if (!await confirmAction('确认撤销这条加入申请吗?')) return; writePending = true; button.disabled = true; setText('[data-my-join-applies-status]', '正在撤销申请…', 'loading'); try { await api.cancelGenealogyJoinApply(applyId); refreshed = await loadApplies(); if (refreshed.some(function (item) { return item.applyId === applyId && item.status === '0'; })) { throw new Error('撤销后申请仍处于待审核状态'); } } catch (error) { if (shouldRedirectToLogin(api, error)) { redirectToLogin(api); return; } setText('[data-my-join-applies-status]', error.message || '撤销加入申请失败,请稍后重试', getApiStateType(error)); } finally { writePending = false; } }); await loadApplies(); } async function init() { if (root.document && root.document.querySelector('[data-join-apply-page]')) { await initJoinApplyPage(); return; } if (root.document && root.document.querySelector('[data-my-join-applies-page]')) { await initMyJoinAppliesPage(); } } return { buildJoinApplyBody: buildJoinApplyBody, validateJoinApplyBody: validateJoinApplyBody, normalizeJoinGenealogy: normalizeJoinGenealogy, normalizeJoinApply: normalizeJoinApply, normalizeJoinApplies: normalizeJoinApplies, renderJoinGenealogyOptions: renderJoinGenealogyOptions, renderMyJoinApplies: renderMyJoinApplies, canJoinGenealogy: canJoinGenealogy, initJoinApplyPage: initJoinApplyPage, initMyJoinAppliesPage: initMyJoinAppliesPage, init: init }; });