raintee-maputils 1.0.42 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6738,6 +6738,583 @@ class RulerControl {
6738
6738
  }
6739
6739
  }
6740
6740
 
6741
+ /**
6742
+ * CustomSearchSelectControl - 紧凑型 Mapbox 控件
6743
+ * 优化UI布局,提高空间利用率
6744
+ */
6745
+ class CustomSearchSelectControl {
6746
+ /**
6747
+ * 构造函数
6748
+ * @param {Object} options 配置选项
6749
+ * @param {Array} options.list 选项列表
6750
+ * @param {Function} options.onSelect 选中回调函数
6751
+ * @param {string} options.placeholder 搜索框占位符
6752
+ * @param {string} options.title 弹窗标题
6753
+ * @param {boolean} options.showSearch 是否显示搜索框
6754
+ * @param {boolean} options.compactMode 是否启用紧凑模式
6755
+ */
6756
+ constructor(options = {}) {
6757
+ this.options = {
6758
+ list: [],
6759
+ onSelect: () => { },
6760
+ onCancel: () => { },
6761
+ placeholder: '搜索...',
6762
+ title: '选择',
6763
+ showSearch: true,
6764
+ compactMode: true, // 新增:紧凑模式
6765
+ maxHeight: '60vh', // 新增:最大高度
6766
+ width: '60vw', // 新增:弹窗宽度
6767
+ ...options
6768
+ };
6769
+
6770
+ this._map = null;
6771
+ this._container = null;
6772
+ this._modal = null;
6773
+ this._overlay = null;
6774
+ this._searchInput = null;
6775
+ this._optionsList = null;
6776
+ this._filteredList = [...this.options.list];
6777
+ }
6778
+
6779
+ /**
6780
+ * 添加到地图
6781
+ * @param {Object} map Mapbox 地图实例
6782
+ * @returns {HTMLElement} 控件容器
6783
+ */
6784
+ onAdd(map) {
6785
+ this._map = map;
6786
+
6787
+ // 创建控件按钮
6788
+ this._container = document.createElement('div');
6789
+ this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group vector-layer-ctrl';
6790
+ this._container.style.cssText = `
6791
+ position: relative;
6792
+ `;
6793
+ this._container.innerHTML = `
6794
+ <button class="vector-layer-btn" title="${this.options.title}">
6795
+ <svg viewBox="0 0 24 24" width="18" height="18">
6796
+ <path d="M9,2V7.5L12,10.5L15,7.5V2H9ZM2,9H7.5L10.5,12L7.5,15H2V9ZM22,9H16.5L13.5,12L16.5,15H22V9ZM9,22V16.5L12,13.5L15,16.5V22H9Z"
6797
+ fill="currentColor"/>
6798
+ </svg>
6799
+ </button>
6800
+ `;
6801
+
6802
+ // 添加点击事件
6803
+ const button = this._container.querySelector('.vector-layer-btn');
6804
+ button.addEventListener('click', () => this.openModal());
6805
+
6806
+ return this._container;
6807
+ }
6808
+
6809
+ /**
6810
+ * 从地图移除
6811
+ */
6812
+ onRemove() {
6813
+ this.closeModal();
6814
+ if (this._container) {
6815
+ this._container.parentNode.removeChild(this._container);
6816
+ }
6817
+ this._map = undefined;
6818
+ }
6819
+
6820
+ /**
6821
+ * 打开弹窗
6822
+ */
6823
+ openModal() {
6824
+ if (this._modal) return;
6825
+
6826
+ // 创建遮罩层
6827
+ this._overlay = document.createElement('div');
6828
+ this._overlay.className = 'vector-layer-overlay';
6829
+ this._overlay.style.cssText = `
6830
+ position: fixed;
6831
+ top: 0;
6832
+ left: 0;
6833
+ width: 100%;
6834
+ height: 100%;
6835
+ background: rgba(0, 0, 0, 0.5);
6836
+ z-index: 9999;
6837
+ display: flex;
6838
+ justify-content: center;
6839
+ backdrop-filter: blur(2px);
6840
+ `;
6841
+ this._overlay.addEventListener('click', (e) => {
6842
+ if (e.target === this._overlay) this.closeModal();
6843
+ });
6844
+
6845
+ // 创建弹窗
6846
+ this._modal = document.createElement('div');
6847
+ this._modal.className = 'vector-layer-modal';
6848
+ this._modal.style.cssText = `
6849
+ background: white;
6850
+ border-radius: 8px;
6851
+ padding: ${this.options.compactMode ? '16px' : '20px'};
6852
+ width: ${this.options.width};
6853
+ max-width: 90vw;
6854
+ max-height: ${this.options.maxHeight};
6855
+ display: flex;
6856
+ flex-direction: column;
6857
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
6858
+ animation: vector-layer-modal-appear 0.2s ease-out;
6859
+ border: 1px solid #e0e0e0;
6860
+ `;
6861
+
6862
+ // 弹窗标题
6863
+ const title = document.createElement('h3');
6864
+ title.className = 'vector-layer-title';
6865
+ title.textContent = this.options.title;
6866
+ title.style.cssText = `
6867
+ margin: 0 0 ${this.options.compactMode ? '12px' : '16px'} 0;
6868
+ font-size: ${this.options.compactMode ? '15px' : '16px'};
6869
+ font-weight: 600;
6870
+ color: #2c3e50;
6871
+ line-height: 1.3;
6872
+ `;
6873
+ this._modal.appendChild(title);
6874
+
6875
+ // 搜索框
6876
+ if (this.options.showSearch) {
6877
+ const searchContainer = document.createElement('div');
6878
+ searchContainer.className = 'vector-layer-search';
6879
+ searchContainer.style.cssText = `
6880
+ margin-bottom: ${this.options.compactMode ? '12px' : '16px'};
6881
+ position: relative;
6882
+ `;
6883
+
6884
+ this._searchInput = document.createElement('input');
6885
+ this._searchInput.type = 'text';
6886
+ this._searchInput.placeholder = this.options.placeholder;
6887
+ this._searchInput.className = 'vector-layer-search-input';
6888
+ this._searchInput.style.cssText = `
6889
+ width: 100%;
6890
+ padding: ${this.options.compactMode ? '8px 12px 8px 36px' : '10px 16px 10px 40px'};
6891
+ border: 1px solid #dcdfe6;
6892
+ border-radius: 6px;
6893
+ font-size: 13px;
6894
+ outline: none;
6895
+ transition: all 0.2s;
6896
+ box-sizing: border-box;
6897
+ background-color: #f8f9fa;
6898
+ `;
6899
+ this._searchInput.addEventListener('input', () => this.filterOptions());
6900
+ this._searchInput.addEventListener('keydown', (e) => {
6901
+ if (e.key === 'Escape') this.closeModal();
6902
+ });
6903
+
6904
+ // 搜索图标
6905
+ const searchIcon = document.createElement('div');
6906
+ searchIcon.innerHTML = `
6907
+ <svg viewBox="0 0 24 24" width="16" height="16"
6908
+ style="position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #888;">
6909
+ <path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
6910
+ fill="currentColor"/>
6911
+ </svg>
6912
+ `;
6913
+
6914
+ searchContainer.appendChild(searchIcon);
6915
+ searchContainer.appendChild(this._searchInput);
6916
+ this._modal.appendChild(searchContainer);
6917
+ }
6918
+
6919
+ // 选项列表容器
6920
+ const listContainer = document.createElement('div');
6921
+ listContainer.className = 'vector-layer-list-container';
6922
+ listContainer.style.cssText = `
6923
+ flex: 1;
6924
+ overflow: hidden;
6925
+ min-height: ${this.options.compactMode ? '120px' : '160px'};
6926
+ border-radius: 6px;
6927
+ border: 1px solid #e0e0e0;
6928
+ `;
6929
+
6930
+ this._optionsList = document.createElement('div');
6931
+ this._optionsList.className = 'vector-layer-options';
6932
+ this._optionsList.style.cssText = `
6933
+ max-height: calc(${this.options.maxHeight} - ${this.options.compactMode ? '140px' : '180px'});
6934
+ overflow-y: auto;
6935
+ scroll-behavior: smooth;
6936
+ `;
6937
+
6938
+ // 填充选项
6939
+ this.renderOptions();
6940
+
6941
+ listContainer.appendChild(this._optionsList);
6942
+ this._modal.appendChild(listContainer);
6943
+
6944
+ // 操作按钮
6945
+ const buttonContainer = document.createElement('div');
6946
+ buttonContainer.className = 'vector-layer-actions';
6947
+ buttonContainer.style.cssText = `
6948
+ display: flex;
6949
+ justify-content: flex-end;
6950
+ gap: 8px;
6951
+ margin-top: ${this.options.compactMode ? '12px' : '16px'};
6952
+ padding-top: ${this.options.compactMode ? '12px' : '16px'};
6953
+ border-top: 1px solid #f0f0f0;
6954
+ `;
6955
+
6956
+ const cancelBtn = document.createElement('button');
6957
+ cancelBtn.textContent = '取消';
6958
+ cancelBtn.className = 'vector-layer-cancel';
6959
+ cancelBtn.style.cssText = `
6960
+ padding: 6px 16px;
6961
+ border: 1px solid #dcdfe6;
6962
+ border-radius: 4px;
6963
+ background: white;
6964
+ color: #606266;
6965
+ cursor: pointer;
6966
+ font-size: 13px;
6967
+ transition: all 0.2s;
6968
+ font-weight: 500;
6969
+ `;
6970
+ cancelBtn.addEventListener('click', () => {
6971
+ this.closeModal();
6972
+ this.options.onCancel(this._map, null);
6973
+ });
6974
+ cancelBtn.addEventListener('mouseenter', () => {
6975
+ cancelBtn.style.backgroundColor = '#f5f7fa';
6976
+ cancelBtn.style.borderColor = '#c0c4cc';
6977
+ });
6978
+ cancelBtn.addEventListener('mouseleave', () => {
6979
+ cancelBtn.style.backgroundColor = 'white';
6980
+ cancelBtn.style.borderColor = '#dcdfe6';
6981
+ });
6982
+
6983
+ buttonContainer.appendChild(cancelBtn);
6984
+ this._modal.appendChild(buttonContainer);
6985
+
6986
+ this._overlay.appendChild(this._modal);
6987
+ document.body.appendChild(this._overlay);
6988
+
6989
+ // 阻止地图事件冒泡
6990
+ this._modal.addEventListener('click', (e) => e.stopPropagation());
6991
+ this._modal.addEventListener('mousedown', (e) => e.stopPropagation());
6992
+ this._modal.addEventListener('mouseup', (e) => e.stopPropagation());
6993
+
6994
+ // 聚焦搜索框
6995
+ if (this._searchInput) {
6996
+ setTimeout(() => this._searchInput.focus(), 50);
6997
+ }
6998
+
6999
+ // 添加动画样式
7000
+ this.addStyles();
7001
+ }
7002
+
7003
+ /**
7004
+ * 渲染选项列表
7005
+ */
7006
+ renderOptions() {
7007
+ this._optionsList.innerHTML = '';
7008
+
7009
+ if (this._filteredList.length === 0) {
7010
+ const emptyItem = document.createElement('div');
7011
+ emptyItem.className = 'vector-layer-option empty';
7012
+ emptyItem.textContent = '无匹配结果';
7013
+ emptyItem.style.cssText = `
7014
+ padding: ${this.options.compactMode ? '20px 16px' : '24px 16px'};
7015
+ text-align: center;
7016
+ color: #999;
7017
+ font-size: 13px;
7018
+ font-style: italic;
7019
+ `;
7020
+ this._optionsList.appendChild(emptyItem);
7021
+ return;
7022
+ }
7023
+
7024
+ this._filteredList.forEach((item, index) => {
7025
+ const option = document.createElement('div');
7026
+ option.className = 'vector-layer-option';
7027
+ option.dataset.value = item.value || item.label;
7028
+ option.dataset.index = index;
7029
+ option.style.cssText = `
7030
+ padding: ${this.options.compactMode ? '8px 12px' : '10px 16px'};
7031
+ cursor: pointer;
7032
+ border-bottom: 1px solid #f5f5f5;
7033
+ font-size: 13px;
7034
+ transition: all 0.15s;
7035
+ display: flex;
7036
+ align-items: center;
7037
+ gap: 8px;
7038
+ min-height: ${this.options.compactMode ? '36px' : '40px'};
7039
+ box-sizing: border-box;
7040
+ `;
7041
+
7042
+ option.innerHTML = `
7043
+ ${item.icon ? `<span class="option-icon" style="flex-shrink: 0; width: 16px; height: 16px;">${item.icon}</span>` : ''}
7044
+ <span class="option-label" style="flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${item.label}</span>
7045
+ ${item.description ? `<span class="option-desc" style="font-size: 12px; color: #888; flex-shrink: 0;">${item.description}</span>` : ''}
7046
+ `;
7047
+
7048
+ option.addEventListener('click', () => this.selectOption(item));
7049
+ option.addEventListener('mouseenter', () => {
7050
+ option.style.backgroundColor = '#f0f7ff';
7051
+ option.style.borderLeftColor = '#1890ff';
7052
+ });
7053
+ option.addEventListener('mouseleave', () => {
7054
+ option.style.backgroundColor = '';
7055
+ option.style.borderLeftColor = '';
7056
+ });
7057
+
7058
+ this._optionsList.appendChild(option);
7059
+ });
7060
+
7061
+ // 移除最后一个选项的下边框
7062
+ const lastOption = this._optionsList.lastChild;
7063
+ if (lastOption && !lastOption.classList.contains('empty')) {
7064
+ lastOption.style.borderBottom = 'none';
7065
+ }
7066
+ }
7067
+
7068
+ /**
7069
+ * 筛选选项
7070
+ */
7071
+ filterOptions() {
7072
+ const searchText = this._searchInput ? this._searchInput.value.toLowerCase() : '';
7073
+
7074
+ if (!searchText) {
7075
+ this._filteredList = [...this.options.list];
7076
+ } else {
7077
+ this._filteredList = this.options.list.filter(item =>
7078
+ item.label.toLowerCase().includes(searchText) ||
7079
+ (item.value && item.value.toLowerCase().includes(searchText)) ||
7080
+ (item.description && item.description.toLowerCase().includes(searchText))
7081
+ );
7082
+ }
7083
+
7084
+ this.renderOptions();
7085
+ }
7086
+
7087
+ /**
7088
+ * 选中选项
7089
+ * @param {Object} item 选中的选项
7090
+ */
7091
+ selectOption(item) {
7092
+ try {
7093
+ // 执行回调
7094
+ this.options.onSelect(this._map, item);
7095
+
7096
+ // 添加选中动画
7097
+ const option = this._optionsList.querySelector(`[data-value="${item.value || item.label}"]`);
7098
+ if (option) {
7099
+ const originalBg = option.style.backgroundColor;
7100
+ const originalColor = option.style.color;
7101
+ option.style.backgroundColor = '#1890ff';
7102
+ option.style.color = 'white';
7103
+ setTimeout(() => {
7104
+ option.style.backgroundColor = originalBg;
7105
+ option.style.color = originalColor;
7106
+ }, 150);
7107
+ setTimeout(() => {
7108
+ this.closeModal();
7109
+ }, 300);
7110
+ } else {
7111
+ this.closeModal();
7112
+ }
7113
+ } catch (error) {
7114
+ console.error('选中回调执行失败:', error);
7115
+ this.closeModal();
7116
+ }
7117
+ }
7118
+
7119
+ /**
7120
+ * 关闭弹窗
7121
+ */
7122
+ closeModal() {
7123
+ if (this._overlay) {
7124
+ this._overlay.style.opacity = '0';
7125
+ this._overlay.style.transition = 'opacity 0.15s ease-out';
7126
+
7127
+ setTimeout(() => {
7128
+ if (this._overlay && this._overlay.parentNode) {
7129
+ document.body.removeChild(this._overlay);
7130
+ }
7131
+ this._overlay = null;
7132
+ this._modal = null;
7133
+ this._searchInput = null;
7134
+ this._optionsList = null;
7135
+ }, 150);
7136
+ }
7137
+ }
7138
+
7139
+ /**
7140
+ * 更新选项列表
7141
+ * @param {Array} newList 新的选项列表
7142
+ */
7143
+ updateList(newList) {
7144
+ this.options.list = newList;
7145
+ this._filteredList = [...newList];
7146
+ if (this._modal) {
7147
+ this.renderOptions();
7148
+ }
7149
+ }
7150
+
7151
+ /**
7152
+ * 添加CSS样式
7153
+ */
7154
+ addStyles() {
7155
+ if (document.querySelector('#vector-layer-styles')) return;
7156
+
7157
+ const style = document.createElement('style');
7158
+ style.id = 'vector-layer-styles';
7159
+ style.textContent = `
7160
+ @keyframes vector-layer-modal-appear {
7161
+ from {
7162
+ opacity: 0;
7163
+ transform: translateY(-8px) scale(0.98);
7164
+ }
7165
+ to {
7166
+ opacity: 1;
7167
+ transform: translateY(0) scale(1);
7168
+ }
7169
+ }
7170
+
7171
+ .vector-layer-ctrl button {
7172
+ background: white;
7173
+ border: 1px solid #d9d9d9;
7174
+ border-radius: 4px;
7175
+ width: 28px;
7176
+ height: 28px;
7177
+ display: flex;
7178
+ align-items: center;
7179
+ justify-content: center;
7180
+ cursor: pointer;
7181
+ color: #555;
7182
+ transition: all 0.2s;
7183
+ padding: 0;
7184
+ margin: 0;
7185
+ }
7186
+
7187
+ .vector-layer-ctrl button:hover {
7188
+ background: #f5f5f5;
7189
+ border-color: #40a9ff;
7190
+ color: #40a9ff;
7191
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
7192
+ }
7193
+
7194
+ .vector-layer-search-input:focus {
7195
+ border-color: #40a9ff;
7196
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
7197
+ background-color: white;
7198
+ }
7199
+
7200
+ .vector-layer-option {
7201
+ position: relative;
7202
+ border-left: 3px solid transparent;
7203
+ }
7204
+
7205
+ .vector-layer-option:hover {
7206
+ border-left-color: #1890ff;
7207
+ }
7208
+
7209
+ .vector-layer-option:active {
7210
+ background-color: #e6f7ff !important;
7211
+ }
7212
+
7213
+ .vector-layer-options::-webkit-scrollbar {
7214
+ width: 4px;
7215
+ }
7216
+
7217
+ .vector-layer-options::-webkit-scrollbar-track {
7218
+ background: #f5f5f5;
7219
+ border-radius: 2px;
7220
+ }
7221
+
7222
+ .vector-layer-options::-webkit-scrollbar-thumb {
7223
+ background: #bfbfbf;
7224
+ border-radius: 2px;
7225
+ }
7226
+
7227
+ .vector-layer-options::-webkit-scrollbar-thumb:hover {
7228
+ background: #8c8c8c;
7229
+ }
7230
+
7231
+ .vector-layer-modal {
7232
+ overflow: hidden;
7233
+ }
7234
+
7235
+ /* 紧凑模式下的额外样式 */
7236
+ .vector-layer-modal.compact {
7237
+ padding: 12px;
7238
+ }
7239
+
7240
+ .vector-layer-modal.compact .vector-layer-title {
7241
+ font-size: 14px;
7242
+ margin-bottom: 10px;
7243
+ }
7244
+
7245
+ .vector-layer-modal.compact .vector-layer-search-input {
7246
+ padding: 6px 10px 6px 32px;
7247
+ font-size: 12px;
7248
+ }
7249
+
7250
+ .vector-layer-modal.compact .vector-layer-option {
7251
+ padding: 6px 10px;
7252
+ font-size: 12px;
7253
+ min-height: 32px;
7254
+ }
7255
+
7256
+ /* 暗色模式支持 */
7257
+ @media (prefers-color-scheme: dark) {
7258
+ .vector-layer-modal {
7259
+ background: #2d2d2d;
7260
+ border-color: #404040;
7261
+ color: #e0e0e0;
7262
+ }
7263
+
7264
+ .vector-layer-title {
7265
+ color: #e0e0e0;
7266
+ }
7267
+
7268
+ .vector-layer-search-input {
7269
+ background-color: #3a3a3a;
7270
+ border-color: #555;
7271
+ color: #e0e0e0;
7272
+ }
7273
+
7274
+ .vector-layer-search-input:focus {
7275
+ border-color: #1890ff;
7276
+ }
7277
+
7278
+ .vector-layer-list-container {
7279
+ border-color: #404040;
7280
+ }
7281
+
7282
+ .vector-layer-option {
7283
+ border-bottom-color: #404040;
7284
+ }
7285
+
7286
+ .vector-layer-option:hover {
7287
+ background-color: #3a3a3a;
7288
+ }
7289
+
7290
+ .option-desc {
7291
+ color: #aaa;
7292
+ }
7293
+
7294
+ .vector-layer-actions {
7295
+ border-top-color: #404040;
7296
+ }
7297
+
7298
+ .vector-layer-cancel {
7299
+ background: #3a3a3a;
7300
+ border-color: #555;
7301
+ color: #e0e0e0;
7302
+ }
7303
+
7304
+ .vector-layer-options::-webkit-scrollbar-track {
7305
+ background: #3a3a3a;
7306
+ }
7307
+
7308
+ .vector-layer-options::-webkit-scrollbar-thumb {
7309
+ background: #666;
7310
+ }
7311
+ }
7312
+ `;
7313
+
7314
+ document.head.appendChild(style);
7315
+ }
7316
+ }
7317
+
6741
7318
  class CustomOptionsControl {
6742
7319
  constructor(args) {
6743
7320
  const { title, options, onConfirm, icon } = args;
@@ -7973,5 +8550,5 @@ const useDrawCache = () => {
7973
8550
  }
7974
8551
  };
7975
8552
 
7976
- export { CustomOptionsControl, CustomToggleControl, DrawCacheFeatureManager, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControl, RulerControl, TerrainToggleControl, useDrawCache };
8553
+ export { CustomOptionsControl, CustomSearchSelectControl, CustomToggleControl, DrawCacheFeatureManager, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControl, RulerControl, TerrainToggleControl, useDrawCache };
7977
8554
  //# sourceMappingURL=index.js.map