Browse Source

ServerView 服务器状态管理、ConnectEnv 连接状态分离、Authorization 服务授权、

master
修宁 6 months ago
parent
commit
fefe376c0c
  1. 51
      src/core/engine/Viewport.ts
  2. 26
      src/core/manager/BackendMessageReceiver.ts
  3. 35
      src/core/manager/EnvManager.ts
  4. 55
      src/core/manager/WorldModel.ts
  5. 84
      src/core/script/LCCScript.ts
  6. 6
      src/editor/ModelMain.less
  7. 110
      src/editor/ModelMain.vue
  8. 14
      src/editor/menus/FileMenu.ts
  9. 5
      src/editor/widgets/IWidgets.ts
  10. 6
      src/editor/widgets/monitor/MonitorView.vue
  11. 6
      src/editor/widgets/script/ScriptMeta.ts
  12. 108
      src/editor/widgets/server/EnvSelectConnect.vue
  13. 2
      src/editor/widgets/server/ServerMeta.ts
  14. 162
      src/editor/widgets/server/ServerView.vue
  15. 4
      src/editor/widgets/task/TaskMeta.ts
  16. 10
      src/editor/widgets/toolbox/ToolboxView.vue
  17. 100
      src/types/LCC.d.ts
  18. 1
      src/types/Model.d.ts
  19. 10
      src/types/Types.d.ts

51
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 { MapControls } from 'three/examples/jsm/controls/MapControls'
import ModelManager from '@/core/script/ModelManager.ts' import ModelManager from '@/core/script/ModelManager.ts'
import RuntimeManager from '@/core/manager/RuntimeManager.ts' import RuntimeManager from '@/core/manager/RuntimeManager.ts'
import EnvManager from '@/core/manager/EnvManager.ts' import Ammo from 'ammojs-typed'
import BackendMessageReceiver from '@/core/manager/BackendMessageReceiver.ts'
import Ammo, {btTransform} from 'ammojs-typed';
/** /**
* *
* ,,,,, * ,,,,,
@ -42,7 +41,7 @@ export default class Viewport {
raycaster: THREE.Raycaster raycaster: THREE.Raycaster
animationFrameId: any = null animationFrameId: any = null
scene: SceneHelp scene: SceneHelp
ammoModel: Ammo = window.Ammo ammoModel: any = window.Ammo
physicsWorld: Ammo.btDiscreteDynamicsWorld physicsWorld: Ammo.btDiscreteDynamicsWorld
transformAux1: Ammo.btTransform transformAux1: Ammo.btTransform
selectManager = new SelectManager() selectManager = new SelectManager()
@ -67,12 +66,12 @@ export default class Viewport {
markRaw(this.itemFindManager), markRaw(this.itemFindManager),
markRaw(this.interactionManager), markRaw(this.interactionManager),
markRaw(this.modelManager), markRaw(this.modelManager),
markRaw(this.runtimeManager), markRaw(this.runtimeManager)
] ]
registerFrameTimerCallBack: Map<string, ()=> void> = new Map() registerFrameTimerCallBack: Map<string, () => void> = new Map()
registerPhysicsUpdateCallBack: Map<string, (deltaTime: number)=> void> = new Map() registerPhysicsUpdateCallBack: Map<string, (deltaTime: number) => void> = new Map()
// 对象实例管理器 moduleName -> InstanceMeshManager // 对象实例管理器 moduleName -> InstanceMeshManager
meshManager: Map<string, InstanceMeshManager> = new Map() meshManager: Map<string, InstanceMeshManager> = new Map()
@ -124,9 +123,11 @@ export default class Viewport {
addFrameTimerCallback(id: string, callback: () => void) { addFrameTimerCallback(id: string, callback: () => void) {
this.registerFrameTimerCallBack.set(id, callback) this.registerFrameTimerCallBack.set(id, callback)
} }
removeFrameTimerCallback(id: string) { removeFrameTimerCallback(id: string) {
this.registerFrameTimerCallBack.delete(id) this.registerFrameTimerCallBack.delete(id)
} }
/** /**
* ID * ID
* @param entityId * @param entityId
@ -169,17 +170,17 @@ export default class Viewport {
async initPhysics() { async initPhysics() {
// Physics variables // Physics variables
let collisionConfiguration; let collisionConfiguration
let dispatcher; let dispatcher
let broadphase; let broadphase
let solver; let solver
collisionConfiguration = new this.ammoModel.btDefaultCollisionConfiguration(); collisionConfiguration = new this.ammoModel.btDefaultCollisionConfiguration()
dispatcher = new this.ammoModel.btCollisionDispatcher( collisionConfiguration ); dispatcher = new this.ammoModel.btCollisionDispatcher(collisionConfiguration)
broadphase = new this.ammoModel.btDbvtBroadphase(); broadphase = new this.ammoModel.btDbvtBroadphase()
solver = new this.ammoModel.btSequentialImpulseConstraintSolver(); solver = new this.ammoModel.btSequentialImpulseConstraintSolver()
this.physicsWorld = new this.ammoModel.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); this.physicsWorld = new this.ammoModel.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration)
this.physicsWorld.setGravity( new this.ammoModel.btVector3( 0, 0, 0 ) ); this.physicsWorld.setGravity(new this.ammoModel.btVector3(0, 0, 0))
// this.transformAux1 = new this.ammoModel.btTransform(); // this.transformAux1 = new this.ammoModel.btTransform();
// tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 ); // tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 );
@ -402,9 +403,9 @@ export default class Viewport {
} }
offset = 0 offset = 0
clock = new THREE.Clock(); clock = new THREE.Clock()
elapsedTime = 0; elapsedTime = 0
interval = 1; // 触发间隔(秒) interval = 1 // 触发间隔(秒)
/** /**
* *
*/ */
@ -418,18 +419,18 @@ export default class Viewport {
} }
this.statsControls?.update() this.statsControls?.update()
const deltaTime = this.clock.getDelta(); const deltaTime = this.clock.getDelta()
this.updatePhysics(deltaTime) this.updatePhysics(deltaTime)
this.renderer?.render(this.scene.scene, this.camera) this.renderer?.render(this.scene.scene, this.camera)
this.css2DRenderer.render(this.scene.scene, this.camera) this.css2DRenderer.render(this.scene.scene, this.camera)
this.css3DRenderer.render(this.scene.scene, this.camera) this.css3DRenderer.render(this.scene.scene, this.camera)
const delta = this.clock.getDelta(); const delta = this.clock.getDelta()
this.elapsedTime += delta; this.elapsedTime += delta
if (this.elapsedTime >= this.interval) { // 每1秒触发一次 if (this.elapsedTime >= this.interval) { // 每1秒触发一次
this.elapsedTime = 0; this.elapsedTime = 0
this.registerFrameTimerCallBack.forEach(callback => { this.registerFrameTimerCallBack.forEach(callback => {
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback() callback()

26
src/core/manager/BackendMessageReceiver.ts

@ -42,11 +42,13 @@ export default class BackendMessageReceiver {
subscribedTopics: [] as string[] subscribedTopics: [] as string[]
}) })
// 启动MQTT连接 setProjectEnv(projectUuid: string, envId: number) {
public async start(projectUuid: string, envId: number, config: MqttConfig): Promise<boolean> {
this.projectUuid = projectUuid this.projectUuid = projectUuid
this.envId = envId this.envId = envId
}
// 启动MQTT连接
public async start(config: MqttConfig): Promise<boolean> {
// 如果已经连接,先断开 // 如果已经连接,先断开
if (this.client?.connected) { if (this.client?.connected) {
await this.dispose() await this.dispose()
@ -69,14 +71,14 @@ export default class BackendMessageReceiver {
return new Promise((resolve) => { return new Promise((resolve) => {
try { 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 = mqtt.connect(config.websocket, options)
// 连接成功 // 连接成功
this.client.on('connect', () => { this.client.on('connect', () => {
this.state.status = ConnectionStatus.CONNECTED this.state.status = ConnectionStatus.CONNECTED
this.state.isConnected = true this.state.isConnected = true
console.log('MQTT connected') console.log('backendMQTT connected')
resolve(true) resolve(true)
}) })
@ -84,20 +86,20 @@ export default class BackendMessageReceiver {
this.client.on('close', () => { this.client.on('close', () => {
this.state.status = ConnectionStatus.DISCONNECTED this.state.status = ConnectionStatus.DISCONNECTED
this.state.isConnected = false this.state.isConnected = false
console.log('MQTT disconnected') console.log('backendMQTT disconnected')
}) })
// 重连中 // 重连中
this.client.on('reconnect', () => { this.client.on('reconnect', () => {
this.state.status = ConnectionStatus.RECONNECTING this.state.status = ConnectionStatus.RECONNECTING
console.log('MQTT reconnecting...') console.log('backendMQTT reconnecting...')
}) })
// 错误处理 // 错误处理
this.client.on('error', (error) => { this.client.on('error', (error) => {
this.state.status = ConnectionStatus.ERROR this.state.status = ConnectionStatus.ERROR
this.state.lastError = error.message this.state.lastError = error.message
console.error('MQTT error:', error) console.error('backendMQTT error:', error)
resolve(false) resolve(false)
}) })
@ -109,7 +111,7 @@ export default class BackendMessageReceiver {
} catch (error) { } catch (error) {
this.state.status = ConnectionStatus.ERROR this.state.status = ConnectionStatus.ERROR
this.state.lastError = (error as Error).message this.state.lastError = (error as Error).message
console.error('MQTT connection error:', error) console.error('backendMQTT connection error:', error)
return false return false
} }
}) })
@ -124,7 +126,7 @@ export default class BackendMessageReceiver {
this.state.isConnected = false this.state.isConnected = false
this.state.status = ConnectionStatus.DISCONNECTED this.state.status = ConnectionStatus.DISCONNECTED
this.state.subscribedTopics = [] this.state.subscribedTopics = []
console.log('MQTT stopped') console.log('backendMQTT stopped')
resolve() resolve()
}) })
} else { } else {
@ -139,7 +141,7 @@ export default class BackendMessageReceiver {
const envId = this.envId const envId = this.envId
switch (type) { switch (type) {
case 'ServerState': case 'ServerState':
return [`/lcc/${projId}/${envId}/server`, this.handleServerState] return [`/lcc/+/+/server`, this.handleServerState]
case 'ClientState': case 'ClientState':
return [`/lcc/${projId}/${envId}/client`, this.handleClientState] return [`/lcc/${projId}/${envId}/client`, this.handleClientState]
case 'TaskUpdate': case 'TaskUpdate':
@ -164,7 +166,7 @@ export default class BackendMessageReceiver {
// 订阅主题 // 订阅主题
public subscribe(type: BackendTopicType, handler: BackendMessageHandler): StopSubscribe { public subscribe(type: BackendTopicType, handler: BackendMessageHandler): StopSubscribe {
if (!this.client?.connected) { 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) const [topic, processFn] = this.getTopicStringByType(type)
@ -196,7 +198,7 @@ export default class BackendMessageReceiver {
// 取消订阅 // 取消订阅
public unsubscribe(type: BackendTopicType, handler: BackendMessageHandler): void { public unsubscribe(type: BackendTopicType, handler: BackendMessageHandler): void {
// if (!this.client?.connected) { // 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) const [topic, processFn] = this.getTopicStringByType(type)

35
src/core/manager/EnvManager.ts

@ -28,11 +28,7 @@ export default class EnvManager {
console.error('Error:', error) console.error('Error:', error)
} }
async start(env: EnvInfo) { async connectEnv() {
if (!env) {
system.showErrorDialog('Environment is not specified, cannot start EnvManager.')
return
}
if (!worldModel.state.isOpened) { if (!worldModel.state.isOpened) {
system.showErrorDialog('WorldModel is not opened, cannot start EnvManager.') system.showErrorDialog('WorldModel is not opened, cannot start EnvManager.')
return return
@ -45,26 +41,22 @@ export default class EnvManager {
system.showErrorDialog('EnvManager is already running, cannot start again.') system.showErrorDialog('EnvManager is already running, cannot start again.')
return return
} }
if (!env.envConfig.mqtt.websocket) { if (!worldModel.state.runState.currentEnv) {
system.showErrorDialog('MQTT websocket URL is not set in the envConfig.mqtt.') system.showErrorDialog('Environment is not specified, cannot start EnvManager.')
return return
} }
if (!env.envConfig.frontendMqtt.websocket) { if (!worldModel.state.runState.currentEnv.envConfig.mqtt.websocket) {
system.showErrorDialog('Frontend MQTT websocket URL is not set in the envConfig.frontendMqtt.') system.showErrorDialog('MQTT websocket URL is not set in the envConfig.mqtt.')
return return
} }
await this.stop() await this.disconnectEnv()
system.showLoading() system.showLoading()
worldModel.state.runState.isLoading = true worldModel.state.runState.isLoading = true
worldModel.state.runState.currentEnv = env const env = worldModel.state.runState.currentEnv
try { try {
await LCC.serverStart() worldModel.backendMessageReceiver.setProjectEnv(worldModel.state.project_uuid, worldModel.state.runState.currentEnvId)
await worldModel.backendMessageReceiver.start(
worldModel.state.project_uuid,
worldModel.state.runState.currentEnvId,
env.envConfig.frontendMqtt)
await LCC.loadInv() await LCC.loadInv()
this.client = mqtt.connect(env.envConfig.mqtt.websocket, { this.client = mqtt.connect(env.envConfig.mqtt.websocket, {
@ -91,12 +83,9 @@ export default class EnvManager {
} }
} }
async stop() { async disconnectEnv() {
system.showLoading() system.showLoading()
try { try {
if (window['LCC']) {
await LCC.serverStop()
}
worldModel.state.runState.isRunning = false worldModel.state.runState.isRunning = false
if (this.client) { if (this.client) {
this.client.removeAllListeners() this.client.removeAllListeners()
@ -126,10 +115,10 @@ export default class EnvManager {
/** /**
* *
*/ */
static async getAllEnv(worldId: string): Promise<ServerResponse<EnvInfo[]>> { static async getAllEnv(worldId: string): Promise<EnvInfo[]> {
// system.invokeServer('') // system.invokeServer('')
if (!worldId) { 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', { const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', {
worldId: worldId worldId: worldId
@ -142,6 +131,6 @@ export default class EnvManager {
* *
*/ */
dispose(): void { dispose(): void {
this.stop() this.disconnectEnv()
} }
} }

55
src/core/manager/WorldModel.ts

@ -4,13 +4,13 @@ import { Request } from '@ease-forge/shared'
import EventBus from '@/runtime/EventBus' import EventBus from '@/runtime/EventBus'
import StateManager from '@/core/manager/StateManager.ts' import StateManager from '@/core/manager/StateManager.ts'
import { getQueryParams, setQueryParam } from '@/utils/webutils.ts' import { getQueryParams, setQueryParam } from '@/utils/webutils.ts'
import localforage from 'localforage'
import BackendMessageReceiver from '@/core/manager/BackendMessageReceiver.ts' import BackendMessageReceiver from '@/core/manager/BackendMessageReceiver.ts'
import EnvManager from '@/core/manager/EnvManager.ts' import EnvManager from '@/core/manager/EnvManager.ts'
import RCSScript from '@/core/script/RCSScript.ts' import RCSScript from '@/core/script/RCSScript.ts'
import LCCScript from '@/core/script/LCCScript.ts' import LCCScript from '@/core/script/LCCScript.ts'
export interface WorldModelState { export interface WorldModelState {
authorizationConfig: ServerAuthorizationConfigVo // 服务器授权配置
isOpened: boolean // 是否已打开世界模型 isOpened: boolean // 是否已打开世界模型
worldData: any // 世界模型数据, 包含排除 items 楼层数据之外的所有数据 worldData: any // 世界模型数据, 包含排除 items 楼层数据之外的所有数据
@ -20,6 +20,7 @@ export interface WorldModelState {
server: string // 当前楼层服务器地址 server: string // 当前楼层服务器地址
project_uuid: string // 当前楼层所在项目ID project_uuid: string // 当前楼层所在项目ID
project_label: string // 项目名称 project_label: string // 项目名称
sub_system_list: string[] // 子系统集合, 用于标识当前楼层所属的子系统
catalogCode: string // 当前楼层的目录代码 catalogCode: string // 当前楼层的目录代码
stateManagerId: string // 当前楼层的状态管理器id stateManagerId: string // 当前楼层的状态管理器id
@ -47,6 +48,7 @@ export default class WorldModel {
* *
*/ */
state: WorldModelState = reactive({ state: WorldModelState = reactive({
authorizationConfig: null,
isOpened: false, // 是否已打开世界模型 isOpened: false, // 是否已打开世界模型
worldData: null, // 世界模型数据, 包含排除 items 楼层数据之外的所有数据 worldData: null, // 世界模型数据, 包含排除 items 楼层数据之外的所有数据
@ -56,11 +58,13 @@ export default class WorldModel {
server: '', server: '',
project_uuid: '', // 项目ID project_uuid: '', // 项目ID
project_label: '', // 项目名称 project_label: '', // 项目名称
sub_system_list: [], // 子系统集合, 用于标识当前楼层所属的子系统
catalogCode: '', // 当前楼层的目录代码 catalogCode: '', // 当前楼层的目录代码
stateManagerId: '', // 当前楼层的状态管理器id, 一般是 项目ID+目录项ID stateManagerId: '', // 当前楼层的状态管理器id, 一般是 项目ID+目录项ID
isDraft: false, // 是否是草稿数据, 如果是草稿数据, 则不需要再从服务器加载数据 isDraft: false, // 是否是草稿数据, 如果是草稿数据, 则不需要再从服务器加载数据
runState: { runState: {
currentEnvId: null, currentEnvId: null,
isLoading: false, 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 方法 // 观察 this.state.catalogCode 的变化, 如果变化就调用 catalogCodeChange 方法
return Promise.all([ return Promise.all([
import('../../modules/measure'), import('../../modules/measure'),
@ -112,8 +122,9 @@ export default class WorldModel {
import('../../modules/amr/ptr/clx'), import('../../modules/amr/ptr/clx'),
import('../../modules/charger') import('../../modules/charger')
]).then(() => { ]).then((configRes) => {
console.log('世界模型初始化完成') console.log('世界模型初始化完成')
system.clearLoading()
// 尝试从草稿中加载数据 // 尝试从草稿中加载数据
const stateManagerId = getQueryParams()?.get('store') const stateManagerId = getQueryParams()?.get('store')
@ -128,6 +139,8 @@ export default class WorldModel {
this.state.catalog = data.catalog this.state.catalog = data.catalog
this.state.server = data.server this.state.server = data.server
this.state.project_uuid = data.project_uuid 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.state.isDraft = true
this.tryOpenCatelog(data.catalogCode, true).then(() => { this.tryOpenCatelog(data.catalogCode, true).then(() => {
@ -186,6 +199,7 @@ export default class WorldModel {
this.state.server = lccModelWorld.server this.state.server = lccModelWorld.server
this.state.project_uuid = lccModelWorld.projectUuid this.state.project_uuid = lccModelWorld.projectUuid
this.state.project_label = lccModelWorld.projectLabel this.state.project_label = lccModelWorld.projectLabel
this.state.sub_system_list = lccModelWorld.subSystemList || []
// 没有打开楼层,不加载 this.state.catalogCode // 没有打开楼层,不加载 this.state.catalogCode
this.state.isDraft = false this.state.isDraft = false
@ -207,6 +221,8 @@ export default class WorldModel {
catalog: _.cloneDeep(this.state.catalog), catalog: _.cloneDeep(this.state.catalog),
server: this.state.server, server: this.state.server,
project_uuid: this.state.project_uuid, project_uuid: this.state.project_uuid,
project_label: this.state.project_label,
sub_system_list: _.cloneDeep(this.state.sub_system_list),
catalogCode: catalogCode, catalogCode: catalogCode,
worldData: _.cloneDeep(this.state.worldData) worldData: _.cloneDeep(this.state.worldData)
} }
@ -222,6 +238,33 @@ export default class WorldModel {
stateManager: null 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() const worldModel = new WorldModel()

84
src/core/script/LCCScript.ts

@ -6,49 +6,53 @@ import { Request } from '@ease-forge/shared'
* LCC API * LCC API
*/ */
export default class LCCScript implements LCC { export default class LCCScript implements LCC {
/**
*
* @param option projectUUID envId
*/
queryServerState(option: { projectUUID?: string; envId?: string } = {}): Promise<ServerResponse<ServerStatusVo[]>> {
return Request.request.post('/api/workbench/ServerController@queryServerState', {
_id: system.createUUID(),
...option
})
}
log(from: string, message: string, ...args: any[]): void { startServer(projectUUID: string, envId: string): Promise<ServerResponse<String>> {
// 插入时分秒 HH:mm:ss.sss return Request.request.post('/api/workbench/ServerController@startServer', {
const now = new Date().toISOString().replace('T', ' ').replace('Z', '').split(' ')[1] projectUUID: projectUUID,
// 打成彩色 envId: envId
// console.log(now + ' [LCC-' + from + '] ' + message, ...args) })
console.log(`%c${now} [${from}] ${message}`, 'color: #00f', ...args)
} }
/** stopServer(projectUUID: string, envId: string): Promise<ServerResponse<String>> {
* - 使 return Request.request.post('/api/workbench/ServerController@stopServer', {
*/ projectUUID: projectUUID,
async sleep(timeOfMs: number = 1000): Promise<void> { envId: envId
return new Promise((resolve) => {
setTimeout(() => {
resolve() // 确保调用 resolve
}, timeOfMs)
}) })
} }
async getAllProjects(): Promise<ServerResponse<LccProjectVo[]>> { connectServer(): Promise<void> {
return Request.request.post('/api/workbench/LccController@getAllProjects', {}) throw new Error('Method not implemented. Please use the connectServer method from the worldModel.')
} }
async serverStart(): Promise<ServerResponse<boolean>> { disconnectServer(): Promise<void> {
if (!worldModel.state.project_uuid || !worldModel.state.runState.currentEnvId) { throw new Error('Method not implemented. Please use the disconnectServer method from the worldModel.')
return Promise.reject(new Error('Project UUID or Environment ID is not set.')) }
}
return Request.request.post('/api/workbench/LccController@serverStart', { getAuthorizationConfig(): Promise<ServerResponse<ServerAuthorizationConfigVo>> {
projectUUID: worldModel.state.project_uuid, return Request.request.post('/api/workbench/AuthController@getAuthorizationConfig', {
envId: worldModel.state.runState.currentEnvId _id: system.createUUID()
}) })
} }
async serverStop(): Promise<ServerResponse<boolean>> { /**
if (!worldModel.state.project_uuid || !worldModel.state.runState.currentEnvId) { * - 使
return Promise.reject(new Error('Project UUID or Environment ID is not set.')) */
} async sleep(timeOfMs: number = 1000): Promise<void> {
return new Promise((resolve) => {
return Request.request.post('/api/workbench/LccController@serverStop', { setTimeout(() => {
projectUUID: worldModel.state.project_uuid, resolve() // 确保调用 resolve
envId: worldModel.state.runState.currentEnvId }, timeOfMs)
}) })
} }
@ -140,11 +144,17 @@ export default class LCCScript implements LCC {
return res.data return res.data
} }
subscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler) { /**
return worldModel.backendMessageReceiver.subscribe(topicType, eventHandler) *
} * @param from Worker
* @param message
unsubscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler) { * @param args
worldModel.backendMessageReceiver.unsubscribe(topicType, eventHandler) */
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)
} }
} }

6
src/editor/ModelMain.less

@ -18,6 +18,12 @@
margin: 0 20px; margin: 0 20px;
} }
.app-header-text {
color: #f4c521;
font-size: large;
line-height: 50px;
}
.app-header-menu-wrap { .app-header-menu-wrap {
flex: 1; flex: 1;
display: flex; display: flex;

110
src/editor/ModelMain.vue

@ -9,38 +9,10 @@
<component :is="renderIcon('element ArrowDown')"></component> <component :is="renderIcon('element ArrowDown')"></component>
</div> </div>
<div style="flex-grow: 1;"> <div style="flex-grow: 1;">
<span>{{ worldModelState.project_label }}</span> <span class="app-header-text">{{ worldModelState.project_label }}</span>
</div> </div>
<div v-if="isModelOpen" style="display: flex; flex-direction: row; align-items: center; margin-right: 10px;"> <div v-if="isModelOpen" style="display: flex; flex-direction: row; align-items: center; margin-right: 10px;">
<div class="field-block" style="margin-right: 5px;"> <EnvSelectConnect />
<el-select style="width:180px;" placeholder="选择运行环境" :disabled="worldModelState.runState.currentEnvId && worldModelState.runState.isRunning"
v-model="currentEnvId">
<el-option v-for="env in envList" :key="env.envId" :label="env.envName" :value="env.envId"></el-option>
<template #footer>
<el-button size="small" type="primary" @click="createEnv" plain>创建虚拟环境</el-button>
<el-button size="small" :icon="renderIcon('Refresh')" @click="reloadEnvList" />
</template>
</el-select>
</div>
<div class="field-block" style="margin-right: 5px;">
<el-select style="width:80px;" placeholder="时间速率"
v-model="worldModelState.runState.timeRate"
v-if="worldModelState.runState.isVirtual">
<el-option v-for="option in timeRateOptions"
:label="option.label" :value="option.value" />
</el-select>
</div>
<el-button :icon="renderIcon('Play')" type="primary"
v-if="!worldModelState.runState.currentEnvId || !worldModelState.runState.isRunning"
:disabled="!worldModelState.runState.currentEnvId"
:loading="worldModelState.runState.isLoading"
@click="startEnv">启动服务
</el-button>
<el-button :icon="renderIcon('Stop')" type="danger" plain
v-if="worldModelState.runState.currentEnvId && worldModelState.runState.isRunning"
:loading="worldModelState.runState.isLoading"
@click="stopEnv">停止服务
</el-button>
</div> </div>
</div> </div>
<div class="user"> <div class="user">
@ -155,10 +127,10 @@ import Logo from '@/assets/images/logo.png'
import './ModelMain.less' import './ModelMain.less'
import EventBus from '@/runtime/EventBus.js' import EventBus from '@/runtime/EventBus.js'
import { worldModel } from '@/core/manager/WorldModel.ts' import { worldModel } from '@/core/manager/WorldModel.ts'
import EnvManager from '@/core/manager/EnvManager.js' import EnvSelectConnect from '@/editor/widgets/server/EnvSelectConnect.vue'
export default { export default {
components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine }, components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine, EnvSelectConnect },
created() { created() {
ModelMainInit() ModelMainInit()
}, },
@ -193,7 +165,7 @@ export default {
} }
}) })
}) })
this.reloadEnvList()
EventBus.on('dataLoadComplete', (data) => { EventBus.on('dataLoadComplete', (data) => {
const { stateManager } = data const { stateManager } = data
if (stateManager) { if (stateManager) {
@ -210,13 +182,6 @@ export default {
data() { data() {
return { return {
Logo, Logo,
timeRateOptions: [
{ label: '1x', value: 1 },
{ label: '2x', value: 2 },
{ label: '5x', value: 5 },
{ label: '10x', value: 10 }
],
envList: [],
isShowEditor: false, isShowEditor: false,
editorHash: 0, editorHash: 0,
currentViewport: null, currentViewport: null,
@ -231,8 +196,7 @@ export default {
sectionRightName: 'property', sectionRightName: 'property',
sectionBottomName: '', sectionBottomName: '',
sectionLeftSearch: '', sectionLeftSearch: '',
centerActiveName: 'ModelEditor', centerActiveName: 'ModelEditor'
currentEnvId: worldModel.state.runState.currentEnvId
} }
}, },
computed: { computed: {
@ -272,48 +236,6 @@ export default {
} }
}, },
watch: { 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) { hideBottom(value) {
if (value) { if (value) {
this.$refs.mainSplit.refreshSize([100, 0]) this.$refs.mainSplit.refreshSize([100, 0])
@ -331,26 +253,6 @@ export default {
methods: { methods: {
renderIcon, renderIcon,
getWidgetBySide, 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() { toHome() {
system.router.push({ name: 'home' }) system.router.push({ name: 'home' })
}, },

14
src/editor/menus/FileMenu.ts

@ -55,7 +55,7 @@ function addProject(successful?: Function) {
export default defineMenu((menus) => { export default defineMenu((menus) => {
menus.insertChildren('file', 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', name: 'save', label: '保存', icon: SvgCode.save, order: 2, tip: 'Ctrl+S',
click: async () => { click: async () => {
if (!worldModel.state.runState.currentEnvId) {
system.showErrorDialog('请先选择环境')
return
}
system.showLoading('正在保存模型数据...') system.showLoading('正在保存模型数据...')
const viewport: Viewport = window['viewport'] const viewport: Viewport = window['viewport']
if (!viewport) {
system.showErrorDialog('not found viewport')
return
}
const vdata: any = await viewport.stateManager.save() const vdata: any = await viewport.stateManager.save()
for (const item of vdata.items) { for (const item of vdata.items) {
@ -128,9 +127,10 @@ export default defineMenu((menus) => {
await Request.request.post('/api/workbench/LccModelManager@addOrUpdateWorld', { await Request.request.post('/api/workbench/LccModelManager@addOrUpdateWorld', {
projectUuid: worldModel.state.project_uuid, projectUuid: worldModel.state.project_uuid,
projectLabel: worldModel.state.project_label, projectLabel: worldModel.state.project_label,
subSystemList: worldModel.state.sub_system_list,
directoryData: JSON.stringify(worldModel.state.catalog), directoryData: JSON.stringify(worldModel.state.catalog),
envId: worldModel.state.runState.currentEnvId, envId: worldModel.state.runState.currentEnvId,
otherData: JSON.stringify(worldModel.state.worldData) otherData: JSON.stringify(worldModel.state.worldData.otherData)
}) })
system.msg('保存成功', 'success') system.msg('保存成功', 'success')

5
src/editor/widgets/IWidgets.ts

@ -32,8 +32,11 @@ export default defineComponent({
if (!worldModel.state.isOpened) { if (!worldModel.state.isOpened) {
return '地图未打开' return '地图未打开'
} }
if (!worldModel.state.runState.currentEnvId) {
return '环境未选择'
}
if (!worldModel.backendMessageReceiver.state.isConnected) { if (!worldModel.backendMessageReceiver.state.isConnected) {
return '后端连接异常' return '项目未启动'
} }
return '' return ''
} }

6
src/editor/widgets/monitor/MonitorView.vue

@ -11,7 +11,7 @@
</span> </span>
</div> </div>
<div class="calc-left-panel"> <div class="calc-left-panel">
<el-empty v-if="!isActivated || errorDescription" :description="errorDescription"> <el-empty v-if="!isActivated || errorDescription" :description="errorDescription" style="width:100%;">
</el-empty> </el-empty>
<div v-else class="monitor-tool-wrap"> <div v-else class="monitor-tool-wrap">
<div class="infor-row"> <div class="infor-row">
@ -150,10 +150,10 @@ export default {
// //
this.stopSubscribe.push( this.stopSubscribe.push(
LCC.subscribe('DeviceAlive', this.onDeviceAliveMessage.bind(this)) worldModel.backendMessageReceiver.subscribe('DeviceAlive', this.onDeviceAliveMessage.bind(this))
) )
this.stopSubscribe.push( this.stopSubscribe.push(
LCC.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this)) worldModel.backendMessageReceiver.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this))
) )
}, },
undescribe() { undescribe() {

6
src/editor/widgets/script/ScriptMeta.ts

@ -1,11 +1,11 @@
import { defineWidget } from '../../../runtime/DefineWidget.ts' import { defineWidget } from '@/runtime/DefineWidget.ts'
import { renderIcon } from '@/utils/webutils.ts' import { renderIcon } from '@/utils/webutils.ts'
import ScriptView from './ScriptView.vue' import ScriptView from './ScriptView.vue'
export default defineWidget({ export default defineWidget({
name: 'script', name: 'script',
title: '脚本', title: '自定义脚本',
icon: renderIcon('antd CodeOutlined'), icon: renderIcon('fa Code'),
side: 'bottom', side: 'bottom',
order: 3, order: 3,
component: ScriptView component: ScriptView

108
src/editor/widgets/server/EnvSelectConnect.vue

@ -0,0 +1,108 @@
<template>
<el-select placeholder="选择运行环境" style="width:180px; margin-right: 5px;"
:disabled="worldModelState.runState.currentEnvId && worldModelState.runState.isRunning"
:model-value="worldModelState.runState.currentEnvId"
@change="setEnvId">
<el-option v-for="env in envList" :key="env.envId" :label="env.envName" :value="env.envId"></el-option>
<template #footer>
<el-button size="small" type="primary" @click="createEnv" plain>创建虚拟环境</el-button>
<el-button size="small" :icon="renderIcon('Refresh')" @click="reloadEnvList" />
</template>
</el-select>
<el-select style="width:80px;margin-right: 5px;" placeholder="时间速率"
v-model="worldModelState.runState.timeRate"
v-if="worldModelState.runState.isVirtual">
<el-option v-for="option in timeRateOptions"
:label="option.label" :value="option.value" />
</el-select>
<el-button :icon="renderIcon('Connection')" type="primary"
v-if="!worldModelState.runState.currentEnvId || !worldModelState.runState.isRunning"
:disabled="!worldModelState.runState.currentEnvId"
:loading="worldModelState.runState.isLoading"
@click="connectEnv">连接服务
</el-button>
<el-button :icon="renderIcon('antd DisconnectOutlined')" type="danger" plain
v-if="worldModelState.runState.currentEnvId && worldModelState.runState.isRunning"
:loading="worldModelState.runState.isLoading"
@click="disconnectEnv">断开连接
</el-button>
</template>
<script>
import { renderIcon } from '@/utils/webutils.js'
import { worldModel } from '@/core/manager/WorldModel.js'
import EnvManager from '@/core/manager/EnvManager.js'
import _ from 'lodash'
export default {
name: 'EnvSelectConnect',
data() {
return {
/**
* @type {Array<EnvInfo>}
*/
envList: [],
timeRateOptions: [
{ label: '1x', value: 1 },
{ label: '2x', value: 2 },
{ label: '5x', value: 5 },
{ label: '10x', value: 10 }
]
}
},
mounted() {
this.reloadEnvList()
},
methods: {
renderIcon,
connectEnv() {
worldModel.envManager.connectEnv()
},
disconnectEnv() {
worldModel.envManager.disconnectEnv()
},
createEnv() {
EnvManager.createEnv(this.worldModelState.project_uuid).then(() => {
this.reloadEnvList()
})
},
reloadEnvList() {
EnvManager.getAllEnv(this.worldModelState.project_uuid)
.then(envList => {
this.envList = envList
})
},
setEnvId(newVal) {
const env = _.find(this.envList, env => env.envId === newVal)
if (!env) {
system.showErrorDialog(`未找到环境ID: ${newVal}`)
}
worldModel.setEnv(env)
}
},
watch: {
'worldModelState.isOpened': {
handler() {
if (this.worldModelState.isOpened) {
this.reloadEnvList()
}
}
},
'worldModelState.project_uuid': {
immediate: true,
handler() {
if (this.worldModelState.isOpened) {
this.reloadEnvList()
}
}
}
},
computed: {
isModelOpen() {
return this.worldModelState.isOpened
},
worldModelState() {
return worldModel.state
}
}
}
</script>

2
src/editor/widgets/server/ServerMeta.ts

@ -5,7 +5,7 @@ import ServerView from './ServerView.vue'
export default defineWidget({ export default defineWidget({
name: 'server', name: 'server',
title: '服务管理', title: '服务管理',
icon: renderIcon('Server'), icon: renderIcon('antd DatabaseTwotone'),
side: 'bottom', side: 'bottom',
order: 3, order: 3,
component: ServerView component: ServerView

162
src/editor/widgets/server/ServerView.vue

@ -6,14 +6,14 @@
<component :is="renderIcon('element Search')"></component> <component :is="renderIcon('element Search')"></component>
</template> </template>
</el-input> </el-input>
<el-button :icon="renderIcon('Refresh')" size="small">刷新</el-button> <el-button :icon="renderIcon('Refresh')" size="small" @click="refreshData">刷新</el-button>
<span class="close" @click="closeMe"> <span class="close" @click="closeMe">
<component :is="renderIcon('element Close')"></component> <component :is="renderIcon('element Close')"></component>
</span> </span>
</div> </div>
<div class="calc-bottom-panel"> <div class="calc-bottom-panel">
<el-table :data="serverList" style="width: 100%"> <el-table :data="serverList" v-loading="isLoading" style="width: 100%">
<el-table-column prop="envID" label="环境ID" width="100" /> <el-table-column prop="envId" label="环境ID" width="100" />
<el-table-column prop="isVirtual" label="环境类型" width="100"> <el-table-column prop="isVirtual" label="环境类型" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="row.isVirtual ? 'info' : 'primary'"> <el-tag :type="row.isVirtual ? 'info' : 'primary'">
@ -26,17 +26,17 @@
<el-table-column label="系统类型" min-width="200"> <el-table-column label="系统类型" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
<div class="system-tags"> <div class="system-tags">
<el-tag v-for="(system, index) in row.systemList" :key="index" size="small" class="system-tag" :class="getSystemTagClass(system)"> <el-tag v-for="(system, index) in row.subSystemList" :key="index" size="small" class="system-tag" :class="getSystemTagClass(system)">
{{ system }} {{ system }}
</el-tag> </el-tag>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="projectIsRunning" label="运行状态" width="100"> <el-table-column prop="isRunning" label="运行状态" width="100">
<template #default="{ row }"> <template #default="{ row }">
<div class="status-indicator"> <div class="status-indicator">
<span class="dot" :class="row.projectIsRunning ? 'running' : 'stopped'"></span> <span class="dot" :class="row.isRunning ? 'running' : 'stopped'"></span>
{{ row.projectIsRunning ? '运行中' : '已停止' }} {{ row.isRunning ? '运行中' : '已停止' }}
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@ -52,15 +52,11 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="120"> <el-table-column label="操作" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button :type="row.projectIsRunning ? 'danger' : 'success'" plain size="small" @click="toggleServerStatus(row)"> <el-button v-if="!row.isRunning" type="success" plain size="small" @click="startServer(row)"
{{ row.projectIsRunning ? '停止' : '启动' }} :icon="renderIcon('Play')">启动
</el-button> </el-button>
</template> <el-button v-if="row.isRunning" type="danger" plain size="small" @click="stopServer(row)"
</el-table-column> :icon="renderIcon('Stop')">停止
<el-table-column label="连接" width="100">
<template #default="{ row }">
<el-button type="primary" plain size="small" @click="connectEnv(row)">
连接
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -69,53 +65,72 @@
</template> </template>
<script> <script>
import IWidgets from '../IWidgets.js' import IWidgets from '../IWidgets.js'
import { worldModel } from '@/core/manager/WorldModel.js'
export default { export default {
name: 'ServerView', name: 'ServerView',
mixins: [IWidgets], mixins: [IWidgets],
data() { data() {
return { return {
isLoading: false,
stopSubscribe: [],
// - API // - API
searchKeyword: '', searchKeyword: '',
serverList: [ /**
{ * @type {Array<ServerStatusVo>}
id: 1, */
isVirtual: true, serverList: []
projectUuid: 'PROJ-001',
envID: '101',
projectLabel: '物流系统A',
systemList: ['WMS', 'WCS', 'RCS'],
projectIsRunning: true,
startTime: Date.now() - 3600000 * 2.5 // 2.5
},
{
id: 2,
isVirtual: false,
projectUuid: 'PROJ-002',
envID: '102',
projectLabel: '仓储系统B',
systemList: ['MFC', 'WES'],
projectIsRunning: false,
startTime: null
},
{
id: 3,
isVirtual: true,
projectUuid: 'PROJ-003',
envID: '103',
projectLabel: '配送系统C',
systemList: ['OES', 'PES', 'WMS', 'WCS'],
projectIsRunning: true,
startTime: Date.now() - 3600000 * 4.2 // 4.2
}
]
} }
}, },
mounted() { mounted() {
window['ServerView'] = this
}, },
beforeUnmount() { unmounted() {
window['ServerView'] = null
this.undescribe()
}, },
methods: { methods: {
/**
* @type {ServerStateFn}
*/
onServerStateMessage(type, topic, data) {
//
const server = this.serverList.find(s => s.envId === data.envId && s.projectUuid === data.projectUuid)
if (server) {
Object.assign(server, data)
}
},
async subscribe() {
await this.refreshData()
//
this.stopSubscribe.push(
worldModel.backendMessageReceiver.subscribe('ServerState', this.onServerStateMessage.bind(this))
)
},
async refreshData() {
this.serverList = []
this.isLoading = true
try {
const res = await LCC.queryServerState()
if (!res.success) {
return
}
this.serverList = res.data
} finally {
this.isLoading = false
}
},
undescribe() {
//
for (const stopFn of this.stopSubscribe) {
stopFn()
}
this.stopSubscribe = []
},
// //
getSystemTagClass(system) { getSystemTagClass(system) {
return `system-${system.toLowerCase()}` return `system-${system.toLowerCase()}`
@ -123,50 +138,49 @@ export default {
// //
formatStartTime(row) { formatStartTime(row) {
if (!row.projectIsRunning || !row.startTime) return '' if (!row.startTime) return ''
const date = new Date(row.startTime) const date = new Date(parseInt(row.startTime))
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}
${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}` ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`
}, },
// //
calculateRuntime(row) { calculateRuntime(row) {
if (!row.projectIsRunning || !row.startTime) return '' if (!row.startTime) return ''
const hours = (Date.now() - row.startTime) / 3600000 const hours = (Date.now() - row.startTime) / 3600000
return hours.toFixed(1) return hours.toFixed(1)
}, },
//
toggleServerStatus(row) {
if (row.projectIsRunning) {
this.stopServer(row)
} else {
this.startServer(row)
}
},
// //
startServer(server) { startServer(server) {
server.projectIsRunning = true system.showLoading()
server.startTime = Date.now() LCC.startServer(server.projectUuid, server.envId)
// API .finally(() => {
console.log('启动服务器:', server.id) system.clearLoading()
})
}, },
// //
stopServer(server) { stopServer(server) {
server.projectIsRunning = false system.showLoading()
// API LCC.stopServer(server.projectUuid, server.envId)
console.log('停止服务器:', server.id) .finally(() => {
}, system.clearLoading()
})
// }
connectEnv(row) { },
console.log('连接环境:', row.envID) watch: {
// isActivated: {
this.$message.success(`正在连接环境: ${row.envID}`) handler(value) {
if (value) {
this.subscribe()
} else {
this.undescribe()
}
},
immediate: true
} }
} }
} }

4
src/editor/widgets/task/TaskMeta.ts

@ -4,8 +4,8 @@ import TaskView from './TaskView2.vue'
export default defineWidget({ export default defineWidget({
name: 'task', name: 'task',
title: '任务', title: 'AGV任务',
icon: renderIcon('element List'), icon: renderIcon('antd CarryOutOutlined'),
side: 'bottom', side: 'bottom',
order: 1, order: 1,
component: TaskView component: TaskView

10
src/editor/widgets/toolbox/ToolboxView.vue

@ -102,8 +102,8 @@ export default {
{ {
name: 'other', icon: 'antd CiOutlined', label: '辅助', name: 'other', icon: 'antd CiOutlined', label: '辅助',
children: [ children: [
{ name: 'source', icon:'antd SoundOutlined',label: '发生器' }, { name: 'source', icon: 'antd SoundOutlined', label: '发生器' },
{ name: 'sink',icon:'fa Eraser', label: '消失器' }, { name: 'sink', icon: 'fa Eraser', label: '消失器' },
{ name: 'dispatcher', label: '任务分配器' }, { name: 'dispatcher', label: '任务分配器' },
{ name: 'text', label: '文本' }, { name: 'text', label: '文本' },
{ name: 'image', label: '图片' }, { name: 'image', label: '图片' },
@ -157,6 +157,12 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
.toolbox-view { .toolbox-view {
width: 100%;
& > .el-menu {
width: 100%;
}
.subtitle { .subtitle {
margin-left: 5px; margin-left: 5px;
color: var(--el-color-info-light-5); color: var(--el-color-info-light-5);

100
src/types/LCC.d.ts

@ -14,54 +14,55 @@ declare interface LCC {
sleep(timeOfMs: number = 1000): Promise<void> sleep(timeOfMs: number = 1000): Promise<void>
/** /**
* * , Model
*/ */
getAllProjects(): Promise<ServerResponse<LccProjectVo[]>> loadInv(): Promise<ServerResponse<InvVo>>
/** /**
* * Model
*/ */
serverStart(): Promise<ServerResponse<boolean>> loadExecutor(): Promise<ExecutorVo>
/** /**
* *
* @param scriptList
*/ */
serverStop(): Promise<ServerResponse<boolean>> saveAndSyncScripts(scriptList: { name: string, content: string }[]): Promise<ServerResponse<{ name: string, content: string }[]>>
/** /**
* , Model *
*/ */
loadInv(): Promise<ServerResponse<InvVo>> queryDeviceInfoList(): Promise<ServerResponse<DeviceVo[]>>
/** /**
* Model *
*/ */
loadExecutor(): Promise<ExecutorVo> queryServerState(option: { projectUUID?: string, envId?: string } = {}): Promise<ServerResponse<ServerStatusVo[]>>
/** /**
* *
* @param scriptList
*/ */
saveAndSyncScripts(scriptList: { name: string, content: string }[]): Promise<ServerResponse<{ name: string, content: string }[]>> startServer(projectUUID: string, envId: string): Promise<ServerResponse<String>>
/** /**
* *
* @param topicType
* @param eventHandler
*/ */
subscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler): StopSubscribe; stopServer(projectUUID: string, envId: string): Promise<ServerResponse<String>>
/** /**
* * , worldModel
* @param topicType
* @param eventHandler
*/ */
unsubscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler); connectServer(): Promise<void>
/** /**
* *
*/ */
queryDeviceInfoList(): Promise<ServerResponse<DeviceVo[]>> disconnectServer(): Promise<void>
/**
*
*/
getAuthorizationConfig(): Promise<ServerResponse<ServerAuthorizationConfigVo>>
} }
/** /**
@ -74,8 +75,30 @@ type DeviceAliveFn = (type: BackendTopicType, topic: string, body: DeviceAliveVo
type DeviceStatusFn = (type: BackendTopicType, topic: string, body: DeviceVo) => void type DeviceStatusFn = (type: BackendTopicType, topic: string, body: DeviceVo) => void
type ServerStateFn = (type: BackendTopicType, topic: string, body: ServerStatusVo) => void
type StopSubscribe = () => 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 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 { interface InvVo {
lpn: string lpn: string
container_type: ContainerT container_type: ContainerT

1
src/types/Model.d.ts

@ -220,7 +220,6 @@ interface EnvInfo {
interface EnvConfigVo { interface EnvConfigVo {
mqtt: MqttConfig; mqtt: MqttConfig;
frontendMqtt: MqttConfig;
} }
interface MqttConfig { interface MqttConfig {

10
src/types/Types.d.ts

@ -360,6 +360,16 @@ interface VData {
project_uuid?: string project_uuid?: string
/** /**
*
*/
project_label?: string
/**
*
*/
sub_system_list?: string[]
/**
* *
*/ */
catalogCode: string catalogCode: string

Loading…
Cancel
Save