diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 9c36621..8d14353 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -26,9 +26,8 @@ import ItemFindManager from '@/core/manager/ItemFindManager.ts' import { MapControls } from 'three/examples/jsm/controls/MapControls' import ModelManager from '@/core/script/ModelManager.ts' import RuntimeManager from '@/core/manager/RuntimeManager.ts' -import EnvManager from '@/core/manager/EnvManager.ts' -import BackendMessageReceiver from '@/core/manager/BackendMessageReceiver.ts' -import Ammo, {btTransform} from 'ammojs-typed'; +import Ammo from 'ammojs-typed' + /** * 视窗对象 * 所有状态管理器,场景,控制器,摄像机,实体管理器, 都在这里可以取到 @@ -42,7 +41,7 @@ export default class Viewport { raycaster: THREE.Raycaster animationFrameId: any = null scene: SceneHelp - ammoModel: Ammo = window.Ammo + ammoModel: any = window.Ammo physicsWorld: Ammo.btDiscreteDynamicsWorld transformAux1: Ammo.btTransform selectManager = new SelectManager() @@ -67,12 +66,12 @@ export default class Viewport { markRaw(this.itemFindManager), markRaw(this.interactionManager), markRaw(this.modelManager), - markRaw(this.runtimeManager), + markRaw(this.runtimeManager) ] - registerFrameTimerCallBack: Map void> = new Map() + registerFrameTimerCallBack: Map void> = new Map() - registerPhysicsUpdateCallBack: Map void> = new Map() + registerPhysicsUpdateCallBack: Map void> = new Map() // 对象实例管理器 moduleName -> InstanceMeshManager meshManager: Map = new Map() @@ -124,9 +123,11 @@ export default class Viewport { addFrameTimerCallback(id: string, callback: () => void) { this.registerFrameTimerCallBack.set(id, callback) } + removeFrameTimerCallback(id: string) { this.registerFrameTimerCallBack.delete(id) } + /** * 根据实体 ID 查找实体 * @param entityId @@ -169,17 +170,17 @@ export default class Viewport { async initPhysics() { // Physics variables - let collisionConfiguration; - let dispatcher; - let broadphase; - let solver; - - collisionConfiguration = new this.ammoModel.btDefaultCollisionConfiguration(); - dispatcher = new this.ammoModel.btCollisionDispatcher( collisionConfiguration ); - broadphase = new this.ammoModel.btDbvtBroadphase(); - solver = new this.ammoModel.btSequentialImpulseConstraintSolver(); - this.physicsWorld = new this.ammoModel.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); - this.physicsWorld.setGravity( new this.ammoModel.btVector3( 0, 0, 0 ) ); + let collisionConfiguration + let dispatcher + let broadphase + let solver + + collisionConfiguration = new this.ammoModel.btDefaultCollisionConfiguration() + dispatcher = new this.ammoModel.btCollisionDispatcher(collisionConfiguration) + broadphase = new this.ammoModel.btDbvtBroadphase() + solver = new this.ammoModel.btSequentialImpulseConstraintSolver() + this.physicsWorld = new this.ammoModel.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration) + this.physicsWorld.setGravity(new this.ammoModel.btVector3(0, 0, 0)) // this.transformAux1 = new this.ammoModel.btTransform(); // tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 ); @@ -402,9 +403,9 @@ export default class Viewport { } offset = 0 - clock = new THREE.Clock(); - elapsedTime = 0; - interval = 1; // 触发间隔(秒) + clock = new THREE.Clock() + elapsedTime = 0 + interval = 1 // 触发间隔(秒) /** * 动画循环 */ @@ -418,18 +419,18 @@ export default class Viewport { } this.statsControls?.update() - const deltaTime = this.clock.getDelta(); + const deltaTime = this.clock.getDelta() this.updatePhysics(deltaTime) this.renderer?.render(this.scene.scene, this.camera) this.css2DRenderer.render(this.scene.scene, this.camera) this.css3DRenderer.render(this.scene.scene, this.camera) - const delta = this.clock.getDelta(); - this.elapsedTime += delta; + const delta = this.clock.getDelta() + this.elapsedTime += delta if (this.elapsedTime >= this.interval) { // 每1秒触发一次 - this.elapsedTime = 0; + this.elapsedTime = 0 this.registerFrameTimerCallBack.forEach(callback => { if (typeof callback === 'function') { callback() diff --git a/src/core/manager/BackendMessageReceiver.ts b/src/core/manager/BackendMessageReceiver.ts index c8e974d..bddf51a 100644 --- a/src/core/manager/BackendMessageReceiver.ts +++ b/src/core/manager/BackendMessageReceiver.ts @@ -42,11 +42,13 @@ export default class BackendMessageReceiver { subscribedTopics: [] as string[] }) - // 启动MQTT连接 - public async start(projectUuid: string, envId: number, config: MqttConfig): Promise { + setProjectEnv(projectUuid: string, envId: number) { this.projectUuid = projectUuid this.envId = envId + } + // 启动MQTT连接 + public async start(config: MqttConfig): Promise { // 如果已经连接,先断开 if (this.client?.connected) { await this.dispose() @@ -69,14 +71,14 @@ export default class BackendMessageReceiver { return new Promise((resolve) => { try { - console.log('Connecting to MQTT broker:', config.websocket) + console.log('Connecting to backendMQTT broker:', config.websocket) this.client = mqtt.connect(config.websocket, options) // 连接成功 this.client.on('connect', () => { this.state.status = ConnectionStatus.CONNECTED this.state.isConnected = true - console.log('MQTT connected') + console.log('backendMQTT connected') resolve(true) }) @@ -84,20 +86,20 @@ export default class BackendMessageReceiver { this.client.on('close', () => { this.state.status = ConnectionStatus.DISCONNECTED this.state.isConnected = false - console.log('MQTT disconnected') + console.log('backendMQTT disconnected') }) // 重连中 this.client.on('reconnect', () => { this.state.status = ConnectionStatus.RECONNECTING - console.log('MQTT reconnecting...') + console.log('backendMQTT reconnecting...') }) // 错误处理 this.client.on('error', (error) => { this.state.status = ConnectionStatus.ERROR this.state.lastError = error.message - console.error('MQTT error:', error) + console.error('backendMQTT error:', error) resolve(false) }) @@ -109,7 +111,7 @@ export default class BackendMessageReceiver { } catch (error) { this.state.status = ConnectionStatus.ERROR this.state.lastError = (error as Error).message - console.error('MQTT connection error:', error) + console.error('backendMQTT connection error:', error) return false } }) @@ -124,7 +126,7 @@ export default class BackendMessageReceiver { this.state.isConnected = false this.state.status = ConnectionStatus.DISCONNECTED this.state.subscribedTopics = [] - console.log('MQTT stopped') + console.log('backendMQTT stopped') resolve() }) } else { @@ -139,7 +141,7 @@ export default class BackendMessageReceiver { const envId = this.envId switch (type) { case 'ServerState': - return [`/lcc/${projId}/${envId}/server`, this.handleServerState] + return [`/lcc/+/+/server`, this.handleServerState] case 'ClientState': return [`/lcc/${projId}/${envId}/client`, this.handleClientState] case 'TaskUpdate': @@ -164,7 +166,7 @@ export default class BackendMessageReceiver { // 订阅主题 public subscribe(type: BackendTopicType, handler: BackendMessageHandler): StopSubscribe { if (!this.client?.connected) { - throw new Error('Cannot subscribe - MQTT not connected') + throw new Error('Cannot subscribe - backendMQTT not connected') } const [topic, processFn] = this.getTopicStringByType(type) @@ -196,7 +198,7 @@ export default class BackendMessageReceiver { // 取消订阅 public unsubscribe(type: BackendTopicType, handler: BackendMessageHandler): void { // if (!this.client?.connected) { - // throw new Error('Cannot unsubscribe - MQTT not connected') + // throw new Error('Cannot unsubscribe - backendMQTT not connected') // } const [topic, processFn] = this.getTopicStringByType(type) diff --git a/src/core/manager/EnvManager.ts b/src/core/manager/EnvManager.ts index 03d28ab..0364545 100644 --- a/src/core/manager/EnvManager.ts +++ b/src/core/manager/EnvManager.ts @@ -28,11 +28,7 @@ export default class EnvManager { console.error('Error:', error) } - async start(env: EnvInfo) { - if (!env) { - system.showErrorDialog('Environment is not specified, cannot start EnvManager.') - return - } + async connectEnv() { if (!worldModel.state.isOpened) { system.showErrorDialog('WorldModel is not opened, cannot start EnvManager.') return @@ -45,26 +41,22 @@ export default class EnvManager { system.showErrorDialog('EnvManager is already running, cannot start again.') return } - if (!env.envConfig.mqtt.websocket) { - system.showErrorDialog('MQTT websocket URL is not set in the envConfig.mqtt.') + if (!worldModel.state.runState.currentEnv) { + system.showErrorDialog('Environment is not specified, cannot start EnvManager.') return } - if (!env.envConfig.frontendMqtt.websocket) { - system.showErrorDialog('Frontend MQTT websocket URL is not set in the envConfig.frontendMqtt.') + if (!worldModel.state.runState.currentEnv.envConfig.mqtt.websocket) { + system.showErrorDialog('MQTT websocket URL is not set in the envConfig.mqtt.') return } - await this.stop() + await this.disconnectEnv() system.showLoading() worldModel.state.runState.isLoading = true - worldModel.state.runState.currentEnv = env + const env = worldModel.state.runState.currentEnv try { - await LCC.serverStart() - await worldModel.backendMessageReceiver.start( - worldModel.state.project_uuid, - worldModel.state.runState.currentEnvId, - env.envConfig.frontendMqtt) + worldModel.backendMessageReceiver.setProjectEnv(worldModel.state.project_uuid, worldModel.state.runState.currentEnvId) await LCC.loadInv() this.client = mqtt.connect(env.envConfig.mqtt.websocket, { @@ -91,12 +83,9 @@ export default class EnvManager { } } - async stop() { + async disconnectEnv() { system.showLoading() try { - if (window['LCC']) { - await LCC.serverStop() - } worldModel.state.runState.isRunning = false if (this.client) { this.client.removeAllListeners() @@ -126,10 +115,10 @@ export default class EnvManager { /** * 获取所有运行环境 */ - static async getAllEnv(worldId: string): Promise> { + static async getAllEnv(worldId: string): Promise { // system.invokeServer('') if (!worldId) { - return Promise.resolve({ success: true, data: [], msg: '' }) + return Promise.reject('World ID is not provided.') } const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', { worldId: worldId @@ -142,6 +131,6 @@ export default class EnvManager { * 卸载资源 */ dispose(): void { - this.stop() + this.disconnectEnv() } } diff --git a/src/core/manager/WorldModel.ts b/src/core/manager/WorldModel.ts index e201a27..3ecf703 100644 --- a/src/core/manager/WorldModel.ts +++ b/src/core/manager/WorldModel.ts @@ -4,13 +4,13 @@ import { Request } from '@ease-forge/shared' import EventBus from '@/runtime/EventBus' import StateManager from '@/core/manager/StateManager.ts' import { getQueryParams, setQueryParam } from '@/utils/webutils.ts' -import localforage from 'localforage' import BackendMessageReceiver from '@/core/manager/BackendMessageReceiver.ts' import EnvManager from '@/core/manager/EnvManager.ts' import RCSScript from '@/core/script/RCSScript.ts' import LCCScript from '@/core/script/LCCScript.ts' export interface WorldModelState { + authorizationConfig: ServerAuthorizationConfigVo // 服务器授权配置 isOpened: boolean // 是否已打开世界模型 worldData: any // 世界模型数据, 包含排除 items 楼层数据之外的所有数据 @@ -20,6 +20,7 @@ export interface WorldModelState { server: string // 当前楼层服务器地址 project_uuid: string // 当前楼层所在项目ID project_label: string // 项目名称 + sub_system_list: string[] // 子系统集合, 用于标识当前楼层所属的子系统 catalogCode: string // 当前楼层的目录代码 stateManagerId: string // 当前楼层的状态管理器id @@ -47,6 +48,7 @@ export default class WorldModel { * 世界模型双向绑定的状态数据 */ state: WorldModelState = reactive({ + authorizationConfig: null, isOpened: false, // 是否已打开世界模型 worldData: null, // 世界模型数据, 包含排除 items 楼层数据之外的所有数据 @@ -56,11 +58,13 @@ export default class WorldModel { server: '', project_uuid: '', // 项目ID project_label: '', // 项目名称 + sub_system_list: [], // 子系统集合, 用于标识当前楼层所属的子系统 catalogCode: '', // 当前楼层的目录代码 stateManagerId: '', // 当前楼层的状态管理器id, 一般是 项目ID+目录项ID isDraft: false, // 是否是草稿数据, 如果是草稿数据, 则不需要再从服务器加载数据 + runState: { currentEnvId: null, isLoading: false, @@ -91,13 +95,19 @@ export default class WorldModel { }) } - constructor() { - } - /** * 初始化世界模型 */ - init() { + async init() { + system.showLoading('Authenticating...') + const configRes = await LCC.getAuthorizationConfig() + if (!configRes.success) { + system.showErrorDialog('Authentication failed: ' + configRes.msg) + return Promise.reject(configRes) + } + this.state.authorizationConfig = configRes.data + await this.backendMessageReceiver.start(configRes.data.frontendMqtt) + // 观察 this.state.catalogCode 的变化, 如果变化就调用 catalogCodeChange 方法 return Promise.all([ import('../../modules/measure'), @@ -112,8 +122,9 @@ export default class WorldModel { import('../../modules/amr/ptr/clx'), import('../../modules/charger') - ]).then(() => { + ]).then((configRes) => { console.log('世界模型初始化完成') + system.clearLoading() // 尝试从草稿中加载数据 const stateManagerId = getQueryParams()?.get('store') @@ -128,6 +139,8 @@ export default class WorldModel { this.state.catalog = data.catalog this.state.server = data.server this.state.project_uuid = data.project_uuid + this.state.project_label = data.project_label + this.state.sub_system_list = data.sub_system_list || [] this.state.isDraft = true this.tryOpenCatelog(data.catalogCode, true).then(() => { @@ -186,6 +199,7 @@ export default class WorldModel { this.state.server = lccModelWorld.server this.state.project_uuid = lccModelWorld.projectUuid this.state.project_label = lccModelWorld.projectLabel + this.state.sub_system_list = lccModelWorld.subSystemList || [] // 没有打开楼层,不加载 this.state.catalogCode this.state.isDraft = false @@ -207,6 +221,8 @@ export default class WorldModel { catalog: _.cloneDeep(this.state.catalog), server: this.state.server, project_uuid: this.state.project_uuid, + project_label: this.state.project_label, + sub_system_list: _.cloneDeep(this.state.sub_system_list), catalogCode: catalogCode, worldData: _.cloneDeep(this.state.worldData) } @@ -222,6 +238,33 @@ export default class WorldModel { stateManager: null }) } + + /** + * 改变当前运行环境 + */ + setEnv(env: EnvInfo) { + if (this.state.runState.currentEnvId && this.state.runState.isRunning) { + system.showErrorDialog('cannot change env when running') + + } else { + // 更新当前环境 ID + if (!env) { + this.state.runState.currentEnvId = null + this.state.runState.isLoading = false + this.state.runState.isRunning = false + this.state.runState.isVirtual = false + this.state.runState.timeRate = 0 + this.state.runState.currentEnv = null + } else { + this.state.runState.currentEnvId = env.envId + this.state.runState.isLoading = false + this.state.runState.isRunning = false + this.state.runState.isVirtual = env.isVirtual + this.state.runState.timeRate = 1 + this.state.runState.currentEnv = env + } + } + } } const worldModel = new WorldModel() diff --git a/src/core/script/LCCScript.ts b/src/core/script/LCCScript.ts index d4e9f79..ab4af78 100644 --- a/src/core/script/LCCScript.ts +++ b/src/core/script/LCCScript.ts @@ -6,49 +6,53 @@ import { Request } from '@ease-forge/shared' * LCC 物流控制中心客户端 API 实现 */ export default class LCCScript implements LCC { + /** + * 获取服务器状态信息 + * @param option 可选参数,包含 projectUUID 和 envId + */ + queryServerState(option: { projectUUID?: string; envId?: string } = {}): Promise> { + return Request.request.post('/api/workbench/ServerController@queryServerState', { + _id: system.createUUID(), + ...option + }) + } - log(from: string, message: string, ...args: any[]): void { - // 插入时分秒 HH:mm:ss.sss - const now = new Date().toISOString().replace('T', ' ').replace('Z', '').split(' ')[1] - // 打成彩色 - // console.log(now + ' [LCC-' + from + '] ' + message, ...args) - console.log(`%c${now} [${from}] ${message}`, 'color: #00f', ...args) + startServer(projectUUID: string, envId: string): Promise> { + return Request.request.post('/api/workbench/ServerController@startServer', { + projectUUID: projectUUID, + envId: envId + }) } - /** - * 延时函数 - 使用普通函数确保正确绑定 - */ - async sleep(timeOfMs: number = 1000): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve() // 确保调用 resolve - }, timeOfMs) + stopServer(projectUUID: string, envId: string): Promise> { + return Request.request.post('/api/workbench/ServerController@stopServer', { + projectUUID: projectUUID, + envId: envId }) } - async getAllProjects(): Promise> { - return Request.request.post('/api/workbench/LccController@getAllProjects', {}) + connectServer(): Promise { + throw new Error('Method not implemented. Please use the connectServer method from the worldModel.') } - async serverStart(): Promise> { - if (!worldModel.state.project_uuid || !worldModel.state.runState.currentEnvId) { - return Promise.reject(new Error('Project UUID or Environment ID is not set.')) - } + disconnectServer(): Promise { + throw new Error('Method not implemented. Please use the disconnectServer method from the worldModel.') + } - return Request.request.post('/api/workbench/LccController@serverStart', { - projectUUID: worldModel.state.project_uuid, - envId: worldModel.state.runState.currentEnvId + getAuthorizationConfig(): Promise> { + return Request.request.post('/api/workbench/AuthController@getAuthorizationConfig', { + _id: system.createUUID() }) } - async serverStop(): Promise> { - if (!worldModel.state.project_uuid || !worldModel.state.runState.currentEnvId) { - return Promise.reject(new Error('Project UUID or Environment ID is not set.')) - } - - return Request.request.post('/api/workbench/LccController@serverStop', { - projectUUID: worldModel.state.project_uuid, - envId: worldModel.state.runState.currentEnvId + /** + * 延时函数 - 使用普通函数确保正确绑定 + */ + async sleep(timeOfMs: number = 1000): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve() // 确保调用 resolve + }, timeOfMs) }) } @@ -140,11 +144,17 @@ export default class LCCScript implements LCC { return res.data } - subscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler) { - return worldModel.backendMessageReceiver.subscribe(topicType, eventHandler) - } - - unsubscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler) { - worldModel.backendMessageReceiver.unsubscribe(topicType, eventHandler) + /** + * 脚本打印日志 + * @param from 系统(Worker)来源 + * @param message 日志内容 + * @param args 可选参数,其他日志内容 + */ + log(from: string, message: string, ...args: any[]): void { + // 插入时分秒 HH:mm:ss.sss + const now = new Date().toISOString().replace('T', ' ').replace('Z', '').split(' ')[1] + // 打成彩色 + // console.log(now + ' [LCC-' + from + '] ' + message, ...args) + console.log(`%c${now} [${from}] ${message}`, 'color: #00f', ...args) } } diff --git a/src/editor/ModelMain.less b/src/editor/ModelMain.less index 57ad390..3d67ee5 100644 --- a/src/editor/ModelMain.less +++ b/src/editor/ModelMain.less @@ -18,6 +18,12 @@ margin: 0 20px; } + .app-header-text { + color: #f4c521; + font-size: large; + line-height: 50px; + } + .app-header-menu-wrap { flex: 1; display: flex; diff --git a/src/editor/ModelMain.vue b/src/editor/ModelMain.vue index d11a4ee..1cb2e41 100644 --- a/src/editor/ModelMain.vue +++ b/src/editor/ModelMain.vue @@ -9,38 +9,10 @@
- {{ worldModelState.project_label }} + {{ worldModelState.project_label }}
-
- - - - -
-
- - - -
- 启动服务 - - 停止服务 - +
@@ -155,10 +127,10 @@ import Logo from '@/assets/images/logo.png' import './ModelMain.less' import EventBus from '@/runtime/EventBus.js' import { worldModel } from '@/core/manager/WorldModel.ts' -import EnvManager from '@/core/manager/EnvManager.js' +import EnvSelectConnect from '@/editor/widgets/server/EnvSelectConnect.vue' export default { - components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine }, + components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine, EnvSelectConnect }, created() { ModelMainInit() }, @@ -193,7 +165,7 @@ export default { } }) }) - this.reloadEnvList() + EventBus.on('dataLoadComplete', (data) => { const { stateManager } = data if (stateManager) { @@ -210,13 +182,6 @@ export default { data() { return { Logo, - timeRateOptions: [ - { label: '1x', value: 1 }, - { label: '2x', value: 2 }, - { label: '5x', value: 5 }, - { label: '10x', value: 10 } - ], - envList: [], isShowEditor: false, editorHash: 0, currentViewport: null, @@ -231,8 +196,7 @@ export default { sectionRightName: 'property', sectionBottomName: '', sectionLeftSearch: '', - centerActiveName: 'ModelEditor', - currentEnvId: worldModel.state.runState.currentEnvId + centerActiveName: 'ModelEditor' } }, computed: { @@ -272,48 +236,6 @@ export default { } }, watch: { - 'worldModelState.isOpened': { - handler() { - if (this.worldModelState.isOpened) { - this.reloadEnvList() - } - } - }, - 'worldModelState.project_uuid': { - immediate: true, - handler() { - if (this.worldModelState.isOpened) { - this.reloadEnvList() - } - } - }, - 'currentEnvId': { - handler(newVal, originalVal) { - if (this.worldModelState.runState.currentEnvId && this.worldModelState.runState.isRunning) { - throw new Error('cannot change env when running') - this.currentEnvId = originalVal - - } else { - // 更新当前环境 ID - const env = _.find(this.envList, env => env.envId === newVal) - if (!env) { - this.worldModelState.runState.currentEnvId = newVal - this.worldModelState.runState.isLoading = false - this.worldModelState.runState.isRunning = false - this.worldModelState.runState.isVirtual = false - this.worldModelState.runState.timeRate = 0 - this.worldModelState.runState.currentEnv = null - } else { - this.worldModelState.runState.currentEnvId = newVal - this.worldModelState.runState.isLoading = false - this.worldModelState.runState.isRunning = false - this.worldModelState.runState.isVirtual = env.isVirtual - this.worldModelState.runState.timeRate = 1 - this.worldModelState.runState.currentEnv = env - } - } - } - }, hideBottom(value) { if (value) { this.$refs.mainSplit.refreshSize([100, 0]) @@ -331,26 +253,6 @@ export default { methods: { renderIcon, getWidgetBySide, - startEnv() { - const env = this.envList.find(env => env.envId === this.worldModelState.runState.currentEnvId) - if (env) { - worldModel.envManager.start(env) - } - }, - stopEnv() { - worldModel.envManager.stop() - }, - createEnv() { - EnvManager.createEnv(this.worldModelState.project_uuid).then(() => { - this.reloadEnvList() - }) - }, - reloadEnvList() { - EnvManager.getAllEnv(this.worldModelState.project_uuid) - .then(envList => { - this.envList = envList - }) - }, toHome() { system.router.push({ name: 'home' }) }, diff --git a/src/editor/menus/FileMenu.ts b/src/editor/menus/FileMenu.ts index e89664c..cfac3d8 100644 --- a/src/editor/menus/FileMenu.ts +++ b/src/editor/menus/FileMenu.ts @@ -55,7 +55,7 @@ function addProject(successful?: Function) { export default defineMenu((menus) => { menus.insertChildren('file', { - name: 'file', label: '模型', icon: renderIcon('ModelFile'), order: 1, disabled: false + name: 'file', label: '地图模型', icon: renderIcon('ModelFile'), order: 1, disabled: false }, [ { @@ -101,13 +101,12 @@ export default defineMenu((menus) => { { name: 'save', label: '保存', icon: SvgCode.save, order: 2, tip: 'Ctrl+S', click: async () => { - if (!worldModel.state.runState.currentEnvId) { - system.showErrorDialog('请先选择环境') - return - } - system.showLoading('正在保存模型数据...') const viewport: Viewport = window['viewport'] + if (!viewport) { + system.showErrorDialog('not found viewport') + return + } const vdata: any = await viewport.stateManager.save() for (const item of vdata.items) { @@ -128,9 +127,10 @@ export default defineMenu((menus) => { await Request.request.post('/api/workbench/LccModelManager@addOrUpdateWorld', { projectUuid: worldModel.state.project_uuid, projectLabel: worldModel.state.project_label, + subSystemList: worldModel.state.sub_system_list, directoryData: JSON.stringify(worldModel.state.catalog), envId: worldModel.state.runState.currentEnvId, - otherData: JSON.stringify(worldModel.state.worldData) + otherData: JSON.stringify(worldModel.state.worldData.otherData) }) system.msg('保存成功', 'success') diff --git a/src/editor/widgets/IWidgets.ts b/src/editor/widgets/IWidgets.ts index 7389b11..1b1b51a 100644 --- a/src/editor/widgets/IWidgets.ts +++ b/src/editor/widgets/IWidgets.ts @@ -32,8 +32,11 @@ export default defineComponent({ if (!worldModel.state.isOpened) { return '地图未打开' } + if (!worldModel.state.runState.currentEnvId) { + return '环境未选择' + } if (!worldModel.backendMessageReceiver.state.isConnected) { - return '后端连接异常' + return '项目未启动' } return '' } diff --git a/src/editor/widgets/monitor/MonitorView.vue b/src/editor/widgets/monitor/MonitorView.vue index b6d1c15..9cc02d0 100644 --- a/src/editor/widgets/monitor/MonitorView.vue +++ b/src/editor/widgets/monitor/MonitorView.vue @@ -11,7 +11,7 @@
- +
@@ -150,10 +150,10 @@ export default { // 订阅设备状态消息 this.stopSubscribe.push( - LCC.subscribe('DeviceAlive', this.onDeviceAliveMessage.bind(this)) + worldModel.backendMessageReceiver.subscribe('DeviceAlive', this.onDeviceAliveMessage.bind(this)) ) this.stopSubscribe.push( - LCC.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this)) + worldModel.backendMessageReceiver.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this)) ) }, undescribe() { diff --git a/src/editor/widgets/script/ScriptMeta.ts b/src/editor/widgets/script/ScriptMeta.ts index aaebd89..ff70e50 100644 --- a/src/editor/widgets/script/ScriptMeta.ts +++ b/src/editor/widgets/script/ScriptMeta.ts @@ -1,12 +1,12 @@ -import { defineWidget } from '../../../runtime/DefineWidget.ts' +import { defineWidget } from '@/runtime/DefineWidget.ts' import { renderIcon } from '@/utils/webutils.ts' import ScriptView from './ScriptView.vue' export default defineWidget({ name: 'script', - title: '脚本', - icon: renderIcon('antd CodeOutlined'), + title: '自定义脚本', + icon: renderIcon('fa Code'), side: 'bottom', order: 3, component: ScriptView -}) \ No newline at end of file +}) diff --git a/src/editor/widgets/server/EnvSelectConnect.vue b/src/editor/widgets/server/EnvSelectConnect.vue new file mode 100644 index 0000000..52de079 --- /dev/null +++ b/src/editor/widgets/server/EnvSelectConnect.vue @@ -0,0 +1,108 @@ + + diff --git a/src/editor/widgets/server/ServerMeta.ts b/src/editor/widgets/server/ServerMeta.ts index 4fcc099..8b9c7a5 100644 --- a/src/editor/widgets/server/ServerMeta.ts +++ b/src/editor/widgets/server/ServerMeta.ts @@ -5,7 +5,7 @@ import ServerView from './ServerView.vue' export default defineWidget({ name: 'server', title: '服务管理', - icon: renderIcon('Server'), + icon: renderIcon('antd DatabaseTwotone'), side: 'bottom', order: 3, component: ServerView diff --git a/src/editor/widgets/server/ServerView.vue b/src/editor/widgets/server/ServerView.vue index f44f74a..c5e4252 100644 --- a/src/editor/widgets/server/ServerView.vue +++ b/src/editor/widgets/server/ServerView.vue @@ -6,14 +6,14 @@ - 刷新 + 刷新
- - + + \ No newline at end of file + diff --git a/src/types/LCC.d.ts b/src/types/LCC.d.ts index 8f46c9d..52f183d 100644 --- a/src/types/LCC.d.ts +++ b/src/types/LCC.d.ts @@ -14,54 +14,55 @@ declare interface LCC { sleep(timeOfMs: number = 1000): Promise /** - * 获取所有项目 + * 读取当前项目所有库存, 并放到 Model 上 */ - getAllProjects(): Promise> + loadInv(): Promise> /** - * 启动当前项目 + * 获取所有车,并放到 Model 上 */ - serverStart(): Promise> + loadExecutor(): Promise /** - * 停止当前项目 + * 保存并同步当前项目所有脚本 + * @param scriptList */ - serverStop(): Promise> + saveAndSyncScripts(scriptList: { name: string, content: string }[]): Promise> /** - * 读取当前项目所有库存, 并放到 Model 上 + * 获取所有设备状态 */ - loadInv(): Promise> + queryDeviceInfoList(): Promise> /** - * 获取所有车,并放到 Model 上 + * 获取服务器状态信息 */ - loadExecutor(): Promise + queryServerState(option: { projectUUID?: string, envId?: string } = {}): Promise> /** - * 保存并同步当前项目所有脚本 - * @param scriptList + * 获取服务器状态信息 */ - saveAndSyncScripts(scriptList: { name: string, content: string }[]): Promise> + startServer(projectUUID: string, envId: string): Promise> /** - * 订阅后端消息 - * @param topicType 消息主题类型 - * @param eventHandler 消息处理函数 + * 停止服务器 */ - subscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler): StopSubscribe; + stopServer(projectUUID: string, envId: string): Promise> /** - * 取消订阅后端消息 - * @param topicType 消息主题类型 - * @param eventHandler 消息处理函数 + * 连接到服务器, 根据 worldModel 当前选中的服务器和环境进行连接 */ - unsubscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler); + connectServer(): Promise /** - * 获取所有设备状态 + * 断开连接 */ - queryDeviceInfoList(): Promise> + disconnectServer(): Promise + + /** + * 等待服务端授权 + */ + getAuthorizationConfig(): Promise> } /** @@ -74,8 +75,30 @@ type DeviceAliveFn = (type: BackendTopicType, topic: string, body: DeviceAliveVo type DeviceStatusFn = (type: BackendTopicType, topic: string, body: DeviceVo) => void +type ServerStateFn = (type: BackendTopicType, topic: string, body: ServerStatusVo) => void + type StopSubscribe = () => void +interface ServerAuthorizationConfigVo { + /** + * 授权码 + */ + authorizationCode: string + /** + * 授权过期时间戳 + */ + expirationTime: number + /** + * 前端推送服务器配置 + */ + frontendMqtt: { + brokerUrl: string + username: string + password: string + websocket: string + } +} + /** * 后端消息回调 */ @@ -144,6 +167,37 @@ interface DeviceVo { bizLpn: string } +/** + * 服务器状态信息 + */ +interface ServerStatusVo { + projectUuid: string; + envId: number; + isVirtual: boolean; + serverId: string; + isRunning: boolean; + + startTime: number; + stopTime: number; + timeRate: number; + subSystemList: string[]; + + cpuUsage: number; + memoryUsage: number; + diskIoLoad: number; + /** + * 空闲内存,单位GB + */ + freeMemory: number; + /** + * 磁盘剩余空间,单位GB + */ + diskFreeSpace: number; + + envConfig: EnvConfigVo; + projectLabel: string; +} + interface InvVo { lpn: string container_type: ContainerT diff --git a/src/types/Model.d.ts b/src/types/Model.d.ts index a0ff7a7..17eb8f7 100644 --- a/src/types/Model.d.ts +++ b/src/types/Model.d.ts @@ -220,7 +220,6 @@ interface EnvInfo { interface EnvConfigVo { mqtt: MqttConfig; - frontendMqtt: MqttConfig; } interface MqttConfig { diff --git a/src/types/Types.d.ts b/src/types/Types.d.ts index 5b9c759..80f812d 100644 --- a/src/types/Types.d.ts +++ b/src/types/Types.d.ts @@ -360,6 +360,16 @@ interface VData { project_uuid?: string /** + * 项目名称 + */ + project_label?: string + + /** + * 子系统集合 + */ + sub_system_list?: string[] + + /** * 当前楼层代码 */ catalogCode: string