| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895 |
- {% extends 'layout.html' %}
- {% block content %}
- <style>
- /* 水滴标记 hover 放大,锚点在尖端底部 */
- .settlement-drop-marker:hover {
- transform: scale(1.25) !important;
- }
- </style>
- <script>
- window.isSuperAdmin = {{ session.get('is_super_admin') | tojson }};
- </script>
- <script type="text/javascript">
- window.AMapPromise = new Promise(function(resolve, reject) {
- if (typeof AMap !== 'undefined') {
- resolve(AMap);
- return;
- }
- const script = document.createElement('script');
- script.src = 'https://webapi.amap.com/maps?v=2.0&key=7bbc2afa6f49b58024780bf647c6f038';
- script.onload = function() { resolve(AMap); };
- script.onerror = function() { reject('高德地图加载失败'); };
- document.head.appendChild(script);
- });
- </script>
- <div class="container-fluid">
- <div class="row">
- <div class="col-md-12">
- <div class="d-flex justify-content-between align-items-center mb-4">
- <h2 class="page-header">聚落地图</h2>
- <div class="d-flex gap-2">
- <div class="btn-group" role="group">
- <button type="button" class="btn btn-primary" id="view-map-btn">地图查看</button>
- <button type="button" class="btn btn-outline-primary" id="view-list-btn">列表查看</button>
- </div>
- {% if session.get('is_super_admin') %}
- <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addSettlementModal">
- <i class="bi bi-plus-circle"></i> 添加聚落
- </button>
- {% endif %}
- </div>
- </div>
- <div id="map-view" class="mb-4" style="display: block;">
- <div id="map-container" style="min-height: 500px; max-height: 80vh; border-radius: 12px; border: 1px solid #e2e8f0; overflow: hidden;">
- <div class="map-loading" style="position: absolute; inset: 0; background: linear-gradient(135deg, #e0f2fe 0%, #f0f9ff 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 100;">
- <div class="spinner-border text-primary" role="status">
- <span class="visually-hidden">加载中...</span>
- </div>
- <p class="mt-3 text-gray-500">地图加载中...</p>
- </div>
- </div>
- </div>
- <div id="list-view" class="mb-4" style="display: none;">
- <div class="card">
- <div class="card-body">
- <div class="table-responsive">
- <table class="table table-hover">
- <thead>
- <tr>
- <th>编号</th>
- <th>聚落名称</th>
- <th>所属区域</th>
- <th>人数</th>
- <th>代表人物</th>
- <th>姓氏类型</th>
- <th>创建时间</th>
- {% if session.get('is_super_admin') %}
- <th>操作</th>
- {% endif %}
- </tr>
- </thead>
- <tbody id="settlements-table-body">
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="addSettlementModal" tabindex="-1" aria-labelledby="addSettlementModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="addSettlementModalLabel">添加聚落</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <form id="settlement-form">
- <input type="hidden" id="settlement-id">
- <div class="row g-3">
- <div class="col-md-6">
- <label class="form-label">聚落名称 *</label>
- <input type="text" class="form-control" id="settlement-name" required>
- </div>
- <div class="col-md-6">
- <label class="form-label">所属区域 *</label>
- <div class="input-group">
- <input type="text" class="form-control" id="settlement-region" placeholder="搜索城市或区域名称" required>
- <button type="button" class="btn btn-outline-primary" id="search-region-btn">
- <i class="bi bi-search"></i>
- </button>
- </div>
- <div id="region-suggestions" class="mt-2" style="display: none;"></div>
- </div>
- <div class="col-md-6 d-none">
- <label class="form-label">纬度</label>
- <input type="number" class="form-control" id="settlement-latitude" step="0.0000001">
- </div>
- <div class="col-md-6 d-none">
- <label class="form-label">经度</label>
- <input type="number" class="form-control" id="settlement-longitude" step="0.0000001">
- </div>
- <div class="col-md-12">
- <div class="alert alert-info alert-dismissible fade show" id="location-info" style="display: none;">
- <i class="bi bi-info-circle me-2"></i>
- <span id="location-info-text"></span>
- <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
- </div>
- </div>
- <div class="col-md-6">
- <label class="form-label">聚落人数</label>
- <input type="number" class="form-control" id="settlement-population" min="0">
- </div>
- <div class="col-md-6">
- <label class="form-label">代表人物</label>
- <div class="input-group">
- <input type="text" id="settlement-representative-display" class="form-control" placeholder="点击选择代表人物" readonly>
- <input type="hidden" id="settlement-representative-id">
- <button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#memberSelectModal">
- <i class="bi bi-search"></i>
- </button>
- </div>
- </div>
- <div class="col-md-6">
- <label class="form-label">姓氏类型</label>
- <select class="form-select" id="settlement-surname-type">
- <option value="0">留姓</option>
- <option value="1">改姓</option>
- </select>
- </div>
- <div class="col-md-6" id="new-surname-container" style="display: none;">
- <label class="form-label">改后姓氏</label>
- <input type="text" class="form-control" id="settlement-new-surname" placeholder="输入改后的姓氏">
- </div>
- <div class="col-md-12">
- <label class="form-label">备注说明</label>
- <textarea class="form-control" id="settlement-description" rows="3"></textarea>
- </div>
- {% if session.get('is_super_admin') %}
- <div class="col-md-12">
- <label class="form-label">热心宗亲</label>
- <input type="text" class="form-control" id="settlement-enthusiastic-members" placeholder="多人请用逗号分隔,如:张三,李四,王五">
- <div class="form-text">仅超级管理员可见,录入后在地图hover和详情中显示</div>
- </div>
- {% endif %}
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
- <button type="button" class="btn btn-primary" id="save-settlement-btn">保存</button>
- </div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="memberSelectModal" tabindex="-1" aria-labelledby="memberSelectModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="memberSelectModalLabel">选择代表人物</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <div class="mb-3">
- <input type="text" id="member-search-input" class="form-control" placeholder="搜索成员名称...">
- </div>
- <div id="member-list-container" style="max-height: 400px; overflow-y: auto;">
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
- </div>
- </div>
- </div>
- </div>
- <div class="modal fade" id="settlementDetailModal" tabindex="-1" aria-labelledby="settlementDetailModalLabel" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="settlementDetailModalLabel">聚落详情</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body" id="settlement-detail-content">
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
- {% if session.get('is_super_admin') %}
- <button type="button" class="btn btn-primary" id="edit-settlement-btn">编辑</button>
- {% endif %}
- </div>
- </div>
- </div>
- </div>
- <script>
- let map = null;
- let markers = [];
- let settlementsData = [];
- function resizeMapContainer() {
- const mapContainer = document.getElementById('map-container');
- const headerHeight = document.querySelector('.page-header')?.offsetHeight || 80;
- const toolbarHeight = 60;
- const padding = 40;
- const windowHeight = window.innerHeight;
- const sidebarWidth = 250;
- const availableHeight = windowHeight - headerHeight - toolbarHeight - padding;
-
- if (window.innerWidth > 768) {
- mapContainer.style.height = Math.max(500, Math.min(availableHeight, windowHeight * 0.85)) + 'px';
- } else {
- mapContainer.style.height = Math.max(400, availableHeight * 0.8) + 'px';
- }
-
- if (window.map) {
- window.map.resize();
- }
- }
- document.addEventListener('DOMContentLoaded', function() {
- loadSettlements();
- initRegionSearch();
-
- resizeMapContainer();
- window.addEventListener('resize', resizeMapContainer);
-
- document.getElementById('addSettlementModal').addEventListener('show.bs.modal', function() {
- if (!window.isEditingSettlement) {
- resetSettlementForm();
- }
- window.isEditingSettlement = false;
- });
-
- document.getElementById('view-map-btn').addEventListener('click', function() {
- document.getElementById('map-view').style.display = 'block';
- document.getElementById('list-view').style.display = 'none';
- this.classList.add('btn-primary');
- this.classList.remove('btn-outline-primary');
- document.getElementById('view-list-btn').classList.add('btn-outline-primary');
- document.getElementById('view-list-btn').classList.remove('btn-primary');
- });
-
- document.getElementById('view-list-btn').addEventListener('click', function() {
- document.getElementById('map-view').style.display = 'none';
- document.getElementById('list-view').style.display = 'block';
- this.classList.add('btn-primary');
- this.classList.remove('btn-outline-primary');
- document.getElementById('view-map-btn').classList.add('btn-outline-primary');
- document.getElementById('view-map-btn').classList.remove('btn-primary');
- });
-
- document.getElementById('member-search-input').addEventListener('input', function() {
- searchMembers(this.value);
- });
-
- document.getElementById('save-settlement-btn').addEventListener('click', saveSettlement);
-
- document.getElementById('edit-settlement-btn').addEventListener('click', function() {
- const id = document.getElementById('settlement-id').value;
- if (id) {
- loadSettlementForEdit(id);
- }
- const modal = bootstrap.Modal.getInstance(document.getElementById('settlementDetailModal'));
- modal.hide();
- });
-
- document.getElementById('settlement-surname-type').addEventListener('change', function() {
- toggleNewSurnameField(this.value);
- });
- });
- function loadSettlements() {
- fetch('/manager/api/settlements', { credentials: 'include' })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- settlementsData = data.settlements;
- renderSettlementList(data.settlements);
- initMapWithData(data.settlements);
- }
- })
- .catch(error => {
- console.error('加载聚落失败:', error);
- });
- }
- function initMapWithData(settlements) {
- window.AMapPromise.then(function(AMap) {
- document.querySelector('.map-loading').style.display = 'none';
-
- if (!map) {
- let initialCenter = [116.397428, 39.90923];
- let initialZoom = 4;
-
- if (settlements.length > 0) {
- const firstLng = parseFloat(settlements[0].longitude) || 116.397428;
- const firstLat = parseFloat(settlements[0].latitude) || 39.90923;
- initialCenter = [firstLng, firstLat];
- initialZoom = settlements.length === 1 ? 10 : 6;
- }
-
- map = new AMap.Map('map-container', {
- center: initialCenter,
- zoom: initialZoom,
- resizeEnable: true,
- mapStyle: 'amap://styles/normal',
- features: ['bg', 'road', 'building', 'point', 'label'],
- viewMode: '2D'
- });
-
- AMap.plugin(['AMap.Scale', 'AMap.ToolBar', 'AMap.LabelsLayer'], function() {
- map.addControl(new AMap.Scale({ position: 'LB' }));
- map.addControl(new AMap.ToolBar({ position: 'RB' }));
- });
- }
-
- clearMarkers();
-
- if (settlements.length > 0) {
- const bounds = new AMap.Bounds();
-
- settlements.forEach(settlement => {
- const lng = parseFloat(settlement.longitude) || 116.397428;
- const lat = parseFloat(settlement.latitude) || 39.90923;
- // 水滴型标记(SVG:圆头朝上,尖端朝下)
- // hover 放大效果用 CSS :hover 实现,不依赖 getContent() DOM 操作
- const markerContent = `
- <div class="settlement-drop-marker" title="${settlement.name}" style="
- position: relative; width: 32px; height: 42px; cursor: pointer;
- filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
- transition: transform 0.15s ease; transform-origin: bottom center;
- ">
- <svg viewBox="0 0 32 42" xmlns="http://www.w3.org/2000/svg" width="32" height="42">
- <path d="M16 0 C7.163 0 0 7.163 0 16 C0 26 16 42 16 42 C16 42 32 26 32 16 C32 7.163 24.837 0 16 0 Z"
- fill="#3B82F6" stroke="#1D4ED8" stroke-width="1.5"/>
- <circle cx="16" cy="15" r="7" fill="white" opacity="0.9"/>
- </svg>
- </div>`;
- const marker = new AMap.Marker({
- position: [lng, lat],
- content: markerContent,
- offset: new AMap.Pixel(-16, -42),
- cursor: 'pointer'
- });
- marker.settlementData = settlement;
- marker.on('click', function() { showSettlementDetail(settlement); });
- marker.on('mouseover', function(e) { showMarkerTooltip(e, settlement); });
- marker.on('mousemove', function(e) { showMarkerTooltip(e, settlement); });
- marker.on('mouseout', function() { hideMarkerTooltip(); });
- map.add(marker);
- markers.push(marker);
- bounds.extend([lng, lat]);
- });
-
- if (settlements.length === 1) {
- const lng = parseFloat(settlements[0].longitude) || 116.397428;
- const lat = parseFloat(settlements[0].latitude) || 39.90923;
- map.setCenter([lng, lat]);
- map.setZoom(10);
- } else {
- setTimeout(function() {
- map.setFitView(bounds, false, [50, 50, 50, 50]);
- }, 100);
- }
- }
- }).catch(function(err) {
- console.error('高德地图加载失败:', err);
- document.querySelector('.map-loading p').textContent = '地图加载失败';
- });
- }
- function clearMarkers() {
- markers.forEach(marker => { map.remove(marker); });
- markers = [];
- }
- function renderSettlementList(settlements) {
- const tbody = document.getElementById('settlements-table-body');
- tbody.innerHTML = '';
-
- if (settlements.length === 0) {
- tbody.innerHTML = '<tr><td colspan="8" class="text-center text-gray-500">暂无聚落数据</td></tr>';
- return;
- }
-
- settlements.forEach((settlement, index) => {
- const surnameType = getSurnameTypeText(settlement.surname_type);
- const newSurname = settlement.new_surname ? '(' + settlement.new_surname + ')' : '';
- const row = document.createElement('tr');
- row.innerHTML = `
- <td>${index + 1}</td>
- <td>${settlement.name}</td>
- <td>${settlement.region || '-'}</td>
- <td>${settlement.population || 0}</td>
- <td>${settlement.representative_name || '-'}</td>
- <td>${surnameType}${newSurname}</td>
- <td>${formatDateTime(settlement.created_at)}</td>
- ${window.isSuperAdmin ? `
- <td>
- <button class="btn btn-sm btn-primary edit-settlement-btn" data-id="${settlement.id}">编辑</button>
- <button class="btn btn-sm btn-danger delete-settlement-btn" data-id="${settlement.id}">删除</button>
- </td>
- ` : ''}
- `;
-
- row.addEventListener('click', function() { showSettlementDetail(settlement); });
- tbody.appendChild(row);
- });
-
- document.querySelectorAll('.edit-settlement-btn').forEach(btn => {
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- loadSettlementForEdit(this.dataset.id);
- });
- });
-
- document.querySelectorAll('.delete-settlement-btn').forEach(btn => {
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- if (confirm('确定要删除这个聚落吗?')) {
- deleteSettlement(this.dataset.id);
- }
- });
- });
- }
- function showSettlementDetail(settlement) {
- document.getElementById('settlement-id').value = settlement.id;
- const surnameType = getSurnameTypeText(settlement.surname_type);
- const content = document.getElementById('settlement-detail-content');
- content.innerHTML = `
- <div class="mb-4">
- <h4>${settlement.name}</h4>
- ${settlement.region ? '<p class="text-muted">区域:' + settlement.region + '</p>' : ''}
- </div>
- <div class="row">
- <div class="col-md-6">
- <div class="card">
- <div class="card-body">
- <h6 class="card-title">基本信息</h6>
- <p class="card-text"><strong>人数:</strong>${settlement.population || 0} 人</p>
- ${settlement.latitude ? '<p class="card-text"><strong>纬度:</strong>' + settlement.latitude + '</p>' : ''}
- ${settlement.longitude ? '<p class="card-text"><strong>经度:</strong>' + settlement.longitude + '</p>' : ''}
- <p class="card-text"><strong>姓氏类型:</strong>${surnameType}${settlement.new_surname ? '(改后:' + settlement.new_surname + ')' : ''}</p>
- </div>
- </div>
- </div>
- <div class="col-md-6">
- <div class="card">
- <div class="card-body">
- <h6 class="card-title">代表人物</h6>
- <p class="card-text">${settlement.representative_name || '未设置'}</p>
- </div>
- </div>
- </div>
- </div>
- ${settlement.description ? '<div class="mt-4"><h6>备注说明</h6><p>' + settlement.description + '</p></div>' : ''}
- ${window.isSuperAdmin && settlement.enthusiastic_members ? '<div class="mt-4"><h6 style="color:#d97706;">热心宗亲</h6><p>' + settlement.enthusiastic_members + '</p></div>' : ''}
- <div class="mt-4 text-muted text-sm">
- 创建时间:${formatDateTime(settlement.created_at)}
- ${settlement.updated_at !== settlement.created_at ? '<br>更新时间:' + formatDateTime(settlement.updated_at) : ''}
- </div>
- `;
-
- const modal = new bootstrap.Modal(document.getElementById('settlementDetailModal'));
- modal.show();
- }
- function showSettlementDetailById(id) {
- fetch('/manager/api/settlements/' + id)
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- showSettlementDetail(data.settlement);
- }
- });
- }
- function loadSettlementForEdit(id) {
- fetch('/manager/api/settlements/' + id)
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- const s = data.settlement;
- document.getElementById('settlement-id').value = s.id;
- document.getElementById('settlement-name').value = s.name;
- document.getElementById('settlement-region').value = s.region || '';
- document.getElementById('settlement-latitude').value = s.latitude || '';
- document.getElementById('settlement-longitude').value = s.longitude || '';
- document.getElementById('settlement-population').value = s.population || '';
- document.getElementById('settlement-representative-id').value = s.representative_id || '';
- document.getElementById('settlement-representative-display').value = s.representative_name || '';
- document.getElementById('settlement-description').value = s.description || '';
- document.getElementById('settlement-surname-type').value = s.surname_type || 0;
- document.getElementById('settlement-new-surname').value = s.new_surname || '';
- const emEl2 = document.getElementById('settlement-enthusiastic-members');
- if (emEl2) emEl2.value = s.enthusiastic_members || '';
-
- toggleNewSurnameField(s.surname_type);
- document.getElementById('addSettlementModalLabel').textContent = '编辑聚落';
-
- if (s.region && s.latitude) {
- document.getElementById('location-info-text').innerHTML = '已选择区域:' + s.region + ',坐标:' + s.latitude + ', ' + s.longitude;
- document.getElementById('location-info').style.display = 'block';
- }
-
- window.isEditingSettlement = true;
- const modal = new bootstrap.Modal(document.getElementById('addSettlementModal'));
- modal.show();
- }
- });
- }
- function resetSettlementForm() {
- document.getElementById('settlement-id').value = '';
- document.getElementById('settlement-name').value = '';
- document.getElementById('settlement-region').value = '';
- document.getElementById('settlement-latitude').value = '';
- document.getElementById('settlement-longitude').value = '';
- document.getElementById('settlement-population').value = '';
- document.getElementById('settlement-representative-id').value = '';
- document.getElementById('settlement-representative-display').value = '';
- document.getElementById('settlement-description').value = '';
- document.getElementById('settlement-surname-type').value = '0';
- document.getElementById('settlement-new-surname').value = '';
- const emEl = document.getElementById('settlement-enthusiastic-members');
- if (emEl) emEl.value = '';
- document.getElementById('location-info').style.display = 'none';
- document.getElementById('region-suggestions').style.display = 'none';
- document.getElementById('new-surname-container').style.display = 'none';
- document.getElementById('addSettlementModalLabel').textContent = '添加聚落';
- }
- function saveSettlement() {
- const emSaveEl = document.getElementById('settlement-enthusiastic-members');
- const data = {
- id: document.getElementById('settlement-id').value,
- name: document.getElementById('settlement-name').value,
- region: document.getElementById('settlement-region').value,
- latitude: document.getElementById('settlement-latitude').value,
- longitude: document.getElementById('settlement-longitude').value,
- population: document.getElementById('settlement-population').value,
- representative_id: document.getElementById('settlement-representative-id').value,
- description: document.getElementById('settlement-description').value,
- surname_type: document.getElementById('settlement-surname-type').value,
- new_surname: document.getElementById('settlement-new-surname').value,
- enthusiastic_members: emSaveEl ? emSaveEl.value : ''
- };
-
- const url = data.id ? '/manager/api/settlements/' + data.id : '/manager/api/settlements';
- const method = data.id ? 'PUT' : 'POST';
-
- fetch(url, {
- method: method,
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data),
- credentials: 'include'
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- alert('保存成功!');
- const modal = bootstrap.Modal.getInstance(document.getElementById('addSettlementModal'));
- modal.hide();
- loadSettlements();
- } else {
- alert(data.message || '保存失败');
- }
- })
- .catch(error => {
- console.error('保存失败:', error);
- alert('保存失败,请检查网络连接');
- });
- }
- function deleteSettlement(id) {
- fetch('/manager/api/settlements/' + id, {
- method: 'DELETE',
- credentials: 'include'
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- loadSettlements();
- } else {
- alert(data.message || '删除失败');
- }
- });
- }
- function searchMembers(keyword) {
- fetch('/manager/api/search_member', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ keyword: keyword })
- })
- .then(response => response.json())
- .then(data => {
- const container = document.getElementById('member-list-container');
- if (data.success && data.members.length > 0) {
- container.innerHTML = data.members.map(member => {
- const simplified = member.simplified_name && member.simplified_name !== member.name ? '(' + member.simplified_name + ')' : '';
- return '<div class="member-item p-3 border-bottom cursor-pointer hover:bg-gray-50" onclick="selectRepresentative(' + member.id + ', \'' + member.name + '\', \'' + (member.simplified_name || '') + '\')">' +
- '<div style="font-weight: 500;">' + member.name + '</div>' +
- (member.simplified_name ? '<div style="font-size: 12px; color: #64748b;">' + simplified + '</div>' : '') +
- '</div>';
- }).join('');
- } else {
- container.innerHTML = '<div class="text-center py-4 text-gray-500">未找到匹配的成员</div>';
- }
- });
- }
- function selectRepresentative(id, name, simplifiedName) {
- document.getElementById('settlement-representative-id').value = id;
- let displayText = name;
- if (simplifiedName && simplifiedName !== name) {
- displayText += ' (' + simplifiedName + ')';
- }
- document.getElementById('settlement-representative-display').value = displayText;
- const modal = bootstrap.Modal.getInstance(document.getElementById('memberSelectModal'));
- modal.hide();
- }
- function formatDateTime(dateStr) {
- if (!dateStr) return '-';
- const date = new Date(dateStr);
- return date.toLocaleString('zh-CN');
- }
- function initRegionSearch() {
- const regionInput = document.getElementById('settlement-region');
- const suggestionsDiv = document.getElementById('region-suggestions');
- const searchBtn = document.getElementById('search-region-btn');
-
- searchBtn.addEventListener('click', function() {
- const keyword = regionInput.value.trim();
- if (keyword) {
- searchRegion(keyword);
- }
- });
-
- let searchTimeout = null;
- regionInput.addEventListener('input', function() {
- const keyword = this.value.trim();
- if (searchTimeout) clearTimeout(searchTimeout);
- if (keyword.length >= 2) {
- searchTimeout = setTimeout(() => { searchRegion(keyword); }, 300);
- } else {
- suggestionsDiv.style.display = 'none';
- }
- });
-
- regionInput.addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- const keyword = this.value.trim();
- if (keyword) {
- searchRegion(keyword);
- }
- }
- });
-
- document.addEventListener('click', function(e) {
- if (!e.target.closest('#settlement-region') && !e.target.closest('#search-region-btn') && !e.target.closest('#region-suggestions')) {
- suggestionsDiv.style.display = 'none';
- }
- });
- }
- function searchRegion(keyword) {
- const suggestionsDiv = document.getElementById('region-suggestions');
-
- window.AMapPromise.then(function(AMap) {
- AMap.plugin(['AMap.PlaceSearch'], function() {
- const placeSearch = new AMap.PlaceSearch({
- city: '全国',
- type: 'district',
- pageSize: 10
- });
-
- placeSearch.search(keyword, function(status, result) {
- if (status === 'complete' && result && result.poiList && result.poiList.pois.length > 0) {
- const pois = result.poiList.pois;
- const formattedData = pois.map(poi => ({
- name: poi.name,
- adcode: poi.adcode,
- address: poi.address || poi.name,
- location: {
- lng: poi.location.lng || poi.location[0],
- lat: poi.location.lat || poi.location[1]
- }
- }));
- renderRegionSuggestions(formattedData);
- } else {
- searchRegionFallback(keyword);
- }
- });
- });
- }).catch(function() {
- searchRegionFallback(keyword);
- });
- }
- function searchRegionFallback(keyword) {
- const suggestionsDiv = document.getElementById('region-suggestions');
-
- const localData = [
- { name: '泉州市鲤城区', adcode: '350502', address: '福建省泉州市鲤城区', location: { lng: 118.58809, lat: 24.90757 } },
- { name: '泉州市丰泽区', adcode: '350503', address: '福建省泉州市丰泽区', location: { lng: 118.62062, lat: 24.88269 } },
- { name: '泉州市洛江区', adcode: '350504', address: '福建省泉州市洛江区', location: { lng: 118.74387, lat: 24.92516 } },
- { name: '泉州市泉港区', adcode: '350505', address: '福建省泉州市泉港区', location: { lng: 118.96936, lat: 25.13699 } },
- { name: '泉州市永春县', adcode: '350525', address: '福建省泉州市永春县', location: { lng: 118.38267, lat: 25.34437 } },
- { name: '泉州市安溪县', adcode: '350524', address: '福建省泉州市安溪县', location: { lng: 118.18598, lat: 25.06531 } },
- { name: '泉州市德化县', adcode: '350526', address: '福建省泉州市德化县', location: { lng: 118.15722, lat: 25.53543 } },
- { name: '泉州市金门县', adcode: '350527', address: '福建省泉州市金门县', location: { lng: 118.32556, lat: 24.43788 } },
- { name: '泉州市南安市', adcode: '350583', address: '福建省泉州市南安市', location: { lng: 118.32211, lat: 24.96694 } },
- { name: '泉州市晋江市', adcode: '350582', address: '福建省泉州市晋江市', location: { lng: 118.56388, lat: 24.80951 } },
- { name: '泉州市石狮市', adcode: '350581', address: '福建省泉州市石狮市', location: { lng: 118.65021, lat: 24.74517 } },
- { name: '漳州市芗城区', adcode: '350602', address: '福建省漳州市芗城区', location: { lng: 117.62588, lat: 24.61406 } },
- { name: '漳州市龙文区', adcode: '350603', address: '福建省漳州市龙文区', location: { lng: 117.71692, lat: 24.62148 } },
- { name: '漳州市龙海市', adcode: '350681', address: '福建省漳州市龙海市', location: { lng: 117.80713, lat: 24.47173 } },
- { name: '漳州市华安县', adcode: '350629', address: '福建省漳州市华安县', location: { lng: 117.4898, lat: 24.7568 } },
- { name: '厦门市思明区', adcode: '350203', address: '福建省厦门市思明区', location: { lng: 118.08942, lat: 24.47977 } },
- { name: '厦门市湖里区', adcode: '350206', address: '福建省厦门市湖里区', location: { lng: 118.14991, lat: 24.52864 } },
- { name: '厦门市同安区', adcode: '350212', address: '福建省厦门市同安区', location: { lng: 118.16728, lat: 24.72657 } },
- { name: '厦门市翔安区', adcode: '350213', address: '福建省厦门市翔安区', location: { lng: 118.26543, lat: 24.63788 } },
- { name: '厦门市集美区', adcode: '350211', address: '福建省厦门市集美区', location: { lng: 118.09767, lat: 24.60224 } },
- { name: '厦门市海沧区', adcode: '350205', address: '福建省厦门市海沧区', location: { lng: 117.97257, lat: 24.53645 } },
- { name: '龙岩市漳平市', adcode: '350881', address: '福建省龙岩市漳平市', location: { lng: 117.45528, lat: 25.36948 } },
- { name: '三明市大田县', adcode: '350425', address: '福建省三明市大田县', location: { lng: 117.83028, lat: 25.69053 } },
- { name: '福州市鼓楼区', adcode: '350102', address: '福建省福州市鼓楼区', location: { lng: 119.29648, lat: 26.08744 } },
- { name: '福州市台江区', adcode: '350103', address: '福建省福州市台江区', location: { lng: 119.30476, lat: 26.05936 } },
- { name: '福州市仓山区', adcode: '350104', address: '福建省福州市仓山区', location: { lng: 119.27744, lat: 26.00716 } },
- { name: '信阳市固始县', adcode: '411525', address: '河南省信阳市固始县', location: { lng: 115.68042, lat: 32.14447 } },
- { name: '六安市寿县', adcode: '341521', address: '安徽省六安市寿县', location: { lng: 116.68658, lat: 32.57654 } },
- { name: '六安市霍邱县', adcode: '341522', address: '安徽省六安市霍邱县', location: { lng: 116.10428, lat: 32.38912 } },
- { name: '六安市李集', adcode: '341522', address: '安徽省六安市霍邱县李集镇', location: { lng: 115.92136, lat: 32.38456 } },
- { name: '长沙市望城区', adcode: '430112', address: '湖南省长沙市望城区', location: { lng: 112.83488, lat: 28.31353 } },
- { name: '广州市增城区', adcode: '440118', address: '广东省广州市增城区', location: { lng: 113.86262, lat: 23.23745 } },
- { name: '吉安市永新县', adcode: '360830', address: '江西省吉安市永新县', location: { lng: 114.22658, lat: 26.90836 } },
- { name: '丽水市青田县', adcode: '331121', address: '浙江省丽水市青田县', location: { lng: 120.28158, lat: 28.19935 } },
- { name: '温州市泰顺县', adcode: '330329', address: '浙江省温州市泰顺县', location: { lng: 119.75248, lat: 27.67386 } },
- { name: '温州市文成县', adcode: '330328', address: '浙江省温州市文成县', location: { lng: 120.08258, lat: 27.68862 } },
- { name: '杭州市临安区', adcode: '330112', address: '浙江省杭州市临安区', location: { lng: 119.72768, lat: 30.23397 } },
- { name: '金华市兰溪市', adcode: '330781', address: '浙江省金华市兰溪市', location: { lng: 119.48938, lat: 29.29735 } },
- { name: '衢州市衢江区', adcode: '330803', address: '浙江省衢州市衢江区', location: { lng: 118.88088, lat: 28.93562 } },
- { name: '衢州市开化县', adcode: '330824', address: '浙江省衢州市开化县', location: { lng: 118.30888, lat: 29.29412 } },
- { name: '长春市农安县', adcode: '220122', address: '吉林省长春市农安县', location: { lng: 125.16718, lat: 44.45183 } },
- { name: '新北市三重区', adcode: '710101', address: '台湾省新北市三重区', location: { lng: 121.48888, lat: 25.05722 } },
- { name: '台北市', adcode: '710100', address: '台湾省台北市', location: { lng: 121.50906, lat: 25.04433 } },
- { name: '云林县', adcode: '710600', address: '台湾省云林县', location: { lng: 120.43758, lat: 23.71465 } },
- { name: '彰化县', adcode: '710500', address: '台湾省彰化县', location: { lng: 120.54869, lat: 24.08279 } },
- { name: '台中市清水区', adcode: '710206', address: '台湾省台中市清水区', location: { lng: 120.56496, lat: 24.26271 } },
- { name: '菲律宾马尼拉', adcode: 'PH001', address: '菲律宾马尼拉', location: { lng: 120.9842, lat: 14.5995 } },
- { name: '北京市', adcode: '110000', address: '北京市', location: { lng: 116.407394, lat: 39.904211 } },
- { name: '上海市', adcode: '310000', address: '上海市', location: { lng: 121.473701, lat: 31.230416 } },
- { name: '广州市', adcode: '440100', address: '广东省广州市', location: { lng: 113.264385, lat: 23.12911 } },
- { name: '深圳市', adcode: '440300', address: '广东省深圳市', location: { lng: 114.057964, lat: 22.543099 } },
- { name: '杭州市', adcode: '330100', address: '浙江省杭州市', location: { lng: 120.197855, lat: 30.274084 } },
- { name: '南京市', adcode: '320100', address: '江苏省南京市', location: { lng: 118.796875, lat: 32.060255 } },
- { name: '成都市', adcode: '510100', address: '四川省成都市', location: { lng: 104.066801, lat: 30.572816 } },
- { name: '武汉市', adcode: '420100', address: '湖北省武汉市', location: { lng: 114.287924, lat: 30.592855 } }
- ];
-
- const filteredData = localData.filter(item => item.name.includes(keyword) || item.address.includes(keyword));
-
- if (filteredData.length > 0) {
- renderRegionSuggestions(filteredData);
- } else {
- suggestionsDiv.innerHTML = '<div class="alert alert-warning">未找到匹配的区域,请尝试其他关键词</div>';
- suggestionsDiv.style.display = 'block';
- }
- }
- function renderRegionSuggestions(pois) {
- const suggestionsDiv = document.getElementById('region-suggestions');
- let html = '<div class="list-group" style="max-height: 300px; overflow-y: auto;">';
-
- pois.slice(0, 8).forEach(poi => {
- const lng = poi.location.lng || poi.location[0];
- const lat = poi.location.lat || poi.location[1];
- html += '<button type="button" class="list-group-item list-group-item-action" onclick="selectRegion(\'' + poi.name + '\', ' + lng + ', ' + lat + ')">' +
- '<div class="d-flex justify-content-between">' +
- '<span style="font-weight: 500;">' + poi.name + '</span>' +
- '<span class="text-muted text-sm">' + poi.adcode + '</span>' +
- '</div>' +
- (poi.address ? '<div class="text-sm text-muted">' + poi.address + '</div>' : '') +
- '</button>';
- });
-
- html += '</div>';
- suggestionsDiv.innerHTML = html;
- suggestionsDiv.style.display = 'block';
- }
- function toggleNewSurnameField(surnameType) {
- const container = document.getElementById('new-surname-container');
- if (surnameType == 1) {
- container.style.display = 'block';
- } else {
- container.style.display = 'none';
- document.getElementById('settlement-new-surname').value = '';
- }
- }
- function getSurnameTypeText(type) {
- return type == 1 ? '改姓' : '留姓';
- }
- function selectRegion(name, lng, lat) {
- document.getElementById('settlement-region').value = name;
- document.getElementById('settlement-latitude').value = lat;
- document.getElementById('settlement-longitude').value = lng;
- document.getElementById('region-suggestions').style.display = 'none';
-
- const infoText = document.getElementById('location-info-text');
- infoText.innerHTML = '已选择区域:' + name + ',坐标:' + lat.toFixed(6) + ', ' + lng.toFixed(6);
- document.getElementById('location-info').style.display = 'block';
- }
- let tooltipDiv = null;
- function showMarkerTooltip(e, settlement) {
- if (!tooltipDiv) {
- tooltipDiv = document.createElement('div');
- tooltipDiv.style.cssText = 'position: fixed; background: rgba(30, 41, 59, 0.95); color: white; padding: 12px; border-radius: 8px; min-width: 200px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 1000; pointer-events: none; font-size: 13px;';
- document.body.appendChild(tooltipDiv);
- }
- const surnameType = getSurnameTypeText(settlement.surname_type);
- tooltipDiv.innerHTML = '<div style="font-weight: 600; margin-bottom: 8px; font-size: 14px;">' + settlement.name + '</div>' +
- (settlement.region ? '<div style="margin-bottom: 4px; color: #94a3b8;">区域:' + settlement.region + '</div>' : '') +
- '<div style="margin-bottom: 4px; color: #94a3b8;">人数:' + (settlement.population || 0) + ' 人</div>' +
- (settlement.representative_name ? '<div style="margin-bottom: 4px; color: #94a3b8;">代表:' + settlement.representative_name + '</div>' : '') +
- '<div style="margin-bottom: 4px; color: #94a3b8;">姓氏:' + surnameType + (settlement.new_surname ? '(改后:' + settlement.new_surname + ')' : '') + '</div>' +
- (settlement.description ? '<div style="margin-bottom: 4px; color: #94a3b8; max-width: 220px; word-break: break-all;">备注:' + settlement.description + '</div>' : '') +
- (window.isSuperAdmin && settlement.enthusiastic_members ? '<div style="margin-bottom: 4px; color: #fbbf24;">热心宗亲:' + settlement.enthusiastic_members + '</div>' : '') +
- '<div style="border-top: 1px solid rgba(255,255,255,0.1); padding-top: 8px; margin-top: 4px;">' +
- '<button onclick="event.stopPropagation(); showSettlementDetail(' + JSON.stringify(settlement).replace(/"/g, '"') + '); hideMarkerTooltip();" style="background: #3B82F6; color: white; border: none; padding: 4px 12px; border-radius: 4px; font-size: 11px; cursor: pointer;">查看详情</button>' +
- '</div>';
- tooltipDiv.style.display = 'block';
- // 定位:AMap.Marker 的 mouseover 事件提供 originEvent(原生 MouseEvent),
- // 直接用 clientX/Y 定位;AMap.Circle 提供 e.pixel(相对地图容器的像素坐标)作为兜底
- let x, y;
- if (e.originEvent && e.originEvent.clientX != null) {
- x = e.originEvent.clientX;
- y = e.originEvent.clientY;
- } else if (e.pixel) {
- const containerRect = document.getElementById('map-container').getBoundingClientRect();
- x = containerRect.left + e.pixel.x;
- y = containerRect.top + e.pixel.y;
- } else {
- return;
- }
- const vw = window.innerWidth;
- const th = tooltipDiv.offsetHeight || 120;
- const tw = tooltipDiv.offsetWidth || 220;
- // 右侧放不下时改为左侧
- tooltipDiv.style.left = (x + 12 + tw > vw ? x - tw - 12 : x + 12) + 'px';
- tooltipDiv.style.top = Math.max(8, y - th / 2) + 'px';
- }
- function hideMarkerTooltip() {
- if (tooltipDiv) {
- tooltipDiv.style.display = 'none';
- }
- }
- </script>
- {% endblock %}
|