Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
Z
zhmes-agecal
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
耿迪迪
zhmes-agecal
Commits
cd00704b
Commit
cd00704b
authored
Aug 04, 2025
by
wanghao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1 机械臂功能开发
parent
fe607a03
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1768 additions
and
124 deletions
+1768
-124
RobotArmCommandController.java
...g/web/controller/equipment/RobotArmCommandController.java
+99
-0
application-dev.yml
zhmes-agecal-admin/src/main/resources/application-dev.yml
+10
-3
application-prd.yml
zhmes-agecal-admin/src/main/resources/application-prd.yml
+9
-1
application-test.yml
zhmes-agecal-admin/src/main/resources/application-test.yml
+9
-1
pom.xml
zhmes-agecal-common/pom.xml
+4
-0
NettyConfig.java
...rc/main/java/com/zehong/framework/config/NettyConfig.java
+1
-1
SecurityConfig.java
...main/java/com/zehong/framework/config/SecurityConfig.java
+4
-0
NettyUdpServerHandler.java
...zehong/framework/netty/handler/NettyUdpServerHandler.java
+50
-7
RobotArmCommand.java
...c/main/java/com/zehong/system/domain/RobotArmCommand.java
+138
-0
RobotArmCommandMapper.java
.../java/com/zehong/system/mapper/RobotArmCommandMapper.java
+81
-0
TStoreyInfoMapper.java
...main/java/com/zehong/system/mapper/TStoreyInfoMapper.java
+2
-0
IRobotArmCommandService.java
...va/com/zehong/system/service/IRobotArmCommandService.java
+70
-0
RobotArmCommandServiceImpl.java
...ehong/system/service/impl/RobotArmCommandServiceImpl.java
+251
-0
RobotArmWebSocketHandler.java
...ng/system/service/websocket/RobotArmWebSocketHandler.java
+131
-0
WebSocketConfig.java
.../com/zehong/system/service/websocket/WebSocketConfig.java
+27
-0
UdpCommandSender.java
...src/main/java/com/zehong/system/udp/UdpCommandSender.java
+68
-0
RobotArmCommandMapper.xml
...rc/main/resources/mapper/system/RobotArmCommandMapper.xml
+123
-0
TStoreyInfoMapper.xml
...em/src/main/resources/mapper/system/TStoreyInfoMapper.xml
+24
-1
robotArmCommand.js
zhmes-agecal-web/src/api/robotArm/robotArmCommand.js
+62
-0
RoboticArm.vue
zhmes-agecal-web/src/views/screen/components/RoboticArm.vue
+605
-110
No files found.
zhmes-agecal-admin/src/main/java/com/zehong/web/controller/equipment/RobotArmCommandController.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
web
.
controller
.
equipment
;
import
java.util.List
;
import
java.util.Map
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.PutMapping
;
import
org.springframework.web.bind.annotation.DeleteMapping
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
com.zehong.common.core.controller.BaseController
;
import
com.zehong.common.core.domain.AjaxResult
;
import
com.zehong.system.domain.RobotArmCommand
;
import
com.zehong.system.service.IRobotArmCommandService
;
import
com.zehong.common.utils.poi.ExcelUtil
;
import
com.zehong.common.core.page.TableDataInfo
;
/**
* 机械臂指令Controller
*
* @author zehong
* @date 2025-08-04
*/
@RestController
@RequestMapping
(
"/robotArm/command"
)
public
class
RobotArmCommandController
extends
BaseController
{
@Autowired
private
IRobotArmCommandService
robotArmCommandService
;
/**
* 查询机械臂指令列表
*/
@GetMapping
(
"/list"
)
public
TableDataInfo
list
(
RobotArmCommand
robotArmCommand
)
{
startPage
();
List
<
RobotArmCommand
>
list
=
robotArmCommandService
.
selectRobotArmCommandList
(
robotArmCommand
);
return
getDataTable
(
list
);
}
/**
* 导出机械臂指令列表
*/
@GetMapping
(
"/export"
)
public
AjaxResult
export
(
RobotArmCommand
robotArmCommand
)
{
List
<
RobotArmCommand
>
list
=
robotArmCommandService
.
selectRobotArmCommandList
(
robotArmCommand
);
ExcelUtil
<
RobotArmCommand
>
util
=
new
ExcelUtil
<
RobotArmCommand
>(
RobotArmCommand
.
class
);
return
util
.
exportExcel
(
list
,
"机械臂指令数据"
);
}
/**
* 获取机械臂指令详细信息
*/
@GetMapping
(
value
=
"/{robotArmCommandId}"
)
public
AjaxResult
getInfo
(
@PathVariable
(
"robotArmCommandId"
)
Long
robotArmCommandId
)
{
return
AjaxResult
.
success
(
robotArmCommandService
.
selectRobotArmCommandById
(
robotArmCommandId
));
}
/**
* 新增机械臂指令
*/
@PostMapping
public
AjaxResult
add
(
@RequestBody
RobotArmCommand
robotArmCommand
)
{
return
toAjax
(
robotArmCommandService
.
insertRobotArmCommand
(
robotArmCommand
));
}
@PostMapping
(
"/powerOn"
)
public
AjaxResult
powerOn
(
@RequestBody
Map
<
String
,
Object
>
params
)
{
Long
commandId
=
Long
.
parseLong
(
params
.
get
(
"commandId"
).
toString
());
robotArmCommandService
.
powerOnCommand
(
commandId
);
return
AjaxResult
.
success
(
"上电操作成功"
);
}
/**
* 修改机械臂指令
*/
@PutMapping
public
AjaxResult
edit
(
@RequestBody
RobotArmCommand
robotArmCommand
)
{
return
toAjax
(
robotArmCommandService
.
updateRobotArmCommand
(
robotArmCommand
));
}
/**
* 删除机械臂指令
*/
@DeleteMapping
(
"/{robotArmCommandIds}"
)
public
AjaxResult
remove
(
@PathVariable
Long
[]
robotArmCommandIds
)
{
return
toAjax
(
robotArmCommandService
.
deleteRobotArmCommandByIds
(
robotArmCommandIds
));
}
}
zhmes-agecal-admin/src/main/resources/application-dev.yml
View file @
cd00704b
# 数据源配置
# 数据源配置
spring
:
spring
:
datasource
:
datasource
:
type
:
com.alibaba.druid.pool.DruidDataSource
type
:
com.alibaba.druid.pool.DruidDataSource
driverClassName
:
com.mysql.cj.jdbc.Driver
driverClassName
:
com.mysql.cj.jdbc.Driver
druid
:
druid
:
...
@@ -56,7 +56,7 @@ spring:
...
@@ -56,7 +56,7 @@ spring:
config
:
config
:
multi-statement-allow
:
true
multi-statement-allow
:
true
# redis 配置
# redis 配置
redis
:
redis
:
# 地址
# 地址
host
:
localhost
host
:
localhost
# 端口,默认为6379
# 端口,默认为6379
...
@@ -102,4 +102,11 @@ netty:
...
@@ -102,4 +102,11 @@ netty:
boss-group-thread-count
:
1
# 主线程数
boss-group-thread-count
:
1
# 主线程数
worker-group-thread-count
:
8
# 工作线程数
worker-group-thread-count
:
8
# 工作线程数
max-frame-length
:
65535
# 最大帧长度
max-frame-length
:
65535
# 最大帧长度
heartbeat-timeout
:
60
# 心跳超时时间(秒)
heartbeat-timeout
:
10
# 心跳超时时间(秒)
\ No newline at end of file
# 机械臂UDP配置
robot
:
arm
:
udp
:
ip
:
192.168.2.16
port
:
6000
zhmes-agecal-admin/src/main/resources/application-prd.yml
View file @
cd00704b
...
@@ -102,4 +102,12 @@ netty:
...
@@ -102,4 +102,12 @@ netty:
boss-group-thread-count
:
1
# 主线程数
boss-group-thread-count
:
1
# 主线程数
worker-group-thread-count
:
8
# 工作线程数
worker-group-thread-count
:
8
# 工作线程数
max-frame-length
:
65535
# 最大帧长度
max-frame-length
:
65535
# 最大帧长度
heartbeat-timeout
:
60
# 心跳超时时间(秒)
heartbeat-timeout
:
10
# 心跳超时时间(秒)
\ No newline at end of file
# 机械臂UDP配置
robot
:
arm
:
udp
:
ip
:
192.168.2.16
port
:
6000
\ No newline at end of file
zhmes-agecal-admin/src/main/resources/application-test.yml
View file @
cd00704b
...
@@ -99,4 +99,12 @@ netty:
...
@@ -99,4 +99,12 @@ netty:
boss-group-thread-count
:
1
# 主线程数
boss-group-thread-count
:
1
# 主线程数
worker-group-thread-count
:
8
# 工作线程数
worker-group-thread-count
:
8
# 工作线程数
max-frame-length
:
65535
# 最大帧长度
max-frame-length
:
65535
# 最大帧长度
heartbeat-timeout
:
60
# 心跳超时时间(秒)
heartbeat-timeout
:
10
# 心跳超时时间(秒)
\ No newline at end of file
# 机械臂UDP配置
robot
:
arm
:
udp
:
ip
:
192.168.2.16
port
:
6000
\ No newline at end of file
zhmes-agecal-common/pom.xml
View file @
cd00704b
...
@@ -130,6 +130,10 @@
...
@@ -130,6 +130,10 @@
<artifactId>
netty-all
</artifactId>
<artifactId>
netty-all
</artifactId>
<version>
4.1.86.Final
</version>
<version>
4.1.86.Final
</version>
</dependency>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-websocket
</artifactId>
</dependency>
</dependencies>
</dependencies>
</project>
</project>
\ No newline at end of file
zhmes-agecal-framework/src/main/java/com/zehong/framework/config/NettyConfig.java
View file @
cd00704b
...
@@ -35,7 +35,7 @@ public class NettyConfig {
...
@@ -35,7 +35,7 @@ public class NettyConfig {
/**
/**
* 心跳检测超时时间(秒)
* 心跳检测超时时间(秒)
*/
*/
private
int
heartbeatTimeout
=
6
0
;
private
int
heartbeatTimeout
=
1
0
;
// getter和setter方法
// getter和setter方法
public
int
getPort
()
{
public
int
getPort
()
{
...
...
zhmes-agecal-framework/src/main/java/com/zehong/framework/config/SecurityConfig.java
View file @
cd00704b
...
@@ -105,6 +105,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
...
@@ -105,6 +105,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
"/**/*.css"
,
"/**/*.css"
,
"/**/*.js"
"/**/*.js"
).
permitAll
()
).
permitAll
()
// 对于 websocket 匿名访问
.
antMatchers
(
"/ws-robot-arm"
).
permitAll
()
.
antMatchers
(
"/profile/**"
).
anonymous
()
.
antMatchers
(
"/profile/**"
).
anonymous
()
.
antMatchers
(
"/common/download**"
).
anonymous
()
.
antMatchers
(
"/common/download**"
).
anonymous
()
.
antMatchers
(
"/common/download/resource**"
).
anonymous
()
.
antMatchers
(
"/common/download/resource**"
).
anonymous
()
...
...
zhmes-agecal-framework/src/main/java/com/zehong/framework/netty/handler/NettyUdpServerHandler.java
View file @
cd00704b
package
com
.
zehong
.
framework
.
netty
.
handler
;
package
com
.
zehong
.
framework
.
netty
.
handler
;
import
com.zehong.system.service.IRobotArmCommandService
;
import
com.zehong.system.service.websocket.RobotArmWebSocketHandler
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.channel.ChannelHandler.Sharable
;
import
io.netty.channel.ChannelHandler.Sharable
;
import
io.netty.channel.ChannelHandlerContext
;
import
io.netty.channel.ChannelHandlerContext
;
...
@@ -11,6 +13,7 @@ import org.slf4j.Logger;
...
@@ -11,6 +13,7 @@ import org.slf4j.Logger;
import
org.slf4j.LoggerFactory
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Component
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.Resource
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.charset.StandardCharsets
;
import
java.text.SimpleDateFormat
;
import
java.text.SimpleDateFormat
;
import
java.util.concurrent.locks.ReentrantLock
;
import
java.util.concurrent.locks.ReentrantLock
;
...
@@ -36,6 +39,12 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
...
@@ -36,6 +39,12 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
// 线程安全锁,确保文件写入安全
// 线程安全锁,确保文件写入安全
private
final
ReentrantLock
fileLock
=
new
ReentrantLock
();
private
final
ReentrantLock
fileLock
=
new
ReentrantLock
();
@Resource
private
RobotArmWebSocketHandler
robotArmWebSocketHandler
;
// 注入WebSocket处理器
@Resource
private
IRobotArmCommandService
robotArmCommandService
;
/**
/**
* 接收UDP消息
* 接收UDP消息
*/
*/
...
@@ -68,18 +77,36 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
...
@@ -68,18 +77,36 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
}
}
// 保存消息到文件
// 保存消息到文件
saveMessageToFile
(
packet
.
sender
().
toString
(),
correctMessage
);
//
saveMessageToFile(packet.sender().toString(), correctMessage);
// 处理消息逻辑
// 处理消息逻辑
String
response
=
"服务器已收到UDP消息:"
+
correctMessage
;
String
responseSave
=
"服务器已收到UDP消息:"
+
correctMessage
;
// 处理机械臂完成消息
if
(
correctMessage
.
startsWith
(
"COMPLETE,"
))
{
String
[]
parts
=
correctMessage
.
split
(
","
);
if
(
parts
.
length
>=
3
)
{
String
trayCode
=
parts
[
1
];
String
storeyCode
=
parts
[
2
];
// 更新指令状态为已完成
robotArmCommandService
.
completeCommand
(
trayCode
,
storeyCode
);
// 发送成功响应
String
response
=
"CMD_COMPLETE_ACK"
;
byte
[]
responseBytes
=
response
.
getBytes
(
StandardCharsets
.
UTF_8
);
ctx
.
writeAndFlush
(
new
DatagramPacket
(
io
.
netty
.
buffer
.
Unpooled
.
copiedBuffer
(
responseBytes
),
packet
.
sender
()));
}
}
// 回复客户端,明确使用UTF-8编码
// 当收到消息时,更新状态为运行中
byte
[]
responseBytes
=
response
.
getBytes
(
StandardCharsets
.
UTF_8
);
sendStatusToWebSocket
(
"running"
);
ctx
.
writeAndFlush
(
new
DatagramPacket
(
io
.
netty
.
buffer
.
Unpooled
.
copiedBuffer
(
responseBytes
),
packet
.
sender
()));
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
.
error
(
"处理UDP消息异常"
,
e
);
log
.
error
(
"处理UDP消息异常"
,
e
);
// 出现异常时发送故障状态
sendStatusToWebSocket
(
"error"
);
}
}
}
}
...
@@ -164,9 +191,25 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
...
@@ -164,9 +191,25 @@ public class NettyUdpServerHandler extends SimpleChannelInboundHandler<DatagramP
if
(
event
.
state
()
==
IdleState
.
ALL_IDLE
)
{
if
(
event
.
state
()
==
IdleState
.
ALL_IDLE
)
{
log
.
info
(
"UDP服务器超过规定时间未收到数据"
);
log
.
info
(
"UDP服务器超过规定时间未收到数据"
);
// UDP无连接,一般不关闭通道
// UDP无连接,一般不关闭通道
// 通过WebSocket发送空闲状态给前端
sendStatusToWebSocket
(
"idle"
);
// 处理空闲状态
robotArmCommandService
.
processPendingCommands
();
}
}
}
else
{
}
else
{
super
.
userEventTriggered
(
ctx
,
evt
);
super
.
userEventTriggered
(
ctx
,
evt
);
}
}
}
}
/**
* 发送状态到WebSocket
*/
private
void
sendStatusToWebSocket
(
String
status
)
{
if
(
robotArmWebSocketHandler
!=
null
)
{
robotArmWebSocketHandler
.
broadcastStatus
(
status
);
}
else
{
log
.
warn
(
"WebSocket处理器未初始化,无法发送状态"
);
}
}
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/domain/RobotArmCommand.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
domain
;
import
java.util.Date
;
import
com.fasterxml.jackson.annotation.JsonFormat
;
import
org.apache.commons.lang3.builder.ToStringBuilder
;
import
org.apache.commons.lang3.builder.ToStringStyle
;
import
com.zehong.common.annotation.Excel
;
import
com.zehong.common.core.domain.BaseEntity
;
/**
* 机械臂指令对象 t_robot_arm_command
*
* @author zehong
* @date 2025-08-04
*/
public
class
RobotArmCommand
extends
BaseEntity
{
private
static
final
long
serialVersionUID
=
1L
;
/** id */
private
Long
robotArmCommandId
;
/** 托盘编号 */
@Excel
(
name
=
"托盘编号"
)
private
String
trayCode
;
/** 绑定层编号 */
@Excel
(
name
=
"绑定层编号"
)
private
String
storeyCode
;
/** 类型:0-待上料;1-待下料 */
@Excel
(
name
=
"类型:0-待上料;1-待下料"
)
private
String
type
;
/** 状态:0-待执行;1-执行中;2-执行结束(上料就是绑定托盘,下料就是解绑托盘) */
@Excel
(
name
=
"状态:0-待执行;1-执行中;2-未上电,3-执行结束(上料就是绑定托盘,下料就是解绑托盘)"
)
private
String
status
;
/** 指令开始执行时间 */
@JsonFormat
(
pattern
=
"yyyy-MM-dd"
)
@Excel
(
name
=
"指令开始执行时间"
,
width
=
30
,
dateFormat
=
"yyyy-MM-dd"
)
private
Date
startExecutionTime
;
/** 指令结束执行时间 */
@JsonFormat
(
pattern
=
"yyyy-MM-dd"
)
@Excel
(
name
=
"指令结束执行时间"
,
width
=
30
,
dateFormat
=
"yyyy-MM-dd"
)
private
Date
endExecutionTime
;
/** 指令 */
private
String
command
;
public
void
setRobotArmCommandId
(
Long
robotArmCommandId
)
{
this
.
robotArmCommandId
=
robotArmCommandId
;
}
public
Long
getRobotArmCommandId
()
{
return
robotArmCommandId
;
}
public
void
setTrayCode
(
String
trayCode
)
{
this
.
trayCode
=
trayCode
;
}
public
String
getTrayCode
()
{
return
trayCode
;
}
public
void
setStoreyCode
(
String
storeyCode
)
{
this
.
storeyCode
=
storeyCode
;
}
public
String
getStoreyCode
()
{
return
storeyCode
;
}
public
void
setType
(
String
type
)
{
this
.
type
=
type
;
}
public
String
getType
()
{
return
type
;
}
public
void
setStatus
(
String
status
)
{
this
.
status
=
status
;
}
public
String
getStatus
()
{
return
status
;
}
public
void
setStartExecutionTime
(
Date
startExecutionTime
)
{
this
.
startExecutionTime
=
startExecutionTime
;
}
public
Date
getStartExecutionTime
()
{
return
startExecutionTime
;
}
public
void
setEndExecutionTime
(
Date
endExecutionTime
)
{
this
.
endExecutionTime
=
endExecutionTime
;
}
public
Date
getEndExecutionTime
()
{
return
endExecutionTime
;
}
public
String
getCommand
()
{
return
command
;
}
public
void
setCommand
(
String
command
)
{
this
.
command
=
command
;
}
@Override
public
String
toString
()
{
return
new
ToStringBuilder
(
this
,
ToStringStyle
.
MULTI_LINE_STYLE
)
.
append
(
"robotArmCommandId"
,
getRobotArmCommandId
())
.
append
(
"trayCode"
,
getTrayCode
())
.
append
(
"storeyCode"
,
getStoreyCode
())
.
append
(
"type"
,
getType
())
.
append
(
"status"
,
getStatus
())
.
append
(
"startExecutionTime"
,
getStartExecutionTime
())
.
append
(
"endExecutionTime"
,
getEndExecutionTime
())
.
append
(
"createTime"
,
getCreateTime
())
.
toString
();
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/mapper/RobotArmCommandMapper.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
mapper
;
import
java.util.List
;
import
com.zehong.system.domain.RobotArmCommand
;
/**
* 机械臂指令Mapper接口
*
* @author zehong
* @date 2025-08-04
*/
public
interface
RobotArmCommandMapper
{
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
public
RobotArmCommand
selectRobotArmCommandById
(
Long
robotArmCommandId
);
public
RobotArmCommand
findExecutingCommand
(
String
trayCode
,
String
storeyCode
);
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令集合
*/
public
List
<
RobotArmCommand
>
selectRobotArmCommandList
(
RobotArmCommand
robotArmCommand
);
public
List
<
RobotArmCommand
>
findByType
(
String
type
);
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public
int
insertRobotArmCommand
(
RobotArmCommand
robotArmCommand
);
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public
int
updateRobotArmCommand
(
RobotArmCommand
robotArmCommand
);
/**
* 更新执行中状态为完成状态
*/
int
updateExecutingToCompleted
();
/**
* 获取待执行的上料指令
*/
List
<
RobotArmCommand
>
selectPendingLoadingCommands
();
/**
* 获取待执行的下料指令
*/
List
<
RobotArmCommand
>
selectPendingUnloadingCommands
();
/**
* 删除机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
public
int
deleteRobotArmCommandById
(
Long
robotArmCommandId
);
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的数据ID
* @return 结果
*/
public
int
deleteRobotArmCommandByIds
(
Long
[]
robotArmCommandIds
);
}
zhmes-agecal-system/src/main/java/com/zehong/system/mapper/TStoreyInfoMapper.java
View file @
cd00704b
...
@@ -27,6 +27,8 @@ public interface TStoreyInfoMapper
...
@@ -27,6 +27,8 @@ public interface TStoreyInfoMapper
*/
*/
public
TStoreyInfo
selectTStoreyInfoByCode
(
String
fStoreyCode
);
public
TStoreyInfo
selectTStoreyInfoByCode
(
String
fStoreyCode
);
// 新增方法:查询离机械臂最近的空闲层
public
TStoreyInfo
selectNearestFreeStorey
();
/**
/**
* 查询老化层信息列表
* 查询老化层信息列表
*
*
...
...
zhmes-agecal-system/src/main/java/com/zehong/system/service/IRobotArmCommandService.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
service
;
import
java.util.List
;
import
com.zehong.system.domain.RobotArmCommand
;
/**
* 机械臂指令Service接口
*
* @author zehong
* @date 2025-08-04
*/
public
interface
IRobotArmCommandService
{
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
public
RobotArmCommand
selectRobotArmCommandById
(
Long
robotArmCommandId
);
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令集合
*/
public
List
<
RobotArmCommand
>
selectRobotArmCommandList
(
RobotArmCommand
robotArmCommand
);
public
List
<
RobotArmCommand
>
findByType
(
String
type
);
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public
int
insertRobotArmCommand
(
RobotArmCommand
robotArmCommand
);
public
void
powerOnCommand
(
Long
commandId
);
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
public
int
updateRobotArmCommand
(
RobotArmCommand
robotArmCommand
);
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的机械臂指令ID
* @return 结果
*/
public
int
deleteRobotArmCommandByIds
(
Long
[]
robotArmCommandIds
);
/**
* 删除机械臂指令信息
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
public
int
deleteRobotArmCommandById
(
Long
robotArmCommandId
);
public
void
processIdleState
();
public
void
processPendingCommands
();
public
void
completeCommand
(
String
trayCode
,
String
storeyCode
);
}
zhmes-agecal-system/src/main/java/com/zehong/system/service/impl/RobotArmCommandServiceImpl.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
service
.
impl
;
import
java.util.Date
;
import
java.util.List
;
import
com.zehong.common.utils.DateUtils
;
import
com.zehong.system.domain.TStoreyInfo
;
import
com.zehong.system.mapper.TStoreyInfoMapper
;
import
com.zehong.system.service.websocket.RobotArmWebSocketHandler
;
import
com.zehong.system.udp.UdpCommandSender
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Service
;
import
com.zehong.system.mapper.RobotArmCommandMapper
;
import
com.zehong.system.domain.RobotArmCommand
;
import
com.zehong.system.service.IRobotArmCommandService
;
import
org.springframework.transaction.annotation.Transactional
;
import
javax.annotation.Resource
;
/**
* 机械臂指令Service业务层处理
*
* @author zehong
* @date 2025-08-04
*/
@Service
public
class
RobotArmCommandServiceImpl
implements
IRobotArmCommandService
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
RobotArmCommandServiceImpl
.
class
);
@Resource
private
RobotArmWebSocketHandler
robotArmWebSocketHandler
;
@Resource
private
RobotArmCommandMapper
robotArmCommandMapper
;
@Resource
private
TStoreyInfoMapper
storeyInfoMapper
;
@Resource
private
UdpCommandSender
udpCommandSender
;
@Override
@Transactional
public
void
processIdleState
()
{
// 1. 更新执行中状态为完成状态
robotArmCommandMapper
.
updateExecutingToCompleted
();
// 2. 优先处理上料指令
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
if
(!
loadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
loadingCommands
.
get
(
0
);
sendLoadingCommand
(
command
);
return
;
}
// 3. 处理下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
if
(!
unloadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
unloadingCommands
.
get
(
0
);
sendUnloadingCommand
(
command
);
}
}
@Override
@Transactional
public
void
processPendingCommands
()
{
// 1. 只处理待执行指令(状态为0)
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
if
(!
loadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
loadingCommands
.
get
(
0
);
sendLoadingCommand
(
command
);
return
;
}
// 2. 处理待执行的下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
if
(!
unloadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
unloadingCommands
.
get
(
0
);
sendUnloadingCommand
(
command
);
}
}
@Override
@Transactional
public
void
completeCommand
(
String
trayCode
,
String
storeyCode
)
{
// 1. 查找对应的执行中指令
RobotArmCommand
command
=
robotArmCommandMapper
.
findExecutingCommand
(
trayCode
,
storeyCode
);
if
(
command
==
null
)
{
log
.
warn
(
"未找到对应的执行中指令: {} @ {}"
,
trayCode
,
storeyCode
);
return
;
}
// 2. 更新状态为已完成
command
.
setStatus
(
"3"
);
command
.
setEndExecutionTime
(
new
Date
());
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
log
.
info
(
"指令完成: {} @ {}"
,
trayCode
,
storeyCode
);
// 3. 广播指令更新
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
}
private
void
sendLoadingCommand
(
RobotArmCommand
command
)
{
// 更新状态为执行中
command
.
setStatus
(
"1"
);
command
.
setStartExecutionTime
(
new
Date
());
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
notifyCommandsUpdate
();
// 发送UDP指令
String
udpMessage
=
String
.
format
(
"LOAD,%s,%s"
,
command
.
getTrayCode
(),
command
.
getStoreyCode
());
udpCommandSender
.
sendCommand
(
udpMessage
);
}
private
void
sendUnloadingCommand
(
RobotArmCommand
command
)
{
// 更新状态为执行中
command
.
setStatus
(
"1"
);
command
.
setStartExecutionTime
(
new
Date
());
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
notifyCommandsUpdate
();
// 发送UDP指令
String
udpMessage
=
String
.
format
(
"UNLOAD,%s,%s"
,
command
.
getTrayCode
(),
command
.
getStoreyCode
());
udpCommandSender
.
sendCommand
(
udpMessage
);
}
/**
* 查询机械臂指令
*
* @param robotArmCommandId 机械臂指令ID
* @return 机械臂指令
*/
@Override
public
RobotArmCommand
selectRobotArmCommandById
(
Long
robotArmCommandId
)
{
return
robotArmCommandMapper
.
selectRobotArmCommandById
(
robotArmCommandId
);
}
/**
* 查询机械臂指令列表
*
* @param robotArmCommand 机械臂指令
* @return 机械臂指令
*/
@Override
public
List
<
RobotArmCommand
>
selectRobotArmCommandList
(
RobotArmCommand
robotArmCommand
)
{
return
robotArmCommandMapper
.
selectRobotArmCommandList
(
robotArmCommand
);
}
@Override
public
List
<
RobotArmCommand
>
findByType
(
String
type
)
{
return
robotArmCommandMapper
.
findByType
(
type
);
}
/**
* 新增机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
@Override
public
int
insertRobotArmCommand
(
RobotArmCommand
robotArmCommand
)
{
robotArmCommand
.
setCreateTime
(
DateUtils
.
getNowDate
());
TStoreyInfo
tStoreyInfo
=
storeyInfoMapper
.
selectNearestFreeStorey
();
if
(
tStoreyInfo
!=
null
)
{
robotArmCommand
.
setStoreyCode
(
tStoreyInfo
.
getfStoreyCode
());
}
else
{
robotArmCommand
.
setStoreyCode
(
"无空闲老化层"
);
}
int
i
=
robotArmCommandMapper
.
insertRobotArmCommand
(
robotArmCommand
);
notifyCommandsUpdate
();
return
i
;
}
@Override
@Transactional
public
void
powerOnCommand
(
Long
commandId
)
{
RobotArmCommand
command
=
robotArmCommandMapper
.
selectRobotArmCommandById
(
commandId
);
if
(
command
==
null
)
{
throw
new
RuntimeException
(
"指令不存在"
);
}
if
(!
"2"
.
equals
(
command
.
getStatus
()))
{
throw
new
RuntimeException
(
"只有未上电状态的指令才能执行上电操作"
);
}
// 更新状态为执行中
command
.
setStatus
(
"1"
);
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
// 发送上电指令给机械臂
String
udpMessage
=
String
.
format
(
"POWER_ON,%s,%s"
,
command
.
getTrayCode
(),
command
.
getStoreyCode
());
udpCommandSender
.
sendCommand
(
udpMessage
);
// 记录操作日志
log
.
info
(
"执行上电操作: 指令ID={}, 托盘={}, 位置={}"
,
commandId
,
command
.
getTrayCode
(),
command
.
getStoreyCode
());
}
/**
* 修改机械臂指令
*
* @param robotArmCommand 机械臂指令
* @return 结果
*/
@Override
public
int
updateRobotArmCommand
(
RobotArmCommand
robotArmCommand
)
{
int
i
=
robotArmCommandMapper
.
updateRobotArmCommand
(
robotArmCommand
);
notifyCommandsUpdate
();
return
i
;
}
/**
* 批量删除机械臂指令
*
* @param robotArmCommandIds 需要删除的机械臂指令ID
* @return 结果
*/
@Override
public
int
deleteRobotArmCommandByIds
(
Long
[]
robotArmCommandIds
)
{
int
i
=
robotArmCommandMapper
.
deleteRobotArmCommandByIds
(
robotArmCommandIds
);
notifyCommandsUpdate
();
return
i
;
}
/**
* 删除机械臂指令信息
*
* @param robotArmCommandId 机械臂指令ID
* @return 结果
*/
@Override
public
int
deleteRobotArmCommandById
(
Long
robotArmCommandId
)
{
int
i
=
robotArmCommandMapper
.
deleteRobotArmCommandById
(
robotArmCommandId
);
notifyCommandsUpdate
();
return
i
;
}
private
void
notifyCommandsUpdate
()
{
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/service/websocket/RobotArmWebSocketHandler.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
service
.
websocket
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zehong.system.domain.RobotArmCommand
;
import
com.zehong.system.service.IRobotArmCommandService
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.socket.CloseStatus
;
import
org.springframework.web.socket.TextMessage
;
import
org.springframework.web.socket.WebSocketSession
;
import
org.springframework.web.socket.handler.TextWebSocketHandler
;
import
javax.annotation.Resource
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.CopyOnWriteArrayList
;
/**
* @author lenovo
* @date 2025/8/4
* @description websocketHandler
*/
@Component
public
class
RobotArmWebSocketHandler
extends
TextWebSocketHandler
{
private
static
final
List
<
WebSocketSession
>
sessions
=
new
CopyOnWriteArrayList
<>();
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
RobotArmWebSocketHandler
.
class
);
@Resource
private
IRobotArmCommandService
robotArmCommandService
;
@Override
public
void
afterConnectionEstablished
(
WebSocketSession
session
)
throws
Exception
{
sessions
.
add
(
session
);
sendInitialData
(
session
);
}
@Override
protected
void
handleTextMessage
(
WebSocketSession
session
,
TextMessage
message
)
throws
Exception
{
String
payload
=
message
.
getPayload
();
// 处理客户端请求
if
(
"{\"type\":\"request\",\"commands\":[\"loading\",\"unloading\"]}"
.
equals
(
payload
))
{
sendInitialData
(
session
);
}
}
private
void
sendInitialData
(
WebSocketSession
session
)
throws
IOException
{
// 发送待上料指令
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandService
.
findByType
(
"0"
);
session
.
sendMessage
(
new
TextMessage
(
createMessage
(
"loading"
,
loadingCommands
)));
// 发送待下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandService
.
findByType
(
"1"
);
session
.
sendMessage
(
new
TextMessage
(
createMessage
(
"unloading"
,
unloadingCommands
)));
}
private
String
createMessage
(
String
type
,
List
<
RobotArmCommand
>
commands
)
{
ObjectMapper
mapper
=
new
ObjectMapper
();
try
{
// 创建包含更多信息的DTO列表
List
<
Map
<
String
,
Object
>>
commandData
=
new
ArrayList
<>();
for
(
RobotArmCommand
cmd
:
commands
)
{
Map
<
String
,
Object
>
cmdMap
=
new
HashMap
<>();
cmdMap
.
put
(
"robotArmCommandId"
,
cmd
.
getRobotArmCommandId
());
cmdMap
.
put
(
"trayCode"
,
cmd
.
getTrayCode
());
cmdMap
.
put
(
"storeyCode"
,
cmd
.
getStoreyCode
());
cmdMap
.
put
(
"status"
,
cmd
.
getStatus
());
commandData
.
add
(
cmdMap
);
}
Map
<
String
,
Object
>
message
=
new
HashMap
<>();
message
.
put
(
"type"
,
type
);
message
.
put
(
"data"
,
commandData
);
return
mapper
.
writeValueAsString
(
message
);
}
catch
(
JsonProcessingException
e
)
{
return
"{\"error\":\"Failed to serialize data\"}"
;
}
}
public
void
broadcastCommandUpdate
()
{
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandService
.
findByType
(
"0"
);
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandService
.
findByType
(
"1"
);
for
(
WebSocketSession
session
:
sessions
)
{
if
(
session
.
isOpen
())
{
try
{
session
.
sendMessage
(
new
TextMessage
(
createMessage
(
"loading"
,
loadingCommands
)));
session
.
sendMessage
(
new
TextMessage
(
createMessage
(
"unloading"
,
unloadingCommands
)));
}
catch
(
IOException
e
)
{
// 处理异常
}
}
}
}
@Override
public
void
afterConnectionClosed
(
WebSocketSession
session
,
CloseStatus
status
)
{
sessions
.
remove
(
session
);
}
/**
* 广播状态消息给所有客户端
*/
public
void
broadcastStatus
(
String
status
)
{
ObjectMapper
mapper
=
new
ObjectMapper
();
try
{
Map
<
String
,
Object
>
message
=
new
HashMap
<>();
message
.
put
(
"type"
,
"status"
);
message
.
put
(
"data"
,
status
);
String
jsonMessage
=
mapper
.
writeValueAsString
(
message
);
for
(
WebSocketSession
session
:
sessions
)
{
if
(
session
.
isOpen
())
{
try
{
session
.
sendMessage
(
new
TextMessage
(
jsonMessage
));
}
catch
(
IOException
e
)
{
log
.
error
(
"发送状态消息到WebSocket失败"
,
e
);
}
}
}
}
catch
(
JsonProcessingException
e
)
{
log
.
error
(
"序列化状态消息失败"
,
e
);
}
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/service/websocket/WebSocketConfig.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
service
.
websocket
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.web.socket.config.annotation.EnableWebSocket
;
import
org.springframework.web.socket.config.annotation.WebSocketConfigurer
;
import
org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
;
/**
* @author lenovo
* @date 2025/8/4
* @description TODO
*/
@Configuration
@EnableWebSocket
public
class
WebSocketConfig
implements
WebSocketConfigurer
{
@Autowired
private
RobotArmWebSocketHandler
robotArmWebSocketHandler
;
@Override
public
void
registerWebSocketHandlers
(
WebSocketHandlerRegistry
registry
)
{
registry
.
addHandler
(
robotArmWebSocketHandler
,
"/ws-robot-arm"
)
.
setAllowedOrigins
(
"*"
);
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/udp/UdpCommandSender.java
0 → 100644
View file @
cd00704b
package
com
.
zehong
.
system
.
udp
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
javax.annotation.PreDestroy
;
import
java.io.IOException
;
import
java.net.DatagramPacket
;
import
java.net.DatagramSocket
;
import
java.net.InetAddress
;
import
java.nio.charset.StandardCharsets
;
/**
* @author lenovo
* @date 2025/8/4
* @description TODO
*/
@Component
public
class
UdpCommandSender
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
UdpCommandSender
.
class
);
@Value
(
"${robot.arm.udp.ip}"
)
private
String
robotArmIp
;
@Value
(
"${robot.arm.udp.port}"
)
private
int
robotArmPort
;
private
DatagramSocket
socket
;
private
InetAddress
address
;
@PostConstruct
public
void
init
()
{
try
{
socket
=
new
DatagramSocket
();
address
=
InetAddress
.
getByName
(
robotArmIp
);
log
.
info
(
"UDP命令发送器初始化成功,目标地址: {}:{}"
,
robotArmIp
,
robotArmPort
);
}
catch
(
Exception
e
)
{
log
.
error
(
"UDP命令发送器初始化失败"
,
e
);
}
}
public
void
sendCommand
(
String
message
)
{
if
(
socket
==
null
||
address
==
null
)
{
log
.
error
(
"UDP命令发送器未初始化,无法发送消息"
);
return
;
}
try
{
byte
[]
buffer
=
message
.
getBytes
(
StandardCharsets
.
UTF_8
);
DatagramPacket
packet
=
new
DatagramPacket
(
buffer
,
buffer
.
length
,
address
,
robotArmPort
);
socket
.
send
(
packet
);
log
.
info
(
"已发送UDP指令: {}"
,
message
);
}
catch
(
IOException
e
)
{
log
.
error
(
"发送UDP指令失败: {}"
,
message
,
e
);
}
}
@PreDestroy
public
void
cleanup
()
{
if
(
socket
!=
null
&&
!
socket
.
isClosed
())
{
socket
.
close
();
log
.
info
(
"UDP命令发送器已关闭"
);
}
}
}
zhmes-agecal-system/src/main/resources/mapper/system/RobotArmCommandMapper.xml
0 → 100644
View file @
cd00704b
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace=
"com.zehong.system.mapper.RobotArmCommandMapper"
>
<resultMap
type=
"RobotArmCommand"
id=
"RobotArmCommandResult"
>
<result
property=
"robotArmCommandId"
column=
"f_robot_arm_command_id"
/>
<result
property=
"trayCode"
column=
"f_tray_code"
/>
<result
property=
"storeyCode"
column=
"f_storey_code"
/>
<result
property=
"type"
column=
"f_type"
/>
<result
property=
"status"
column=
"f_status"
/>
<result
property=
"startExecutionTime"
column=
"f_start_execution_time"
/>
<result
property=
"endExecutionTime"
column=
"f_end_execution_time"
/>
<result
property=
"createTime"
column=
"f_create_time"
/>
</resultMap>
<sql
id=
"selectRobotArmCommandVo"
>
select f_robot_arm_command_id, f_tray_code, f_storey_code, f_type, f_status, f_start_execution_time, f_end_execution_time, f_create_time, f_command from t_robot_arm_command
</sql>
<select
id=
"findByType"
parameterType=
"string"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
where f_type = #{type}
</select>
<select
id=
"selectRobotArmCommandList"
parameterType=
"RobotArmCommand"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
<where>
<if
test=
"trayCode != null and trayCode != ''"
>
and f_tray_code = #{trayCode}
</if>
<if
test=
"storeyCode != null and storeyCode != ''"
>
and f_storey_code = #{storeyCode}
</if>
<if
test=
"type != null and type != ''"
>
and f_type = #{type}
</if>
<if
test=
"status != null and status != ''"
>
and f_status = #{status}
</if>
<if
test=
"startExecutionTime != null "
>
and f_start_execution_time = #{startExecutionTime}
</if>
<if
test=
"endExecutionTime != null "
>
and f_end_execution_time = #{endExecutionTime}
</if>
<if
test=
"createTime != null "
>
and f_create_time = #{createTime}
</if>
</where>
</select>
<select
id=
"selectRobotArmCommandById"
parameterType=
"Long"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
where f_robot_arm_command_id = #{robotArmCommandId}
</select>
<!-- 查找执行中的指令 -->
<select
id=
"findExecutingCommand"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
WHERE f_tray_code = #{trayCode}
AND f_storey_code = #{storeyCode}
AND f_status = '1'
<!-- 状态为执行中 -->
LIMIT 1
</select>
<insert
id=
"insertRobotArmCommand"
parameterType=
"RobotArmCommand"
useGeneratedKeys=
"true"
keyProperty=
"robotArmCommandId"
>
insert into t_robot_arm_command
<trim
prefix=
"("
suffix=
")"
suffixOverrides=
","
>
<if
test=
"trayCode != null"
>
f_tray_code,
</if>
<if
test=
"storeyCode != null"
>
f_storey_code,
</if>
<if
test=
"type != null"
>
f_type,
</if>
<if
test=
"status != null"
>
f_status,
</if>
<if
test=
"startExecutionTime != null"
>
f_start_execution_time,
</if>
<if
test=
"endExecutionTime != null"
>
f_end_execution_time,
</if>
<if
test=
"createTime != null"
>
f_create_time,
</if>
<if
test=
"command != null"
>
f_command,
</if>
</trim>
<trim
prefix=
"values ("
suffix=
")"
suffixOverrides=
","
>
<if
test=
"trayCode != null"
>
#{trayCode},
</if>
<if
test=
"storeyCode != null"
>
#{storeyCode},
</if>
<if
test=
"type != null"
>
#{type},
</if>
<if
test=
"status != null"
>
#{status},
</if>
<if
test=
"startExecutionTime != null"
>
#{startExecutionTime},
</if>
<if
test=
"endExecutionTime != null"
>
#{endExecutionTime},
</if>
<if
test=
"createTime != null"
>
#{createTime},
</if>
<if
test=
"command != null"
>
#{command},
</if>
</trim>
</insert>
<update
id=
"updateRobotArmCommand"
parameterType=
"RobotArmCommand"
>
update t_robot_arm_command
<trim
prefix=
"SET"
suffixOverrides=
","
>
<if
test=
"trayCode != null"
>
f_tray_code = #{trayCode},
</if>
<if
test=
"storeyCode != null"
>
f_storey_code = #{storeyCode},
</if>
<if
test=
"type != null"
>
f_type = #{type},
</if>
<if
test=
"status != null"
>
f_status = #{status},
</if>
<if
test=
"startExecutionTime != null"
>
f_start_execution_time = #{startExecutionTime},
</if>
<if
test=
"endExecutionTime != null"
>
f_end_execution_time = #{endExecutionTime},
</if>
<if
test=
"createTime != null"
>
f_create_time = #{createTime},
</if>
<if
test=
"command != null"
>
f_command = #{command},
</if>
</trim>
where f_robot_arm_command_id = #{robotArmCommandId}
</update>
<!-- 在 XML 中添加 -->
<!-- 更新执行中状态为完成状态 -->
<update
id=
"updateExecutingToCompleted"
>
UPDATE t_robot_arm_command
SET f_status = '2'
WHERE f_status = '1' and f_end_execution_time IS NOT NULL
</update>
<!-- 获取待执行的上料指令 -->
<select
id=
"selectPendingLoadingCommands"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
WHERE f_type = '0' AND f_status = '0'
ORDER BY f_create_time ASC
LIMIT 1
</select>
<!-- 获取待执行的下料指令 -->
<select
id=
"selectPendingUnloadingCommands"
resultMap=
"RobotArmCommandResult"
>
<include
refid=
"selectRobotArmCommandVo"
/>
WHERE f_type = '1' AND f_status = '0'
ORDER BY f_create_time ASC
LIMIT 1
</select>
<delete
id=
"deleteRobotArmCommandById"
parameterType=
"Long"
>
delete from t_robot_arm_command where f_robot_arm_command_id = #{robotArmCommandId}
</delete>
<delete
id=
"deleteRobotArmCommandByIds"
parameterType=
"String"
>
delete from t_robot_arm_command where f_robot_arm_command_id in
<foreach
item=
"robotArmCommandId"
collection=
"array"
open=
"("
separator=
","
close=
")"
>
#{robotArmCommandId}
</foreach>
</delete>
</mapper>
\ No newline at end of file
zhmes-agecal-system/src/main/resources/mapper/system/TStoreyInfoMapper.xml
View file @
cd00704b
...
@@ -48,7 +48,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
...
@@ -48,7 +48,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include
refid=
"selectTStoreyInfoVo"
/>
<include
refid=
"selectTStoreyInfoVo"
/>
where f_storey_code = #{fStoreyCode}
where f_storey_code = #{fStoreyCode}
</select>
</select>
<select
id=
"selectNearestFreeStorey"
resultMap=
"TStoreyInfoResult"
>
SELECT
f_storey_id, f_equipment_id, f_storey_code, f_ip, f_status, f_port,
f_aging_start_time, f_update_time, f_create_time, f_alarm_time
FROM (
SELECT
*,
CASE
WHEN cabinet_num BETWEEN 1 AND 18 THEN (cabinet_num - 1) * 10 + layer_num
WHEN cabinet_num BETWEEN 19 AND 36 THEN (cabinet_num - 19) * 10 + layer_num
END AS distance
FROM (
SELECT
*,
CAST(SUBSTRING_INDEX(f_storey_code, '-', 1) AS UNSIGNED) AS cabinet_num,
CAST(SUBSTRING_INDEX(f_storey_code, '-', -1) AS UNSIGNED) AS layer_num
FROM t_storey_info
WHERE f_status = '0' -- 空闲状态
) AS parsed
) AS calculated
ORDER BY distance ASC
LIMIT 1
</select>
<insert
id=
"insertTStoreyInfo"
parameterType=
"TStoreyInfo"
>
<insert
id=
"insertTStoreyInfo"
parameterType=
"TStoreyInfo"
>
insert into t_storey_info
insert into t_storey_info
<trim
prefix=
"("
suffix=
")"
suffixOverrides=
","
>
<trim
prefix=
"("
suffix=
")"
suffixOverrides=
","
>
...
...
zhmes-agecal-web/src/api/robotArm/robotArmCommand.js
0 → 100644
View file @
cd00704b
import
request
from
'@/utils/request'
// 查询机械臂指令列表
export
function
listCommand
(
query
)
{
return
request
({
url
:
'/robotArm/command/list'
,
method
:
'get'
,
params
:
query
})
}
// 查询机械臂指令详细
export
function
getCommand
(
robotArmCommandId
)
{
return
request
({
url
:
'/robotArm/command/'
+
robotArmCommandId
,
method
:
'get'
})
}
// 新增机械臂指令
export
function
addCommand
(
data
)
{
return
request
({
url
:
'/robotArm/command'
,
method
:
'post'
,
data
:
data
})
}
// 执行上电操作
export
function
powerOnCommand
(
commandId
)
{
return
request
({
url
:
'/robotArm/command/powerOn'
,
method
:
'post'
,
data
:
{
commandId
}
})
}
// 修改机械臂指令
export
function
updateCommand
(
data
)
{
return
request
({
url
:
'/robotArm/command'
,
method
:
'put'
,
data
:
data
})
}
// 删除机械臂指令
export
function
delCommand
(
robotArmCommandId
)
{
return
request
({
url
:
'/robotArm/command/'
+
robotArmCommandId
,
method
:
'delete'
})
}
// 导出机械臂指令
export
function
exportCommand
(
query
)
{
return
request
({
url
:
'/robotArm/command/export'
,
method
:
'get'
,
params
:
query
})
}
zhmes-agecal-web/src/views/screen/components/RoboticArm.vue
View file @
cd00704b
<
template
>
<
template
>
<div
class=
"robotic-arm-panel"
>
<div
class=
"robotic-arm-panel"
>
<!-- 左上角标题 -->
<!-- 标题区域 -->
<!-- 标题区域 -->
<div
class=
"panel-title"
>
<div
class=
"panel-title"
>
<div
class=
"title-text"
>
机械臂
</div>
<!-- 左侧标题+状态指示灯组合 -->
<div
class=
"title-line"
></div>
<div
class=
"title-with-status"
>
<div
class=
"board-header"
></div>
<div
class=
"title-left"
>
<div
class=
"title-text"
>
机械臂
</div>
<div
class=
"title-line"
></div>
</div>
<!-- 状态指示灯:紧挨着标题文字右侧 -->
<div
class=
"status-indicator"
>
<div
class=
"status-light"
:class=
"statusClass"
></div>
<div
class=
"status-text"
>
{{
statusText
}}
</div>
</div>
</div>
<!-- 上料按钮 -->
<div
class=
"title-right"
>
<button
class=
"add-button"
@
click=
"showAddDialog = true"
>
<i
class=
"el-icon-plus"
></i>
上料
</button>
</div>
</div>
</div>
<!-- 状态指示灯 -->
<!-- 扫码对话框 -->
<div
class=
"status-indicator"
>
<div
class=
"dialog-mask"
v-if=
"showAddDialog"
@
click
.
self=
"closeDialog"
>
<div
class=
"status-light"
:class=
"statusClass"
></div>
<div
class=
"dialog-container"
>
<div
class=
"status-text"
>
{{
statusText
}}
</div>
<div
class=
"dialog-header"
>
上料操作
</div>
<div
class=
"dialog-body"
>
<div
class=
"dialog-content"
>
<div
class=
"scan-prompt"
>
请扫描托盘二维码
</div>
<div
class=
"scan-input"
>
<input
type=
"text"
v-model=
"trayCode"
placeholder=
"手动输入或扫码"
ref=
"trayInput"
@
keyup
.
enter=
"confirmAdd"
>
</div>
</div>
</div>
<div
class=
"dialog-footer"
>
<button
class=
"cancel-button"
@
click=
"closeDialog"
>
取消
</button>
<button
class=
"confirm-button"
@
click=
"confirmAdd"
>
确定
</button>
</div>
</div>
</div>
</div>
<!-- 主内容区:指令区+机械臂 -->
<!-- 主内容区:指令区+机械臂 -->
...
@@ -21,10 +55,17 @@
...
@@ -21,10 +55,17 @@
<div
class=
"loading-command"
>
<div
class=
"loading-command"
>
<div
class=
"command-title"
>
待上料指令
</div>
<div
class=
"command-title"
>
待上料指令
</div>
<div
class=
"command-list"
>
<div
class=
"command-list"
>
<div
v-for=
"(cmd, index) in loadingCommands"
:key=
"index"
class=
"command-item"
>
<div
v-for=
"(cmd, index) in loadingCommands"
:key=
"index"
class=
"command-item"
:class=
"getCommandStatusClass(cmd.status)"
@
click=
"handleCommandClick(cmd)"
>
<div
class=
"cmd-info"
>
<div
class=
"cmd-info"
>
<div
class=
"cmd-tray"
>
托盘:
{{
cmd
.
tray
}}
</div>
<div
class=
"cmd-tray"
>
托盘:
{{
cmd
.
tray
Code
}}
</div>
<div
class=
"cmd-position"
>
位置:
{{
cmd
.
position
}}
</div>
<div
class=
"cmd-position"
>
位置:
{{
cmd
.
position
}}
</div>
<div
class=
"cmd-status"
>
状态:
{{
getStatusText
(
cmd
.
status
)
}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -34,33 +75,23 @@
...
@@ -34,33 +75,23 @@
<div
class=
"arm-center-wrapper"
>
<div
class=
"arm-center-wrapper"
>
<div
class=
"robotic-arm-container"
>
<div
class=
"robotic-arm-container"
>
<div
class=
"robotic-arm"
>
<div
class=
"robotic-arm"
>
<!-- 机械臂
底座
-->
<!-- 机械臂
各部件
-->
<div
class=
"arm-base"
>
<div
class=
"arm-base"
>
<div
class=
"base-top"
></div>
<div
class=
"base-top"
></div>
<div
class=
"base-bottom"
></div>
<div
class=
"base-bottom"
></div>
</div>
</div>
<!-- 机械臂关节1 -->
<div
class=
"arm-joint joint-1"
>
<div
class=
"arm-joint joint-1"
>
<div
class=
"joint-body"
></div>
<div
class=
"joint-body"
></div>
</div>
</div>
<!-- 机械臂臂1 -->
<div
class=
"arm-segment segment-1"
>
<div
class=
"arm-segment segment-1"
>
<div
class=
"segment-body"
></div>
<div
class=
"segment-body"
></div>
</div>
</div>
<!-- 机械臂关节2 -->
<div
class=
"arm-joint joint-2"
>
<div
class=
"arm-joint joint-2"
>
<div
class=
"joint-body"
></div>
<div
class=
"joint-body"
></div>
</div>
</div>
<!-- 机械臂臂2 -->
<div
class=
"arm-segment segment-2"
>
<div
class=
"arm-segment segment-2"
>
<div
class=
"segment-body"
></div>
<div
class=
"segment-body"
></div>
</div>
</div>
<!-- 机械臂夹具 -->
<div
class=
"arm-gripper"
>
<div
class=
"arm-gripper"
>
<div
class=
"gripper-left"
></div>
<div
class=
"gripper-left"
></div>
<div
class=
"gripper-right"
></div>
<div
class=
"gripper-right"
></div>
...
@@ -73,53 +104,90 @@
...
@@ -73,53 +104,90 @@
<div
class=
"unloading-command"
>
<div
class=
"unloading-command"
>
<div
class=
"command-title"
>
待下料指令
</div>
<div
class=
"command-title"
>
待下料指令
</div>
<div
class=
"command-list"
>
<div
class=
"command-list"
>
<div
v-for=
"(cmd, index) in unloadingCommands"
:key=
"index"
class=
"command-item"
>
<div
v-for=
"(cmd, index) in unloadingCommands"
:key=
"index"
class=
"command-item"
:class=
"getCommandStatusClass(cmd.status)"
@
click=
"handleCommandClick(cmd)"
>
<div
class=
"cmd-info"
>
<div
class=
"cmd-info"
>
<div
class=
"cmd-tray"
>
托盘:
{{
cmd
.
tray
}}
</div>
<div
class=
"cmd-tray"
>
托盘:
{{
cmd
.
tray
Code
}}
</div>
<div
class=
"cmd-position"
>
位置:
{{
cmd
.
position
}}
</div>
<div
class=
"cmd-position"
>
位置:
{{
cmd
.
position
}}
</div>
<div
class=
"cmd-status"
>
状态:
{{
getStatusText
(
cmd
.
status
)
}}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 上电对话框 -->
<div
class=
"dialog-mask"
v-if=
"showPowerOnDialog"
@
click
.
self=
"closePowerOnDialog"
>
<div
class=
"dialog-container"
>
<div
class=
"dialog-header"
>
上电操作
</div>
<div
class=
"dialog-body"
>
<div
class=
"dialog-content"
>
<div
class=
"scan-prompt"
>
确认对以下托盘执行上电操作?
</div>
<div
class=
"power-on-info"
>
<div><span
class=
"label"
>
托盘编号:
</span>
{{
selectedCommand
.
trayCode
}}
</div>
<div><span
class=
"label"
>
位置:
</span>
{{
selectedCommand
.
position
}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"dialog-footer"
>
<button
class=
"cancel-button"
@
click=
"closePowerOnDialog"
>
取消
</button>
<button
class=
"confirm-button"
@
click=
"confirmPowerOn"
>
确认上电
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
{
addCommand
,
powerOnCommand
}
from
"@/api/robotArm/robotArmCommand"
export
default
{
export
default
{
name
:
'RoboticArm'
,
name
:
'RoboticArm'
,
data
()
{
data
()
{
return
{
return
{
status
:
'idle'
,
// idle, running, error
status
:
'idle'
,
// idle, running, error
showAddDialog
:
false
,
trayCode
:
''
,
loadingCommands
:
[
loadingCommands
:
[
{
tray
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
Code
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
Code
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
Code
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
Code
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
Code
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
Code
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
Code
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
Code
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
Code
:
'TP-10067'
,
position
:
'C区-5号架'
},
{
tray
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
Code
:
'TP-10023'
,
position
:
'A区-3号架'
},
{
tray
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
Code
:
'TP-10045'
,
position
:
'B区-1号架'
},
{
tray
:
'TP-10067'
,
position
:
'C区-5号架'
}
{
tray
Code
:
'TP-10067'
,
position
:
'C区-5号架'
}
],
],
unloadingCommands
:
[
unloadingCommands
:
[
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
},
{
tray
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
trayCode
:
'TP-10089'
,
position
:
'老化区-1号柜'
},
{
tray
:
'TP-10101'
,
position
:
'老化区-3号柜'
}
{
trayCode
:
'TP-10101'
,
position
:
'老化区-3号柜'
}
]
],
websocket
:
null
,
reconnectInterval
:
null
,
showPowerOnDialog
:
false
,
selectedCommand
:
null
};
};
},
},
computed
:
{
computed
:
{
...
@@ -139,35 +207,233 @@ export default {
...
@@ -139,35 +207,233 @@ export default {
}
}
},
},
mounted
()
{
mounted
()
{
this
.
simulateStatus
();
this
.
initWebSocket
();
},
beforeDestroy
()
{
this
.
disconnectWebSocket
();
},
watch
:
{
showAddDialog
(
val
)
{
if
(
val
)
{
this
.
$nextTick
(()
=>
{
this
.
$refs
.
trayInput
.
focus
();
});
}
else
{
this
.
trayCode
=
''
;
}
}
},
},
methods
:
{
methods
:
{
simulateStatus
()
{
initWebSocket
()
{
setInterval
(()
=>
{
const
backendUrl
=
process
.
env
.
VUE_APP_API_BASE_URL
||
'http://localhost:8080'
;
const
statuses
=
[
'idle'
,
'running'
,
'error'
];
const
wsUrl
=
backendUrl
.
replace
(
'http'
,
'ws'
)
+
'/ws-robot-arm'
;
const
randomIndex
=
Math
.
floor
(
Math
.
random
()
*
statuses
.
length
);
this
.
status
=
statuses
[
randomIndex
];
try
{
},
10000
);
this
.
websocket
=
new
WebSocket
(
wsUrl
);
this
.
websocket
.
onopen
=
()
=>
{
console
.
log
(
'机械臂指令WebSocket连接成功'
);
this
.
status
=
'running'
;
this
.
sendWebSocketMessage
({
type
:
'request'
,
commands
:
[
'loading'
,
'unloading'
]
});
};
this
.
websocket
.
onmessage
=
(
event
)
=>
{
try
{
const
message
=
JSON
.
parse
(
event
.
data
);
if
(
message
.
type
===
'loading'
)
{
this
.
loadingCommands
=
message
.
data
.
map
(
cmd
=>
({
robotArmCommandId
:
cmd
.
robotArmCommandId
,
trayCode
:
cmd
.
trayCode
,
position
:
cmd
.
storeyCode
,
status
:
cmd
.
status
||
'0'
// 默认待执行状态
}));
}
else
if
(
message
.
type
===
'unloading'
)
{
this
.
unloadingCommands
=
message
.
data
.
map
(
cmd
=>
({
robotArmCommandId
:
cmd
.
robotArmCommandId
,
trayCode
:
cmd
.
trayCode
,
position
:
cmd
.
storeyCode
,
status
:
cmd
.
status
||
'0'
// 默认待执行状态
}));
}
// 新增:处理状态更新消息
else
if
(
message
.
type
===
'status'
)
{
this
.
status
=
message
.
data
;
// 更新机械臂状态
}
}
catch
(
e
)
{
console
.
error
(
'解析WebSocket消息失败:'
,
e
);
}
};
this
.
websocket
.
onerror
=
(
error
)
=>
{
console
.
error
(
'WebSocket错误:'
,
error
);
this
.
status
=
'error'
;
this
.
scheduleReconnect
();
};
this
.
websocket
.
onclose
=
()
=>
{
console
.
log
(
'WebSocket连接关闭'
);
this
.
scheduleReconnect
();
};
}
catch
(
e
)
{
console
.
error
(
'创建WebSocket失败:'
,
e
);
this
.
scheduleReconnect
();
}
},
sendWebSocketMessage
(
message
)
{
if
(
this
.
websocket
&&
this
.
websocket
.
readyState
===
WebSocket
.
OPEN
)
{
this
.
websocket
.
send
(
JSON
.
stringify
(
message
));
}
},
scheduleReconnect
()
{
if
(
this
.
reconnectInterval
)
clearInterval
(
this
.
reconnectInterval
);
this
.
reconnectInterval
=
setInterval
(()
=>
{
if
(
!
this
.
websocket
||
this
.
websocket
.
readyState
!==
WebSocket
.
OPEN
)
{
console
.
log
(
'尝试重新连接WebSocket...'
);
this
.
disconnectWebSocket
();
this
.
initWebSocket
();
}
},
5000
);
},
disconnectWebSocket
()
{
if
(
this
.
websocket
)
{
try
{
this
.
websocket
.
close
();
}
catch
(
e
)
{
console
.
error
(
'关闭WebSocket时出错:'
,
e
);
}
}
if
(
this
.
reconnectInterval
)
{
clearInterval
(
this
.
reconnectInterval
);
this
.
reconnectInterval
=
null
;
}
this
.
websocket
=
null
;
},
closeDialog
()
{
this
.
showAddDialog
=
false
;
},
confirmAdd
()
{
if
(
!
this
.
trayCode
.
trim
())
{
alert
(
'请输入托盘编号'
);
return
;
}
const
robotArmCommand
=
{
trayCode
:
this
.
trayCode
,
storeyCode
:
'待分配位置'
,
type
:
'0'
};
addCommand
(
robotArmCommand
).
then
(
res
=>
{
if
(
res
.
code
===
200
)
{
this
.
msgSuccess
(
"添加成功"
);
this
.
showAddDialog
=
false
;
}
else
{
this
.
msgSuccess
(
"添加失败"
);
}
})
},
getCommandStatusClass
(
status
)
{
return
{
'status-pending'
:
status
==
'0'
,
// 待执行
'status-executing'
:
status
==
'1'
,
// 执行中
'status-poweroff'
:
status
==
'2'
,
// 未上电
'status-completed'
:
status
==
'3'
// 执行结束
};
},
getStatusText
(
status
)
{
const
statusMap
=
{
'0'
:
'待执行'
,
'1'
:
'执行中'
,
'2'
:
'未上电'
,
'3'
:
'执行结束'
};
return
statusMap
[
status
]
||
'未知状态'
;
},
handleCommandClick
(
cmd
)
{
// 只有未上电状态的指令才可点击
if
(
cmd
.
status
===
'2'
)
{
this
.
selectedCommand
=
cmd
;
this
.
showPowerOnDialog
=
true
;
}
},
closePowerOnDialog
()
{
this
.
showPowerOnDialog
=
false
;
this
.
selectedCommand
=
null
;
},
confirmPowerOn
()
{
if
(
!
this
.
selectedCommand
)
return
;
powerOnCommand
(
this
.
selectedCommand
.
robotArmCommandId
).
then
(
res
=>
{
if
(
res
.
code
===
200
)
{
this
.
msgSuccess
(
"上电操作成功"
);
// 更新本地指令状态
this
.
updateCommandStatus
(
this
.
selectedCommand
.
robotArmCommandId
,
'1'
);
this
.
closePowerOnDialog
();
}
else
{
this
.
msgError
(
"上电操作失败"
);
}
}).
catch
(
err
=>
{
this
.
msgError
(
"上电操作失败"
);
});
},
updateCommandStatus
(
commandId
,
newStatus
)
{
// 更新上料指令
const
loadingIndex
=
this
.
loadingCommands
.
findIndex
(
c
=>
c
.
robotArmCommandId
===
commandId
);
if
(
loadingIndex
!==
-
1
)
{
this
.
loadingCommands
[
loadingIndex
].
status
=
newStatus
;
return
;
}
// 更新下料指令
const
unloadingIndex
=
this
.
unloadingCommands
.
findIndex
(
c
=>
c
.
robotArmCommandId
===
commandId
);
if
(
unloadingIndex
!==
-
1
)
{
this
.
unloadingCommands
[
unloadingIndex
].
status
=
newStatus
;
}
}
}
}
}
};
};
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
/*
左上角标题
样式 */
/*
标题区域
样式 */
.panel-title
{
.panel-title
{
position
:
absolute
;
position
:
absolute
;
top
:
20px
;
top
:
20px
;
left
:
20px
;
left
:
20px
;
right
:
20px
;
right
:
20px
;
z-index
:
20
;
/* 确保在其他元素上方 */
z-index
:
20
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
/* 标题组合居左,按钮居右 */
margin-bottom
:
25px
;
padding-bottom
:
15px
;
border-bottom
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
}
}
/* 标题区域横线样式 - 放在title-line下方 */
/* 标题+状态指示灯组合容器:水平排列 */
.board-header
{
.title-with-status
{
border-bottom
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
display
:
flex
;
padding-right
:
20px
;
align-items
:
center
;
/* 垂直居中对齐 */
width
:
100%
;
/* 横线全屏宽度 */
gap
:
15px
;
/* 标题与状态指示灯之间的间距 */
}
.title-left
{
display
:
flex
;
flex-direction
:
column
;
}
}
.title-text
{
.title-text
{
...
@@ -186,41 +452,23 @@ export default {
...
@@ -186,41 +452,23 @@ export default {
border-radius
:
2px
;
border-radius
:
2px
;
}
}
/* 外层容器:强制填满父元素高度 */
/* 状态指示灯:紧挨着标题文字右侧 */
.robotic-arm-panel
{
width
:
100%
;
height
:
100%
;
min-height
:
100%
;
background
:
rgba
(
10
,
20
,
40
,
0.85
);
border-radius
:
12px
;
padding
:
20px
;
display
:
flex
;
flex-direction
:
column
;
border
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
box-shadow
:
0
0
20px
rgba
(
0
,
50
,
120
,
0.3
);
box-sizing
:
border-box
;
overflow
:
hidden
;
position
:
relative
;
/* 为标题绝对定位提供参考 */
}
/* 状态指示灯区域 - 调整位置避免与标题重叠 */
.status-indicator
{
.status-indicator
{
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
margin-bottom
:
20px
;
padding
:
6px
12px
;
padding
:
10px
;
background
:
rgba
(
0
,
30
,
60
,
0.4
);
background
:
rgba
(
0
,
30
,
60
,
0.4
);
border-radius
:
8
px
;
border-radius
:
20
px
;
width
:
fit-content
;
border
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
)
;
margin
-top
:
60px
;
/* 增加顶部间距避开标题
*/
margin
:
0
;
/* 清除原有margin
*/
}
}
.status-light
{
.status-light
{
width
:
1
6
px
;
width
:
1
2
px
;
height
:
1
6
px
;
height
:
1
2
px
;
border-radius
:
50%
;
border-radius
:
50%
;
margin-right
:
12
px
;
margin-right
:
8
px
;
box-shadow
:
0
0
10
px
currentColor
;
box-shadow
:
0
0
6
px
currentColor
;
}
}
.status-light.idle
{
.status-light.idle
{
...
@@ -240,33 +488,174 @@ export default {
...
@@ -240,33 +488,174 @@ export default {
animation
:
blink
1s
infinite
;
animation
:
blink
1s
infinite
;
}
}
@keyframes
pulse
{
.status-text
{
0
%
{
opacity
:
0.6
;
}
font-size
:
14px
;
50
%
{
opacity
:
1
;
}
font-weight
:
bold
;
100
%
{
opacity
:
0.6
;
}
color
:
#64c8ff
;
}
}
@keyframes
blink
{
.title-right
{
0
%,
100
%
{
opacity
:
1
;
}
display
:
flex
;
50
%
{
opacity
:
0.3
;
}
align-items
:
center
;
}
}
.status-text
{
.add-button
{
background
:
linear-gradient
(
to
right
,
#409EFF
,
#64c8ff
);
color
:
white
;
border
:
none
;
padding
:
8px
16px
;
border-radius
:
4px
;
cursor
:
pointer
;
font-size
:
14px
;
font-weight
:
bold
;
display
:
flex
;
align-items
:
center
;
box-shadow
:
0
2px
6px
rgba
(
0
,
0
,
0
,
0.3
);
transition
:
all
0.3s
;
}
.add-button
:hover
{
background
:
linear-gradient
(
to
right
,
#64c8ff
,
#409EFF
);
transform
:
translateY
(
-2px
);
box-shadow
:
0
4px
8px
rgba
(
0
,
0
,
0
,
0.4
);
}
.add-button
i
{
margin-right
:
5px
;
}
/* 对话框样式(不变) */
.dialog-mask
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background-color
:
rgba
(
0
,
0
,
0
,
0.6
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
1000
;
}
.dialog-container
{
width
:
400px
;
background
:
linear-gradient
(
145deg
,
#1a2a3a
,
#0f1c2a
);
border-radius
:
10px
;
border
:
1px
solid
rgba
(
64
,
158
,
255
,
0.4
);
box-shadow
:
0
5px
20px
rgba
(
0
,
0
,
0
,
0.7
);
overflow
:
hidden
;
}
.dialog-header
{
padding
:
16px
20px
;
background
:
rgba
(
0
,
30
,
60
,
0.8
);
border-bottom
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
font-size
:
18px
;
font-size
:
18px
;
font-weight
:
bold
;
font-weight
:
bold
;
color
:
#64c8ff
;
color
:
#64c8ff
;
text-align
:
center
;
}
.dialog-body
{
padding
:
30px
20px
;
}
.dialog-content
{
text-align
:
center
;
}
.scan-prompt
{
font-size
:
18px
;
color
:
#a0d2ff
;
margin-bottom
:
20px
;
}
.scan-input
input
{
width
:
100%
;
padding
:
12px
15px
;
background
:
rgba
(
0
,
20
,
40
,
0.6
);
border
:
1px
solid
rgba
(
64
,
158
,
255
,
0.5
);
border-radius
:
6px
;
color
:
white
;
font-size
:
16px
;
outline
:
none
;
transition
:
all
0.3s
;
}
.scan-input
input
:focus
{
border-color
:
#409EFF
;
box-shadow
:
0
0
10px
rgba
(
64
,
158
,
255
,
0.5
);
}
.dialog-footer
{
padding
:
15px
20px
;
display
:
flex
;
justify-content
:
flex-end
;
background
:
rgba
(
0
,
20
,
40
,
0.5
);
border-top
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
}
.cancel-button
,
.confirm-button
{
padding
:
8px
20px
;
border-radius
:
4px
;
font-size
:
14px
;
font-weight
:
bold
;
cursor
:
pointer
;
transition
:
all
0.3s
;
margin-left
:
10px
;
}
.cancel-button
{
background
:
rgba
(
100
,
100
,
100
,
0.4
);
color
:
#a0d2ff
;
border
:
1px
solid
rgba
(
100
,
150
,
255
,
0.3
);
}
.cancel-button
:hover
{
background
:
rgba
(
120
,
120
,
120
,
0.5
);
border-color
:
rgba
(
100
,
150
,
255
,
0.5
);
}
.confirm-button
{
background
:
linear-gradient
(
to
right
,
#409EFF
,
#64c8ff
);
color
:
white
;
border
:
none
;
}
.confirm-button
:hover
{
background
:
linear-gradient
(
to
right
,
#64c8ff
,
#409EFF
);
transform
:
translateY
(
-2px
);
box-shadow
:
0
4px
8px
rgba
(
0
,
0
,
0
,
0.3
);
}
/* 外层容器(不变) */
.robotic-arm-panel
{
width
:
100%
;
height
:
100%
;
min-height
:
100%
;
background
:
rgba
(
10
,
20
,
40
,
0.85
);
border-radius
:
12px
;
padding
:
20px
;
display
:
flex
;
flex-direction
:
column
;
border
:
1px
solid
rgba
(
64
,
158
,
255
,
0.3
);
box-shadow
:
0
0
20px
rgba
(
0
,
50
,
120
,
0.3
);
box-sizing
:
border-box
;
overflow
:
hidden
;
position
:
relative
;
}
}
/* 主内容区
:填充剩余高度
*/
/* 主内容区
(不变)
*/
.main-content
{
.main-content
{
display
:
flex
;
display
:
flex
;
flex
:
1
;
flex
:
1
;
gap
:
15px
;
gap
:
15px
;
min-height
:
0
;
min-height
:
0
;
overflow
:
hidden
;
overflow
:
hidden
;
margin-top
:
60px
;
/* 为标题区域留出空间 */
}
}
/* 机械臂中心容器:保持固定比例和位置 */
.arm-center-wrapper
{
.arm-center-wrapper
{
flex
:
1
;
flex
:
1
;
min-width
:
0
;
min-width
:
0
;
...
@@ -292,7 +681,7 @@ export default {
...
@@ -292,7 +681,7 @@ export default {
transform
:
scale
(
0.9
);
transform
:
scale
(
0.9
);
}
}
/* 指令区域
:自适应高度,超出滚动
*/
/* 指令区域
(不变)
*/
.loading-command
,
.unloading-command
{
.loading-command
,
.unloading-command
{
width
:
160px
;
width
:
160px
;
min-width
:
160px
;
min-width
:
160px
;
...
@@ -315,7 +704,6 @@ export default {
...
@@ -315,7 +704,6 @@ export default {
border-bottom
:
1px
solid
rgba
(
100
,
180
,
255
,
0.2
);
border-bottom
:
1px
solid
rgba
(
100
,
180
,
255
,
0.2
);
}
}
/* 指令列表:自动扩展,超出滚动 */
.command-list
{
.command-list
{
flex
:
1
;
flex
:
1
;
overflow-y
:
auto
;
overflow-y
:
auto
;
...
@@ -358,7 +746,7 @@ export default {
...
@@ -358,7 +746,7 @@ export default {
justify-content
:
space-between
;
justify-content
:
space-between
;
}
}
/* 机械臂各部件样式 */
/* 机械臂各部件样式
(不变)
*/
.arm-base
{
.arm-base
{
position
:
absolute
;
position
:
absolute
;
bottom
:
0
;
bottom
:
0
;
...
@@ -476,7 +864,6 @@ export default {
...
@@ -476,7 +864,6 @@ export default {
border-radius
:
5px
;
border-radius
:
5px
;
}
}
.gripper-left
{
.gripper-left
{
transform
:
rotate
(
-15deg
);
transform
:
rotate
(
-15deg
);
}
}
...
@@ -485,7 +872,7 @@ export default {
...
@@ -485,7 +872,7 @@ export default {
transform
:
rotate
(
15deg
);
transform
:
rotate
(
15deg
);
}
}
/*
机械臂动画
*/
/*
动画效果(不变)
*/
@keyframes
rotateJoint1
{
@keyframes
rotateJoint1
{
0
%,
100
%
{
transform
:
translateX
(
-50%
)
rotate
(
0deg
);
}
0
%,
100
%
{
transform
:
translateX
(
-50%
)
rotate
(
0deg
);
}
25
%
{
transform
:
translateX
(
-50%
)
rotate
(
45deg
);
}
25
%
{
transform
:
translateX
(
-50%
)
rotate
(
45deg
);
}
...
@@ -516,6 +903,17 @@ export default {
...
@@ -516,6 +903,17 @@ export default {
75
%
{
transform
:
translateX
(
-40%
);
}
75
%
{
transform
:
translateX
(
-40%
);
}
}
}
@keyframes
pulse
{
0
%
{
opacity
:
0.6
;
}
50
%
{
opacity
:
1
;
}
100
%
{
opacity
:
0.6
;
}
}
@keyframes
blink
{
0
%,
100
%
{
opacity
:
1
;
}
50
%
{
opacity
:
0.3
;
}
}
/* 响应式调整 */
/* 响应式调整 */
@media
(
max-width
:
800px
)
{
@media
(
max-width
:
800px
)
{
.main-content
{
.main-content
{
...
@@ -537,13 +935,24 @@ export default {
...
@@ -537,13 +935,24 @@ export default {
min-height
:
200px
;
min-height
:
200px
;
}
}
/* 响应式标题调整 */
/* 标题区域响应式调整 */
.title-with-status
{
flex-direction
:
column
;
/* 小屏幕下标题和状态指示灯垂直排列 */
align-items
:
flex-start
;
gap
:
8px
;
}
.title-text
{
.title-text
{
font-size
:
20px
;
font-size
:
20px
;
}
}
.status-indicator
{
.panel-title
{
margin-top
:
50px
;
flex-wrap
:
wrap
;
}
.title-right
{
margin-top
:
10px
;
order
:
3
;
}
}
}
}
...
@@ -567,6 +976,10 @@ export default {
...
@@ -567,6 +976,10 @@ export default {
.title-line
{
.title-line
{
width
:
80px
;
width
:
80px
;
}
}
.dialog-container
{
width
:
90%
;
}
}
}
@media
(
max-width
:
300px
)
{
@media
(
max-width
:
300px
)
{
...
@@ -579,4 +992,86 @@ export default {
...
@@ -579,4 +992,86 @@ export default {
min-width
:
auto
;
min-width
:
auto
;
}
}
}
}
/* 指令状态样式 */
.command-item.status-pending
{
background
:
rgba
(
0
,
40
,
80
,
0.3
);
border
:
1px
solid
rgba
(
100
,
180
,
255
,
0.3
);
}
.command-item.status-executing
{
background
:
rgba
(
0
,
80
,
40
,
0.3
);
border
:
1px
solid
rgba
(
100
,
255
,
180
,
0.5
);
animation
:
pulse-green
1.5s
infinite
;
}
.command-item.status-poweroff
{
background
:
rgba
(
80
,
0
,
0
,
0.3
);
border
:
1px
solid
rgba
(
255
,
100
,
100
,
0.5
);
cursor
:
pointer
;
animation
:
blink-red
1s
infinite
;
}
.command-item.status-completed
{
background
:
rgba
(
60
,
60
,
60
,
0.3
);
border
:
1px
solid
rgba
(
180
,
180
,
180
,
0.5
);
}
/* 状态文本样式 */
.cmd-status
{
font-size
:
12px
;
margin-top
:
4px
;
font-weight
:
bold
;
}
.status-executing
.cmd-status
{
color
:
#64ffaa
;
}
.status-poweroff
.cmd-status
{
color
:
#ff6464
;
}
.status-completed
.cmd-status
{
color
:
#a0a0a0
;
}
/* 上电对话框样式 */
.power-on-info
{
background
:
rgba
(
0
,
20
,
40
,
0.4
);
padding
:
15px
;
border-radius
:
6px
;
margin-top
:
15px
;
text-align
:
left
;
}
.power-on-info
div
{
margin-bottom
:
10px
;
}
.power-on-info
.label
{
display
:
inline-block
;
width
:
80px
;
color
:
#64c8ff
;
font-weight
:
bold
;
}
/* 新增动画 */
@keyframes
pulse-green
{
0
%
{
opacity
:
0.8
;
box-shadow
:
0
0
5px
rgba
(
100
,
255
,
180
,
0.5
);
}
50
%
{
opacity
:
1
;
box-shadow
:
0
0
15px
rgba
(
100
,
255
,
180
,
0.8
);
}
100
%
{
opacity
:
0.8
;
box-shadow
:
0
0
5px
rgba
(
100
,
255
,
180
,
0.5
);
}
}
@keyframes
blink-red
{
0
%,
100
%
{
opacity
:
0.8
;
}
50
%
{
opacity
:
0.5
;
}
}
/* 响应式调整 */
@media
(
max-width
:
500px
)
{
.cmd-status
{
font-size
:
11px
;
}
}
</
style
>
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment