(function (root, factory) { // 行政区划模块同时支持浏览器页面和 Node 单元测试。 if (typeof module === 'object' && module.exports) { module.exports = factory(root); return; } root.RegionPages = factory(root); if (root.document) { root.document.addEventListener('DOMContentLoaded', function () { root.RegionPages.init(); }); } })(typeof globalThis !== 'undefined' ? globalThis : window, function (root) { 'use strict'; var documentRef = root.document; function normalizeList(data) { // ApiClient 已解包 PC ListResult;组件只接收正式的数组结果。 if (Array.isArray(data)) return data; return []; } function escapeHtml(value) { // 地区名称和编码进入 option 前统一转义。 return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getRegionCode(item) { return item && item.regionCode || ''; } function getRegionName(item) { return item && item.regionName || '未命名地区'; } function getRegionLevel(item) { var level = Number(item && item.regionLevel); return Number.isInteger(level) ? level : 0; } function buildRegionSelection(items) { var selection = { provinceCode: '', cityCode: '', districtCode: '' }; normalizeList(items).forEach(function (item) { var code = getRegionCode(item); var level = getRegionLevel(item); if (level === 1) selection.provinceCode = code; if (level === 2) selection.cityCode = code; if (level === 3) selection.districtCode = code; }); return selection; } function buildRegionOption(item) { return ''; } function buildPathText(items) { return normalizeList(items).map(function (item) { return getRegionName(item); }).filter(Boolean).join(' / '); } function getFinalRegionCode(values) { var source = values || {}; return source.districtCode || source.cityCode || source.provinceCode || ''; } 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 redirectToLogin() { root.NavigationUtil.open('login.html'); } function redirectUnauthorized(api, error) { if (!shouldRedirectToLogin(api, error)) return false; if (api && api.clearToken) api.clearToken(); redirectToLogin(); return true; } function isForbidden(error) { return Number(error && (error.status || error.code)) === 403; } function query(selector, rootNode) { if (!documentRef && !rootNode) return null; return (rootNode || documentRef).querySelector(selector); } function queryAll(selector, rootNode) { if (!documentRef && !rootNode) return []; return 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 getFormValues(form) { var values = {}; queryAll('[name]', form).forEach(function (field) { values[field.name] = field.value; }); return values; } function setApiState(target, type, message) { if (!target) return; if (type && root.ProfileUI && root.ProfileUI.setApiState) { root.ProfileUI.setApiState(target, type, message); return; } target.textContent = message || ''; } function setPickerState(picker, type, message) { var status = picker && query('[data-region-picker-status]', picker.parentNode); setApiState(status, type, message); } function setOptions(select, items, placeholder) { var list = normalizeList(items); if (!select) return; select.innerHTML = '' + list.map(buildRegionOption).join(''); select.disabled = false; } function resetSelect(select, placeholder) { if (!select) return; select.__regionRequestVersion = (select.__regionRequestVersion || 0) + 1; select.innerHTML = ''; select.disabled = true; } function updateTargetCode(picker) { var values = { provinceCode: query('[data-region-level="province"]', picker).value, cityCode: query('[data-region-level="city"]', picker).value, districtCode: query('[data-region-level="district"]', picker).value }; var target = query('[data-region-code]', picker); if (target) target.value = getFinalRegionCode(values); } async function loadChildrenInto(select, parentCode, placeholder) { var api = getApi(); var requestVersion; var items; if (!api || !select) return false; requestVersion = (select.__regionRequestVersion || 0) + 1; select.__regionRequestVersion = requestVersion; select.disabled = true; try { items = await api.regionChildren(parentCode || '0'); if (select.__regionRequestVersion !== requestVersion) return false; setOptions(select, items, placeholder); return true; } catch (error) { if (select.__regionRequestVersion === requestVersion) { select.innerHTML = ''; select.disabled = true; } throw error; } } function handlePickerError(picker, error) { var api = getApi(); var message; if (redirectUnauthorized(api, error)) return; message = isForbidden(error) ? '当前账号无权读取地区数据。' : (error.message || '地区加载失败'); setPickerState(picker, isForbidden(error) ? 'forbidden' : 'error', message); showMessage(message); } function bindPickerEvents(picker, province, city, district) { if (picker.__regionEventsBound) return; picker.__regionEventsBound = true; province.addEventListener('change', function () { resetSelect(city, '请选择市'); resetSelect(district, '请选择区县'); updateTargetCode(picker); if (!province.value) { setPickerState(picker, '', ''); return; } setPickerState(picker, 'loading', '正在加载市…'); loadChildrenInto(city, province.value, '请选择市').then(function () { setPickerState(picker, '', ''); }).catch(function (error) { handlePickerError(picker, error); }); }); city.addEventListener('change', function () { resetSelect(district, '请选择区县'); updateTargetCode(picker); if (!city.value) { setPickerState(picker, '', ''); return; } setPickerState(picker, 'loading', '正在加载区县…'); loadChildrenInto(district, city.value, '请选择区县').then(function () { setPickerState(picker, '', ''); }).catch(function (error) { handlePickerError(picker, error); }); }); district.addEventListener('change', function () { updateTargetCode(picker); }); } function initPicker(picker) { var province = query('[data-region-level="province"]', picker); var city = query('[data-region-level="city"]', picker); var district = query('[data-region-level="district"]', picker); if (!picker || !province || !city || !district) return Promise.resolve(); if (picker.__regionInitPromise) return picker.__regionInitPromise; resetSelect(city, '请选择市'); resetSelect(district, '请选择区县'); bindPickerEvents(picker, province, city, district); setPickerState(picker, 'loading', '正在加载省…'); picker.__regionInitPromise = loadChildrenInto(province, '0', '请选择省').then(function (result) { setPickerState(picker, '', ''); return result; }).catch(function (error) { picker.__regionInitPromise = null; throw error; }); return picker.__regionInitPromise; } async function applyRegionSelection(picker, selection) { var province = query('[data-region-level="province"]', picker); var city = query('[data-region-level="city"]', picker); var district = query('[data-region-level="district"]', picker); var values = selection || {}; await initPicker(picker); if (!values.provinceCode) { province.value = ''; resetSelect(city, '请选择市'); resetSelect(district, '请选择区县'); updateTargetCode(picker); return; } province.value = values.provinceCode; resetSelect(city, '请选择市'); resetSelect(district, '请选择区县'); if (values.cityCode) { await loadChildrenInto(city, values.provinceCode, '请选择市'); city.value = values.cityCode; } if (values.districtCode && values.cityCode) { await loadChildrenInto(district, values.cityCode, '请选择区县'); district.value = values.districtCode; } updateTargetCode(picker); } function applyProfileRegionSelection(selection) { return Promise.all(queryAll('[data-region-picker]').map(function (picker) { return applyRegionSelection(picker, selection); })); } function renderSearchResults(container, items) { var list = normalizeList(items); if (!container) return; if (!list.length) { setApiState(container, 'empty', '未找到匹配地区'); return; } container.innerHTML = list.map(function (item) { var code = getRegionCode(item); var name = getRegionName(item); return ''; }).join(''); } async function submitRegionSearch(form) { var api = getApi(); var values = getFormValues(form); var results = query('[data-region-search-results]', form.parentNode); var pathText = query('[data-region-search-path]', form.parentNode); var status = query('[data-region-search-status]', form); var items; var message; if (redirectUnauthorized(api)) return; if (!values.regionKeyword || !values.regionKeyword.trim()) { setApiState(status, 'error', '请输入地区名称或编码'); showMessage('请输入地区名称或编码'); return; } setApiState(status, 'loading', '正在搜索地区…'); setApiState(results, 'loading', '正在搜索地区…'); if (pathText) pathText.textContent = '搜索中...'; try { items = await api.regionSearch({ keyword: values.regionKeyword.trim(), limit: 20 }); renderSearchResults(results, items); setApiState(status, '', ''); if (pathText) pathText.textContent = normalizeList(items).length ? '请选择一个地区' : '未找到匹配地区'; } catch (error) { if (redirectUnauthorized(api, error)) return; message = isForbidden(error) ? '当前账号无权搜索地区。' : (error.message || '地区搜索失败'); setApiState(results, isForbidden(error) ? 'forbidden' : 'error', message); setApiState(status, isForbidden(error) ? 'forbidden' : 'error', message); if (pathText) pathText.textContent = message; showMessage(message); } } async function selectSearchResult(button) { var api = getApi(); var code = button.getAttribute('data-region-search-result'); var section = button.closest('.module-panel'); var picker = section && query('[data-region-picker]', section); var pathText = section && query('[data-region-search-path]', section); var status = section && query('[data-region-search-status]', section); var detail; var path; var message; if (!code || !picker || redirectUnauthorized(api)) return; setApiState(status, 'loading', '正在定位地区…'); if (pathText) pathText.textContent = '正在定位地区...'; try { detail = await api.regionDetail(code); path = await api.regionPath(getRegionCode(detail) || code); await applyRegionSelection(picker, buildRegionSelection(path)); if (pathText) pathText.textContent = buildPathText(path); setApiState(status, '', ''); } catch (error) { if (redirectUnauthorized(api, error)) return; message = isForbidden(error) ? '当前账号无权定位地区。' : (error.message || '地区定位失败'); setApiState(status, isForbidden(error) ? 'forbidden' : 'error', message); if (pathText) pathText.textContent = message; showMessage(message); } } function bindSearchForms() { queryAll('[data-region-search-form]').forEach(function (form) { form.addEventListener('submit', function (event) { event.preventDefault(); submitRegionSearch(form); }); }); documentRef.addEventListener('click', function (event) { var button = event.target.closest('[data-region-search-result]'); if (!button) return; event.preventDefault(); selectSearchResult(button); }); } function init() { var api; if (!documentRef) return; if (!queryAll('[data-region-picker]').length && !queryAll('[data-region-search-form]').length) return; api = getApi(); if (redirectUnauthorized(api)) return; queryAll('[data-region-picker]').forEach(function (picker) { initPicker(picker).catch(function (error) { handlePickerError(picker, error); }); }); bindSearchForms(); } return { normalizeList: normalizeList, getRegionCode: getRegionCode, getRegionName: getRegionName, getRegionLevel: getRegionLevel, buildRegionOption: buildRegionOption, buildPathText: buildPathText, buildRegionSelection: buildRegionSelection, getFinalRegionCode: getFinalRegionCode, applyRegionSelection: applyRegionSelection, applyProfileRegionSelection: applyProfileRegionSelection, shouldRedirectToLogin: shouldRedirectToLogin, isForbidden: isForbidden, init: init }; });