@yangyongtao/gaea 1.1.12 → 1.1.14
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/CHANGELOG.md +5 -0
- package/package.json +5 -2
- package/src/GaeaMethods.js +127 -38
- package/src/components/VectorPolygonControl.vue +396 -0
- package/src/components/index.cjs +6 -0
- package/src/components/index.js +1 -0
- package/src/index.cjs +11 -0
- package/src/plugin.cjs +6 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangyongtao/gaea",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
4
4
|
"description": "Gaea 3D visualization component library - Vue 3 components based on Godot WASM engine. Includes WMTS layer loading, camera view control, Vite plugin auto-injection, etc.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/index.
|
|
6
|
+
"main": "src/index.cjs",
|
|
7
7
|
"module": "src/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./src/index.js",
|
|
11
|
+
"require": "./src/index.cjs",
|
|
11
12
|
"default": "./src/index.js"
|
|
12
13
|
},
|
|
13
14
|
"./components": {
|
|
14
15
|
"import": "./src/components/index.js",
|
|
16
|
+
"require": "./src/components/index.cjs",
|
|
15
17
|
"default": "./src/components/index.js"
|
|
16
18
|
},
|
|
17
19
|
"./components/*": "./src/components/*",
|
|
18
20
|
"./plugin": {
|
|
19
21
|
"import": "./src/plugin.js",
|
|
22
|
+
"require": "./src/plugin.cjs",
|
|
20
23
|
"default": "./src/plugin.js"
|
|
21
24
|
}
|
|
22
25
|
},
|
package/src/GaeaMethods.js
CHANGED
|
@@ -274,6 +274,7 @@ export class GaeaApp {
|
|
|
274
274
|
this._screenIconList = [];
|
|
275
275
|
this._secIsClickArr = [];
|
|
276
276
|
this._secScreenIconKeyValue = {};
|
|
277
|
+
this._groundPolygonElements = [];
|
|
277
278
|
this.tweenGroup = new TWEEN.Group();
|
|
278
279
|
this._tourConfig = {
|
|
279
280
|
'沿浦水闸': { direction: [-0.5, 0.3, 0.3], positionList: [
|
|
@@ -335,47 +336,59 @@ export class GaeaApp {
|
|
|
335
336
|
await this._initLayers();
|
|
336
337
|
console.log('[GaeaApp] 图层加载完成');
|
|
337
338
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
339
|
+
// 影像/地形就绪即可显示地图,隐藏 loading,避免用户等待模型/接口
|
|
340
|
+
hideLoading();
|
|
341
|
+
this._addResetButton();
|
|
342
|
+
console.log('[GaeaApp] 地图已就绪,模型与图层树数据后台加载中...');
|
|
343
|
+
|
|
344
|
+
// 剩余重资源(模型位置、GLTF、图层树接口、屏幕图标)后台异步加载,不阻塞地图显示
|
|
345
|
+
this._loadRestAsync();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** 后台异步加载:模型位置调整 → GLTF → 图层树数据 → 屏幕图标 */
|
|
349
|
+
async _loadRestAsync() {
|
|
350
|
+
try {
|
|
351
|
+
// 5. 调整模型位置
|
|
352
|
+
console.log('[GaeaApp] 开始调整模型位置...');
|
|
353
|
+
const models = [
|
|
354
|
+
{ nodeName: '沿浦水闸', position: '-2876603.788, 4892658.352, 2900208.622', rotation: '-37.24, 162.42, -17.17' },
|
|
355
|
+
{ nodeName: '下在水闸', position: '-2875959.024, 4893109.058, 2900101.919', rotation: '-23.42, -165.20, -34.52' },
|
|
356
|
+
{ nodeName: '联盟水闸', position: '-2878251.995, 4893011.199, 2897998.160', rotation: '-23.43, -165.98, -33.40' },
|
|
357
|
+
{ nodeName: '岭尾新闸', position: '-2877382.212, 4892920.226, 2898965.484', rotation: '7.79, 36.98, 40.83' },
|
|
358
|
+
{ nodeName: '岭尾水闸', position: '-2877582.576, 4892520.352, 2899499.184', rotation: '-41.04, 126.37, 6.29' },
|
|
359
|
+
];
|
|
360
|
+
for (const model of models) {
|
|
361
|
+
try {
|
|
362
|
+
const node = Gaea.World.Instance.GetNode(model.nodeName);
|
|
363
|
+
if (!node) { console.warn(`[GaeaApp] 未找到节点: ${model.nodeName}`); continue; }
|
|
364
|
+
const inst = Gaea.MeshInstance.ConvertBy(node);
|
|
365
|
+
const pos = model.position.split(',').map(v => parseFloat(v.trim()));
|
|
366
|
+
const rot = model.rotation.split(',').map(v => parseFloat(v.trim()));
|
|
367
|
+
if (inst.Translation) inst.Translation = new Gaea.Vector3(pos[0], pos[1], pos[2]);
|
|
368
|
+
if (inst.RotationDegrees) inst.RotationDegrees = new Gaea.Vector3(rot[0], rot[1], rot[2]);
|
|
369
|
+
} catch (e) {
|
|
370
|
+
console.warn(`[GaeaApp] 调整模型 ${model.nodeName} 失败:`, e.message);
|
|
371
|
+
}
|
|
359
372
|
}
|
|
360
|
-
|
|
361
|
-
console.log('[GaeaApp] 所有模型位置调整完成');
|
|
373
|
+
console.log('[GaeaApp] 所有模型位置调整完成');
|
|
362
374
|
|
|
363
|
-
|
|
364
|
-
|
|
375
|
+
// 6. 加载 GLTF 模型
|
|
376
|
+
await this._loadGltfModels();
|
|
365
377
|
|
|
366
|
-
|
|
367
|
-
|
|
378
|
+
// 7. 获取图层树数据(有接口走接口,没接口用默认)
|
|
379
|
+
await this._fetchLayerTreeData();
|
|
368
380
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
381
|
+
// 同步 layerCategories(修复数据更新未同步的问题)
|
|
382
|
+
this.layerCategories = this.config.layerCategories || [];
|
|
383
|
+
console.log('[GaeaApp] layerCategories 更新后:', this.layerCategories.length);
|
|
372
384
|
|
|
373
|
-
|
|
374
|
-
|
|
385
|
+
// 8. 初始化屏幕图标
|
|
386
|
+
this._initScreenIcons();
|
|
375
387
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
388
|
+
console.log('[GaeaApp] 后台资源加载完成');
|
|
389
|
+
} catch (e) {
|
|
390
|
+
console.error('[GaeaApp] 后台资源加载出错:', e);
|
|
391
|
+
}
|
|
379
392
|
}
|
|
380
393
|
|
|
381
394
|
_waitForCanvas() {
|
|
@@ -570,13 +583,14 @@ export class GaeaApp {
|
|
|
570
583
|
}
|
|
571
584
|
|
|
572
585
|
console.log('[GaeaApp] 开始加载 GLTF 模型...');
|
|
573
|
-
|
|
586
|
+
// 并行加载所有模型(原串行逐个 await 会累加耗时)
|
|
587
|
+
await Promise.all(gltfList.map(async (item) => {
|
|
574
588
|
item.url = encodeURI(item.url);
|
|
575
589
|
try {
|
|
576
590
|
const el = await this.AddGltf(item);
|
|
577
591
|
if (el) this._gltfElements[item.name] = el;
|
|
578
592
|
} catch (e) { console.warn(`[GaeaApp] 模型 ${item.name} 加载失败:`, e.message); }
|
|
579
|
-
}
|
|
593
|
+
}));
|
|
580
594
|
console.log('[GaeaApp] GLTF 模型加载完成');
|
|
581
595
|
}
|
|
582
596
|
|
|
@@ -619,7 +633,7 @@ export class GaeaApp {
|
|
|
619
633
|
});
|
|
620
634
|
|
|
621
635
|
Gaea.World.Instance.RenderableObjectList.AddLast(element);
|
|
622
|
-
setTimeout(() => res(element),
|
|
636
|
+
setTimeout(() => res(element), 8000);
|
|
623
637
|
});
|
|
624
638
|
};
|
|
625
639
|
|
|
@@ -948,6 +962,81 @@ export class GaeaApp {
|
|
|
948
962
|
}
|
|
949
963
|
}
|
|
950
964
|
|
|
965
|
+
// ---- 贴地面(矢量面绘制) ----
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* 根据坐标数组添加贴地面
|
|
969
|
+
* @param {Array} coords - 坐标数组,格式 [[lng, lat], [lng, lat], ...]
|
|
970
|
+
* @param {string} name - 面名称
|
|
971
|
+
* @param {string} colorHex - 颜色 hex 值,如 '#3498db'
|
|
972
|
+
* @returns {object|null} 创建的 PolygonGeometryElement,失败返回 null
|
|
973
|
+
*/
|
|
974
|
+
addGroundPolygonByCoords(coords, name, colorHex) {
|
|
975
|
+
if (!coords || coords.length < 3) {
|
|
976
|
+
console.warn(`坐标点不足,跳过: ${name}`);
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
// 检查是否已存在同名面
|
|
980
|
+
if (this._groundPolygonElements.find(p => p.name === name)) {
|
|
981
|
+
console.warn(`贴地面 "${name}" 已存在,跳过`);
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const positions = coords.map(c => new Gaea.Vector3(c[1], c[0], 50));
|
|
986
|
+
const geometry = Gaea.Geometry.Create1(Gaea.GeometryType.Polygon, positions);
|
|
987
|
+
|
|
988
|
+
const res = Gaea.GaeaResourceLoader.Instance.LoadLoaclResource('res://assets/shaders/polygonClampGround.shader');
|
|
989
|
+
const shader = Gaea.Shader.ConvertBy(res);
|
|
990
|
+
const material = new Gaea.ShaderMaterial();
|
|
991
|
+
material.Shader = shader;
|
|
992
|
+
|
|
993
|
+
const hex = colorHex.replace('#', '');
|
|
994
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
995
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
996
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
997
|
+
Gaea.World.Instance.SetMtlColor(material, 'albedo', new Gaea.Color(r, g, b, 0.5));
|
|
998
|
+
|
|
999
|
+
const symbol = new Gaea.GroundPolygonSymbol();
|
|
1000
|
+
symbol.PrismHeight = 9999;
|
|
1001
|
+
symbol.GroundPolygonMtl = material;
|
|
1002
|
+
|
|
1003
|
+
const polygonElement = new Gaea.PolygonGeometryElement();
|
|
1004
|
+
polygonElement.Symbol = symbol;
|
|
1005
|
+
polygonElement.Shape = geometry;
|
|
1006
|
+
polygonElement.EnablePick = true;
|
|
1007
|
+
polygonElement.EnableEdit = true;
|
|
1008
|
+
polygonElement.NeedIntersectWithMesh = false;
|
|
1009
|
+
polygonElement.EnableVirtualPoint = true;
|
|
1010
|
+
polygonElement.Name = name;
|
|
1011
|
+
|
|
1012
|
+
Gaea.World.Instance.RenderableObjectList.AddLast(polygonElement);
|
|
1013
|
+
this._groundPolygonElements.push({ name, color: colorHex, element: polygonElement });
|
|
1014
|
+
return polygonElement;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** 移除指定贴地面 */
|
|
1018
|
+
removeGroundPolygon(element) {
|
|
1019
|
+
try {
|
|
1020
|
+
Gaea.World.Instance.RenderableObjectList.Remove(element);
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
console.error('移除贴地面失败:', err);
|
|
1023
|
+
}
|
|
1024
|
+
const idx = this._groundPolygonElements.findIndex(p => p.element === element);
|
|
1025
|
+
if (idx >= 0) this._groundPolygonElements.splice(idx, 1);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** 清除所有贴地面 */
|
|
1029
|
+
clearAllGroundPolygons() {
|
|
1030
|
+
this._groundPolygonElements.forEach(item => {
|
|
1031
|
+
try {
|
|
1032
|
+
Gaea.World.Instance.RenderableObjectList.Remove(item.element);
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
console.error('移除贴地面失败:', err);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
this._groundPolygonElements = [];
|
|
1038
|
+
}
|
|
1039
|
+
|
|
951
1040
|
/** 定位到指定视角(经纬度高程 + 相机参数) */
|
|
952
1041
|
SetPosition(numStr) {
|
|
953
1042
|
const arr = numStr.split(',').map(s => Number(s.trim()));
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vector-polygon-panel" v-drag>
|
|
3
|
+
<div class="panel-header title">
|
|
4
|
+
<span class="panel-title">画矢量面</span>
|
|
5
|
+
<button class="close-btn" @click="handleClose">×</button>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="panel-body">
|
|
9
|
+
<!-- 颜色选择 -->
|
|
10
|
+
<div class="section">
|
|
11
|
+
<div class="section-header">
|
|
12
|
+
<span class="section-label"><i class="fas fa-palette"></i> 面颜色</span>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="color-row">
|
|
15
|
+
<div
|
|
16
|
+
v-for="c in presetColors"
|
|
17
|
+
:key="c"
|
|
18
|
+
class="color-block"
|
|
19
|
+
:class="{ active: polygonColor === c }"
|
|
20
|
+
:style="{ backgroundColor: c }"
|
|
21
|
+
@click="polygonColor = c"
|
|
22
|
+
></div>
|
|
23
|
+
<div class="color-custom">
|
|
24
|
+
<input type="color" v-model="polygonColor" class="color-picker" />
|
|
25
|
+
<span class="color-hex">{{ polygonColor }}</span>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- 上传GeoJSON -->
|
|
31
|
+
<div class="section">
|
|
32
|
+
<div class="section-header">
|
|
33
|
+
<span class="section-label"><i class="fas fa-file-upload"></i> 上传GeoJSON</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div
|
|
36
|
+
class="upload-area"
|
|
37
|
+
:class="{ 'drag-over': isDragOver }"
|
|
38
|
+
@click="triggerFileInput"
|
|
39
|
+
@dragover.prevent="isDragOver = true"
|
|
40
|
+
@dragleave.prevent="isDragOver = false"
|
|
41
|
+
@drop.prevent="handleFileDrop"
|
|
42
|
+
>
|
|
43
|
+
<i class="fas fa-cloud-upload-alt upload-icon"></i>
|
|
44
|
+
<span class="upload-text">点击或拖拽.geojson文件到此处</span>
|
|
45
|
+
<input
|
|
46
|
+
ref="fileInput"
|
|
47
|
+
type="file"
|
|
48
|
+
accept=".geojson,.json"
|
|
49
|
+
multiple
|
|
50
|
+
style="display: none"
|
|
51
|
+
@change="handleFileSelect"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div v-if="uploadingFile" class="upload-status">
|
|
55
|
+
<i class="fas fa-spinner fa-spin"></i> 正在处理: {{ uploadingFile }}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- 已加载的面列表 -->
|
|
60
|
+
<div class="section" v-if="polygonList.length > 0">
|
|
61
|
+
<div class="section-header">
|
|
62
|
+
<span class="section-label"><i class="fas fa-list"></i> 已加载矢量面 ({{ polygonList.length }})</span>
|
|
63
|
+
<button class="btn-clear-small" @click="clearAll">清空全部</button>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="polygon-items">
|
|
66
|
+
<div v-for="(item, i) in polygonList" :key="i" class="polygon-item">
|
|
67
|
+
<span
|
|
68
|
+
class="polygon-color-dot"
|
|
69
|
+
:style="{ backgroundColor: item.color }"
|
|
70
|
+
></span>
|
|
71
|
+
<span class="polygon-name" :title="item.name">{{ item.name }}</span>
|
|
72
|
+
<button class="polygon-delete" @click="removePolygon(i)" title="删除">
|
|
73
|
+
<i class="fas fa-trash-alt"></i>
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<script setup>
|
|
83
|
+
import { ref } from 'vue';
|
|
84
|
+
import { GaeaApp } from '../GaeaMethods.js';
|
|
85
|
+
import vDrag from '../directives/vDrag.js';
|
|
86
|
+
|
|
87
|
+
const props = defineProps({
|
|
88
|
+
appInstance: { type: Object, default: null }
|
|
89
|
+
});
|
|
90
|
+
const emit = defineEmits(['close']);
|
|
91
|
+
|
|
92
|
+
const APP = props.appInstance || (window.APP || new GaeaApp());
|
|
93
|
+
|
|
94
|
+
const presetColors = [
|
|
95
|
+
'#e74c3c', '#e67e22', '#f1c40f', '#2ecc71',
|
|
96
|
+
'#1abc9c', '#3498db', '#9b59b6', '#34495e',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const polygonColor = ref('#3498db');
|
|
100
|
+
|
|
101
|
+
const fileInput = ref(null);
|
|
102
|
+
const isDragOver = ref(false);
|
|
103
|
+
const uploadingFile = ref('');
|
|
104
|
+
const polygonList = ref([]);
|
|
105
|
+
|
|
106
|
+
const triggerFileInput = () => {
|
|
107
|
+
fileInput.value?.click();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleFileDrop = (e) => {
|
|
111
|
+
isDragOver.value = false;
|
|
112
|
+
const files = e.dataTransfer.files;
|
|
113
|
+
if (files.length > 0) processFiles(files);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleFileSelect = (e) => {
|
|
117
|
+
const files = e.target.files;
|
|
118
|
+
if (files.length > 0) processFiles(files);
|
|
119
|
+
e.target.value = '';
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const processFiles = async (files) => {
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
if (!file.name.endsWith('.geojson') && !file.name.endsWith('.json')) continue;
|
|
125
|
+
|
|
126
|
+
uploadingFile.value = file.name;
|
|
127
|
+
try {
|
|
128
|
+
const text = await file.text();
|
|
129
|
+
const geojson = JSON.parse(text);
|
|
130
|
+
const polygons = extractPolygons(geojson, file.name.replace(/\.(geojson|json)$/i, ''));
|
|
131
|
+
for (const poly of polygons) {
|
|
132
|
+
const el = APP.addGroundPolygonByCoords(poly.coords, poly.name, polygonColor.value);
|
|
133
|
+
if (el) {
|
|
134
|
+
polygonList.value.push({ name: poly.name, color: polygonColor.value, element: el });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(`解析GeoJSON文件失败: ${file.name}`, err);
|
|
139
|
+
alert(`解析文件 "${file.name}" 失败: ${err.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
uploadingFile.value = '';
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const extractPolygons = (geojson, defaultName) => {
|
|
146
|
+
const results = [];
|
|
147
|
+
|
|
148
|
+
const processGeometry = (geom, name) => {
|
|
149
|
+
if (!geom) return;
|
|
150
|
+
if (geom.type === 'Polygon') {
|
|
151
|
+
results.push({ name, coords: geom.coordinates[0] });
|
|
152
|
+
} else if (geom.type === 'MultiPolygon') {
|
|
153
|
+
geom.coordinates.forEach((poly, idx) => {
|
|
154
|
+
results.push({ name: `${name}_${idx + 1}`, coords: poly[0] });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (geojson.type === 'FeatureCollection') {
|
|
160
|
+
geojson.features.forEach((feature, idx) => {
|
|
161
|
+
const featureName = feature.properties?.name || feature.properties?.Name || `${defaultName}_${idx + 1}`;
|
|
162
|
+
processGeometry(feature.geometry, featureName);
|
|
163
|
+
});
|
|
164
|
+
} else if (geojson.type === 'Feature') {
|
|
165
|
+
const featureName = geojson.properties?.name || geojson.properties?.Name || defaultName;
|
|
166
|
+
processGeometry(geojson.geometry, featureName);
|
|
167
|
+
} else if (geojson.type === 'Polygon') {
|
|
168
|
+
results.push({ name: defaultName, coords: geojson.coordinates[0] });
|
|
169
|
+
} else if (geojson.type === 'MultiPolygon') {
|
|
170
|
+
geojson.coordinates.forEach((poly, idx) => {
|
|
171
|
+
results.push({ name: `${defaultName}_${idx + 1}`, coords: poly[0] });
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return results;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const removePolygon = (index) => {
|
|
179
|
+
const item = polygonList.value[index];
|
|
180
|
+
if (!item) return;
|
|
181
|
+
APP.removeGroundPolygon(item.element);
|
|
182
|
+
polygonList.value.splice(index, 1);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const clearAll = () => {
|
|
186
|
+
APP.clearAllGroundPolygons();
|
|
187
|
+
polygonList.value = [];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleClose = () => {
|
|
191
|
+
emit('close');
|
|
192
|
+
};
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<style lang="less" scoped>
|
|
196
|
+
.vector-polygon-panel {
|
|
197
|
+
position: absolute;
|
|
198
|
+
top: 100px;
|
|
199
|
+
left: 600px;
|
|
200
|
+
width: 360px;
|
|
201
|
+
background: rgba(30, 41, 59, 0.95);
|
|
202
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
203
|
+
border-radius: 10px;
|
|
204
|
+
color: #e2e8f0;
|
|
205
|
+
font-size: 13px;
|
|
206
|
+
z-index: 10000;
|
|
207
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
208
|
+
backdrop-filter: blur(8px);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.panel-header {
|
|
212
|
+
display: flex;
|
|
213
|
+
justify-content: space-between;
|
|
214
|
+
align-items: center;
|
|
215
|
+
padding: 10px 14px;
|
|
216
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.panel-title {
|
|
220
|
+
font-weight: 600;
|
|
221
|
+
font-size: 15px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.close-btn {
|
|
225
|
+
background: none;
|
|
226
|
+
border: none;
|
|
227
|
+
color: #94a3b8;
|
|
228
|
+
font-size: 18px;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
padding: 0 4px;
|
|
231
|
+
&:hover { color: #fff; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.panel-body {
|
|
235
|
+
padding: 12px 14px;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.section {
|
|
239
|
+
margin-bottom: 16px;
|
|
240
|
+
&:last-child { margin-bottom: 0; }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.section-header {
|
|
244
|
+
display: flex;
|
|
245
|
+
justify-content: space-between;
|
|
246
|
+
align-items: center;
|
|
247
|
+
margin-bottom: 8px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.section-label {
|
|
251
|
+
font-weight: 500;
|
|
252
|
+
font-size: 13px;
|
|
253
|
+
color: #cbd5e1;
|
|
254
|
+
i { margin-right: 6px; color: #3b82f6; }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.btn-clear-small {
|
|
258
|
+
background: rgba(239, 68, 68, 0.15);
|
|
259
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
260
|
+
color: #ef4444;
|
|
261
|
+
font-size: 11px;
|
|
262
|
+
padding: 3px 8px;
|
|
263
|
+
border-radius: 4px;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
&:hover { background: rgba(239, 68, 68, 0.3); }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.color-row {
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
gap: 8px;
|
|
272
|
+
flex-wrap: wrap;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.color-block {
|
|
276
|
+
width: 26px;
|
|
277
|
+
height: 26px;
|
|
278
|
+
border-radius: 4px;
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
border: 2px solid transparent;
|
|
281
|
+
transition: all 0.15s;
|
|
282
|
+
&:hover { transform: scale(1.1); }
|
|
283
|
+
&.active {
|
|
284
|
+
border-color: #fff;
|
|
285
|
+
box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.color-custom {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 6px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.color-picker {
|
|
296
|
+
width: 28px;
|
|
297
|
+
height: 28px;
|
|
298
|
+
border: none;
|
|
299
|
+
border-radius: 4px;
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
padding: 0;
|
|
302
|
+
background: transparent;
|
|
303
|
+
&::-webkit-color-swatch-wrapper { padding: 0; }
|
|
304
|
+
&::-webkit-color-swatch {
|
|
305
|
+
border-radius: 4px;
|
|
306
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.color-hex {
|
|
311
|
+
font-size: 11px;
|
|
312
|
+
color: #94a3b8;
|
|
313
|
+
font-family: monospace;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.upload-area {
|
|
317
|
+
border: 2px dashed rgba(148, 163, 184, 0.3);
|
|
318
|
+
border-radius: 8px;
|
|
319
|
+
padding: 20px;
|
|
320
|
+
text-align: center;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
transition: all 0.2s;
|
|
323
|
+
&:hover {
|
|
324
|
+
border-color: rgba(59, 130, 246, 0.5);
|
|
325
|
+
background: rgba(59, 130, 246, 0.05);
|
|
326
|
+
}
|
|
327
|
+
&.drag-over {
|
|
328
|
+
border-color: #3b82f6;
|
|
329
|
+
background: rgba(59, 130, 246, 0.1);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.upload-icon {
|
|
334
|
+
font-size: 28px;
|
|
335
|
+
color: #64748b;
|
|
336
|
+
display: block;
|
|
337
|
+
margin-bottom: 8px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.upload-text {
|
|
341
|
+
font-size: 12px;
|
|
342
|
+
color: #94a3b8;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.upload-status {
|
|
346
|
+
margin-top: 8px;
|
|
347
|
+
font-size: 12px;
|
|
348
|
+
color: #3b82f6;
|
|
349
|
+
i { margin-right: 4px; }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.polygon-items {
|
|
353
|
+
max-height: 200px;
|
|
354
|
+
overflow-y: auto;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.polygon-item {
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
padding: 6px 8px;
|
|
361
|
+
margin-bottom: 4px;
|
|
362
|
+
background: rgba(255, 255, 255, 0.04);
|
|
363
|
+
border-radius: 6px;
|
|
364
|
+
&:hover { background: rgba(255, 255, 255, 0.08); }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.polygon-color-dot {
|
|
368
|
+
width: 12px;
|
|
369
|
+
height: 12px;
|
|
370
|
+
border-radius: 3px;
|
|
371
|
+
flex-shrink: 0;
|
|
372
|
+
margin-right: 8px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.polygon-name {
|
|
376
|
+
flex: 1;
|
|
377
|
+
font-size: 12px;
|
|
378
|
+
overflow: hidden;
|
|
379
|
+
text-overflow: ellipsis;
|
|
380
|
+
white-space: nowrap;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.polygon-delete {
|
|
384
|
+
background: none;
|
|
385
|
+
border: none;
|
|
386
|
+
color: #64748b;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
padding: 2px 6px;
|
|
389
|
+
font-size: 12px;
|
|
390
|
+
border-radius: 4px;
|
|
391
|
+
&:hover {
|
|
392
|
+
color: #ef4444;
|
|
393
|
+
background: rgba(239, 68, 68, 0.1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
</style>
|
package/src/components/index.js
CHANGED
|
@@ -5,3 +5,4 @@ export { default as CommonDialog } from './CommonDialog.vue';
|
|
|
5
5
|
export { default as HydrologicStationDialog } from './HydrologicStationDialog.vue';
|
|
6
6
|
export { default as SecTourView } from './SecTourView.vue';
|
|
7
7
|
export { default as WaterGateTour } from './WaterGateTour.vue';
|
|
8
|
+
export { default as VectorPolygonControl } from './VectorPolygonControl.vue';
|
package/src/index.cjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @yangyongtao/gaea - CJS 入口
|
|
3
|
+
*
|
|
4
|
+
* CommonJS 用法:
|
|
5
|
+
* const gaea = await import('@yangyongtao/gaea');
|
|
6
|
+
* 或
|
|
7
|
+
* require('@yangyongtao/gaea').then(({ GaeaApp, setConfig }) => { ... })
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
module.exports = import('./index.js').then(m => m);
|