fl-web-component 2.1.19 → 2.1.21

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.
@@ -1,6 +1,124 @@
1
1
  import { gunzipSync } from 'fflate';
2
2
  import StreamLoaderParserWorker from './StreamLoaderParser.worker.js';
3
3
 
4
+ const STREAM_LOADER_DB_NAME = 'StreamLoaderDB';
5
+ const STREAM_LOADER_BOX_STORE = 'boxCache';
6
+
7
+ // IndexedDB 连接在模块级复用,保证没有 StreamLoader 实例时也能读写缓存
8
+ let streamLoaderDbReady = null;
9
+
10
+ /**
11
+ * 生成包围盒缓存主键,必须与写入时保持完全一致
12
+ * @param {string|number} projectId - 项目ID
13
+ * @param {string|number} documentId - 模型文档ID
14
+ * @returns {string} 形如 `${projectId}:${documentId}` 的主键
15
+ */
16
+ function buildBoxCacheKey(projectId, documentId) {
17
+ return `${projectId || 0}:${documentId}`;
18
+ }
19
+
20
+ /**
21
+ * 打开并缓存 StreamLoader 使用的 IndexedDB
22
+ * @returns {Promise<IDBDatabase|null>} 环境不支持或打开失败时返回 null
23
+ */
24
+ function openStreamLoaderDB() {
25
+ if (streamLoaderDbReady) return streamLoaderDbReady;
26
+ streamLoaderDbReady = new Promise(resolve => {
27
+ if (typeof indexedDB === 'undefined') {
28
+ resolve(null);
29
+ return;
30
+ }
31
+ // 打开数据库
32
+ const request = indexedDB.open(STREAM_LOADER_DB_NAME, 1);
33
+ request.onerror = () => {
34
+ console.warn('IndexedDB open failed');
35
+ resolve(null);
36
+ };
37
+ request.onsuccess = () => resolve(request.result);
38
+ request.onupgradeneeded = event => {
39
+ const db = event.target.result;
40
+ if (!db.objectStoreNames.contains(STREAM_LOADER_BOX_STORE)) {
41
+ db.createObjectStore(STREAM_LOADER_BOX_STORE);
42
+ }
43
+ };
44
+ });
45
+ return streamLoaderDbReady;
46
+ }
47
+
48
+ /**
49
+ * 删除指定项目下的全部包围盒缓存
50
+ * @param {string|number} [projectId] - 项目ID,缺省时按 0 处理(与写入默认值一致)
51
+ * @returns {Promise<boolean>}
52
+ * cleared 为该项目下是否已确认无缓存残留(原本就没有缓存时同样为 true);
53
+ * deletedCount 为本次实际删除条数,环境不支持 IndexedDB 或事务失败时 cleared 为 false
54
+ */
55
+ export async function clearProjectBoxCache(projectId) {
56
+ const keyPrefix = `${projectId || 0}:`;
57
+ const matchPrefix = keys => (keys || []).filter(key => String(key).startsWith(keyPrefix));
58
+ let deletedCount = 0;
59
+ let remainingCount = -1;
60
+ try {
61
+ const db = await openStreamLoaderDB();
62
+ if (!db) return { cleared: false, deletedCount };
63
+ await new Promise((resolve, reject) => {
64
+ const transaction = db.transaction([STREAM_LOADER_BOX_STORE], 'readwrite');
65
+ const store = transaction.objectStore(STREAM_LOADER_BOX_STORE);
66
+ const keysRequest = store.getAllKeys();
67
+ keysRequest.onsuccess = () => {
68
+ // 主键带 projectId 前缀与冒号,因此不会误删相邻 ID(如 1 与 12)
69
+ const keys = matchPrefix(keysRequest.result);
70
+ deletedCount = keys.length;
71
+ keys.forEach(key => store.delete(key));
72
+ // 同一事务内请求按发起顺序执行,回查可确认该项目下是否真的清空
73
+ const verifyRequest = store.getAllKeys();
74
+ verifyRequest.onsuccess = () => {
75
+ remainingCount = matchPrefix(verifyRequest.result).length;
76
+ };
77
+ verifyRequest.onerror = () => reject(verifyRequest.error);
78
+ };
79
+ keysRequest.onerror = () => reject(keysRequest.error);
80
+ transaction.oncomplete = () => resolve();
81
+ transaction.onabort = () => reject(transaction.error);
82
+ });
83
+ return remainingCount === 0;
84
+ } catch (e) {
85
+ console.warn('Error clearing IndexedDB box cache:', e);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 删除指定项目下单条包围盒缓存
92
+ * @param {string|number} documentId - 模型文档ID
93
+ * @param {string|number} [projectId] - 项目ID,缺省时按 0 处理(与写入默认值一致)
94
+ * @returns {Promise<boolean>} 命中的缓存是否存在并被删除
95
+ */
96
+ export async function clearDocumentBoxCache(documentId, projectId) {
97
+ if (documentId === undefined || documentId === null || documentId === '') return false;
98
+ const key = buildBoxCacheKey(projectId, documentId);
99
+ try {
100
+ const db = await openStreamLoaderDB();
101
+ if (!db) return false;
102
+ let existed = false;
103
+ await new Promise((resolve, reject) => {
104
+ const transaction = db.transaction([STREAM_LOADER_BOX_STORE], 'readwrite');
105
+ const store = transaction.objectStore(STREAM_LOADER_BOX_STORE);
106
+ const countRequest = store.count(key);
107
+ countRequest.onsuccess = () => {
108
+ existed = countRequest.result > 0;
109
+ if (existed) store.delete(key);
110
+ };
111
+ countRequest.onerror = () => reject(countRequest.error);
112
+ transaction.oncomplete = () => resolve();
113
+ transaction.onabort = () => reject(transaction.error);
114
+ });
115
+ return existed;
116
+ } catch (e) {
117
+ console.warn('Error clearing IndexedDB box cache:', e);
118
+ return false;
119
+ }
120
+ }
121
+
4
122
  /**
5
123
  * StreamLoader 类
6
124
  * 封装流式加载、解析与渲染调度的核心逻辑
@@ -1468,68 +1586,68 @@ export class StreamLoader {
1468
1586
  }
1469
1587
  }
1470
1588
 
1471
- async getBox({ id, projectId = this.projectId || 0, isDebug }) {
1472
- // 1. 尝试从 IndexedDB 读取
1473
- const cached = await this._getFromDB(id, projectId);
1474
- if (cached && !isDebug) {
1475
- return cached;
1476
- }
1477
-
1478
- const getLegacyBox = async () => {
1479
- if (!this.modelApi || typeof this.modelApi.getBox !== 'function') {
1480
- throw new Error('modelApi.getBox is not available');
1481
- }
1482
- const res = await this.modelApi.getBox({ id, projectId });
1483
- if (!res) return null;
1484
- this._applyPrefixId(res, 'id', id);
1485
- this._saveToDB(id, res, projectId);
1486
- return res;
1487
- };
1488
-
1489
- // 2. 未配置批次数接口时,兼容旧版一次性请求。
1490
- if (!this.modelApi || typeof this.modelApi.getMeshBatchCount !== 'function') {
1491
- return getLegacyBox();
1492
- }
1493
-
1494
- // 3. 先获取批次数量,再顺序拉取每一批包围盒数据。
1495
- let batchResult;
1496
- try {
1497
- batchResult = await this.modelApi.getMeshBatchCount({ id, projectId });
1498
- } catch (error) {
1499
- const status = error && error.response ? error.response.status : error && error.status;
1500
- if (status === 404) {
1501
- return getLegacyBox();
1502
- }
1503
- throw error;
1504
- }
1505
- const batchCount = Number(
1506
- batchResult && typeof batchResult === 'object' ? batchResult.index : batchResult
1507
- );
1508
- if (!Number.isInteger(batchCount) || batchCount < 0) {
1509
- throw new Error('Invalid getBox batch count');
1510
- }
1511
- if (batchCount === 0) {
1512
- return [];
1513
- }
1514
-
1515
- const boxList = [];
1516
- for (let index = 0; index < batchCount; index++) {
1517
- const batch = await this.modelApi.getBox({ id, projectId, index });
1518
- if (!batch) {
1519
- throw new Error(`Failed to get box batch: ${index}`);
1520
- }
1521
- if (Array.isArray(batch)) {
1522
- boxList.push(...batch);
1523
- } else {
1524
- boxList.push(batch);
1525
- }
1526
- }
1527
-
1528
- // 4. 全部批次成功后统一处理并写入完整缓存。
1529
- this._applyPrefixId(boxList, 'id', id);
1530
- this._saveToDB(id, boxList, projectId);
1531
- return boxList;
1532
- }
1589
+ async getBox({ id, projectId = this.projectId || 0, isDebug }) {
1590
+ // 1. 尝试从 IndexedDB 读取
1591
+ const cached = await this._getFromDB(id, projectId);
1592
+ if (cached && !isDebug) {
1593
+ return cached;
1594
+ }
1595
+
1596
+ const getLegacyBox = async () => {
1597
+ if (!this.modelApi || typeof this.modelApi.getBox !== 'function') {
1598
+ throw new Error('modelApi.getBox is not available');
1599
+ }
1600
+ const res = await this.modelApi.getBox({ id, projectId });
1601
+ if (!res) return null;
1602
+ this._applyPrefixId(res, 'id', id);
1603
+ this._saveToDB(id, res, projectId);
1604
+ return res;
1605
+ };
1606
+
1607
+ // 2. 未配置批次数接口时,兼容旧版一次性请求。
1608
+ if (!this.modelApi || typeof this.modelApi.getMeshBatchCount !== 'function') {
1609
+ return getLegacyBox();
1610
+ }
1611
+
1612
+ // 3. 先获取批次数量,再顺序拉取每一批包围盒数据。
1613
+ let batchResult;
1614
+ try {
1615
+ batchResult = await this.modelApi.getMeshBatchCount({ id, projectId });
1616
+ } catch (error) {
1617
+ const status = error && error.response ? error.response.status : error && error.status;
1618
+ if (status === 404) {
1619
+ return getLegacyBox();
1620
+ }
1621
+ throw error;
1622
+ }
1623
+ const batchCount = Number(
1624
+ batchResult && typeof batchResult === 'object' ? batchResult.index : batchResult
1625
+ );
1626
+ if (!Number.isInteger(batchCount) || batchCount < 0) {
1627
+ throw new Error('Invalid getBox batch count');
1628
+ }
1629
+ if (batchCount === 0) {
1630
+ return [];
1631
+ }
1632
+
1633
+ const boxList = [];
1634
+ for (let index = 0; index < batchCount; index++) {
1635
+ const batch = await this.modelApi.getBox({ id, projectId, index });
1636
+ if (!batch) {
1637
+ throw new Error(`Failed to get box batch: ${index}`);
1638
+ }
1639
+ if (Array.isArray(batch)) {
1640
+ boxList.push(...batch);
1641
+ } else {
1642
+ boxList.push(batch);
1643
+ }
1644
+ }
1645
+
1646
+ // 4. 全部批次成功后统一处理并写入完整缓存。
1647
+ this._applyPrefixId(boxList, 'id', id);
1648
+ this._saveToDB(id, boxList, projectId);
1649
+ return boxList;
1650
+ }
1533
1651
 
1534
1652
  async handleCameraControlForStream(args) {
1535
1653
  // 性能优化注释:禁用 range 被注释字段的解构声明与初始化
@@ -1771,27 +1889,7 @@ export class StreamLoader {
1771
1889
  // ----------------------------------------------------------------
1772
1890
 
1773
1891
  _initDB() {
1774
- if (this._dbReady) return this._dbReady;
1775
- this._dbReady = new Promise((resolve, reject) => {
1776
- if (typeof indexedDB === 'undefined') {
1777
- resolve(null);
1778
- return;
1779
- }
1780
- // 打开数据库
1781
- const request = indexedDB.open('StreamLoaderDB', 1);
1782
- request.onerror = () => {
1783
- console.warn('IndexedDB open failed');
1784
- resolve(null);
1785
- };
1786
- request.onsuccess = () => resolve(request.result);
1787
- request.onupgradeneeded = event => {
1788
- const db = event.target.result;
1789
- if (!db.objectStoreNames.contains('boxCache')) {
1790
- db.createObjectStore('boxCache');
1791
- }
1792
- };
1793
- });
1794
- return this._dbReady;
1892
+ return openStreamLoaderDB();
1795
1893
  }
1796
1894
 
1797
1895
  async _getFromDB(id, projectId = this.projectId || 0) {
@@ -1801,7 +1899,7 @@ export class StreamLoader {
1801
1899
  return new Promise(resolve => {
1802
1900
  const transaction = db.transaction(['boxCache'], 'readonly');
1803
1901
  const store = transaction.objectStore('boxCache');
1804
- const request = store.get(`${projectId}:${id}`);
1902
+ const request = store.get(buildBoxCacheKey(projectId, id));
1805
1903
  request.onsuccess = () => resolve(request.result);
1806
1904
  request.onerror = () => resolve(null);
1807
1905
  });
@@ -1817,9 +1915,28 @@ export class StreamLoader {
1817
1915
  if (!db) return;
1818
1916
  const transaction = db.transaction(['boxCache'], 'readwrite');
1819
1917
  const store = transaction.objectStore('boxCache');
1820
- store.put(data, `${projectId}:${id}`);
1918
+ store.put(data, buildBoxCacheKey(projectId, id));
1821
1919
  } catch (e) {
1822
1920
  console.warn('Error saving to IndexedDB:', e);
1823
1921
  }
1824
1922
  }
1923
+
1924
+ /**
1925
+ * 清除指定项目下的全部包围盒缓存
1926
+ * @param {string|number} [projectId=this.projectId] - 项目ID
1927
+ * @returns {Promise<{cleared: boolean, deletedCount: number}>} 该项目下是否已清空及实际删除条数
1928
+ */
1929
+ clearBoxCacheByProject(projectId = this.projectId || 0) {
1930
+ return clearProjectBoxCache(projectId);
1931
+ }
1932
+
1933
+ /**
1934
+ * 清除指定项目下单条包围盒缓存
1935
+ * @param {string|number} documentId - 模型文档ID
1936
+ * @param {string|number} [projectId=this.projectId] - 项目ID
1937
+ * @returns {Promise<boolean>} 命中的缓存是否存在并被删除
1938
+ */
1939
+ clearBoxCacheByDocumentId(documentId, projectId = this.projectId || 0) {
1940
+ return clearDocumentBoxCache(documentId, projectId);
1941
+ }
1825
1942
  }
@@ -427,10 +427,13 @@ MeasureArea.prototype = {
427
427
  transparent: true,
428
428
  opacity: 0.5,
429
429
  side: THREE.DoubleSide,
430
+ depthTest: false,
431
+ depthWrite: false,
430
432
  name: this.measureName
431
433
  });
432
434
  const mesh = new THREE.Mesh(geom, material);
433
435
  mesh.frustumCulled = false;
436
+ mesh.renderOrder = 998;
434
437
  _this.markMeasureObject(mesh);
435
438
  _this.polygonMesh = mesh;
436
439
  _this.scene.add(mesh);