Browse Source

使用 troika-three-text 绘制文本标签

master
修宁 7 months ago
parent
commit
0e6666577b
  1. 92
      README.md
  2. 828
      doc/物流模型总体介绍.md
  3. 5
      package.json
  4. 61
      pnpm-lock.yaml
  5. 97
      src/core/manager/WorldModel.ts
  6. 59
      src/example/example1.js
  7. 73
      src/modules/measure/MeasureRenderer.ts
  8. 10
      src/router/index.ts

92
README.md

@ -1,4 +1,94 @@
# yvan-rcs-web
# 系统概述
凯乐士物流控制中心(Galaxis logistics control center)
作为全球领先的智慧物流中枢平台,凯乐士创新性地集成物流建模、物联控制、数字孪生与智能调度四大核心模块,为企业提供从数字建模到物理执行的全链路闭环管理,构建新一代柔性化智能仓储体系。
## 平台架构
- 数据建模 ModelEditor
- 设备数字孪生:支持8大类仓储设备建模
- 存储体系:动态配置暂存区/地堆货架/ASRS立库/穿梭式密集库等多元存储方案
- 执行矩阵:堆垛机/四向穿梭车/智能提升机/机械臂集群等智能设备组态
- 运输网络:模块化集成输送线/智能AGV/无人叉车等多形态运输体系
- 容器管理:适配原箱/托盘/周转箱等12种工业级载具规格
- 流程可视化编排:通过拖拽式界面实现仓储动线设计、工序流程配置及工艺参数优化
- 物联控制 Monitor
- 多协议兼容:支持OPC UA/Modbus/MQTT等15+工业协议
- 设备健康管理:实时诊断200+设备运行指标
- 边缘计算能力:部署设备级智能决策节点
- 任务调度中心
- 任务调度:业务工单 → 调度指令 → 设备作业 → 控制报文(四层精准拆解)
- 对接多种调度系统:
- RCS机器人调度系统
- MFC多层穿梭车控制系统
- PES穿梭板调度系统
- WCS设备控制系统智能联动
- WMS仓储业务管理系统
- 仿真优化中枢
- 数字孪生预演:支持3D可视化仓储布局仿真
- 智能推演系统:
» 吞吐量压力测试 » 设备配置验证 » 异常工况模拟
- ROI分析引擎:精准测算产能提升率/空间利用率/设备投入产出比
## 核心价值
- 规避试错成本:通过数字仿真降低85%的实体测试投入
- 提升运营效率:智能调度算法使设备协同效率提升40%+
- 强化系统韧性:实时物联监控降低70%非计划停机风险
- 加速部署周期:模块化架构实现新项目部署周期缩短60%
## **核心特点**
**1️⃣ 智能建模编辑器——柔性化仓储设计中枢**
- **三维可视化布局**:支持拖拽式设备排布与动线规划,实时渲染仓储空间热力图
- **动态属性配置**:可调整200+设备参数(速度/载重/能耗等),支持参数联动优化
- **批量工程管理**:一键同步调整多设备组态,支持设备模板库快速复用
- **跨平台兼容**:导入/导出FBX/STEP/GLTF等8种工业模型格式,兼容AutoCAD/BIM数据
**2️⃣ 全景监控体系——全链路数字化透视**
- **过程回溯引擎**
▸ 设备日志回放
▸ 任务执行路径追踪
▸ 报文级通信过程可视化
- **智能诊断矩阵**
» 实时性能看板:监测设备OEE/任务吞吐量/系统响应延迟等18项核心指标
» 调试沙盒环境:支持API报文模拟注入与响应解析
» 日志智能检索:基于自然语言的日志关联分析
- **可配置仪表盘**:自定义监控看板,支持多维度数据钻取与预警阈值设置
**3️⃣ 智能仿真系统——风险预判与决策沙盒**
- **数字孪生沙盒**
▶ 模拟真实设备通信报文(支持Modbus-TCP/Profinet等协议仿真)
▶ 冲突场景库:预设200+典型设备干涉模型(路径冲突/资源抢占/流量瓶颈)
- **智能推演引擎**
» 历史日志回放:基于真实运营数据的仿真复现
» 随机事件发生器:模拟设备故障/订单波动/紧急插单等34类异常工况
» 数据扰动测试:注入噪声数据验证系统鲁棒性
- **仿真报告生成**:自动输出瓶颈分析报告与优化建议(含空间利用率/设备稼动率预测)
**4️⃣ 自适应调度中枢——动态环境下的智能决策**
- **冲突感知网络**
▸ 实时识别6类设备冲突(路径交叉/任务死锁/资源竞争等)
▸ 冲突热力图谱:可视化展示高频冲突区域
- **解决方案工坊**
» 录制专家处理逻辑,构建冲突解决知识库(支持if-then规则/决策树录制)
» AI自动优化:基于深度强化学习的动态路径规划算法
- **智能仲裁机制**
▶ 优先级动态调整(订单紧急度/设备状态/能耗成本多目标优化)
▶ 微秒级响应:复杂冲突场景决策延迟<5秒
## 文件结构构成
```

828
doc/物流模型总体介绍.md

File diff suppressed because it is too large

5
package.json

@ -5,9 +5,9 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"build": "vite build",
"preview": "vite preview",
"build-only": "vite build",
"build-check": "run-p type-check \"build-only {@}\" --",
"type-check": "vue-tsc --build",
"format": "prettier --write src/"
},
@ -48,6 +48,7 @@
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.2.0",
"@vue/tsconfig": "^0.7.0",
"troika-three-text": "^0.52.4",
"mitt": "^3.0.1",
"tslib": "2.8.1",
"npm-run-all2": "^7.0.2",

61
pnpm-lock.yaml

@ -115,7 +115,7 @@ importers:
specifier: 2.10.1
version: 2.10.1(three@0.176.0)
mitt:
specifier: ^3.0.0
specifier: ^3.0.1
version: 3.0.1
npm-run-all2:
specifier: ^7.0.2
@ -132,6 +132,9 @@ importers:
three-mesh-bvh:
specifier: ^0.9.0
version: 0.9.0(three@0.176.0)
troika-three-text:
specifier: ^0.52.4
version: 0.52.4(three@0.176.0)
tslib:
specifier: 2.8.1
version: 2.8.1
@ -555,56 +558,67 @@ packages:
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.41.0':
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.41.0':
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.41.0':
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.41.0':
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.41.0':
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.41.0':
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.41.0':
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.41.0':
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.41.0':
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.41.0':
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==}
@ -848,6 +862,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
birpc@2.3.0:
resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==}
@ -1406,6 +1423,10 @@ packages:
resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
engines: {node: ^18.17.0 || >=20.5.0}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
@ -1515,6 +1536,19 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
troika-three-text@0.52.4:
resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
peerDependencies:
three: '>=0.125.0'
troika-three-utils@0.52.4:
resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==}
peerDependencies:
three: '>=0.125.0'
troika-worker-utils@0.52.0:
resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -1648,6 +1682,9 @@ packages:
typescript:
optional: true
webgl-sdf-generator@1.1.1:
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@ -2373,6 +2410,10 @@ snapshots:
balanced-match@1.0.2: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
birpc@2.3.0: {}
brace-expansion@2.0.1:
@ -2894,6 +2935,8 @@ snapshots:
json-parse-even-better-errors: 4.0.0
npm-normalize-package-bin: 4.0.0
require-from-string@2.0.2: {}
rfdc@1.4.1: {}
rimraf@6.0.1:
@ -3005,6 +3048,20 @@ snapshots:
totalist@3.0.1: {}
troika-three-text@0.52.4(three@0.176.0):
dependencies:
bidi-js: 1.0.3
three: 0.176.0
troika-three-utils: 0.52.4(three@0.176.0)
troika-worker-utils: 0.52.0
webgl-sdf-generator: 1.1.1
troika-three-utils@0.52.4(three@0.176.0):
dependencies:
three: 0.176.0
troika-worker-utils@0.52.0: {}
tslib@2.8.1: {}
typescript@5.8.3: {}
@ -3122,6 +3179,8 @@ snapshots:
optionalDependencies:
typescript: 5.8.3
webgl-sdf-generator@1.1.1: {}
which@2.0.2:
dependencies:
isexe: 2.0.0

97
src/core/manager/WorldModel.ts

@ -13,7 +13,7 @@ export interface WorldModelState {
}
/**
*
*
*/
export default class WorldModel {
data: any = null
@ -23,10 +23,10 @@ export default class WorldModel {
*/
state: WorldModelState = reactive({
isOpened: false, // 是否已打开世界模型
catalogCode: '',
isDraft: false,
stateManagerId: '', // 当前楼层的状态管理器id
catalog: [] as Catalog // 世界模型目录数据
catalogCode: '', // 当前楼层的目录代码
isDraft: false, // 是否是草稿数据, 如果是草稿数据, 则不需要再从服务器加载数据
stateManagerId: '', // 当前楼层的状态管理器id, 一般是 项目ID+目录项ID
catalog: [] as Catalog // 世界模型目录
})
get gridOption(): IGridHelper {
@ -51,6 +51,9 @@ export default class WorldModel {
constructor() {
}
/**
*
*/
init() {
// 观察 this.state.catalogCode 的变化, 如果变化就调用 catalogCodeChange 方法
watch(() => this.state.catalogCode, this.onCatalogCodeChanged.bind(this))
@ -84,7 +87,7 @@ export default class WorldModel {
}
/**
* ,
* ,
*/
onCatalogCodeChanged(catalogCode: string) {
if (this.state.isDraft) {
@ -120,6 +123,9 @@ export default class WorldModel {
})
}
/**
*
*/
async getCatalogData(catalogCode: string): Promise<Partial<VData>> {
if (!this.data || !this.data.items) {
return Promise.reject('楼层数据未加载, catalogCode=' + catalogCode)
@ -144,83 +150,4 @@ export default class WorldModel {
}
return Promise.reject('楼层不存在, catalogCode=' + catalogCode)
}
// loadFloorToScene(viewport: Viewport, scene: THREE.Scene, levelCode: string) {
// let floor = _.find(this.data.items, r => r.name === levelCode && r.t === 'floor')
// if (!floor) {
// console.info(`新建楼层: ${levelCode}`)
//
// if (!_.isArray(this.data.items)) {
// this.data.items = []
// }
// floor = { name: levelCode, t: 'floor', items: [] }
// this.data.items.push(floor)
// }
//
// loadSceneFromJson(viewport, scene, floor.items)
// }
// open() {
// if (this.sceneMap.size > 0) {
// // 释放旧场景
// this.sceneMap.forEach((scene: Scene) => {
// this.sceneDispose(scene)
// })
// }
// if (this.viewPorts.length > 0) {
// // 注销视口
// this.viewPorts.forEach((viewport: Viewport) => {
// this.unregisterViewport(viewport)
// })
// }
//
// system.msg('打开世界地图完成')
// this.data = markRaw(Example1)
// this.state.openFileName = 'example1'
// this.state.allLevels = reactive(this.data.allLevels)
// }
// /**
// * 获取当前楼层的场景, 如果没有则创建一个新的场景
// */
// getSceneByFloor(viewport: Viewport, floor: string) {
// if (this.sceneMap.has(floor)) {
// return this.sceneMap.get(floor)
// } else {
// const scene = this.createScene(viewport, floor)
//
// this.sceneMap.set(floor, scene)
// return scene
// }
// }
//
// /**
// * 创建一个新的场景
// */
// createScene(viewport: Viewport, floor: string) {
// const scene = new Scene()
// scene.background = new THREE.Color(0xeeeeee)
//
// this.loadFloorToScene(viewport, scene, floor)
// return scene
// }
// /**
// * 注册视口
// */
// registerViewport(viewport: Viewport) {
// this.viewPorts = this.viewPorts || []
// this.viewPorts.push(viewport)
// }
//
// /**
// * 注销视口
// */
// unregisterViewport(viewport: Viewport) {
// const index = this.viewPorts.indexOf(viewport)
// if (index > -1) {
// this.viewPorts.splice(index, 1)
// }
// }
}

59
src/example/example1.js

@ -1,33 +1,32 @@
export default {
project_uuid: 'example1',
Tool: {
Group: [],
GlobalVariables: [],
UserCommand: [],
ProcessFlow: [],
Dashboard: [],
DataTable: [],
Trigger: [
{ name: 'OnOpen', fn: '' },
{ name: 'OnReset', fn: '' },
{ name: 'OnStart', fn: '' },
{ name: 'OnStop', fn: '' }
Group: [], // 分组
GlobalVariables: [], // 全局变量
UserCommand: [], // 全局脚本
Dashboard: [], // 监控面板
DataTable: [], // 地图自带的数据
Trigger: [ // 触发器
{ name: 'OnOpen', fn: '' }, // 打开
{ name: 'OnReset', fn: '' }, // 仿真重置
{ name: 'OnStart', fn: '' }, // 开始仿真
{ name: 'OnStop', fn: '' } // 停止仿真
],
gridHelper: {
axesEnabled: true,
axesSize: 1000,
axesDivisions: 4,
axesColor: 0x000000,
axesOpacity: 1,
gridHelper: { // 网格辅助线
axesEnabled: true, // 是否显示中心轴
axesSize: 50, // 中心轴长度
axesDivisions: 2, // 中心轴分割数
axesColor: 0x000000, // 中心轴颜色
axesOpacity: 1, // 中心轴透明度
gridEnabled: true,
gridSize: 1000,
gridDivisions: 1000,
gridColor: 0x999999,
gridOpacity: 0.8,
gridEnabled: true, // 是否显示网格
gridSize: 1000, // 网格大小
gridDivisions: 1000, // 网格分割数
gridColor: 0x999999, // 网格颜色
gridOpacity: 0.8, // 网格透明度
snapEnabled: true,
snapDistance: 0.25
snapEnabled: true, // 是否启用吸附
snapDistance: 0.25 // 吸附距离
}
},
items: [
@ -80,14 +79,14 @@ export default {
]
}
],
elevator: [],
wall: [],
pillar: [],
catalog: [
elevator: [], // 电梯
wall: [], // 墙体
pillar: [], // 柱子
catalog: [ // 目录
{
label: '仓库楼层',
label: '仓库楼层', // 目录分组名
items: [
{ catalogCode: '-f1', label: '地下室 (-f1)' },
{ catalogCode: '-f1', label: '地下室 (-f1)' }, // 目录项
{ catalogCode: 'f1', label: '一楼 (f1)' },
{ catalogCode: 'f2', label: '二楼 (f2)' },
{ catalogCode: 'OUT', label: '外场 (OUT)' },

73
src/modules/measure/MeasureRenderer.ts

@ -6,6 +6,7 @@ import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { numberToString } from '@/utils/webutils.ts'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Text } from 'troika-three-text'
/**
*
@ -21,6 +22,8 @@ export default class MeasureRenderer extends BaseRenderer {
static POINT_NAME = 'measure_point'
static LINE_NAME = 'measure_line'
public useHtmlLabel = false
pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
lineMaterial = new LineMaterial({
@ -75,18 +78,27 @@ export default class MeasureRenderer extends BaseRenderer {
const p1 = endPoint.position
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)} m`
const label = `长度 ${numberToString(dist)} m`
const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5)
let labelObj: CSS2DObject | undefined = objects[0].userData.labelObj
let labelObj: Text | CSS2DObject | undefined = objects[0].userData.labelObj
if (!labelObj || !labelObj.parent) {
labelObj = this.createLabel(label)
this.group.add(labelObj)
objects[0].userData.labelObj = labelObj
}
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
labelObj.position.set(position.x, position.y + 0.3, position.z)
if (this.useHtmlLabel) {
labelObj.element.innerHTML = label
} else {
// 让文本朝向摄像机
labelObj.quaternion.copy(this.tempViewport.camera.quaternion)
labelObj.text = label
labelObj.sync()
}
}
afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, objects: THREE.Object3D[]) {
@ -102,21 +114,42 @@ export default class MeasureRenderer extends BaseRenderer {
/**
*
*/
createLabel(text: string): CSS2DObject {
const div = document.createElement('div')
div.className = 'css2dObjectLabel'
div.innerHTML = text
div.style.padding = '5px 8px'
div.style.color = '#fff'
div.style.fontSize = '14px'
div.style.position = 'absolute'
div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)'
div.style.borderRadius = '12px'
div.style.top = '0px'
div.style.left = '0px'
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
const obj = new CSS2DObject(div)
obj.name = MeasureRenderer.LABEL_NAME
return obj
createLabel(text: string): Text | CSS2DObject {
if (this.useHtmlLabel) {
const div = document.createElement('div')
div.className = 'css2dObjectLabel'
div.innerHTML = text
div.style.padding = '5px 8px'
div.style.color = '#fff'
div.style.fontSize = '14px'
div.style.position = 'absolute'
div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)'
div.style.borderRadius = '12px'
div.style.top = '0px'
div.style.left = '0px'
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
const obj = new CSS2DObject(div)
obj.name = MeasureRenderer.LABEL_NAME
return obj
} else {
const label = new Text()
label.text = text
label.fontSize = 0.4
label.color = '#ff0000'
label.opacity = 0.8
label.padding = 0.2
label.anchorX = 'center'
label.anchorY = 'middle'
label.depthOffset = 1
label.backgroundColor = '#000000' // 黑色背景
label.backgroundOpacity = 0.6 // 背景半透明
label.padding = 0.2 // 内边距
label.material.depthTest = false
label.name = MeasureRenderer.LABEL_NAME
label.sync()
return label
}
}
}

10
src/router/index.ts

@ -1,6 +1,4 @@
import { createRouter, createWebHashHistory } from 'vue-router'
// import HomeView from '../views/HomeView.vue'
import ModelMain from '../editor/ModelMain.vue'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
@ -8,8 +6,14 @@ const router = createRouter({
{
path: '/',
name: 'home',
// 自动引导到 /editor
redirect: '/editor'
},
{
path: '/editor',
name: 'editor',
// component: HomeView,
component: ModelMain
component: () => import('../editor/ModelMain.vue')
},
{
path: '/about',

Loading…
Cancel
Save