打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:Common.js:修订间差异

MediaWiki界面页面
无编辑摘要
无编辑摘要
第5行: 第5行:


     function setupLogout() {
     function setupLogout() {
         $( document ).on( 'click', '[href*="action=logout"], [href*="UserLogout"], [data-mw-logouturl]', function(e) {
         $( document ).on( 'click', '[href*="action=logout"], [href*="UserLogout"], [data-mw-logouturl]', function ( e ) {
             e.preventDefault();
             e.preventDefault();
             e.stopImmediatePropagation();
             e.stopImmediatePropagation();
             new mw.Api().postWithToken( 'csrf', { action: 'logout' } )
             new mw.Api().postWithToken( 'csrf', { action: 'logout' } )
            .then( function() { return fetch( MAIN_SITE + '/api/auth/cookie-logout', { method: 'POST', credentials: 'include' } ); } )
                .then( function () {
            .catch( function(){} )
                    return fetch( MAIN_SITE + '/api/auth/cookie-logout', { method: 'POST', credentials: 'include' } );
            .finally( function() { window.location.href = WIKI_BASE + '/wiki/首页'; } );
                } )
                .catch( function () {} )
                .finally( function () {
                    window.location.href = WIKI_BASE + '/wiki/首页';
                } );
             return false;
             return false;
         } );
         } );
第17行: 第21行:


     function interceptLoginButton() {
     function interceptLoginButton() {
         $( document ).on( 'click', 'a[href*="Special:UserLogin"], a[href*="action=login"]', function(e) {
         $( document ).on( 'click', 'a[href*="Special:UserLogin"], a[href*="action=login"]', function ( e ) {
             e.preventDefault();
             e.preventDefault();
             window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
             window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
第24行: 第28行:


     function interceptRegisterButton() {
     function interceptRegisterButton() {
         $( document ).on( 'click', 'a[href*="Special:CreateAccount"], a[href*="action=createaccount"]', function(e) {
         $( document ).on( 'click', 'a[href*="Special:CreateAccount"], a[href*="action=createaccount"]', function ( e ) {
             e.preventDefault();
             e.preventDefault();
             window.location.href = MAIN_SITE + '/register.html?redirect=' + encodeURIComponent( window.location.href );
             window.location.href = MAIN_SITE + '/register.html?redirect=' + encodeURIComponent( window.location.href );
第31行: 第35行:


     function interceptEditForAnon() {
     function interceptEditForAnon() {
         if ( mw.config.get( 'wgUserId' ) !== 0 ) return;
         if ( mw.config.get( 'wgUserId' ) !== 0 ) {
         $( document ).on( 'click', 'a[href*="action=edit"], a[href*="veaction=edit"]', function(e) {
            return;
        }
         $( document ).on( 'click', 'a[href*="action=edit"], a[href*="veaction=edit"]', function ( e ) {
             e.preventDefault();
             e.preventDefault();
             window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
             window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
第50行: 第56行:


     function setupLangSwitch() {
     function setupLangSwitch() {
         $( document ).on( 'click', '.lang-btn', function() { applyLang( $( this ).data( 'lang' ) ); } );
         $( document ).on( 'click', '.lang-btn', function () {
            applyLang( $( this ).data( 'lang' ) );
        } );
         applyLang( currentLang );
         applyLang( currentLang );
     }
     }


     // ── 地图公共 ──────────────────────────────────────────
     // ── 地图公共 ──────────────────────────────────────────
     var MAP_W = 4096, MAP_H = 2048;
     var MAP_W = 4096;
     var GAME_W = 16384, GAME_H = 8192;
    var MAP_H = 2048;
     var GAME_W = 16384;
    var GAME_H = 8192;
     var IMG = '/images/e/ea/Map.jpg';
     var IMG = '/images/e/ea/Map.jpg';
     // 循环副本偏移量(足够覆盖无限拖动)
     // 循环副本偏移量(足够覆盖无限拖动)
     var OFFSETS = [ 0, -1, 1, -2, 2, -3, 3, -4, 4 ].map( function(n) { return n * MAP_W; } );
     var OFFSETS = [ 0, -1, 1, -2, 2, -3, 3, -4, 4 ].map( function ( n ) {
        return n * MAP_W;
    } );


     function gameToMap( gx, gy ) {
     function gameToMap( gx, gy ) {
第67行: 第79行:
     function parseCityWikitext( title, wikitext ) {
     function parseCityWikitext( title, wikitext ) {
         var city = { title: title };
         var city = { title: title };
         [ 'id', 'zh', 'zh-tw', 'ja', 'en', '坐标', '船厂', 'type', 'region', 'country' ].forEach( function( f ) {
         [ 'id', 'zh', 'zh-tw', 'ja', 'en', '坐标', '船厂', 'type', 'type_id', 'region', 'country' ].forEach( function ( f ) {
             var m = wikitext.match( new RegExp( '\\|' + f + '=([^\\|\\}]*)' ) );
             var m = wikitext.match( new RegExp( '\\|' + f + '=([^\\|\\}]*)' ) );
             city[ f ] = m ? m[1].trim() : '';
             city[ f ] = m ? m[ 1 ].trim() : '';
         } );
         } );
         return city;
         return city;
第75行: 第87行:


     function getCityName( city ) {
     function getCityName( city ) {
         return city[ currentLang ] || city['zh'] || city.title;
         return city[ currentLang ] || city.zh || city.title;
     }
     }


     function setupMapCursor( map ) {
     function setupMapCursor( map ) {
         map.getContainer().style.cursor = 'default';
         map.getContainer().style.cursor = 'default';
         map.on( 'mousedown', function() { map.getContainer().style.cursor = 'grabbing'; } );
         map.on( 'mousedown', function () {
         map.on( 'mouseup dragend', function() { map.getContainer().style.cursor = 'default'; } );
            map.getContainer().style.cursor = 'grabbing';
        } );
         map.on( 'mouseup dragend', function () {
            map.getContainer().style.cursor = 'default';
        } );
     }
     }


     function addMapImages( L, map ) {
     function addMapImages( L, map ) {
         OFFSETS.forEach( function( ox ) {
         OFFSETS.forEach( function ( ox ) {
             L.imageOverlay( IMG, [ [0, ox], [MAP_H, ox + MAP_W] ] ).addTo( map );
             L.imageOverlay( IMG, [ [ 0, ox ], [ MAP_H, ox + MAP_W ] ] ).addTo( map );
         } );
         } );
         map.setMaxBounds( [ [-MAP_H * 0.2, -MAP_W * 4.5], [MAP_H * 1.2, MAP_W * 4.5] ] );
         map.setMaxBounds( [ [ -MAP_H * 0.2, -MAP_W * 4.5 ], [ MAP_H * 1.2, MAP_W * 4.5 ] ] );
     }
     }


     // ── 世界地图 ──────────────────────────────────────────
     // ── 世界地图 ──────────────────────────────────────────
    // 城市图标(多图标)
    // 规则:按 city.type 匹配对应图片文件名;找不到则使用默认图标。
    // 你需要先在 Wiki 上传对应图片(例如 File:CityIcon_Capital.png)。
    var CITY_ICON_MAP = {
        '首都': 'CityIcon_首都.png',
        // 你的说明:支配/领地港 共用同一套图标(CityIcon_港口.png)
        '领地港': 'CityIcon_港口.png',
        '同盟港': 'CityIcon_港口.png',
        // 你的说明:海盗港单独一套图标
        '海盗港': 'CityIcon_海盗港.png',
        // 你的说明:内陆/开拓/农场等使用各自图标
        '内陆城市': 'CityIcon_内陆城市.png',
        '补给港': 'CityIcon_补给港.png',
        '登陆点': 'CityIcon_登陆点.png',
        '城市郊外': 'CityIcon_城市郊外.png',
        '商会开拓城市': 'CityIcon_商会开拓城市.png',
        '私人农场': 'CityIcon_私人农场.png',
        '奥斯曼首都': 'CityIcon_奥斯曼首都.png',
        '奥斯曼领地': 'CityIcon_奥斯曼.png',
        '奥斯曼同盟港': 'CityIcon_奥斯曼.png',
        '陆地二层': 'CityIcon_陆点.png',
        '工业港': 'CityIcon_工业港.png'
    };
    var CITY_ICON_DEFAULT = 'CityIcon_Default.png';
    // 城市 ID 单独覆盖图标(特殊港口/特殊城市)
    // 写法:key 用 city.id 的字符串形式,比如 '42'
    // value:你上传到 Wiki 的文件名,比如 'CityIcon_SpecialPort.png'
    var CITY_ICON_BY_ID = {
        // '10007': 'CityIcon_SpecialPort.png',
    };
    // 城市类型ID -> 图标文件名(用于“扩展/覆盖图标规则”)
    // 注意:key 必须是 city.type_id 的字符串形式(例如 '1'、'2')。
    // 如果你不填,这个功能会自动回退到 CITY_ICON_MAP(按 city.type 名称)。
    var CITY_ICON_BY_TYPE_ID = {
        // '2': 'CityIcon_领地港.png',
        // '3': 'CityIcon_同盟港.png'
    };
    var cityIconCache = {};
    function getCityIcon( L, city ) {
        // 优先按 ID 精确覆盖(特殊港口等)
        var idKey = ( city.id || '' ).toString().trim();
        if ( idKey && CITY_ICON_BY_ID[ idKey ] ) {
            var byIdFileName = CITY_ICON_BY_ID[ idKey ];
            if ( !cityIconCache[ byIdFileName ] ) {
                cityIconCache[ byIdFileName ] = L.icon( {
                    iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( byIdFileName ),
                iconSize: [ 16, 16 ],    // 你的图标尺寸:16x16
                iconAnchor: [ 8, 8 ]      // 锚点:放在中心
                } );
            }
            return cityIconCache[ byIdFileName ];
        }
        // 再尝试:按 type_id 扩展规则(例如把多个 type_id 映射到同一类图标)
        var typeIdKey = ( city.type_id || '' ).toString().trim();
        if ( typeIdKey && CITY_ICON_BY_TYPE_ID[ typeIdKey ] ) {
            var byTypeIdFileName = CITY_ICON_BY_TYPE_ID[ typeIdKey ];
            if ( !cityIconCache[ byTypeIdFileName ] ) {
                cityIconCache[ byTypeIdFileName ] = L.icon( {
                    iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( byTypeIdFileName ),
                    iconSize: [ 16, 16 ],
                    iconAnchor: [ 8, 8 ]
                } );
            }
            return cityIconCache[ byTypeIdFileName ];
        }
        // 最后回退:按 type 名称
        var key = ( city.type || '' ).trim();
        var fileName = CITY_ICON_MAP[ key ] || CITY_ICON_DEFAULT;
        if ( !cityIconCache[ fileName ] ) {
            cityIconCache[ fileName ] = L.icon( {
                iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( fileName ),
                iconSize: [ 16, 16 ],  // 你的图标尺寸:16x16
                iconAnchor: [ 8, 8 ]  // 锚点:放在中心
            } );
        }
        return cityIconCache[ fileName ];
    }
     var worldMap = null;
     var worldMap = null;
     var cityMarkers = [];
     var cityMarkers = [];
第97行: 第201行:


     function updateMarkerTooltips() {
     function updateMarkerTooltips() {
         cityMarkers.forEach( function( item ) { item.marker.setTooltipContent( getCityName( item.city ) ); } );
         cityMarkers.forEach( function ( item ) {
            item.marker.setTooltipContent( getCityName( item.city ) );
        } );
     }
     }


     function showCityDetail( city ) {
     function showCityDetail( city ) {
         selectedCity = city;
         selectedCity = city;
         $( '#city-detail-name' ).text( getCityName( city ) );
         $( '#city-detail-name' ).text( getCityName( city ) );
         $( '#city-detail-type' ).text( city.type );
         $( '#city-detail-type' ).text( city.type || '' );
         $( '#city-detail-region' ).text( city.region );
         $( '#city-detail-region' ).text( city.region || '' );
         $( '#city-detail-country' ).text( city.country );
         $( '#city-detail-country' ).text( city.country || '' );
         $( '#city-detail-coord' ).text( city['坐标'] );
         $( '#city-detail-coord' ).text( city.坐标 || '' );
         $( '#city-detail-shipyard' ).text( city['船厂'] );
         $( '#city-detail-shipyard' ).text( city.船厂 || '' );
         $( '#city-detail-link' ).attr( 'href', WIKI_BASE + '/wiki/' + encodeURIComponent( city.title ) );
         $( '#city-detail-link' ).attr( 'href', WIKI_BASE + '/wiki/' + encodeURIComponent( city.title ) );
         $( '#city-detail' ).show();
         $( '#city-detail' ).show();
     }
     }
第118行: 第226行:


     function addCityMarkers( L, map, markers, cities, clickHandler ) {
     function addCityMarkers( L, map, markers, cities, clickHandler ) {
         var mOpts = { radius: 4, fillColor: '#c9a84c', color: '#fff', weight: 1, opacity: 1, fillOpacity: 0.9 };
         var tOpts = {
        var tOpts = { permanent: false, direction: 'top', className: 'city-tooltip' };
            permanent: false,
            direction: 'top',
            className: 'city-tooltip'
        };


         // 按坐标去重:相同坐标只保留 ID 最小的
         // 按坐标去重:相同坐标只保留 ID 最小的
         var coordMap = {};
         var coordMap = {};
         cities.forEach( function( city ) {
         cities.forEach( function ( city ) {
             if ( !city['坐标'] ) return;
             if ( !city.坐标 ) {
             var key = city['坐标'];
                return;
             var id = parseInt( city['id'] ) || 999999;
            }
             if ( !coordMap[ key ] || id < parseInt( coordMap[ key ]['id'] ) ) {
 
             var key = city.坐标;
             var id = parseInt( city.id, 10 ) || 999999;
 
             if ( !coordMap[ key ] || id < ( parseInt( coordMap[ key ].id, 10 ) || 999999 ) ) {
                 coordMap[ key ] = city;
                 coordMap[ key ] = city;
             }
             }
         } );
         } );


         Object.keys( coordMap ).forEach( function( key ) {
         Object.keys( coordMap ).forEach( function ( key ) {
             var city = coordMap[ key ];
             var city = coordMap[ key ];
             var parts = key.split( ',' );
             var parts = key.split( ',' );
             if ( parts.length !== 2 ) return;
             if ( parts.length !== 2 ) {
             var gx = parseInt( parts[0] ), gy = parseInt( parts[1] );
                return;
             if ( isNaN(gx) || isNaN(gy) ) return;
            }
 
             var gx = parseInt( parts[ 0 ], 10 );
            var gy = parseInt( parts[ 1 ], 10 );
             if ( isNaN( gx ) || isNaN( gy ) ) {
                return;
            }
 
             var latlng = gameToMap( gx, gy );
             var latlng = gameToMap( gx, gy );


             OFFSETS.forEach( function( ox ) {
             OFFSETS.forEach( function ( ox ) {
                 var m = L.circleMarker( [ latlng[0], latlng[1] + ox ], mOpts ).addTo( map );
                 var marker = L.marker(
                 m.bindTooltip( getCityName( city ), tOpts );
                    [ latlng[ 0 ], latlng[ 1 ] + ox ],
                    { icon: getCityIcon( L, city ) }
                ).addTo( map );
 
                 marker.bindTooltip( getCityName( city ), tOpts );


                 if ( clickHandler ) m.on( 'click', ( function( c ) { return function( e ) {
                 if ( clickHandler ) {
                    // 防止点击标记触发 map 的 click 事件(否则会立刻关闭右侧面板)
                    marker.on( 'click', ( function ( c ) {
                    L.DomEvent.stopPropagation( e );
                        return function ( e ) {
                    if ( e.originalEvent ) L.DomEvent.stopPropagation( e.originalEvent );
                            // 阻止点击标记时冒泡到地图,避免立刻触发关闭逻辑
                    clickHandler( c );
                            if ( e ) {
                }; } )( city ) );
                                L.DomEvent.stopPropagation( e );
                                if ( e.originalEvent ) {
                                    L.DomEvent.stopPropagation( e.originalEvent );
                                }
                            }
                            clickHandler( c );
                        };
                    } )( city ) );
                }


                 markers.push( { marker: m, city: city } );
                 markers.push( { marker: marker, city: city } );
             } );
             } );
         } );
         } );
第158行: 第292行:
     function loadAllCities( callback ) {
     function loadAllCities( callback ) {
         var api = new mw.Api();
         var api = new mw.Api();
         api.get( { action: 'query', list: 'allpages', apprefix: '城市/', aplimit: 500, format: 'json' } )
 
        .then( function( data ) {
         api.get( {
             var titles = data.query.allpages.map( function( p ) { return p.title; } );
            action: 'query',
             if ( !titles.length ) return;
            list: 'allpages',
             var batches = [], allCities = [], done = 0;
            apprefix: '城市/',
             for ( var i = 0; i < titles.length; i += 50 ) batches.push( titles.slice( i, i + 50 ) );
            aplimit: 500,
             batches.forEach( function( batch ) {
            format: 'json'
                 api.get( { action: 'query', titles: batch.join( '|' ), prop: 'revisions', rvprop: 'content', format: 'json' } )
        } ).then( function ( data ) {
                .then( function( res ) {
             var titles = data.query.allpages.map( function ( p ) {
                     Object.keys( res.query.pages ).forEach( function( pid ) {
                return p.title;
            } );
 
             if ( !titles.length ) {
                callback( [] );
                return;
            }
 
             var batches = [];
            var allCities = [];
            var done = 0;
 
             for ( var i = 0; i < titles.length; i += 50 ) {
                batches.push( titles.slice( i, i + 50 ) );
            }
 
             batches.forEach( function ( batch ) {
                 api.get( {
                    action: 'query',
                    titles: batch.join( '|' ),
                    prop: 'revisions',
                    rvprop: 'content',
                    format: 'json'
                } ).then( function ( res ) {
                     Object.keys( res.query.pages ).forEach( function ( pid ) {
                         var page = res.query.pages[ pid ];
                         var page = res.query.pages[ pid ];
                         if ( !page.revisions ) return;
                         if ( !page.revisions ) {
                         var wt = page.revisions[0]['*'] || page.revisions[0].content || '';
                            return;
                        }
 
                         var wt = page.revisions[ 0 ][ '*' ] || page.revisions[ 0 ].content || '';
                         allCities.push( parseCityWikitext( page.title, wt ) );
                         allCities.push( parseCityWikitext( page.title, wt ) );
                     } );
                     } );
                     if ( ++done === batches.length ) callback( allCities );
 
                    done++;
                     if ( done === batches.length ) {
                        callback( allCities );
                    }
                 } );
                 } );
             } );
             } );
第180行: 第345行:


     function initWorldMap() {
     function initWorldMap() {
         if ( !document.getElementById( 'world-map' ) ) return;
         if ( !document.getElementById( 'world-map' ) ) {
            return;
        }


         mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );
         mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );
         var script = document.createElement( 'script' );
         var script = document.createElement( 'script' );
         script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
         script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
         script.onload = function() {
 
         script.onload = function () {
             var L = window.L;
             var L = window.L;
             worldMap = L.map( 'world-map', { crs: L.CRS.Simple, minZoom: -2, maxZoom: 3, zoomSnap: 0.5, attributionControl: false } );
 
             worldMap = L.map( 'world-map', {
                crs: L.CRS.Simple,
                minZoom: -2,
                maxZoom: 3,
                zoomSnap: 0.5,
                attributionControl: false
            } );
 
             addMapImages( L, worldMap );
             addMapImages( L, worldMap );
             setupMapCursor( worldMap );
             setupMapCursor( worldMap );
             worldMap.setView( [ 1248, -145 ], 0 );
             worldMap.setView( [ 1248, -145 ], 0 );


             loadAllCities( function( cities ) {
             loadAllCities( function ( cities ) {
                 addCityMarkers( L, worldMap, cityMarkers, cities, showCityDetail );
                 addCityMarkers( L, worldMap, cityMarkers, cities, showCityDetail );
             } );
             } );


             // 点击地图空白处隐藏城市详情(你的第二个需求)
             // 点击地图空白区域时关闭右侧城市详情
             worldMap.on( 'click', function() {
             worldMap.on( 'click', function () {
                 hideCityDetail();
                 hideCityDetail();
             } );
             } );


             // 点击空白后,语言切换时保持正确显示
             // 语言切换时更新 tooltip 和当前选中城市的详情
             $( document ).on( 'dol-lang-change', function() {
             $( document ).on( 'dol-lang-change', function () {
                 updateMarkerTooltips();
                 updateMarkerTooltips();
                 if ( selectedCity ) showCityDetail( selectedCity );
                 if ( selectedCity ) {
                    showCityDetail( selectedCity );
                }
             } );
             } );
         };
         };
         document.head.appendChild( script );
         document.head.appendChild( script );
     }
     }
第213行: 第393行:
     function initCityMiniMap() {
     function initCityMiniMap() {
         var container = document.getElementById( 'city-mini-map' );
         var container = document.getElementById( 'city-mini-map' );
         if ( !container ) return;
         if ( !container ) {
            return;
        }
 
         var coordStr = container.getAttribute( 'data-coord' );
         var coordStr = container.getAttribute( 'data-coord' );
         if ( !coordStr ) return;
         if ( !coordStr ) {
            return;
        }
 
         var parts = coordStr.split( ',' );
         var parts = coordStr.split( ',' );
         if ( parts.length !== 2 ) return;
         if ( parts.length !== 2 ) {
         var gx = parseInt( parts[0] ), gy = parseInt( parts[1] );
            return;
         if ( isNaN(gx) || isNaN(gy) ) return;
        }
 
         var gx = parseInt( parts[ 0 ], 10 );
        var gy = parseInt( parts[ 1 ], 10 );
         if ( isNaN( gx ) || isNaN( gy ) ) {
            return;
        }


         mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );
         mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );
         var script = document.createElement( 'script' );
         var script = document.createElement( 'script' );
         script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
         script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
         script.onload = function() {
 
         script.onload = function () {
             var L = window.L;
             var L = window.L;
             var latlng = gameToMap( gx, gy );
             var latlng = gameToMap( gx, gy );
             var miniMap = L.map( 'city-mini-map', { crs: L.CRS.Simple, minZoom: -1, maxZoom: 3, zoomSnap: 0.5, attributionControl: false } );
 
             var miniMap = L.map( 'city-mini-map', {
                crs: L.CRS.Simple,
                minZoom: -1,
                maxZoom: 3,
                zoomSnap: 0.5,
                attributionControl: false
            } );
 
             addMapImages( L, miniMap );
             addMapImages( L, miniMap );
             setupMapCursor( miniMap );
             setupMapCursor( miniMap );
             miniMap.setView( latlng, 1 );
             miniMap.setView( latlng, 1 );
             L.circleMarker( latlng, { radius: 6, fillColor: '#e74c3c', color: '#fff', weight: 2, opacity: 1, fillOpacity: 1 } ).addTo( miniMap );
 
             L.circleMarker( latlng, {
                radius: 6,
                fillColor: '#e74c3c',
                color: '#fff',
                weight: 2,
                opacity: 1,
                fillOpacity: 1
            } ).addTo( miniMap );
         };
         };
         document.head.appendChild( script );
         document.head.appendChild( script );
     }
     }
第238行: 第449行:
     // ── 交易品分类切换 ────────────────────────────────────
     // ── 交易品分类切换 ────────────────────────────────────
     function initGoodsFilter() {
     function initGoodsFilter() {
         $( '.goods-table' ).each( function() {
         $( '.goods-table' ).each( function () {
             var table = $( this );
             var table = $( this );
             // 收集所有分类
             // 收集所有分类
             var types = [];
             var types = [];
             table.find( 'tr' ).each( function() {
 
             table.find( 'tr' ).each( function () {
                 var td = $( this ).find( 'td' ).eq( 1 ).text().trim();
                 var td = $( this ).find( 'td' ).eq( 1 ).text().trim();
                 if ( td && types.indexOf( td ) === -1 ) types.push( td );
                 if ( td && types.indexOf( td ) === -1 ) {
                    types.push( td );
                }
             } );
             } );
             if ( !types.length ) return;
 
             if ( !types.length ) {
                return;
            }


             // 生成切换按钮
             // 生成切换按钮
             var bar = $( '<div class="goods-filter-bar" style="margin-bottom:6px;"></div>' );
             var bar = $( '<div class="goods-filter-bar" style="margin-bottom:6px;"></div>' );
             var allBtn = $( '<span class="goods-filter-btn active" data-type="all" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#c9a84c;color:#0a1628;">全部</span>' );
             var allBtn = $( '<span class="goods-filter-btn active" data-type="all" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#c9a84c;color:#0a1628;">全部</span>' );
             bar.append( allBtn );
             bar.append( allBtn );
             types.forEach( function( t ) {
 
             types.forEach( function ( t ) {
                 bar.append( $( '<span class="goods-filter-btn" data-type="' + t + '" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#1a3a5c;color:#b0c4d8;">' + t + '</span>' ) );
                 bar.append( $( '<span class="goods-filter-btn" data-type="' + t + '" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#1a3a5c;color:#b0c4d8;">' + t + '</span>' ) );
             } );
             } );
             table.before( bar );
             table.before( bar );


             // 点击切换
             // 点击切换
             bar.on( 'click', '.goods-filter-btn', function() {
             bar.on( 'click', '.goods-filter-btn', function () {
                 var type = $( this ).data( 'type' );
                 var type = $( this ).data( 'type' );
                 bar.find( '.goods-filter-btn' ).css( { background: '#1a3a5c', color: '#b0c4d8' } );
                 bar.find( '.goods-filter-btn' ).css( { background: '#1a3a5c', color: '#b0c4d8' } );
                 $( this ).css( { background: '#c9a84c', color: '#0a1628' } );
                 $( this ).css( { background: '#c9a84c', color: '#0a1628' } );
                 table.find( 'tr' ).each( function() {
 
                 table.find( 'tr' ).each( function () {
                     var tds = $( this ).find( 'td' );
                     var tds = $( this ).find( 'td' );
                     if ( !tds.length ) return; // 表头行
                     if ( !tds.length ) {
                        return; // 表头行
                    }
 
                     if ( type === 'all' ) {
                     if ( type === 'all' ) {
                         $( this ).show();
                         $( this ).show();
第275行: 第500行:
     }
     }


     mw.loader.using( 'mediawiki.util' ).done( function() {
     mw.loader.using( 'mediawiki.util' ).done( function () {
         $( document ).ready( function() {
         $( document ).ready( function () {
             setupLogout();
             setupLogout();
             interceptLoginButton();
             interceptLoginButton();

2026年4月16日 (四) 20:50的版本

( function () {
    'use strict';
    var MAIN_SITE = 'https://www.dolshipmaker.vip';
    var WIKI_BASE = 'https://wiki.dolshipmaker.vip';

    function setupLogout() {
        $( document ).on( 'click', '[href*="action=logout"], [href*="UserLogout"], [data-mw-logouturl]', function ( e ) {
            e.preventDefault();
            e.stopImmediatePropagation();
            new mw.Api().postWithToken( 'csrf', { action: 'logout' } )
                .then( function () {
                    return fetch( MAIN_SITE + '/api/auth/cookie-logout', { method: 'POST', credentials: 'include' } );
                } )
                .catch( function () {} )
                .finally( function () {
                    window.location.href = WIKI_BASE + '/wiki/首页';
                } );
            return false;
        } );
    }

    function interceptLoginButton() {
        $( document ).on( 'click', 'a[href*="Special:UserLogin"], a[href*="action=login"]', function ( e ) {
            e.preventDefault();
            window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
        } );
    }

    function interceptRegisterButton() {
        $( document ).on( 'click', 'a[href*="Special:CreateAccount"], a[href*="action=createaccount"]', function ( e ) {
            e.preventDefault();
            window.location.href = MAIN_SITE + '/register.html?redirect=' + encodeURIComponent( window.location.href );
        } );
    }

    function interceptEditForAnon() {
        if ( mw.config.get( 'wgUserId' ) !== 0 ) {
            return;
        }
        $( document ).on( 'click', 'a[href*="action=edit"], a[href*="veaction=edit"]', function ( e ) {
            e.preventDefault();
            window.location.href = MAIN_SITE + '/login.html?redirect=' + encodeURIComponent( window.location.href );
        } );
    }

    // ── 语言切换 ──────────────────────────────────────────
    var currentLang = localStorage.getItem( 'dol-wiki-lang' ) || 'zh';

    function applyLang( lang ) {
        currentLang = lang;
        localStorage.setItem( 'dol-wiki-lang', lang );
        $( '.lang-btn' ).css( { background: 'transparent', color: '#b0c4d8' } );
        $( '.lang-btn[data-lang="' + lang + '"]' ).css( { background: '#c9a84c', color: '#0a1628' } );
        $( document ).trigger( 'dol-lang-change', [ lang ] );
    }

    function setupLangSwitch() {
        $( document ).on( 'click', '.lang-btn', function () {
            applyLang( $( this ).data( 'lang' ) );
        } );
        applyLang( currentLang );
    }

    // ── 地图公共 ──────────────────────────────────────────
    var MAP_W = 4096;
    var MAP_H = 2048;
    var GAME_W = 16384;
    var GAME_H = 8192;
    var IMG = '/images/e/ea/Map.jpg';
    // 循环副本偏移量(足够覆盖无限拖动)
    var OFFSETS = [ 0, -1, 1, -2, 2, -3, 3, -4, 4 ].map( function ( n ) {
        return n * MAP_W;
    } );

    function gameToMap( gx, gy ) {
        return [ MAP_H - ( gy / GAME_H * MAP_H ), gx / GAME_W * MAP_W ];
    }

    function parseCityWikitext( title, wikitext ) {
        var city = { title: title };
        [ 'id', 'zh', 'zh-tw', 'ja', 'en', '坐标', '船厂', 'type', 'type_id', 'region', 'country' ].forEach( function ( f ) {
            var m = wikitext.match( new RegExp( '\\|' + f + '=([^\\|\\}]*)' ) );
            city[ f ] = m ? m[ 1 ].trim() : '';
        } );
        return city;
    }

    function getCityName( city ) {
        return city[ currentLang ] || city.zh || city.title;
    }

    function setupMapCursor( map ) {
        map.getContainer().style.cursor = 'default';
        map.on( 'mousedown', function () {
            map.getContainer().style.cursor = 'grabbing';
        } );
        map.on( 'mouseup dragend', function () {
            map.getContainer().style.cursor = 'default';
        } );
    }

    function addMapImages( L, map ) {
        OFFSETS.forEach( function ( ox ) {
            L.imageOverlay( IMG, [ [ 0, ox ], [ MAP_H, ox + MAP_W ] ] ).addTo( map );
        } );
        map.setMaxBounds( [ [ -MAP_H * 0.2, -MAP_W * 4.5 ], [ MAP_H * 1.2, MAP_W * 4.5 ] ] );
    }

    // ── 世界地图 ──────────────────────────────────────────
    // 城市图标(多图标)
    // 规则:按 city.type 匹配对应图片文件名;找不到则使用默认图标。
    // 你需要先在 Wiki 上传对应图片(例如 File:CityIcon_Capital.png)。
    var CITY_ICON_MAP = {
        '首都': 'CityIcon_首都.png',

        // 你的说明:支配/领地港 共用同一套图标(CityIcon_港口.png)
        '领地港': 'CityIcon_港口.png',
        '同盟港': 'CityIcon_港口.png',

        // 你的说明:海盗港单独一套图标
        '海盗港': 'CityIcon_海盗港.png',

        // 你的说明:内陆/开拓/农场等使用各自图标
        '内陆城市': 'CityIcon_内陆城市.png',
        '补给港': 'CityIcon_补给港.png',
        '登陆点': 'CityIcon_登陆点.png',
        '城市郊外': 'CityIcon_城市郊外.png',
        '商会开拓城市': 'CityIcon_商会开拓城市.png',
        '私人农场': 'CityIcon_私人农场.png',

        '奥斯曼首都': 'CityIcon_奥斯曼首都.png',
        '奥斯曼领地': 'CityIcon_奥斯曼.png',
        '奥斯曼同盟港': 'CityIcon_奥斯曼.png',

        '陆地二层': 'CityIcon_陆点.png',
        '工业港': 'CityIcon_工业港.png'
    };
    var CITY_ICON_DEFAULT = 'CityIcon_Default.png';
    // 城市 ID 单独覆盖图标(特殊港口/特殊城市)
    // 写法:key 用 city.id 的字符串形式,比如 '42'
    // value:你上传到 Wiki 的文件名,比如 'CityIcon_SpecialPort.png'
    var CITY_ICON_BY_ID = {
        // '10007': 'CityIcon_SpecialPort.png',
    };

    // 城市类型ID -> 图标文件名(用于“扩展/覆盖图标规则”)
    // 注意:key 必须是 city.type_id 的字符串形式(例如 '1'、'2')。
    // 如果你不填,这个功能会自动回退到 CITY_ICON_MAP(按 city.type 名称)。
    var CITY_ICON_BY_TYPE_ID = {
        // '2': 'CityIcon_领地港.png',
        // '3': 'CityIcon_同盟港.png'
    };
    var cityIconCache = {};

    function getCityIcon( L, city ) {
        // 优先按 ID 精确覆盖(特殊港口等)
        var idKey = ( city.id || '' ).toString().trim();
        if ( idKey && CITY_ICON_BY_ID[ idKey ] ) {
            var byIdFileName = CITY_ICON_BY_ID[ idKey ];
            if ( !cityIconCache[ byIdFileName ] ) {
                cityIconCache[ byIdFileName ] = L.icon( {
                    iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( byIdFileName ),
                iconSize: [ 16, 16 ],     // 你的图标尺寸:16x16
                iconAnchor: [ 8, 8 ]       // 锚点:放在中心
                } );
            }
            return cityIconCache[ byIdFileName ];
        }

        // 再尝试:按 type_id 扩展规则(例如把多个 type_id 映射到同一类图标)
        var typeIdKey = ( city.type_id || '' ).toString().trim();
        if ( typeIdKey && CITY_ICON_BY_TYPE_ID[ typeIdKey ] ) {
            var byTypeIdFileName = CITY_ICON_BY_TYPE_ID[ typeIdKey ];
            if ( !cityIconCache[ byTypeIdFileName ] ) {
                cityIconCache[ byTypeIdFileName ] = L.icon( {
                    iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( byTypeIdFileName ),
                    iconSize: [ 16, 16 ],
                    iconAnchor: [ 8, 8 ]
                } );
            }
            return cityIconCache[ byTypeIdFileName ];
        }

        // 最后回退:按 type 名称
        var key = ( city.type || '' ).trim();
        var fileName = CITY_ICON_MAP[ key ] || CITY_ICON_DEFAULT;

        if ( !cityIconCache[ fileName ] ) {
            cityIconCache[ fileName ] = L.icon( {
                iconUrl: WIKI_BASE + '/wiki/Special:FilePath/' + encodeURIComponent( fileName ),
                iconSize: [ 16, 16 ],  // 你的图标尺寸:16x16
                iconAnchor: [ 8, 8 ]  // 锚点:放在中心
            } );
        }
        return cityIconCache[ fileName ];
    }

    var worldMap = null;
    var cityMarkers = [];
    var selectedCity = null;

    function updateMarkerTooltips() {
        cityMarkers.forEach( function ( item ) {
            item.marker.setTooltipContent( getCityName( item.city ) );
        } );
    }

    function showCityDetail( city ) {
        selectedCity = city;

        $( '#city-detail-name' ).text( getCityName( city ) );
        $( '#city-detail-type' ).text( city.type || '' );
        $( '#city-detail-region' ).text( city.region || '' );
        $( '#city-detail-country' ).text( city.country || '' );
        $( '#city-detail-coord' ).text( city.坐标 || '' );
        $( '#city-detail-shipyard' ).text( city.船厂 || '' );
        $( '#city-detail-link' ).attr( 'href', WIKI_BASE + '/wiki/' + encodeURIComponent( city.title ) );

        $( '#city-detail' ).show();
    }

    function hideCityDetail() {
        selectedCity = null;
        $( '#city-detail' ).hide();
    }

    function addCityMarkers( L, map, markers, cities, clickHandler ) {
        var tOpts = {
            permanent: false,
            direction: 'top',
            className: 'city-tooltip'
        };

        // 按坐标去重:相同坐标只保留 ID 最小的
        var coordMap = {};
        cities.forEach( function ( city ) {
            if ( !city.坐标 ) {
                return;
            }

            var key = city.坐标;
            var id = parseInt( city.id, 10 ) || 999999;

            if ( !coordMap[ key ] || id < ( parseInt( coordMap[ key ].id, 10 ) || 999999 ) ) {
                coordMap[ key ] = city;
            }
        } );

        Object.keys( coordMap ).forEach( function ( key ) {
            var city = coordMap[ key ];
            var parts = key.split( ',' );
            if ( parts.length !== 2 ) {
                return;
            }

            var gx = parseInt( parts[ 0 ], 10 );
            var gy = parseInt( parts[ 1 ], 10 );
            if ( isNaN( gx ) || isNaN( gy ) ) {
                return;
            }

            var latlng = gameToMap( gx, gy );

            OFFSETS.forEach( function ( ox ) {
                var marker = L.marker(
                    [ latlng[ 0 ], latlng[ 1 ] + ox ],
                    { icon: getCityIcon( L, city ) }
                ).addTo( map );

                marker.bindTooltip( getCityName( city ), tOpts );

                if ( clickHandler ) {
                    marker.on( 'click', ( function ( c ) {
                        return function ( e ) {
                            // 阻止点击标记时冒泡到地图,避免立刻触发关闭逻辑
                            if ( e ) {
                                L.DomEvent.stopPropagation( e );
                                if ( e.originalEvent ) {
                                    L.DomEvent.stopPropagation( e.originalEvent );
                                }
                            }
                            clickHandler( c );
                        };
                    } )( city ) );
                }

                markers.push( { marker: marker, city: city } );
            } );
        } );
    }

    function loadAllCities( callback ) {
        var api = new mw.Api();

        api.get( {
            action: 'query',
            list: 'allpages',
            apprefix: '城市/',
            aplimit: 500,
            format: 'json'
        } ).then( function ( data ) {
            var titles = data.query.allpages.map( function ( p ) {
                return p.title;
            } );

            if ( !titles.length ) {
                callback( [] );
                return;
            }

            var batches = [];
            var allCities = [];
            var done = 0;

            for ( var i = 0; i < titles.length; i += 50 ) {
                batches.push( titles.slice( i, i + 50 ) );
            }

            batches.forEach( function ( batch ) {
                api.get( {
                    action: 'query',
                    titles: batch.join( '|' ),
                    prop: 'revisions',
                    rvprop: 'content',
                    format: 'json'
                } ).then( function ( res ) {
                    Object.keys( res.query.pages ).forEach( function ( pid ) {
                        var page = res.query.pages[ pid ];
                        if ( !page.revisions ) {
                            return;
                        }

                        var wt = page.revisions[ 0 ][ '*' ] || page.revisions[ 0 ].content || '';
                        allCities.push( parseCityWikitext( page.title, wt ) );
                    } );

                    done++;
                    if ( done === batches.length ) {
                        callback( allCities );
                    }
                } );
            } );
        } );
    }

    function initWorldMap() {
        if ( !document.getElementById( 'world-map' ) ) {
            return;
        }

        mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );

        var script = document.createElement( 'script' );
        script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';

        script.onload = function () {
            var L = window.L;

            worldMap = L.map( 'world-map', {
                crs: L.CRS.Simple,
                minZoom: -2,
                maxZoom: 3,
                zoomSnap: 0.5,
                attributionControl: false
            } );

            addMapImages( L, worldMap );
            setupMapCursor( worldMap );
            worldMap.setView( [ 1248, -145 ], 0 );

            loadAllCities( function ( cities ) {
                addCityMarkers( L, worldMap, cityMarkers, cities, showCityDetail );
            } );

            // 点击地图空白区域时关闭右侧城市详情
            worldMap.on( 'click', function () {
                hideCityDetail();
            } );

            // 语言切换时更新 tooltip 和当前选中城市的详情
            $( document ).on( 'dol-lang-change', function () {
                updateMarkerTooltips();
                if ( selectedCity ) {
                    showCityDetail( selectedCity );
                }
            } );
        };

        document.head.appendChild( script );
    }

    // ── 城市详情页小地图 ──────────────────────────────────
    function initCityMiniMap() {
        var container = document.getElementById( 'city-mini-map' );
        if ( !container ) {
            return;
        }

        var coordStr = container.getAttribute( 'data-coord' );
        if ( !coordStr ) {
            return;
        }

        var parts = coordStr.split( ',' );
        if ( parts.length !== 2 ) {
            return;
        }

        var gx = parseInt( parts[ 0 ], 10 );
        var gy = parseInt( parts[ 1 ], 10 );
        if ( isNaN( gx ) || isNaN( gy ) ) {
            return;
        }

        mw.loader.load( 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css' );

        var script = document.createElement( 'script' );
        script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';

        script.onload = function () {
            var L = window.L;
            var latlng = gameToMap( gx, gy );

            var miniMap = L.map( 'city-mini-map', {
                crs: L.CRS.Simple,
                minZoom: -1,
                maxZoom: 3,
                zoomSnap: 0.5,
                attributionControl: false
            } );

            addMapImages( L, miniMap );
            setupMapCursor( miniMap );
            miniMap.setView( latlng, 1 );

            L.circleMarker( latlng, {
                radius: 6,
                fillColor: '#e74c3c',
                color: '#fff',
                weight: 2,
                opacity: 1,
                fillOpacity: 1
            } ).addTo( miniMap );
        };

        document.head.appendChild( script );
    }

    // ── 交易品分类切换 ────────────────────────────────────
    function initGoodsFilter() {
        $( '.goods-table' ).each( function () {
            var table = $( this );
            // 收集所有分类
            var types = [];

            table.find( 'tr' ).each( function () {
                var td = $( this ).find( 'td' ).eq( 1 ).text().trim();
                if ( td && types.indexOf( td ) === -1 ) {
                    types.push( td );
                }
            } );

            if ( !types.length ) {
                return;
            }

            // 生成切换按钮
            var bar = $( '<div class="goods-filter-bar" style="margin-bottom:6px;"></div>' );
            var allBtn = $( '<span class="goods-filter-btn active" data-type="all" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#c9a84c;color:#0a1628;">全部</span>' );

            bar.append( allBtn );

            types.forEach( function ( t ) {
                bar.append( $( '<span class="goods-filter-btn" data-type="' + t + '" style="cursor:pointer;padding:3px 10px;border-radius:3px;margin:0 3px 3px 0;display:inline-block;background:#1a3a5c;color:#b0c4d8;">' + t + '</span>' ) );
            } );

            table.before( bar );

            // 点击切换
            bar.on( 'click', '.goods-filter-btn', function () {
                var type = $( this ).data( 'type' );

                bar.find( '.goods-filter-btn' ).css( { background: '#1a3a5c', color: '#b0c4d8' } );
                $( this ).css( { background: '#c9a84c', color: '#0a1628' } );

                table.find( 'tr' ).each( function () {
                    var tds = $( this ).find( 'td' );
                    if ( !tds.length ) {
                        return; // 表头行
                    }

                    if ( type === 'all' ) {
                        $( this ).show();
                    } else {
                        $( this ).toggle( tds.eq( 1 ).text().trim() === type );
                    }
                } );
            } );
        } );
    }

    mw.loader.using( 'mediawiki.util' ).done( function () {
        $( document ).ready( function () {
            setupLogout();
            interceptLoginButton();
            interceptRegisterButton();
            interceptEditForAnon();
            setupLangSwitch();
            initWorldMap();
            initCityMiniMap();
            initGoodsFilter();
        } );
    } );
}() );